From af0606b0b59c2023fc091dcfece59082a4f6105d Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 05:46:11 +0000 Subject: [PATCH 1/8] Add VSCode Extension implementation plan (PLAN_VSCODE_EXTENSION.md) Detailed plan for a VSCode extension equivalent to the existing JetBrains plugin (jetbrains-plugin/). Covers feature mapping, tech stack, directory structure, data models, services, UI layout, communication protocol, implementation phases, and risk analysis. https://claude.ai/code/session_01EabgSSMXSA25Pjf1aTEmB2 --- PLAN_VSCODE_EXTENSION.md | 842 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 842 insertions(+) create mode 100644 PLAN_VSCODE_EXTENSION.md diff --git a/PLAN_VSCODE_EXTENSION.md b/PLAN_VSCODE_EXTENSION.md new file mode 100644 index 0000000..75c25b3 --- /dev/null +++ b/PLAN_VSCODE_EXTENSION.md @@ -0,0 +1,842 @@ +# VSCode Extension 実装計画: MariaDB Profiler Viewer + +## 概要 + +`php-ext-mariadb-salvage` が生成するクエリプロファイリングデータを Visual Studio Code 上でわかりやすく可視化・操作する拡張機能を作成する。 + +既存の JetBrains Plugin (`jetbrains-plugin/`) と同等の機能を VSCode で実現し、PhpStorm 以外の開発環境でもプロファイリングデータを活用可能にする。 + +--- + +## JetBrains Plugin との機能対応表 + +| 機能 | JetBrains Plugin | VSCode Extension | +|------|------------------|------------------| +| クエリログビューア | Swing JTable (QueryLogPanel) | Webview (React/Preact テーブル) | +| クエリ詳細表示 | Swing JTextArea + HTML (QueryDetailPanel) | Webview (シンタックスハイライト付き SQL 表示) | +| ジョブマネージャ | Swing JList (JobListPanel) | TreeView (Native VSCode API) | +| バックトレースナビゲーション | OpenFileDescriptor (BacktracePanel) | `vscode.workspace.openTextDocument` + `showTextDocument` | +| リアルタイム監視 | Timer + FileWatcher (LiveTailPanel) | `fs.watch` / `vscode.workspace.FileSystemWatcher` | +| 統計ダッシュボード | Graphics2D バーチャート (StatisticsPanel) | Webview (CSS/SVG バーチャート) | +| 設定 | IntelliJ Configurable (ProfilerConfigurable) | `contributes.configuration` (VSCode Settings) | +| IDE アクション | AnAction (Start/Stop/Open) | `contributes.commands` + コマンドパレット | +| フレームリゾルバ | Groovy スクリプト (FrameResolverService) | JavaScript スクリプト (`vm` モジュール) | +| エラーログ | ErrorLogPanel | OutputChannel (`vscode.window.createOutputChannel`) | +| パスマッピング | ProfilerState テキスト設定 | VSCode Settings (JSON 形式) | + +--- + +## 技術スタック + +| 項目 | 選択 | 理由 | +|------|------|------| +| 言語 | TypeScript | VSCode Extension 標準言語 | +| ビルド | esbuild (バンドル) + tsc (型チェック) | 高速ビルド & 小バンドルサイズ | +| UI フレームワーク | Webview (Preact + htm) | 軽量、JSX 不要、ビルド簡易 | +| チャート | CSS/SVG ベース | 依存ゼロ、軽量 | +| JSON パース | ネイティブ `JSON.parse` | 追加依存不要 | +| ファイル監視 | `vscode.workspace.FileSystemWatcher` + `fs.watch` | VSCode ネイティブ API | +| テスト | Vitest (ユニット) + @vscode/test-electron (統合) | 高速・設定簡易 | +| パッケージ | `@vscode/vsce` | VSCode Marketplace 公式ツール | + +--- + +## ディレクトリ構成 + +``` +vscode-extension/ +├── package.json # Extension マニフェスト +├── tsconfig.json # TypeScript 設定 +├── esbuild.mjs # ビルドスクリプト +├── .vscodeignore # パッケージ除外設定 +├── README.md # Marketplace 用ドキュメント +│ +├── src/ +│ ├── extension.ts # Extension エントリポイント (activate/deactivate) +│ │ +│ ├── model/ # データモデル +│ │ ├── QueryEntry.ts # クエリログエントリ +│ │ ├── JobInfo.ts # ジョブ情報 +│ │ └── BacktraceFrame.ts # バックトレースフレーム +│ │ +│ ├── service/ # サービス層 +│ │ ├── LogParserService.ts # JSONL パーサ (オフセット対応) +│ │ ├── JobManagerService.ts # jobs.json 読み書き + CLI 連携 +│ │ ├── StatisticsService.ts # クエリ統計計算 +│ │ ├── FileWatcherService.ts # ログファイル変更検知 +│ │ └── FrameResolverService.ts # JS スクリプトによるフレーム解決 +│ │ +│ ├── provider/ # VSCode UI プロバイダ +│ │ ├── JobTreeProvider.ts # TreeView: ジョブ一覧 +│ │ └── ProfilerWebviewProvider.ts # WebviewView: メインパネル +│ │ +│ ├── command/ # VSCode コマンド +│ │ ├── startJob.ts # ジョブ開始 +│ │ ├── stopJob.ts # ジョブ停止 +│ │ └── openLog.ts # ログファイルを開く +│ │ +│ └── util/ # ユーティリティ +│ ├── pathMapping.ts # Docker パスマッピング +│ └── queryUtils.ts # SQL 短縮・パラメータバインド +│ +├── webview/ # Webview UI ソース +│ ├── index.html # Webview エントリ HTML +│ ├── main.ts # Webview メインスクリプト +│ ├── style.css # スタイルシート (VSCode テーマ連動) +│ │ +│ ├── components/ # UI コンポーネント +│ │ ├── App.ts # ルートコンポーネント (タブ管理) +│ │ ├── QueryLogTable.ts # クエリ一覧テーブル +│ │ ├── QueryDetail.ts # クエリ詳細パネル +│ │ ├── StatisticsView.ts # 統計ダッシュボード +│ │ ├── LiveTailView.ts # リアルタイム監視 +│ │ └── FilterBar.ts # フィルタ・検索バー +│ │ +│ └── lib/ # Webview ユーティリティ +│ ├── vscodeApi.ts # VSCode Webview API ラッパー +│ └── sqlHighlight.ts # 簡易 SQL シンタックスハイライト +│ +└── test/ + ├── unit/ # ユニットテスト (Vitest) + │ ├── LogParserService.test.ts + │ ├── JobManagerService.test.ts + │ ├── StatisticsService.test.ts + │ └── QueryEntry.test.ts + │ + └── integration/ # 統合テスト (@vscode/test-electron) + └── extension.test.ts +``` + +--- + +## Extension マニフェスト (package.json 設計) + +```jsonc +{ + "name": "mariadb-profiler-viewer", + "displayName": "MariaDB Profiler Viewer", + "description": "Visualize and analyze MariaDB/MySQL query profiling data from php-ext-mariadb-salvage", + "version": "0.1.0", + "publisher": "mariadb-profiler", + "engines": { "vscode": "^1.85.0" }, + "categories": ["Other", "Debuggers"], + "activationEvents": [], + + "main": "./dist/extension.js", + + "contributes": { + // ジョブ一覧 TreeView + "viewsContainers": { + "activitybar": [{ + "id": "mariadb-profiler", + "title": "MariaDB Profiler", + "icon": "resources/icons/profiler.svg" + }] + }, + "views": { + "mariadb-profiler": [ + { + "id": "mariadbProfiler.jobs", + "name": "Jobs", + "type": "tree" + }, + { + "id": "mariadbProfiler.main", + "name": "Profiler", + "type": "webview" + } + ] + }, + + // コマンド + "commands": [ + { "command": "mariadbProfiler.startJob", "title": "Start Profiling Job", "category": "MariaDB Profiler", "icon": "$(play)" }, + { "command": "mariadbProfiler.stopJob", "title": "Stop Profiling Job", "category": "MariaDB Profiler", "icon": "$(debug-stop)" }, + { "command": "mariadbProfiler.openLog", "title": "Open Profiler Log", "category": "MariaDB Profiler", "icon": "$(folder-opened)" }, + { "command": "mariadbProfiler.refresh", "title": "Refresh", "category": "MariaDB Profiler", "icon": "$(refresh)" } + ], + + // ツールバーボタン + "menus": { + "view/title": [ + { "command": "mariadbProfiler.startJob", "when": "view == mariadbProfiler.jobs", "group": "navigation" }, + { "command": "mariadbProfiler.stopJob", "when": "view == mariadbProfiler.jobs", "group": "navigation" }, + { "command": "mariadbProfiler.refresh", "when": "view == mariadbProfiler.jobs", "group": "navigation" } + ] + }, + + // 設定 + "configuration": { + "title": "MariaDB Profiler", + "properties": { + "mariadbProfiler.logDirectory": { + "type": "string", + "default": "/tmp/mariadb_profiler", + "description": "Directory where profiler writes jobs.json and *.jsonl files" + }, + "mariadbProfiler.phpPath": { + "type": "string", + "default": "php", + "description": "Path to PHP executable for CLI operations" + }, + "mariadbProfiler.cliScriptPath": { + "type": "string", + "default": "", + "description": "Path to mariadb_profiler.php CLI tool (auto-detected from workspace if empty)" + }, + "mariadbProfiler.maxQueries": { + "type": "number", + "default": 10000, + "description": "Maximum number of queries to display" + }, + "mariadbProfiler.refreshInterval": { + "type": "number", + "default": 5, + "description": "Auto-refresh interval in seconds" + }, + "mariadbProfiler.tailBufferSize": { + "type": "number", + "default": 500, + "description": "Number of lines to keep in live tail buffer" + }, + "mariadbProfiler.pathMappings": { + "type": "object", + "default": {}, + "description": "Path mappings for Docker environments (container path → local path)", + "additionalProperties": { "type": "string" } + }, + "mariadbProfiler.frameResolverScript": { + "type": "string", + "default": "", + "description": "JavaScript code for custom frame resolution (receives trace, tag, query variables)" + } + } + } + } +} +``` + +--- + +## 主要データフロー + +``` +[PHP Extension] + │ + ├── /var/profiler/jobs.json ──→ JobManagerService ──→ JobTreeProvider (TreeView) + │ │ + │ └──→ Webview (タブ切替) + │ + ├── /var/profiler/.jsonl ──→ LogParserService ──→ Webview: QueryLogTable + │ (オフセット読み込み) │ + │ ├──→ QueryDetail + │ │ │ + │ │ └──→ vscode.openTextDocument (ジャンプ) + │ │ + │ └──→ StatisticsView + │ + └── /var/profiler/.raw.log ──→ FileWatcherService ──→ Webview: LiveTailView + +[CLI Tool] ←── startJob / stopJob コマンド (child_process.execFile) +``` + +--- + +## Extension ⇔ Webview 通信プロトコル + +VSCode Extension (Host) と Webview 間は `postMessage` で通信する。 + +### Extension → Webview メッセージ + +```typescript +// ジョブ選択時にクエリデータを送信 +{ type: 'loadEntries', entries: QueryEntry[], jobKey: string } + +// 差分エントリ追加 (インクリメンタル更新) +{ type: 'appendEntries', entries: QueryEntry[] } + +// 統計データ更新 +{ type: 'updateStats', stats: QueryStats } + +// Live Tail データ追加 +{ type: 'tailData', lines: string } + +// Live Tail クリア +{ type: 'clearTail' } + +// フレーム解決結果 +{ type: 'resolvedFrame', entryIndex: number, frameIndex: number } +``` + +### Webview → Extension メッセージ + +```typescript +// クエリ選択 (詳細表示 & フレーム解決要求) +{ type: 'selectEntry', index: number } + +// バックトレースフレームクリック (エディタジャンプ) +{ type: 'openFile', file: string, line: number } + +// フィルタ変更 +{ type: 'filterChanged', queryType: string | null, searchText: string } + +// タブ切替 +{ type: 'tabChanged', tab: 'queryLog' | 'statistics' | 'liveTail' } + +// Live Tail 開始/停止 +{ type: 'startTail' | 'stopTail' } +``` + +--- + +## UI レイアウト + +### サイドバー (Activity Bar) + +``` +┌──────────────────────┐ +│ ☰ MariaDB Profiler │ +├──────────────────────┤ +│ JOBS [▶][■][↻]│ +│ │ +│ ● job-abc123 (42) │ +│ ● job-def456 (18) │ +│ ○ job-ghi789 (156) │ +│ ○ job-jkl012 (73) │ +│ │ +├──────────────────────┤ +│ PROFILER │ +│ (Webview - 下記参照) │ +│ │ +└──────────────────────┘ +``` + +`●` = Active job, `○` = Completed job, `(N)` = クエリ数 + +### メイン Webview パネル + +``` +┌──────────────────────────────────────────────────────────────┐ +│ [Query Log] [Statistics] [Live Tail] │ +├──────────────────────────────────────────────────────────────┤ +│ Filter: [All ▼] [SELECT] [INSERT] [UPDATE] [DELETE] │ +│ Search: [________________________] 🔍 │ +├──────────────────────────────────────────────────────────────┤ +│ # Time Type SQL Tags │ +│ 1 14:23:01 SELECT SELECT u.* FROM us… [api] │ +│ 2 14:23:01 INSERT INSERT INTO logs … [api] │ +│ 3 14:23:02 SELECT SELECT p.*, u.name… [web] │ +│ 4 14:23:02 UPDATE UPDATE users SET l… [web] │ +├──────────────────────────────────────────────────────────────┤ +│ ▾ Query Detail │ +│ ┌────────────────────────────────────────────────────────┐ │ +│ │ SELECT u.*, p.title, p.content │ │ +│ │ FROM users u │ │ +│ │ JOIN posts p ON p.user_id = u.id │ │ +│ │ WHERE u.active = ? │ │ +│ └────────────────────────────────────────────────────────┘ │ +│ │ +│ Bound Parameters: ?1 = 1 │ +│ Tables: users, posts │ +│ Tags: [api] [v2] │ +│ │ +│ Backtrace: │ +│ ▸ UserController.php:42 getUserPosts() [↗ Open] │ ← 緑ハイライト +│ Router.php:128 dispatch() [↗ Open] │ +│ index.php:15 main() [↗ Open] │ +└──────────────────────────────────────────────────────────────┘ +``` + +### Statistics タブ + +``` +┌──────────────────────────────────────────────────────────────┐ +│ [Query Log] [Statistics] [Live Tail] │ +├──────────────────────────────────────────────────────────────┤ +│ │ +│ Total Queries: 156 │ +│ │ +│ Query Types │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ SELECT ████████████████████████████░░░░ 78% │ │ +│ │ INSERT ████████░░░░░░░░░░░░░░░░░░░░░░░ 12% │ │ +│ │ UPDATE █████░░░░░░░░░░░░░░░░░░░░░░░░░░ 8% │ │ +│ │ DELETE ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2% │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ Top Tables │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ users ████████████████████████░░░░░░ 45% │ │ +│ │ posts ████████████████░░░░░░░░░░░░░░ 30% │ │ +│ │ comments ██████████░░░░░░░░░░░░░░░░░░░░ 18% │ │ +│ │ logs ████░░░░░░░░░░░░░░░░░░░░░░░░░░ 7% │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +│ Top Tags │ +│ ┌──────────────────────────────────────────────────┐ │ +│ │ api ████████████████████░░░░░░░░░░ 60% │ │ +│ │ web ████████████░░░░░░░░░░░░░░░░░░ 30% │ │ +│ │ cron ████░░░░░░░░░░░░░░░░░░░░░░░░░░ 10% │ │ +│ └──────────────────────────────────────────────────┘ │ +│ │ +└──────────────────────────────────────────────────────────────┘ +``` + +### Live Tail タブ + +``` +┌──────────────────────────────────────────────────────────────┐ +│ [Query Log] [Statistics] [Live Tail] │ +├──────────────────────────────────────────────────────────────┤ +│ Status: 🟢 Watching job-abc123 [Clear] [Pause] │ +├──────────────────────────────────────────────────────────────┤ +│ [2025-01-23 14:23:01.000] OK [api] SELECT u.* FROM users… │ +│ #0 /var/www/app/Http/Controllers/UserController.php:42 │ +│ #1 /var/www/vendor/laravel/framework/.../Router.php:128 │ +│ │ +│ [2025-01-23 14:23:01.050] OK [api] INSERT INTO logs … │ +│ #0 /var/www/app/Services/LogService.php:28 │ +│ │ +│ [2025-01-23 14:23:02.100] OK [web] UPDATE users SET … │ +│ #0 /var/www/app/Http/Controllers/AuthController.php:95 │ +│ █ │ +└──────────────────────────────────────────────────────────────┘ +``` + +--- + +## 実装詳細 + +### データモデル + +#### QueryEntry.ts + +```typescript +export interface BacktraceFrame { + file: string; + line: number; + call: string; + function?: string; + class?: string; +} + +export interface QueryEntry { + query: string; + timestamp: string; + jobKey?: string; + status?: string; + tag?: string; + params?: string[]; + trace?: BacktraceFrame[]; +} + +// 算出プロパティはユーティリティ関数として提供 +export function getQueryType(entry: QueryEntry): 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'OTHER'; +export function getBoundQuery(entry: QueryEntry): string; +export function getTables(entry: QueryEntry): string[]; +export function getShortSql(entry: QueryEntry, maxLen?: number): string; +export function getSourceFile(entry: QueryEntry): string | null; +``` + +#### JobInfo.ts + +```typescript +export interface JobInfo { + key: string; + startedAt: string; + endedAt?: string; + queryCount?: number; + parent?: string; + isActive: boolean; +} + +export interface JobsFile { + active: Record; + completed: Record; +} +``` + +### サービス層 + +#### LogParserService.ts + +JetBrains 版と同等のオフセット対応 JSONL パーサ。 + +```typescript +export class LogParserService { + // ファイル全体パース + parseJsonlFile(filePath: string): QueryEntry[]; + + // オフセットからの差分パース (インクリメンタル更新用) + parseJsonlFileFromOffset(filePath: string, offset: number): { + entries: QueryEntry[]; + newOffset: number; + }; + + // Raw ログの末尾 N 行読み込み + readRawLogTail(filePath: string, lines: number): string; + + // Raw ログのオフセット読み込み (Live Tail 用) + tailRawLog(filePath: string, offset: number): { + content: string; + newOffset: number; + }; +} +``` + +**実装ポイント:** +- `fs.readFileSync` / `fs.openSync` + `fs.readSync` でオフセット読み込み +- JSON パースエラーは行単位でスキップし、OutputChannel にログ +- バッファサイズは設定値 (`tailBufferSize`) に従う + +#### JobManagerService.ts + +```typescript +export class JobManagerService { + constructor(private context: vscode.ExtensionContext); + + // jobs.json からジョブ一覧読み込み + loadJobs(): JobInfo[]; + + // アクティブ/完了済みジョブ取得 + getActiveJobs(): JobInfo[]; + getCompletedJobs(): JobInfo[]; + + // CLI 経由でジョブ開始/停止 + startJob(): Promise; // returns jobKey + stopJob(jobKey: string): Promise; + + // パスヘルパー + getJsonlPath(jobKey: string): string; + getRawLogPath(jobKey: string): string; + + // 設定値取得 + private getLogDir(): string; + private getPhpPath(): string; + private getCliScriptPath(): string; +} +``` + +**CLI 連携:** +- `child_process.execFile` で PHP CLI を呼び出し +- タイムアウト: 60 秒 +- CLI スクリプトパス: 設定値 → ワークスペースルート `cli/mariadb_profiler.php` の順にフォールバック + +#### FileWatcherService.ts + +```typescript +export class FileWatcherService implements vscode.Disposable { + // ファイル監視開始 + watchFile(filePath: string, onChange: () => void): void; + + // 監視停止 + unwatchFile(filePath: string): void; + + // 全監視停止 + dispose(): void; +} +``` + +**実装方針:** +- `fs.watchFile` (ポーリング、1000ms 間隔) を使用 + - `fs.watch` はネットワークマウントや Docker ボリュームで不安定なため +- ファイルサイズと `mtime` の変更を検知 +- `Disposable` パターンで Extension 終了時にクリーンアップ + +#### StatisticsService.ts + +```typescript +export interface QueryStats { + totalQueries: number; + byType: Record; // { SELECT: 78, INSERT: 12, ... } + byTable: Record; // { users: 45, posts: 30, ... } + byTag: Record; // { api: 60, web: 30, ... } +} + +export class StatisticsService { + computeStats(entries: QueryEntry[]): QueryStats; +} +``` + +#### FrameResolverService.ts + +JetBrains 版は Groovy スクリプトを使用しているが、VSCode 版では JavaScript に置き換える。 + +```typescript +export class FrameResolverService { + // ユーザースクリプトによるフレーム解決 + resolve(entry: QueryEntry): number; // returns frame index + + // スクリプトのキャッシュ無効化 + invalidateCache(): void; +} +``` + +**実装方針:** +- Node.js `vm` モジュールでサンドボックス実行 +- バインド変数: `trace`, `tag`, `query` (JetBrains 版と同じ) +- 戻り値: ハイライトすべきフレームのインデックス (0 始まり) +- コンパイルエラー / 実行エラーは OutputChannel にログ +- スクリプトキャッシュ: テキスト変更時のみ再コンパイル + +**デフォルトスクリプト例:** +```javascript +// trace: Array<{file, line, call, function, class_name}> +// tag: string, query: string +// Return: frame index to highlight (0-based) + +const tagDepthMap = { + 'api': 1, + 'web': 1, + 'cron': 0, +}; + +if (tag && tagDepthMap[tag] !== undefined) { + return tagDepthMap[tag]; +} +return 0; +``` + +### UI プロバイダ + +#### JobTreeProvider.ts + +VSCode ネイティブ TreeView で ジョブ一覧を表示。 + +```typescript +export class JobTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + refresh(): void; + getTreeItem(element: JobTreeItem): vscode.TreeItem; + getChildren(element?: JobTreeItem): JobTreeItem[]; +} + +export class JobTreeItem extends vscode.TreeItem { + constructor(public readonly job: JobInfo); +} +``` + +**表示:** +- アイコン: `$(circle-filled)` (アクティブ) / `$(circle-outline)` (完了) +- ラベル: `job.key` (先頭 12 文字に短縮) +- 説明 (description): `"42 queries, 3.2s"` 形式 +- コンテキスト値: `activeJob` / `completedJob` (コンテキストメニュー制御用) + +#### ProfilerWebviewProvider.ts + +メイン UI を Webview で提供。 + +```typescript +export class ProfilerWebviewProvider implements vscode.WebviewViewProvider { + resolveWebviewView(webviewView: vscode.WebviewView): void; + + // ジョブ選択時の処理 + selectJob(jobKey: string): void; + + // 定期更新タイマー + private startRefreshTimer(): void; + private stopRefreshTimer(): void; + + // Webview ↔ Extension メッセージハンドリング + private handleWebviewMessage(message: any): void; + private postMessage(message: any): void; +} +``` + +**Webview セキュリティ:** +- `localResourceRoots` で Webview がアクセスできるファイルを制限 +- CSP (Content Security Policy) を適切に設定 +- `nonce` ベースのスクリプト実行許可 + +--- + +## Webview 実装詳細 + +### テーマ連動 + +VSCode のカラーテーマに自動適応するため、CSS 変数を活用: + +```css +:root { + /* VSCode テーマ変数を継承 */ + --vscode-editor-background: var(--vscode-editor-background); + --vscode-editor-foreground: var(--vscode-editor-foreground); + --vscode-list-activeSelectionBackground: var(--vscode-list-activeSelectionBackground); + /* ... */ +} +``` + +### 仮想スクロール + +大量クエリ (10,000+) のパフォーマンス対策: +- テーブルは仮想スクロールで描画 (表示領域 + バッファ行のみ DOM に存在) +- 全件データは Extension 側で保持し、フィルタ結果を Webview に送信 +- ページネーションは不要 (仮想スクロールで対応) + +### SQL シンタックスハイライト + +軽量な正規表現ベースのハイライター: +- キーワード: `SELECT`, `FROM`, `WHERE`, `JOIN`, `INSERT`, `UPDATE`, `DELETE` etc. +- 文字列リテラル: `'...'` +- 数値リテラル +- コメント: `--`, `/* */` +- パラメータプレースホルダー: `?` + +--- + +## 実装ステップ + +### Phase 1: 基盤構築 + +1. **プロジェクトスキャフォールド** + - `package.json`, `tsconfig.json`, `esbuild.mjs` 作成 + - `.vscodeignore`, `.eslintrc.json` 設定 + - `npm init` + 依存パッケージインストール + +2. **Extension エントリポイント** + - `extension.ts` に `activate()` / `deactivate()` 実装 + - コマンド登録、TreeView 登録、WebviewProvider 登録 + +3. **データモデル定義** + - `QueryEntry.ts`, `JobInfo.ts`, `BacktraceFrame.ts` + - ユーティリティ関数 (queryType, boundQuery, tables, shortSql) + +4. **設定スキーマ** + - `package.json` の `contributes.configuration` 定義 + +### Phase 2: コア機能 - クエリログビューア + +5. **LogParserService** + - JSONL ファイルパーサ実装 + - オフセット対応の差分読み込み + +6. **JobManagerService** + - `jobs.json` 読み込み + - アクティブ/完了済みジョブ分類 + +7. **JobTreeProvider** + - TreeView でジョブ一覧表示 + - ジョブ選択イベント発火 + +8. **ProfilerWebviewProvider + Webview UI** + - Webview 基本構成 (HTML + CSS + JS) + - QueryLogTable コンポーネント + - QueryDetail コンポーネント + - フィルタ・検索 UI + +### Phase 3: ナビゲーション & Live Tail + +9. **バックトレースナビゲーション** + - `openFile` メッセージハンドラ + - `vscode.workspace.openTextDocument` + `showTextDocument` でジャンプ + - パスマッピング適用 + +10. **FrameResolverService** + - JavaScript (`vm` モジュール) によるフレーム解決 + - デフォルトスクリプト提供 + +11. **FileWatcherService** + - `fs.watchFile` によるポーリング監視 + - 変更検知コールバック + +12. **LiveTailView** + - Raw ログのリアルタイム表示 + - 一時停止/再開/クリア機能 + +### Phase 4: 統計 & CLI 連携 + +13. **StatisticsService** + - クエリ統計計算 + +14. **StatisticsView** + - CSS/SVG バーチャート + - クエリ種別分布、テーブル別頻度、タグ別頻度 + +15. **CLI コマンド統合** + - `startJob` / `stopJob` コマンド実装 + - `child_process.execFile` で PHP CLI 呼び出し + +16. **OpenLog コマンド** + - ファイルピッカーで `.jsonl` ファイル選択 + - 選択ファイルをエディタで開く + +### Phase 5: 品質 & 仕上げ + +17. **ユニットテスト** + - LogParserService, JobManagerService, StatisticsService, QueryEntry のテスト + - Vitest で実行 + +18. **統合テスト** + - @vscode/test-electron で Extension 全体テスト + +19. **アイコン・UI 微調整** + - SVG アイコン作成 (JetBrains 版を参考) + - CSS テーマ最適化 (ライト/ダーク両対応) + +20. **ドキュメント** + - README.md (Marketplace 用) + - CHANGELOG.md + +--- + +## JetBrains Plugin との差異・VSCode 固有の考慮事項 + +### 1. UI アーキテクチャ + +| 観点 | JetBrains | VSCode | +|------|-----------|--------| +| UI 描画 | Swing (ネイティブ Java UI) | Webview (HTML/CSS/JS) | +| テーブル | JTable + TableModel | HTML `` + 仮想スクロール | +| チャート | Graphics2D / JFreeChart | CSS/SVG | +| スプリットペイン | JSplitPane | CSS flexbox / resizable divider | +| ファイル選択 | JFileChooser | `vscode.window.showOpenDialog` | +| 通知 | Messages.showInfoMessage | `vscode.window.showInformationMessage` | + +### 2. パフォーマンス + +- **Webview の制約**: DOM 操作は JTable より重いため、仮想スクロール必須 +- **メッセージパッシング**: Extension ↔ Webview 間は非同期 `postMessage`、大量データは分割送信 +- **メモリ**: Webview はブラウザプロセスで動作するため、大量データは Extension 側でフィルタしてから送信 + +### 3. フレームリゾルバ + +- JetBrains 版: **Groovy** スクリプト (JVM 上で実行) +- VSCode 版: **JavaScript** スクリプト (Node.js `vm` モジュール) +- API は同等 (`trace`, `tag`, `query` 変数を提供) +- ユーザーへの影響: 既存の Groovy スクリプトは JavaScript に書き換えが必要 (構文は類似) + +### 4. ファイル監視 + +- JetBrains 版: IntelliJ VFS イベント + Timer ポーリング +- VSCode 版: `fs.watchFile` ポーリング (Docker ボリューム対応) +- `vscode.workspace.FileSystemWatcher` はワークスペース内のみ対象のため、外部ディレクトリには `fs.watchFile` を使用 + +### 5. 設定管理 + +- JetBrains 版: `PersistentStateComponent` + カスタム設定ダイアログ +- VSCode 版: `contributes.configuration` (VSCode 標準 Settings UI で編集) +- JSON 形式のパスマッピングは VSCode Settings の JSON エディタで編集可能 + +--- + +## リスク・課題 + +| リスク | 影響度 | 対策 | +|--------|--------|------| +| Webview パフォーマンス (大量クエリ) | 中 | 仮想スクロール、フィルタ済みデータのみ送信 | +| Docker ボリュームのファイル監視 | 中 | `fs.watchFile` ポーリング (1秒間隔) | +| Extension ↔ Webview 通信オーバーヘッド | 低 | バッチ送信、差分更新 | +| Remote SSH / WSL 環境 | 低 | Remote 環境では Extension がリモートで動作するため自然対応 | +| Webview 永続性 (タブ非表示時) | 中 | `retainContextWhenHidden: true` で Webview 状態保持 | + +--- + +## 将来の拡張可能性 + +1. **CodeLens 統合** - PHP ファイル上でクエリ実行元行にインラインでクエリ情報表示 +2. **Diagnostic 統合** - 重いクエリを Warning として表示 +3. **Notebook 統合** - プロファイリング結果を Jupyter Notebook 形式でエクスポート +4. **Language Server** - SQL 補完・バリデーション +5. **Testing Integration** - テスト実行時の自動プロファイリング From 0e4c62b2b2467d139ba8ceacb2c1050c61123ca3 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 06:01:54 +0000 Subject: [PATCH 2/8] Rewrite VSCode Extension plan: full native API (no Webview) Replace all Webview-based UI with VSCode native APIs: - QueryLogTable (Webview) -> QueryTreeProvider (TreeView with expandable details) - StatisticsView (Webview) -> StatisticsTreeProvider (TreeView with Unicode bar charts) - SQL detail (Webview) -> QueryDocumentProvider (Virtual Document .sql with syntax highlighting) - LiveTailView (Webview) -> OutputChannel - FilterBar (Webview) -> QuickPick commands + toolbar buttons Benefits: zero dependencies, no HTML/CSS/JS build pipeline, lighter memory footprint, full theme integration, better Remote SSH/WSL stability. https://claude.ai/code/session_01EabgSSMXSA25Pjf1aTEmB2 --- PLAN_VSCODE_EXTENSION.md | 714 +++++++++++++++++++++++---------------- 1 file changed, 424 insertions(+), 290 deletions(-) diff --git a/PLAN_VSCODE_EXTENSION.md b/PLAN_VSCODE_EXTENSION.md index 75c25b3..9a57ec7 100644 --- a/PLAN_VSCODE_EXTENSION.md +++ b/PLAN_VSCODE_EXTENSION.md @@ -6,23 +6,26 @@ 既存の JetBrains Plugin (`jetbrains-plugin/`) と同等の機能を VSCode で実現し、PhpStorm 以外の開発環境でもプロファイリングデータを活用可能にする。 +**UI 方針: Webview を使用せず、VSCode ネイティブ API のみで構成する。** 軽量・高速・テーマ完全統合を優先する。 + --- ## JetBrains Plugin との機能対応表 | 機能 | JetBrains Plugin | VSCode Extension | |------|------------------|------------------| -| クエリログビューア | Swing JTable (QueryLogPanel) | Webview (React/Preact テーブル) | -| クエリ詳細表示 | Swing JTextArea + HTML (QueryDetailPanel) | Webview (シンタックスハイライト付き SQL 表示) | -| ジョブマネージャ | Swing JList (JobListPanel) | TreeView (Native VSCode API) | -| バックトレースナビゲーション | OpenFileDescriptor (BacktracePanel) | `vscode.workspace.openTextDocument` + `showTextDocument` | -| リアルタイム監視 | Timer + FileWatcher (LiveTailPanel) | `fs.watch` / `vscode.workspace.FileSystemWatcher` | -| 統計ダッシュボード | Graphics2D バーチャート (StatisticsPanel) | Webview (CSS/SVG バーチャート) | +| クエリログビューア | Swing JTable (QueryLogPanel) | **TreeView** (クエリ一覧、展開で詳細表示) | +| クエリ詳細表示 | Swing JTextArea + HTML (QueryDetailPanel) | **Virtual Document** (`.sql` として開き、シンタックスハイライト自動適用) | +| ジョブマネージャ | Swing JList (JobListPanel) | **TreeView** (Native VSCode API) | +| バックトレースナビゲーション | OpenFileDescriptor (BacktracePanel) | TreeView 子要素 + `vscode.workspace.openTextDocument` | +| リアルタイム監視 | Timer + FileWatcher (LiveTailPanel) | **OutputChannel** (`vscode.window.createOutputChannel`) | +| 統計ダッシュボード | Graphics2D バーチャート (StatisticsPanel) | **TreeView** (Unicode バーチャート `████`) | | 設定 | IntelliJ Configurable (ProfilerConfigurable) | `contributes.configuration` (VSCode Settings) | | IDE アクション | AnAction (Start/Stop/Open) | `contributes.commands` + コマンドパレット | | フレームリゾルバ | Groovy スクリプト (FrameResolverService) | JavaScript スクリプト (`vm` モジュール) | -| エラーログ | ErrorLogPanel | OutputChannel (`vscode.window.createOutputChannel`) | +| エラーログ | ErrorLogPanel | **OutputChannel** (`vscode.window.createOutputChannel`) | | パスマッピング | ProfilerState テキスト設定 | VSCode Settings (JSON 形式) | +| フィルタ・検索 | テーブル上のフィルタバー | **QuickPick** (コマンドパレット) + TreeView `view/title` メニュー | --- @@ -32,10 +35,9 @@ |------|------|------| | 言語 | TypeScript | VSCode Extension 標準言語 | | ビルド | esbuild (バンドル) + tsc (型チェック) | 高速ビルド & 小バンドルサイズ | -| UI フレームワーク | Webview (Preact + htm) | 軽量、JSX 不要、ビルド簡易 | -| チャート | CSS/SVG ベース | 依存ゼロ、軽量 | +| UI | VSCode ネイティブ API のみ | Webview 不使用、依存ゼロ、省メモリ、テーマ完全統合 | | JSON パース | ネイティブ `JSON.parse` | 追加依存不要 | -| ファイル監視 | `vscode.workspace.FileSystemWatcher` + `fs.watch` | VSCode ネイティブ API | +| ファイル監視 | `fs.watchFile` (ポーリング) | Docker ボリューム対応 | | テスト | Vitest (ユニット) + @vscode/test-electron (統合) | 高速・設定簡易 | | パッケージ | `@vscode/vsce` | VSCode Marketplace 公式ツール | @@ -68,34 +70,21 @@ vscode-extension/ │ │ │ ├── provider/ # VSCode UI プロバイダ │ │ ├── JobTreeProvider.ts # TreeView: ジョブ一覧 -│ │ └── ProfilerWebviewProvider.ts # WebviewView: メインパネル +│ │ ├── QueryTreeProvider.ts # TreeView: クエリ一覧 (展開で詳細) +│ │ ├── StatisticsTreeProvider.ts # TreeView: 統計ダッシュボード +│ │ └── QueryDocumentProvider.ts # Virtual Document: SQL 詳細表示 │ │ │ ├── command/ # VSCode コマンド │ │ ├── startJob.ts # ジョブ開始 │ │ ├── stopJob.ts # ジョブ停止 -│ │ └── openLog.ts # ログファイルを開く +│ │ ├── openLog.ts # ログファイルを開く +│ │ ├── filterQueries.ts # クエリフィルタ (QuickPick) +│ │ └── searchQueries.ts # クエリ検索 (QuickPick) │ │ │ └── util/ # ユーティリティ │ ├── pathMapping.ts # Docker パスマッピング │ └── queryUtils.ts # SQL 短縮・パラメータバインド │ -├── webview/ # Webview UI ソース -│ ├── index.html # Webview エントリ HTML -│ ├── main.ts # Webview メインスクリプト -│ ├── style.css # スタイルシート (VSCode テーマ連動) -│ │ -│ ├── components/ # UI コンポーネント -│ │ ├── App.ts # ルートコンポーネント (タブ管理) -│ │ ├── QueryLogTable.ts # クエリ一覧テーブル -│ │ ├── QueryDetail.ts # クエリ詳細パネル -│ │ ├── StatisticsView.ts # 統計ダッシュボード -│ │ ├── LiveTailView.ts # リアルタイム監視 -│ │ └── FilterBar.ts # フィルタ・検索バー -│ │ -│ └── lib/ # Webview ユーティリティ -│ ├── vscodeApi.ts # VSCode Webview API ラッパー -│ └── sqlHighlight.ts # 簡易 SQL シンタックスハイライト -│ └── test/ ├── unit/ # ユニットテスト (Vitest) │ ├── LogParserService.test.ts @@ -107,6 +96,11 @@ vscode-extension/ └── extension.test.ts ``` +**Webview 版との差分:** +- `webview/` ディレクトリが不要 (HTML/CSS/JS ビルドパイプラインなし) +- `ProfilerWebviewProvider.ts` → `QueryTreeProvider.ts` + `StatisticsTreeProvider.ts` + `QueryDocumentProvider.ts` に分解 +- フィルタ・検索は `command/` 配下に QuickPick ベースで実装 + --- ## Extension マニフェスト (package.json 設計) @@ -125,7 +119,7 @@ vscode-extension/ "main": "./dist/extension.js", "contributes": { - // ジョブ一覧 TreeView + // Activity Bar コンテナ "viewsContainers": { "activitybar": [{ "id": "mariadb-profiler", @@ -133,6 +127,8 @@ vscode-extension/ "icon": "resources/icons/profiler.svg" }] }, + + // 全て TreeView (Webview なし) "views": { "mariadb-profiler": [ { @@ -141,27 +137,48 @@ vscode-extension/ "type": "tree" }, { - "id": "mariadbProfiler.main", - "name": "Profiler", - "type": "webview" + "id": "mariadbProfiler.queries", + "name": "Queries", + "type": "tree" + }, + { + "id": "mariadbProfiler.statistics", + "name": "Statistics", + "type": "tree" } ] }, // コマンド "commands": [ - { "command": "mariadbProfiler.startJob", "title": "Start Profiling Job", "category": "MariaDB Profiler", "icon": "$(play)" }, - { "command": "mariadbProfiler.stopJob", "title": "Stop Profiling Job", "category": "MariaDB Profiler", "icon": "$(debug-stop)" }, - { "command": "mariadbProfiler.openLog", "title": "Open Profiler Log", "category": "MariaDB Profiler", "icon": "$(folder-opened)" }, - { "command": "mariadbProfiler.refresh", "title": "Refresh", "category": "MariaDB Profiler", "icon": "$(refresh)" } + { "command": "mariadbProfiler.startJob", "title": "Start Profiling Job", "category": "MariaDB Profiler", "icon": "$(play)" }, + { "command": "mariadbProfiler.stopJob", "title": "Stop Profiling Job", "category": "MariaDB Profiler", "icon": "$(debug-stop)" }, + { "command": "mariadbProfiler.openLog", "title": "Open Profiler Log", "category": "MariaDB Profiler", "icon": "$(folder-opened)" }, + { "command": "mariadbProfiler.refresh", "title": "Refresh", "category": "MariaDB Profiler", "icon": "$(refresh)" }, + { "command": "mariadbProfiler.filterByType", "title": "Filter by Query Type", "category": "MariaDB Profiler", "icon": "$(filter)" }, + { "command": "mariadbProfiler.filterByTag", "title": "Filter by Tag", "category": "MariaDB Profiler", "icon": "$(tag)" }, + { "command": "mariadbProfiler.searchQuery", "title": "Search Queries", "category": "MariaDB Profiler", "icon": "$(search)" }, + { "command": "mariadbProfiler.clearFilter", "title": "Clear Filters", "category": "MariaDB Profiler", "icon": "$(clear-all)" }, + { "command": "mariadbProfiler.showQuerySql", "title": "Show Full SQL", "category": "MariaDB Profiler", "icon": "$(open-preview)" }, + { "command": "mariadbProfiler.startLiveTail", "title": "Start Live Tail", "category": "MariaDB Profiler", "icon": "$(eye)" }, + { "command": "mariadbProfiler.stopLiveTail", "title": "Stop Live Tail", "category": "MariaDB Profiler", "icon": "$(eye-closed)" } ], // ツールバーボタン "menus": { "view/title": [ - { "command": "mariadbProfiler.startJob", "when": "view == mariadbProfiler.jobs", "group": "navigation" }, - { "command": "mariadbProfiler.stopJob", "when": "view == mariadbProfiler.jobs", "group": "navigation" }, - { "command": "mariadbProfiler.refresh", "when": "view == mariadbProfiler.jobs", "group": "navigation" } + { "command": "mariadbProfiler.startJob", "when": "view == mariadbProfiler.jobs", "group": "navigation" }, + { "command": "mariadbProfiler.stopJob", "when": "view == mariadbProfiler.jobs", "group": "navigation" }, + { "command": "mariadbProfiler.refresh", "when": "view == mariadbProfiler.jobs", "group": "navigation" }, + { "command": "mariadbProfiler.filterByType", "when": "view == mariadbProfiler.queries", "group": "navigation" }, + { "command": "mariadbProfiler.filterByTag", "when": "view == mariadbProfiler.queries", "group": "navigation" }, + { "command": "mariadbProfiler.searchQuery", "when": "view == mariadbProfiler.queries", "group": "navigation" }, + { "command": "mariadbProfiler.clearFilter", "when": "view == mariadbProfiler.queries", "group": "navigation" }, + { "command": "mariadbProfiler.startLiveTail","when": "view == mariadbProfiler.queries", "group": "2_liveTail" }, + { "command": "mariadbProfiler.stopLiveTail", "when": "view == mariadbProfiler.queries", "group": "2_liveTail" } + ], + "view/item/context": [ + { "command": "mariadbProfiler.showQuerySql", "when": "view == mariadbProfiler.queries && viewItem == queryEntry", "group": "inline" } ] }, @@ -202,7 +219,7 @@ vscode-extension/ "mariadbProfiler.pathMappings": { "type": "object", "default": {}, - "description": "Path mappings for Docker environments (container path → local path)", + "description": "Path mappings for Docker environments (container path -> local path)", "additionalProperties": { "type": "string" } }, "mariadbProfiler.frameResolverScript": { @@ -222,186 +239,138 @@ vscode-extension/ ``` [PHP Extension] - │ - ├── /var/profiler/jobs.json ──→ JobManagerService ──→ JobTreeProvider (TreeView) - │ │ - │ └──→ Webview (タブ切替) - │ - ├── /var/profiler/.jsonl ──→ LogParserService ──→ Webview: QueryLogTable - │ (オフセット読み込み) │ - │ ├──→ QueryDetail - │ │ │ - │ │ └──→ vscode.openTextDocument (ジャンプ) - │ │ - │ └──→ StatisticsView - │ - └── /var/profiler/.raw.log ──→ FileWatcherService ──→ Webview: LiveTailView - -[CLI Tool] ←── startJob / stopJob コマンド (child_process.execFile) + | + +-- /var/profiler/jobs.json --> JobManagerService --> JobTreeProvider (TreeView) + | | + | +--> QueryTreeProvider (TreeView) + | | + +-- /var/profiler/.jsonl --> LogParserService -+ | + | (offset read) | +--> QueryDocumentProvider + | | | (Virtual Document .sql) + | | | + | | +--> vscode.openTextDocument + | | (backtrace jump) + | | + | +--> StatisticsService + | | + | +--> StatisticsTreeProvider (TreeView) + | + +-- /var/profiler/.raw.log --> FileWatcherService --> OutputChannel (Live Tail) + +[CLI Tool] <-- startJob / stopJob commands (child_process.execFile) ``` --- -## Extension ⇔ Webview 通信プロトコル - -VSCode Extension (Host) と Webview 間は `postMessage` で通信する。 - -### Extension → Webview メッセージ - -```typescript -// ジョブ選択時にクエリデータを送信 -{ type: 'loadEntries', entries: QueryEntry[], jobKey: string } - -// 差分エントリ追加 (インクリメンタル更新) -{ type: 'appendEntries', entries: QueryEntry[] } - -// 統計データ更新 -{ type: 'updateStats', stats: QueryStats } - -// Live Tail データ追加 -{ type: 'tailData', lines: string } +## UI レイアウト -// Live Tail クリア -{ type: 'clearTail' } +### サイドバー全体像 -// フレーム解決結果 -{ type: 'resolvedFrame', entryIndex: number, frameIndex: number } +``` ++--------------------------------------------------------------+ +| (i) MariaDB Profiler | ++--------------------------------------------------------------+ +| JOBS [>] [#] [refresh] | +| | +| * job-abc123 42 queries, 3.2s | +| * job-def456 18 queries, 1.1s | +| o job-ghi789 156 queries, 45.0s | +| o job-jkl012 73 queries, 12.3s | +| | ++--------------------------------------------------------------+ +| QUERIES [filter] [tag] [search] [clear] Filter: SELECT | +| | +| > SELECT SELECT u.* FROM us... [api] 14:23:01 | +| v INSERT INSERT INTO logs ... [api] 14:23:01 | +| +-- Tables: logs | +| +-- Tags: api | +| +-- Params: ?1 = 1 | +| +-- Backtrace: | +| > LogService.php:28 log() <- click | +| Router.php:128 dispatch() <- click | +| +-- [Show Full SQL] | +| > SELECT SELECT p.*, u.name... [web] 14:23:02 | +| > UPDATE UPDATE users SET l... [web] 14:23:02 | +| | ++--------------------------------------------------------------+ +| STATISTICS | +| | +| Total Queries: 156 | +| | +| v Query Types | +| SELECT |||||||||||||||||||||||| 78 (50%) | +| INSERT |||||||| 12 (8%) | +| UPDATE |||||| 8 (5%) | +| DELETE || 2 (1%) | +| | +| v Top Tables | +| users |||||||||||||||||||| 45 (29%) | +| posts |||||||||||||||| 30 (19%) | +| comments |||||||||| 18 (12%) | +| logs |||| 7 (4%) | +| | +| v Top Tags | +| api |||||||||||||||||||||||| 60 (38%) | +| web |||||||||||| 30 (19%) | +| cron |||| 10 (6%) | +| | ++--------------------------------------------------------------+ ``` -### Webview → Extension メッセージ - -```typescript -// クエリ選択 (詳細表示 & フレーム解決要求) -{ type: 'selectEntry', index: number } - -// バックトレースフレームクリック (エディタジャンプ) -{ type: 'openFile', file: string, line: number } - -// フィルタ変更 -{ type: 'filterChanged', queryType: string | null, searchText: string } +### エディタ領域 (Virtual Document) -// タブ切替 -{ type: 'tabChanged', tab: 'queryLog' | 'statistics' | 'liveTail' } +クエリを選択して "Show Full SQL" すると、エディタタブとして SQL が開く: -// Live Tail 開始/停止 -{ type: 'startTail' | 'stopTail' } +``` ++--------------------------------------------------------------+ +| [x] Query #3 - SELECT (mariadb-profiler) | ++--------------------------------------------------------------+ +| -- Job: job-abc123 | +| -- Time: 2025-01-23 14:23:02.000 | +| -- Tags: web | +| -- Status: OK | +| | +| SELECT p.*, u.name | +| FROM posts p | +| JOIN users u ON u.id = p.user_id | +| WHERE u.active = ? | +| | +| -- Bound Parameters: | +| -- ?1 = 1 | +| | +| -- Backtrace: | +| -- #0 /app/Http/Controllers/UserController.php:42 | +| -- #1 /vendor/laravel/framework/.../Router.php:128 | +| -- #2 /public/index.php:15 | ++--------------------------------------------------------------+ ``` ---- +- `.sql` として登録するため、VSCode の SQL シンタックスハイライトが自動適用 +- メタデータ (ジョブ、タイムスタンプ、パラメータ、バックトレース) は SQL コメント (`--`) として記述 +- 読み取り専用 (`TextDocumentContentProvider`) -## UI レイアウト +### OutputChannel (Live Tail) -### サイドバー (Activity Bar) - -``` -┌──────────────────────┐ -│ ☰ MariaDB Profiler │ -├──────────────────────┤ -│ JOBS [▶][■][↻]│ -│ │ -│ ● job-abc123 (42) │ -│ ● job-def456 (18) │ -│ ○ job-ghi789 (156) │ -│ ○ job-jkl012 (73) │ -│ │ -├──────────────────────┤ -│ PROFILER │ -│ (Webview - 下記参照) │ -│ │ -└──────────────────────┘ -``` - -`●` = Active job, `○` = Completed job, `(N)` = クエリ数 - -### メイン Webview パネル - -``` -┌──────────────────────────────────────────────────────────────┐ -│ [Query Log] [Statistics] [Live Tail] │ -├──────────────────────────────────────────────────────────────┤ -│ Filter: [All ▼] [SELECT] [INSERT] [UPDATE] [DELETE] │ -│ Search: [________________________] 🔍 │ -├──────────────────────────────────────────────────────────────┤ -│ # Time Type SQL Tags │ -│ 1 14:23:01 SELECT SELECT u.* FROM us… [api] │ -│ 2 14:23:01 INSERT INSERT INTO logs … [api] │ -│ 3 14:23:02 SELECT SELECT p.*, u.name… [web] │ -│ 4 14:23:02 UPDATE UPDATE users SET l… [web] │ -├──────────────────────────────────────────────────────────────┤ -│ ▾ Query Detail │ -│ ┌────────────────────────────────────────────────────────┐ │ -│ │ SELECT u.*, p.title, p.content │ │ -│ │ FROM users u │ │ -│ │ JOIN posts p ON p.user_id = u.id │ │ -│ │ WHERE u.active = ? │ │ -│ └────────────────────────────────────────────────────────┘ │ -│ │ -│ Bound Parameters: ?1 = 1 │ -│ Tables: users, posts │ -│ Tags: [api] [v2] │ -│ │ -│ Backtrace: │ -│ ▸ UserController.php:42 getUserPosts() [↗ Open] │ ← 緑ハイライト -│ Router.php:128 dispatch() [↗ Open] │ -│ index.php:15 main() [↗ Open] │ -└──────────────────────────────────────────────────────────────┘ -``` - -### Statistics タブ - -``` -┌──────────────────────────────────────────────────────────────┐ -│ [Query Log] [Statistics] [Live Tail] │ -├──────────────────────────────────────────────────────────────┤ -│ │ -│ Total Queries: 156 │ -│ │ -│ Query Types │ -│ ┌──────────────────────────────────────────────────┐ │ -│ │ SELECT ████████████████████████████░░░░ 78% │ │ -│ │ INSERT ████████░░░░░░░░░░░░░░░░░░░░░░░ 12% │ │ -│ │ UPDATE █████░░░░░░░░░░░░░░░░░░░░░░░░░░ 8% │ │ -│ │ DELETE ██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ 2% │ │ -│ └──────────────────────────────────────────────────┘ │ -│ │ -│ Top Tables │ -│ ┌──────────────────────────────────────────────────┐ │ -│ │ users ████████████████████████░░░░░░ 45% │ │ -│ │ posts ████████████████░░░░░░░░░░░░░░ 30% │ │ -│ │ comments ██████████░░░░░░░░░░░░░░░░░░░░ 18% │ │ -│ │ logs ████░░░░░░░░░░░░░░░░░░░░░░░░░░ 7% │ │ -│ └──────────────────────────────────────────────────┘ │ -│ │ -│ Top Tags │ -│ ┌──────────────────────────────────────────────────┐ │ -│ │ api ████████████████████░░░░░░░░░░ 60% │ │ -│ │ web ████████████░░░░░░░░░░░░░░░░░░ 30% │ │ -│ │ cron ████░░░░░░░░░░░░░░░░░░░░░░░░░░ 10% │ │ -│ └──────────────────────────────────────────────────┘ │ -│ │ -└──────────────────────────────────────────────────────────────┘ -``` - -### Live Tail タブ - -``` -┌──────────────────────────────────────────────────────────────┐ -│ [Query Log] [Statistics] [Live Tail] │ -├──────────────────────────────────────────────────────────────┤ -│ Status: 🟢 Watching job-abc123 [Clear] [Pause] │ -├──────────────────────────────────────────────────────────────┤ -│ [2025-01-23 14:23:01.000] OK [api] SELECT u.* FROM users… │ -│ #0 /var/www/app/Http/Controllers/UserController.php:42 │ -│ #1 /var/www/vendor/laravel/framework/.../Router.php:128 │ -│ │ -│ [2025-01-23 14:23:01.050] OK [api] INSERT INTO logs … │ -│ #0 /var/www/app/Services/LogService.php:28 │ -│ │ -│ [2025-01-23 14:23:02.100] OK [web] UPDATE users SET … │ -│ #0 /var/www/app/Http/Controllers/AuthController.php:95 │ -│ █ │ -└──────────────────────────────────────────────────────────────┘ ``` ++--------------------------------------------------------------+ +| OUTPUT [MariaDB Profiler Live Tail v] | ++--------------------------------------------------------------+ +| [2025-01-23 14:23:01.000] OK [api] SELECT u.* FROM users... | +| #0 /app/Http/Controllers/UserController.php:42 | +| #1 /vendor/laravel/framework/.../Router.php:128 | +| | +| [2025-01-23 14:23:01.050] OK [api] INSERT INTO logs ... | +| #0 /app/Services/LogService.php:28 | +| | +| [2025-01-23 14:23:02.100] OK [web] UPDATE users SET ... | +| #0 /app/Http/Controllers/AuthController.php:95 | ++--------------------------------------------------------------+ +``` + +- `vscode.window.createOutputChannel("MariaDB Profiler Live Tail")` で作成 +- `.show(true)` でフォーカスを奪わずに表示 +- `appendLine()` でリアルタイム追記 +- `clear()` でバッファクリア --- @@ -601,7 +570,7 @@ return 0; #### JobTreeProvider.ts -VSCode ネイティブ TreeView で ジョブ一覧を表示。 +VSCode ネイティブ TreeView でジョブ一覧を表示。 ```typescript export class JobTreeProvider implements vscode.TreeDataProvider { @@ -623,66 +592,192 @@ export class JobTreeItem extends vscode.TreeItem { - ラベル: `job.key` (先頭 12 文字に短縮) - 説明 (description): `"42 queries, 3.2s"` 形式 - コンテキスト値: `activeJob` / `completedJob` (コンテキストメニュー制御用) +- クリック時: `QueryTreeProvider` と `StatisticsTreeProvider` をそのジョブのデータで更新 -#### ProfilerWebviewProvider.ts +#### QueryTreeProvider.ts -メイン UI を Webview で提供。 +クエリ一覧を TreeView で表示。展開すると詳細情報を表示。 ```typescript -export class ProfilerWebviewProvider implements vscode.WebviewViewProvider { - resolveWebviewView(webviewView: vscode.WebviewView): void; +export class QueryTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + // ジョブのクエリデータをロード + loadEntries(entries: QueryEntry[]): void; + + // フィルタ・検索 + setFilter(queryType: string | null): void; + setTagFilter(tag: string | null): void; + setSearchText(text: string | null): void; + clearFilters(): void; + + // TreeDataProvider + getTreeItem(element: QueryTreeItem): vscode.TreeItem; + getChildren(element?: QueryTreeItem): QueryTreeItem[]; +} - // ジョブ選択時の処理 - selectJob(jobKey: string): void; +type QueryTreeItem = QueryEntryItem | QueryMetadataItem | BacktraceFrameItem; - // 定期更新タイマー - private startRefreshTimer(): void; - private stopRefreshTimer(): void; +// 第1階層: クエリエントリ (折りたたみ可能) +export class QueryEntryItem extends vscode.TreeItem { + contextValue = 'queryEntry'; + // ラベル: "SELECT SELECT u.* FROM us..." + // 説明: "[api] 14:23:01" + // アイコン: クエリ種別に応じた色 (ThemeIcon) + // SELECT=$(database), INSERT=$(add), UPDATE=$(edit), DELETE=$(trash) +} + +// 第2階層: メタデータ (展開時に表示) +export class QueryMetadataItem extends vscode.TreeItem { + // "Tables: users, posts" + // "Tags: api, v2" + // "Params: ?1 = 1" + // "Status: OK" +} - // Webview ↔ Extension メッセージハンドリング - private handleWebviewMessage(message: any): void; - private postMessage(message: any): void; +// 第2階層: バックトレースフレーム (クリックでファイルジャンプ) +export class BacktraceFrameItem extends vscode.TreeItem { + // ラベル: "UserController.php:42 getUserPosts()" + // アイコン: $(arrow-right) (通常) / $(arrow-right) + 緑色 (解決済みフレーム) + // command: vscode.open (クリックでエディタにジャンプ) } ``` -**Webview セキュリティ:** -- `localResourceRoots` で Webview がアクセスできるファイルを制限 -- CSP (Content Security Policy) を適切に設定 -- `nonce` ベースのスクリプト実行許可 +**TreeView の description を活用したカラム風表示:** +``` + [icon] SELECT u.* FROM us... [api] 14:23:01 + ^^^^^ ^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^ + icon label description +``` ---- +TreeItem の `label` にクエリ SQL (短縮)、`description` にタグ+時刻を設定することで、擬似的な 2 カラム表示を実現。 + +#### StatisticsTreeProvider.ts + +統計情報を TreeView で表示。Unicode バーチャートで視覚化。 + +```typescript +export class StatisticsTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + // 統計データ更新 + updateStats(stats: QueryStats): void; + + // TreeDataProvider + getTreeItem(element: StatTreeItem): vscode.TreeItem; + getChildren(element?: StatTreeItem): StatTreeItem[]; +} -## Webview 実装詳細 +type StatTreeItem = StatCategoryItem | StatBarItem; -### テーマ連動 +// 第1階層: カテゴリ (折りたたみ可能) +export class StatCategoryItem extends vscode.TreeItem { + // "Total Queries: 156" + // "Query Types" (collapsible) + // "Top Tables" (collapsible) + // "Top Tags" (collapsible) +} -VSCode のカラーテーマに自動適応するため、CSS 変数を活用: +// 第2階層: 個別統計 (Unicode バーチャート) +export class StatBarItem extends vscode.TreeItem { + // ラベル: "SELECT ████████████████████████" + // 説明: "78 (50%)" +} +``` -```css -:root { - /* VSCode テーマ変数を継承 */ - --vscode-editor-background: var(--vscode-editor-background); - --vscode-editor-foreground: var(--vscode-editor-foreground); - --vscode-list-activeSelectionBackground: var(--vscode-list-activeSelectionBackground); - /* ... */ +**Unicode バーチャート生成:** +```typescript +function generateBar(value: number, max: number, barWidth: number = 24): string { + const filled = Math.round((value / max) * barWidth); + return '█'.repeat(filled) + '░'.repeat(barWidth - filled); } + +// 例: generateBar(78, 156, 24) → "████████████░░░░░░░░░░░░" ``` -### 仮想スクロール +#### QueryDocumentProvider.ts -大量クエリ (10,000+) のパフォーマンス対策: -- テーブルは仮想スクロールで描画 (表示領域 + バッファ行のみ DOM に存在) -- 全件データは Extension 側で保持し、フィルタ結果を Webview に送信 -- ページネーションは不要 (仮想スクロールで対応) +Virtual Document で SQL 詳細を表示。 -### SQL シンタックスハイライト +```typescript +export class QueryDocumentProvider implements vscode.TextDocumentContentProvider { + // URI スキーム: "mariadb-profiler" + static readonly scheme = 'mariadb-profiler'; -軽量な正規表現ベースのハイライター: -- キーワード: `SELECT`, `FROM`, `WHERE`, `JOIN`, `INSERT`, `UPDATE`, `DELETE` etc. -- 文字列リテラル: `'...'` -- 数値リテラル -- コメント: `--`, `/* */` -- パラメータプレースホルダー: `?` + provideTextDocumentContent(uri: vscode.Uri): string; + + // クエリエントリを Virtual Document として開く + showQueryDetail(entry: QueryEntry, index: number): void; +} +``` + +**URI 設計:** +``` +mariadb-profiler:query-3.sql?job=abc123&index=3 +``` + +- `.sql` 拡張子 → VSCode が SQL 言語モードを自動適用 +- `TextDocumentContentProvider` のため読み取り専用 +- ドキュメント内容: SQL + パラメータ + バックトレース (全て SQL コメントで装飾) + +**利点:** +- SQL シンタックスハイライトが無料で得られる (VSCode 組み込み) +- ユーザーが好みの SQL Extension を入れていればそれも適用される +- `editor.wordWrap` など通常のエディタ設定が有効 + +--- + +## フィルタ・検索の UI フロー + +Webview ではフィルタバーを常時表示できるが、ネイティブ API ではそれができない。 +代わりに以下の方法で操作性を確保する: + +### 1. QuickPick によるフィルタ + +``` +[コマンドパレット or ツールバーボタン] + ↓ ++------------------------------------------+ +| Filter by Query Type | ++------------------------------------------+ +| > All (clear filter) | +| SELECT (78 queries) | +| INSERT (12 queries) | +| UPDATE (8 queries) | +| DELETE (2 queries) | ++------------------------------------------+ +``` + +### 2. QuickPick によるタグフィルタ + +``` ++------------------------------------------+ +| Filter by Tag | ++------------------------------------------+ +| > All (clear filter) | +| api (60 queries) | +| web (30 queries) | +| cron (10 queries) | ++------------------------------------------+ +``` + +### 3. InputBox による検索 + +``` ++------------------------------------------+ +| Search queries (SQL text) | ++------------------------------------------+ +| users | ++------------------------------------------+ +``` + +### 4. 現在のフィルタ状態表示 + +TreeView のタイトル (view/title) の `description` でフィルタ状態を表示: +- `"QUERIES (SELECT, tag:api, search:'users')"` のように表示 +- StatusBar アイテムでも現在のフィルタ状態を表示可能 --- @@ -697,7 +792,8 @@ VSCode のカラーテーマに自動適応するため、CSS 変数を活用: 2. **Extension エントリポイント** - `extension.ts` に `activate()` / `deactivate()` 実装 - - コマンド登録、TreeView 登録、WebviewProvider 登録 + - コマンド登録、全 TreeView 登録、OutputChannel 登録 + - `TextDocumentContentProvider` 登録 3. **データモデル定義** - `QueryEntry.ts`, `JobInfo.ts`, `BacktraceFrame.ts` @@ -720,92 +816,131 @@ VSCode のカラーテーマに自動適応するため、CSS 変数を活用: - TreeView でジョブ一覧表示 - ジョブ選択イベント発火 -8. **ProfilerWebviewProvider + Webview UI** - - Webview 基本構成 (HTML + CSS + JS) - - QueryLogTable コンポーネント - - QueryDetail コンポーネント - - フィルタ・検索 UI +8. **QueryTreeProvider** + - TreeView でクエリ一覧表示 + - 展開でメタデータ (テーブル、タグ、パラメータ) 表示 + - 展開でバックトレース表示 + +9. **QueryDocumentProvider** + - Virtual Document (`.sql`) でフル SQL 表示 + - SQL コメントでメタデータ・バックトレース記述 -### Phase 3: ナビゲーション & Live Tail +### Phase 3: フィルタ・ナビゲーション & Live Tail -9. **バックトレースナビゲーション** - - `openFile` メッセージハンドラ - - `vscode.workspace.openTextDocument` + `showTextDocument` でジャンプ - - パスマッピング適用 +10. **フィルタ・検索コマンド** + - `filterByType`: QuickPick でクエリ種別フィルタ + - `filterByTag`: QuickPick でタグフィルタ + - `searchQuery`: InputBox でテキスト検索 + - `clearFilter`: 全フィルタクリア -10. **FrameResolverService** +11. **バックトレースナビゲーション** + - `BacktraceFrameItem` クリックで `vscode.workspace.openTextDocument` + `showTextDocument` + - パスマッピング適用 + +12. **FrameResolverService** - JavaScript (`vm` モジュール) によるフレーム解決 - デフォルトスクリプト提供 + - 解決済みフレームに `$(arrow-right)` + 緑色アイコン -11. **FileWatcherService** +13. **FileWatcherService** - `fs.watchFile` によるポーリング監視 - 変更検知コールバック -12. **LiveTailView** - - Raw ログのリアルタイム表示 - - 一時停止/再開/クリア機能 +14. **Live Tail (OutputChannel)** + - `vscode.window.createOutputChannel("MariaDB Profiler Live Tail")` + - `startLiveTail` / `stopLiveTail` コマンド + - `appendLine()` でリアルタイム追記 + - バッファサイズ超過時に `clear()` + 再出力 ### Phase 4: 統計 & CLI 連携 -13. **StatisticsService** +15. **StatisticsService** - クエリ統計計算 -14. **StatisticsView** - - CSS/SVG バーチャート - - クエリ種別分布、テーブル別頻度、タグ別頻度 +16. **StatisticsTreeProvider** + - TreeView で統計表示 + - Unicode バーチャート (`████░░░░`) で視覚化 + - カテゴリ: クエリ種別分布、テーブル別頻度、タグ別頻度 -15. **CLI コマンド統合** +17. **CLI コマンド統合** - `startJob` / `stopJob` コマンド実装 - `child_process.execFile` で PHP CLI 呼び出し -16. **OpenLog コマンド** +18. **OpenLog コマンド** - ファイルピッカーで `.jsonl` ファイル選択 - - 選択ファイルをエディタで開く ### Phase 5: 品質 & 仕上げ -17. **ユニットテスト** +19. **ユニットテスト** - LogParserService, JobManagerService, StatisticsService, QueryEntry のテスト - Vitest で実行 -18. **統合テスト** +20. **統合テスト** - @vscode/test-electron で Extension 全体テスト -19. **アイコン・UI 微調整** +21. **アイコン・UI 微調整** - SVG アイコン作成 (JetBrains 版を参考) - - CSS テーマ最適化 (ライト/ダーク両対応) + - ThemeIcon カラー設定 -20. **ドキュメント** +22. **ドキュメント** - README.md (Marketplace 用) - CHANGELOG.md --- +## Webview 版との比較 + +### メリット (ネイティブ API 方式) + +| 項目 | 詳細 | +|------|------| +| **依存ゼロ** | HTML/CSS/JS ビルドパイプライン不要。Webview フレームワーク (Preact/React) 不要 | +| **軽量** | Chromium プロセスを起動しないため省メモリ | +| **テーマ完全統合** | ネイティブ UI は VSCode テーマに自動追従。CSS 変数のマッピング不要 | +| **実装が速い** | Extension ↔ Webview 間の `postMessage` 通信プロトコル設計が不要 | +| **Remote SSH / WSL** | Webview より安定動作 | +| **ビルド簡易** | esbuild で Extension のみバンドル。Webview 用の別ビルド不要 | +| **セキュリティ** | CSP 設定、nonce 管理、`localResourceRoots` 管理が不要 | + +### デメリット・制約 + +| 項目 | 詳細 | 緩和策 | +|------|------|--------| +| **テーブル表示** | TreeView にカラム幅調整・ソート・水平スクロールがない | `label` + `description` で擬似 2 カラム表示 | +| **統計チャート** | 棒グラフは Unicode 文字 (`████`) での近似表現 | 十分視覚的に分かりやすい | +| **フィルタ UI** | 常時表示のフィルタバーが作れない | QuickPick + ツールバーボタン + ステータスバー表示 | +| **レイアウト** | スプリットペイン・複雑なレイアウト不可 | サイドバー 3 セクション構成で十分 | +| **仮想スクロール** | TreeView は VSCode が管理 (10,000 件でもパフォーマンス良好) | `maxQueries` 設定で上限制御 | + +--- + ## JetBrains Plugin との差異・VSCode 固有の考慮事項 ### 1. UI アーキテクチャ | 観点 | JetBrains | VSCode | |------|-----------|--------| -| UI 描画 | Swing (ネイティブ Java UI) | Webview (HTML/CSS/JS) | -| テーブル | JTable + TableModel | HTML `
` + 仮想スクロール | -| チャート | Graphics2D / JFreeChart | CSS/SVG | -| スプリットペイン | JSplitPane | CSS flexbox / resizable divider | +| UI 描画 | Swing (ネイティブ Java UI) | VSCode TreeView + Virtual Document + OutputChannel | +| テーブル | JTable + TableModel | TreeView (展開式) | +| チャート | Graphics2D | Unicode バーチャート (`████░░░░`) | +| スプリットペイン | JSplitPane | サイドバー 3 セクション | +| SQL 表示 | JTextArea + HTML | Virtual Document (.sql) - エディタタブ | +| Live Tail | カスタムパネル | OutputChannel | +| フィルタ | テーブル上のフィルタバー | QuickPick (コマンドパレット) | | ファイル選択 | JFileChooser | `vscode.window.showOpenDialog` | | 通知 | Messages.showInfoMessage | `vscode.window.showInformationMessage` | ### 2. パフォーマンス -- **Webview の制約**: DOM 操作は JTable より重いため、仮想スクロール必須 -- **メッセージパッシング**: Extension ↔ Webview 間は非同期 `postMessage`、大量データは分割送信 -- **メモリ**: Webview はブラウザプロセスで動作するため、大量データは Extension 側でフィルタしてから送信 +- **TreeView**: VSCode が内部で仮想化しているため、大量アイテムでもパフォーマンス良好 +- **Virtual Document**: エディタの標準パスで動作するため、大きな SQL でも問題なし +- **OutputChannel**: ネイティブテキスト出力のため、高速にログ追記可能 ### 3. フレームリゾルバ - JetBrains 版: **Groovy** スクリプト (JVM 上で実行) - VSCode 版: **JavaScript** スクリプト (Node.js `vm` モジュール) - API は同等 (`trace`, `tag`, `query` 変数を提供) -- ユーザーへの影響: 既存の Groovy スクリプトは JavaScript に書き換えが必要 (構文は類似) ### 4. ファイル監視 @@ -817,7 +952,6 @@ VSCode のカラーテーマに自動適応するため、CSS 変数を活用: - JetBrains 版: `PersistentStateComponent` + カスタム設定ダイアログ - VSCode 版: `contributes.configuration` (VSCode 標準 Settings UI で編集) -- JSON 形式のパスマッピングは VSCode Settings の JSON エディタで編集可能 --- @@ -825,11 +959,11 @@ VSCode のカラーテーマに自動適応するため、CSS 変数を活用: | リスク | 影響度 | 対策 | |--------|--------|------| -| Webview パフォーマンス (大量クエリ) | 中 | 仮想スクロール、フィルタ済みデータのみ送信 | +| TreeView で大量クエリ表示時のパフォーマンス | 低 | VSCode の TreeView は内部で遅延読み込み。`maxQueries` で上限設定 | | Docker ボリュームのファイル監視 | 中 | `fs.watchFile` ポーリング (1秒間隔) | -| Extension ↔ Webview 通信オーバーヘッド | 低 | バッチ送信、差分更新 | -| Remote SSH / WSL 環境 | 低 | Remote 環境では Extension がリモートで動作するため自然対応 | -| Webview 永続性 (タブ非表示時) | 中 | `retainContextWhenHidden: true` で Webview 状態保持 | +| フィルタ操作の手数 (QuickPick 呼び出し) | 中 | ツールバーボタンで 1 クリックアクセス。キーボードショートカット設定可能 | +| TreeView のカラム表示制限 | 低 | `label` + `description` で十分な情報表示可能 | +| Remote SSH / WSL 環境 | 低 | ネイティブ API のみ使用のため、Webview より安定 | --- @@ -837,6 +971,6 @@ VSCode のカラーテーマに自動適応するため、CSS 変数を活用: 1. **CodeLens 統合** - PHP ファイル上でクエリ実行元行にインラインでクエリ情報表示 2. **Diagnostic 統合** - 重いクエリを Warning として表示 -3. **Notebook 統合** - プロファイリング結果を Jupyter Notebook 形式でエクスポート -4. **Language Server** - SQL 補完・バリデーション -5. **Testing Integration** - テスト実行時の自動プロファイリング +3. **Testing Integration** - テスト実行時の自動プロファイリング +4. **TreeView Drag & Drop** - クエリをエディタにドラッグして SQL 挿入 +5. **StatusBar 統合** - アクティブジョブのクエリ数をリアルタイム表示 From 95556d5d6eb7728dcda5d0ed321c145e6b382476 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 06:15:58 +0000 Subject: [PATCH 3/8] Implement VSCode extension with full native API (no Webview) All UI uses VSCode native APIs only - no Webview, no HTML/CSS/JS: - TreeView for jobs list, query log (expandable details), and statistics - Virtual Document (.sql) for full SQL display with syntax highlighting - OutputChannel for live tail streaming - QuickPick for filter/search interactions Structure: - src/model/: QueryEntry, JobInfo (matching PHP JSONL/JSON formats) - src/service/: LogParser, JobManager, Statistics, FileWatcher, FrameResolver - src/provider/: JobTree, QueryTree, StatisticsTree, QueryDocument - src/command/: startJob, stopJob, openLog, filter, search, liveTail - test/unit/: 56 tests passing (QueryEntry, JobInfo, Statistics, LogParser) TypeScript compiles cleanly, esbuild production bundle works. https://claude.ai/code/session_01EabgSSMXSA25Pjf1aTEmB2 --- vscode-extension/.gitignore | 3 + vscode-extension/.vscodeignore | 10 + vscode-extension/esbuild.mjs | 25 + vscode-extension/package-lock.json | 4682 +++++++++++++++++ vscode-extension/package.json | 240 + vscode-extension/resources/icons/profiler.svg | 7 + vscode-extension/src/command/filterQueries.ts | 99 + vscode-extension/src/command/liveTail.ts | 150 + vscode-extension/src/command/openLog.ts | 29 + vscode-extension/src/command/searchQueries.ts | 27 + vscode-extension/src/command/startJob.ts | 27 + vscode-extension/src/command/stopJob.ts | 45 + vscode-extension/src/extension.ts | 239 + vscode-extension/src/model/JobInfo.ts | 82 + vscode-extension/src/model/QueryEntry.ts | 167 + .../src/provider/JobTreeProvider.ts | 63 + .../src/provider/QueryDocumentProvider.ts | 85 + .../src/provider/QueryTreeProvider.ts | 282 + .../src/provider/StatisticsTreeProvider.ts | 94 + .../src/service/FileWatcherService.ts | 71 + .../src/service/FrameResolverService.ts | 73 + .../src/service/JobManagerService.ts | 116 + .../src/service/LogParserService.ts | 90 + .../src/service/StatisticsService.ts | 48 + vscode-extension/src/util/pathMapping.ts | 14 + vscode-extension/src/util/queryUtils.ts | 15 + vscode-extension/test/unit/JobInfo.test.ts | 106 + .../test/unit/LogParserService.test.ts | 164 + vscode-extension/test/unit/QueryEntry.test.ts | 239 + .../test/unit/StatisticsService.test.ts | 75 + vscode-extension/tsconfig.json | 19 + vscode-extension/vitest.config.ts | 8 + 32 files changed, 7394 insertions(+) create mode 100644 vscode-extension/.gitignore create mode 100644 vscode-extension/.vscodeignore create mode 100644 vscode-extension/esbuild.mjs create mode 100644 vscode-extension/package-lock.json create mode 100644 vscode-extension/package.json create mode 100644 vscode-extension/resources/icons/profiler.svg create mode 100644 vscode-extension/src/command/filterQueries.ts create mode 100644 vscode-extension/src/command/liveTail.ts create mode 100644 vscode-extension/src/command/openLog.ts create mode 100644 vscode-extension/src/command/searchQueries.ts create mode 100644 vscode-extension/src/command/startJob.ts create mode 100644 vscode-extension/src/command/stopJob.ts create mode 100644 vscode-extension/src/extension.ts create mode 100644 vscode-extension/src/model/JobInfo.ts create mode 100644 vscode-extension/src/model/QueryEntry.ts create mode 100644 vscode-extension/src/provider/JobTreeProvider.ts create mode 100644 vscode-extension/src/provider/QueryDocumentProvider.ts create mode 100644 vscode-extension/src/provider/QueryTreeProvider.ts create mode 100644 vscode-extension/src/provider/StatisticsTreeProvider.ts create mode 100644 vscode-extension/src/service/FileWatcherService.ts create mode 100644 vscode-extension/src/service/FrameResolverService.ts create mode 100644 vscode-extension/src/service/JobManagerService.ts create mode 100644 vscode-extension/src/service/LogParserService.ts create mode 100644 vscode-extension/src/service/StatisticsService.ts create mode 100644 vscode-extension/src/util/pathMapping.ts create mode 100644 vscode-extension/src/util/queryUtils.ts create mode 100644 vscode-extension/test/unit/JobInfo.test.ts create mode 100644 vscode-extension/test/unit/LogParserService.test.ts create mode 100644 vscode-extension/test/unit/QueryEntry.test.ts create mode 100644 vscode-extension/test/unit/StatisticsService.test.ts create mode 100644 vscode-extension/tsconfig.json create mode 100644 vscode-extension/vitest.config.ts diff --git a/vscode-extension/.gitignore b/vscode-extension/.gitignore new file mode 100644 index 0000000..a08e1da --- /dev/null +++ b/vscode-extension/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +dist/ +*.vsix diff --git a/vscode-extension/.vscodeignore b/vscode-extension/.vscodeignore new file mode 100644 index 0000000..7efcac3 --- /dev/null +++ b/vscode-extension/.vscodeignore @@ -0,0 +1,10 @@ +.vscode/** +node_modules/** +src/** +test/** +tsconfig.json +esbuild.mjs +.eslintrc.json +**/*.map +**/*.ts +!dist/** diff --git a/vscode-extension/esbuild.mjs b/vscode-extension/esbuild.mjs new file mode 100644 index 0000000..c630626 --- /dev/null +++ b/vscode-extension/esbuild.mjs @@ -0,0 +1,25 @@ +import * as esbuild from 'esbuild'; + +const production = process.argv.includes('--production'); +const watch = process.argv.includes('--watch'); + +const ctx = await esbuild.context({ + entryPoints: ['src/extension.ts'], + bundle: true, + format: 'cjs', + minify: production, + sourcemap: !production, + sourcesContent: false, + platform: 'node', + outfile: 'dist/extension.js', + external: ['vscode'], + logLevel: 'info', +}); + +if (watch) { + await ctx.watch(); + console.log('Watching for changes...'); +} else { + await ctx.rebuild(); + await ctx.dispose(); +} diff --git a/vscode-extension/package-lock.json b/vscode-extension/package-lock.json new file mode 100644 index 0000000..d1ebb91 --- /dev/null +++ b/vscode-extension/package-lock.json @@ -0,0 +1,4682 @@ +{ + "name": "mariadb-profiler-viewer", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mariadb-profiler-viewer", + "version": "0.1.0", + "devDependencies": { + "@types/node": "^20.11.0", + "@types/vscode": "^1.85.0", + "@vscode/vsce": "^2.22.0", + "esbuild": "^0.19.0", + "typescript": "^5.3.0", + "vitest": "^1.2.0" + }, + "engines": { + "vscode": "^1.85.0" + } + }, + "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.1", + "resolved": "https://registry.npmjs.org/@azure/core-client/-/core-client-1.10.1.tgz", + "integrity": "sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w==", + "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.22.2", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.2.tgz", + "integrity": "sha512-MzHym+wOi8CLUlKCQu12de0nwcq9k9Kuv43j4Wa++CsCpJwps2eeBQwD2Bu8snkxTtDKDx4GwjuR9E8yC8LNrg==", + "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.0", + "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.0", + "resolved": "https://registry.npmjs.org/@azure/identity/-/identity-4.13.0.tgz", + "integrity": "sha512-uWC0fssc+hs1TGGVkkghiaFkkS7NkTxfnCH+Hdg+yTehTpMcehpok4PgUKKdyCH+9ldu6FhiHRv84Ntqj1vVcw==", + "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": "^4.2.0", + "@azure/msal-node": "^3.5.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": "4.28.2", + "resolved": "https://registry.npmjs.org/@azure/msal-browser/-/msal-browser-4.28.2.tgz", + "integrity": "sha512-6vYUMvs6kJxJgxaCmHn/F8VxjLHNh7i9wzfwPGf8kyBJ8Gg2yvBXx175Uev8LdrD1F5C4o7qHa2CC4IrhGE1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "15.14.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-common": { + "version": "15.14.2", + "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-15.14.2.tgz", + "integrity": "sha512-n8RBJEUmd5QotoqbZfd+eGBkzuFI1KX6jw2b3WcpSyGjwmzoeI/Jb99opIBPHpb8y312NB+B6+FGi2ZVSR8yfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@azure/msal-node": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-3.8.7.tgz", + "integrity": "sha512-a+Xnrae+uwLnlw68bplS1X4kuJ9F/7K6afuMFyRkNIskhjgDezl5Fhrx+1pmAlDmC0VaaAxjRQMp1OmcqVwkIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/msal-common": "15.14.2", + "jsonwebtoken": "^9.0.0", + "uuid": "^8.3.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", + "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", + "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", + "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", + "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", + "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", + "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", + "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", + "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", + "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", + "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", + "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", + "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", + "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", + "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", + "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", + "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", + "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", + "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", + "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", + "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", + "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", + "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.19.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", + "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.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/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.33", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.33.tgz", + "integrity": "sha512-Rs1bVAIdBs5gbTIKza/tgpMuG1k3U/UMJLWecIMxNdJFDMzcM5LOiLVRYh3PilWEYDIeUDv7bpiHPLPsbydGcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/vscode": { + "version": "1.109.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.109.0.tgz", + "integrity": "sha512-0Pf95rnwEIwDbmXGC08r0B4TQhAbsHQ5UyTIgVgoieDe4cOnf92usuR5dEczb6bTKEp7ziZH4TV1TRGPPCExtw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typespec/ts-http-runtime": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.3.tgz", + "integrity": "sha512-91fp6CAAJSRtH5ja95T1FHSKa8aPW9/Zw6cta81jlZTUw/+Vq8jM/AfF/14h2b71wwR84JUTW/3Y8QPhDAawFA==", + "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/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vscode/vsce": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/@vscode/vsce/-/vsce-2.32.0.tgz", + "integrity": "sha512-3EFJfsgrSftIqt3EtdRcAygy/OJ3hstyI1cDmIgkU9CFZW5C+3djr6mfosndCUqcVYuyjmxOK1xmFp/Bq7+NIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@azure/identity": "^4.1.0", + "@vscode/vsce-sign": "^2.0.0", + "azure-devops-node-api": "^12.5.0", + "chalk": "^2.4.2", + "cheerio": "^1.0.0-rc.9", + "cockatiel": "^3.1.2", + "commander": "^6.2.1", + "form-data": "^4.0.0", + "glob": "^7.0.6", + "hosted-git-info": "^4.0.2", + "jsonc-parser": "^3.2.0", + "leven": "^3.1.0", + "markdown-it": "^12.3.2", + "mime": "^1.3.4", + "minimatch": "^3.0.3", + "parse-semver": "^1.1.1", + "read": "^1.0.7", + "semver": "^7.5.2", + "tmp": "^0.2.1", + "typed-rest-client": "^1.8.4", + "url-join": "^4.0.1", + "xml2js": "^0.5.0", + "yauzl": "^2.3.1", + "yazl": "^2.2.2" + }, + "bin": { + "vsce": "vsce" + }, + "engines": { + "node": ">= 16" + }, + "optionalDependencies": { + "keytar": "^7.7.0" + } + }, + "node_modules/@vscode/vsce-sign": { + "version": "2.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/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "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/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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/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/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/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "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/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "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/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/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": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "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": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "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/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-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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/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/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/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/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.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "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.19.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", + "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.19.12", + "@esbuild/android-arm": "0.19.12", + "@esbuild/android-arm64": "0.19.12", + "@esbuild/android-x64": "0.19.12", + "@esbuild/darwin-arm64": "0.19.12", + "@esbuild/darwin-x64": "0.19.12", + "@esbuild/freebsd-arm64": "0.19.12", + "@esbuild/freebsd-x64": "0.19.12", + "@esbuild/linux-arm": "0.19.12", + "@esbuild/linux-arm64": "0.19.12", + "@esbuild/linux-ia32": "0.19.12", + "@esbuild/linux-loong64": "0.19.12", + "@esbuild/linux-mips64el": "0.19.12", + "@esbuild/linux-ppc64": "0.19.12", + "@esbuild/linux-riscv64": "0.19.12", + "@esbuild/linux-s390x": "0.19.12", + "@esbuild/linux-x64": "0.19.12", + "@esbuild/netbsd-x64": "0.19.12", + "@esbuild/openbsd-x64": "0.19.12", + "@esbuild/sunos-x64": "0.19.12", + "@esbuild/win32-arm64": "0.19.12", + "@esbuild/win32-ia32": "0.19.12", + "@esbuild/win32-x64": "0.19.12" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "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/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "dev": true, + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "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.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-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "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/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/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "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.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "dev": true, + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/htmlparser2": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", + "integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "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/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, + "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/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-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-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-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "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/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/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "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/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/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/linkify-it": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", + "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^1.0.1" + } + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "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/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", + "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, + "bin": { + "markdown-it": "bin/markdown-it.js" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", + "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", + "dev": true, + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdurl": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", + "integrity": "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g==", + "dev": true, + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "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-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "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": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT", + "optional": true + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "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/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "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/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "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/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==", + "dev": true, + "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-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/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "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/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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==", + "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/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "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/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "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/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/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "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/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "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.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "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.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "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/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "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/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "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/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "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-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "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": "1.0.6", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", + "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/underscore": { + "version": "1.13.7", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", + "integrity": "sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.22.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.22.0.tgz", + "integrity": "sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==", + "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/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", + "optional": true + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "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/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "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/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/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/yazl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", + "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3" + } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/vscode-extension/package.json b/vscode-extension/package.json new file mode 100644 index 0000000..af0638f --- /dev/null +++ b/vscode-extension/package.json @@ -0,0 +1,240 @@ +{ + "name": "mariadb-profiler-viewer", + "displayName": "MariaDB Profiler Viewer", + "description": "Visualize and analyze MariaDB/MySQL query profiling data from php-ext-mariadb-salvage", + "version": "0.1.0", + "publisher": "mariadb-profiler", + "engines": { + "vscode": "^1.85.0" + }, + "categories": [ + "Other", + "Debuggers" + ], + "activationEvents": [], + "main": "./dist/extension.js", + "contributes": { + "viewsContainers": { + "activitybar": [ + { + "id": "mariadb-profiler", + "title": "MariaDB Profiler", + "icon": "resources/icons/profiler.svg" + } + ] + }, + "views": { + "mariadb-profiler": [ + { + "id": "mariadbProfiler.jobs", + "name": "Jobs", + "type": "tree" + }, + { + "id": "mariadbProfiler.queries", + "name": "Queries", + "type": "tree" + }, + { + "id": "mariadbProfiler.statistics", + "name": "Statistics", + "type": "tree" + } + ] + }, + "commands": [ + { + "command": "mariadbProfiler.startJob", + "title": "Start Profiling Job", + "category": "MariaDB Profiler", + "icon": "$(play)" + }, + { + "command": "mariadbProfiler.stopJob", + "title": "Stop Profiling Job", + "category": "MariaDB Profiler", + "icon": "$(debug-stop)" + }, + { + "command": "mariadbProfiler.openLog", + "title": "Open Profiler Log", + "category": "MariaDB Profiler", + "icon": "$(folder-opened)" + }, + { + "command": "mariadbProfiler.refresh", + "title": "Refresh", + "category": "MariaDB Profiler", + "icon": "$(refresh)" + }, + { + "command": "mariadbProfiler.filterByType", + "title": "Filter by Query Type", + "category": "MariaDB Profiler", + "icon": "$(filter)" + }, + { + "command": "mariadbProfiler.filterByTag", + "title": "Filter by Tag", + "category": "MariaDB Profiler", + "icon": "$(tag)" + }, + { + "command": "mariadbProfiler.searchQuery", + "title": "Search Queries", + "category": "MariaDB Profiler", + "icon": "$(search)" + }, + { + "command": "mariadbProfiler.clearFilter", + "title": "Clear Filters", + "category": "MariaDB Profiler", + "icon": "$(clear-all)" + }, + { + "command": "mariadbProfiler.showQuerySql", + "title": "Show Full SQL", + "category": "MariaDB Profiler", + "icon": "$(open-preview)" + }, + { + "command": "mariadbProfiler.startLiveTail", + "title": "Start Live Tail", + "category": "MariaDB Profiler", + "icon": "$(eye)" + }, + { + "command": "mariadbProfiler.stopLiveTail", + "title": "Stop Live Tail", + "category": "MariaDB Profiler", + "icon": "$(eye-closed)" + } + ], + "menus": { + "view/title": [ + { + "command": "mariadbProfiler.startJob", + "when": "view == mariadbProfiler.jobs", + "group": "navigation@1" + }, + { + "command": "mariadbProfiler.stopJob", + "when": "view == mariadbProfiler.jobs", + "group": "navigation@2" + }, + { + "command": "mariadbProfiler.refresh", + "when": "view == mariadbProfiler.jobs", + "group": "navigation@3" + }, + { + "command": "mariadbProfiler.filterByType", + "when": "view == mariadbProfiler.queries", + "group": "navigation@1" + }, + { + "command": "mariadbProfiler.filterByTag", + "when": "view == mariadbProfiler.queries", + "group": "navigation@2" + }, + { + "command": "mariadbProfiler.searchQuery", + "when": "view == mariadbProfiler.queries", + "group": "navigation@3" + }, + { + "command": "mariadbProfiler.clearFilter", + "when": "view == mariadbProfiler.queries && mariadbProfiler.hasFilter", + "group": "navigation@4" + }, + { + "command": "mariadbProfiler.startLiveTail", + "when": "view == mariadbProfiler.queries && !mariadbProfiler.liveTailActive", + "group": "2_liveTail" + }, + { + "command": "mariadbProfiler.stopLiveTail", + "when": "view == mariadbProfiler.queries && mariadbProfiler.liveTailActive", + "group": "2_liveTail" + } + ], + "view/item/context": [ + { + "command": "mariadbProfiler.showQuerySql", + "when": "view == mariadbProfiler.queries && viewItem == queryEntry", + "group": "inline" + }, + { + "command": "mariadbProfiler.stopJob", + "when": "view == mariadbProfiler.jobs && viewItem == activeJob" + } + ] + }, + "configuration": { + "title": "MariaDB Profiler", + "properties": { + "mariadbProfiler.logDirectory": { + "type": "string", + "default": "/tmp/mariadb_profiler", + "description": "Directory where profiler writes jobs.json and *.jsonl files" + }, + "mariadbProfiler.phpPath": { + "type": "string", + "default": "php", + "description": "Path to PHP executable for CLI operations" + }, + "mariadbProfiler.cliScriptPath": { + "type": "string", + "default": "", + "description": "Path to mariadb_profiler.php CLI tool (auto-detected from workspace if empty)" + }, + "mariadbProfiler.maxQueries": { + "type": "number", + "default": 10000, + "description": "Maximum number of queries to display" + }, + "mariadbProfiler.refreshInterval": { + "type": "number", + "default": 5, + "description": "Auto-refresh interval in seconds" + }, + "mariadbProfiler.tailBufferSize": { + "type": "number", + "default": 500, + "description": "Number of lines to keep in live tail buffer" + }, + "mariadbProfiler.pathMappings": { + "type": "object", + "default": {}, + "description": "Path mappings for Docker environments (container path -> local path)", + "additionalProperties": { + "type": "string" + } + }, + "mariadbProfiler.frameResolverScript": { + "type": "string", + "default": "", + "description": "JavaScript code for custom frame resolution (receives trace, tag, query variables)" + } + } + } + }, + "scripts": { + "vscode:prepublish": "npm run build", + "build": "node esbuild.mjs --production", + "watch": "node esbuild.mjs --watch", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest", + "lint": "tsc --noEmit", + "package": "vsce package" + }, + "devDependencies": { + "@types/node": "^20.11.0", + "@types/vscode": "^1.85.0", + "@vscode/vsce": "^2.22.0", + "esbuild": "^0.19.0", + "typescript": "^5.3.0", + "vitest": "^1.2.0" + } +} diff --git a/vscode-extension/resources/icons/profiler.svg b/vscode-extension/resources/icons/profiler.svg new file mode 100644 index 0000000..54a2c3a --- /dev/null +++ b/vscode-extension/resources/icons/profiler.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/vscode-extension/src/command/filterQueries.ts b/vscode-extension/src/command/filterQueries.ts new file mode 100644 index 0000000..1ebf9ec --- /dev/null +++ b/vscode-extension/src/command/filterQueries.ts @@ -0,0 +1,99 @@ +import * as vscode from 'vscode'; +import { QueryTreeProvider } from '../provider/QueryTreeProvider'; +import { getQueryType, QueryType } from '../model/QueryEntry'; + +export function registerFilterByTypeCommand( + context: vscode.ExtensionContext, + queryTreeProvider: QueryTreeProvider, +): vscode.Disposable { + return vscode.commands.registerCommand('mariadbProfiler.filterByType', async () => { + const entries = queryTreeProvider.getEntries(); + + // Count queries by type + const typeCounts: Record = {}; + for (const entry of entries) { + const qtype = getQueryType(entry); + typeCounts[qtype] = (typeCounts[qtype] || 0) + 1; + } + + const items: vscode.QuickPickItem[] = [ + { label: 'All', description: `(${entries.length} queries)`, detail: 'Clear type filter' }, + ]; + + for (const [type, count] of Object.entries(typeCounts)) { + items.push({ label: type, description: `(${count} queries)` }); + } + + const selected = await vscode.window.showQuickPick(items, { + placeHolder: 'Filter by query type', + }); + + if (!selected) { return; } + + if (selected.label === 'All') { + queryTreeProvider.setFilter(null); + } else { + queryTreeProvider.setFilter(selected.label); + } + + updateFilterContext(queryTreeProvider); + }); +} + +export function registerFilterByTagCommand( + context: vscode.ExtensionContext, + queryTreeProvider: QueryTreeProvider, +): vscode.Disposable { + return vscode.commands.registerCommand('mariadbProfiler.filterByTag', async () => { + const entries = queryTreeProvider.getEntries(); + const tags = queryTreeProvider.getAllTags(); + + // Count by tag + const tagCounts: Record = {}; + for (const entry of entries) { + if (entry.tag) { + tagCounts[entry.tag] = (tagCounts[entry.tag] || 0) + 1; + } + } + + const items: vscode.QuickPickItem[] = [ + { label: 'All', description: `(${entries.length} queries)`, detail: 'Clear tag filter' }, + ]; + + for (const tag of tags) { + items.push({ label: tag, description: `(${tagCounts[tag] || 0} queries)` }); + } + + const selected = await vscode.window.showQuickPick(items, { + placeHolder: 'Filter by tag', + }); + + if (!selected) { return; } + + if (selected.label === 'All') { + queryTreeProvider.setTagFilter(null); + } else { + queryTreeProvider.setTagFilter(selected.label); + } + + updateFilterContext(queryTreeProvider); + }); +} + +export function registerClearFilterCommand( + context: vscode.ExtensionContext, + queryTreeProvider: QueryTreeProvider, +): vscode.Disposable { + return vscode.commands.registerCommand('mariadbProfiler.clearFilter', () => { + queryTreeProvider.clearFilters(); + updateFilterContext(queryTreeProvider); + }); +} + +export function updateFilterContext(queryTreeProvider: QueryTreeProvider): void { + vscode.commands.executeCommand( + 'setContext', + 'mariadbProfiler.hasFilter', + queryTreeProvider.hasActiveFilters(), + ); +} diff --git a/vscode-extension/src/command/liveTail.ts b/vscode-extension/src/command/liveTail.ts new file mode 100644 index 0000000..b8988e9 --- /dev/null +++ b/vscode-extension/src/command/liveTail.ts @@ -0,0 +1,150 @@ +import * as vscode from 'vscode'; +import { JobManagerService } from '../service/JobManagerService'; +import { LogParserService } from '../service/LogParserService'; +import { FileWatcherService } from '../service/FileWatcherService'; +import { getQueryType, getShortSql, formatTimestamp } from '../model/QueryEntry'; + +export class LiveTailManager implements vscode.Disposable { + private outputChannel: vscode.OutputChannel; + private fileWatcher: FileWatcherService; + private logParser: LogParserService; + private jobManager: JobManagerService; + + private isActive = false; + private currentJobKey: string | null = null; + private jsonlOffset = 0; + + constructor( + outputChannel: vscode.OutputChannel, + fileWatcher: FileWatcherService, + logParser: LogParserService, + jobManager: JobManagerService, + ) { + this.outputChannel = outputChannel; + this.fileWatcher = fileWatcher; + this.logParser = logParser; + this.jobManager = jobManager; + } + + start(jobKey: string): void { + this.stop(); + + this.currentJobKey = jobKey; + this.isActive = true; + this.jsonlOffset = 0; + + vscode.commands.executeCommand('setContext', 'mariadbProfiler.liveTailActive', true); + + this.outputChannel.clear(); + this.outputChannel.appendLine(`--- Live Tail: ${jobKey} ---`); + this.outputChannel.appendLine(''); + this.outputChannel.show(true); // Don't steal focus + + const jsonlPath = this.jobManager.getJsonlPath(jobKey); + + // Initial load + this.readNewEntries(jsonlPath); + + // Watch for changes + this.fileWatcher.watchFile(jsonlPath, () => { + if (this.isActive) { + this.readNewEntries(jsonlPath); + } + }); + } + + stop(): void { + if (this.currentJobKey) { + const jsonlPath = this.jobManager.getJsonlPath(this.currentJobKey); + this.fileWatcher.unwatchFile(jsonlPath); + } + + this.isActive = false; + this.currentJobKey = null; + this.jsonlOffset = 0; + + vscode.commands.executeCommand('setContext', 'mariadbProfiler.liveTailActive', false); + } + + getIsActive(): boolean { + return this.isActive; + } + + getCurrentJobKey(): string | null { + return this.currentJobKey; + } + + dispose(): void { + this.stop(); + } + + private readNewEntries(jsonlPath: string): void { + const result = this.logParser.parseJsonlFileFromOffset(jsonlPath, this.jsonlOffset); + this.jsonlOffset = result.newOffset; + + for (const entry of result.entries) { + const qtype = getQueryType(entry); + const shortSql = getShortSql(entry, 80); + const tag = entry.tag ? ` [${entry.tag}]` : ''; + const status = entry.status || 'ok'; + const time = formatTimestamp(entry.timestamp); + + this.outputChannel.appendLine( + `[${time}] ${status.toUpperCase()}${tag} ${shortSql}` + ); + + // Show backtrace frames + if (entry.trace) { + for (const frame of entry.trace.slice(0, 3)) { + this.outputChannel.appendLine(` #${entry.trace.indexOf(frame)} ${frame.file}:${frame.line}`); + } + if (entry.trace.length > 3) { + this.outputChannel.appendLine(` ... (${entry.trace.length - 3} more frames)`); + } + this.outputChannel.appendLine(''); + } + } + } +} + +export function registerLiveTailCommands( + context: vscode.ExtensionContext, + liveTailManager: LiveTailManager, + jobManager: JobManagerService, +): vscode.Disposable[] { + const startCmd = vscode.commands.registerCommand('mariadbProfiler.startLiveTail', async () => { + const activeJobs = jobManager.getActiveJobs(); + if (activeJobs.length === 0) { + vscode.window.showInformationMessage('No active profiling jobs to tail'); + return; + } + + let jobKey: string; + + if (activeJobs.length === 1) { + jobKey = activeJobs[0].key; + } else { + const items = activeJobs.map(j => ({ + label: j.key, + description: `Started: ${new Date(j.startedAt * 1000).toLocaleString()}`, + })); + + const selected = await vscode.window.showQuickPick(items, { + placeHolder: 'Select a job to tail', + }); + + if (!selected) { return; } + jobKey = selected.label; + } + + liveTailManager.start(jobKey); + vscode.window.showInformationMessage(`Live tail started for job '${jobKey}'`); + }); + + const stopCmd = vscode.commands.registerCommand('mariadbProfiler.stopLiveTail', () => { + liveTailManager.stop(); + vscode.window.showInformationMessage('Live tail stopped'); + }); + + return [startCmd, stopCmd]; +} diff --git a/vscode-extension/src/command/openLog.ts b/vscode-extension/src/command/openLog.ts new file mode 100644 index 0000000..aae6ed6 --- /dev/null +++ b/vscode-extension/src/command/openLog.ts @@ -0,0 +1,29 @@ +import * as vscode from 'vscode'; +import { JobManagerService } from '../service/JobManagerService'; +import { LogParserService } from '../service/LogParserService'; +import { QueryTreeProvider } from '../provider/QueryTreeProvider'; + +export function registerOpenLogCommand( + context: vscode.ExtensionContext, + jobManager: JobManagerService, + logParser: LogParserService, + queryTreeProvider: QueryTreeProvider, +): vscode.Disposable { + return vscode.commands.registerCommand('mariadbProfiler.openLog', async () => { + const uris = await vscode.window.showOpenDialog({ + canSelectFiles: true, + canSelectMany: false, + filters: { 'JSONL files': ['jsonl'] }, + defaultUri: vscode.Uri.file(jobManager.getLogDir()), + title: 'Open Profiler Log File', + }); + + if (!uris || uris.length === 0) { return; } + + const filePath = uris[0].fsPath; + const entries = logParser.parseJsonlFile(filePath); + queryTreeProvider.loadEntries(entries); + + vscode.window.showInformationMessage(`Loaded ${entries.length} queries from log file`); + }); +} diff --git a/vscode-extension/src/command/searchQueries.ts b/vscode-extension/src/command/searchQueries.ts new file mode 100644 index 0000000..cdcefd9 --- /dev/null +++ b/vscode-extension/src/command/searchQueries.ts @@ -0,0 +1,27 @@ +import * as vscode from 'vscode'; +import { QueryTreeProvider } from '../provider/QueryTreeProvider'; +import { updateFilterContext } from './filterQueries'; + +export function registerSearchQueryCommand( + context: vscode.ExtensionContext, + queryTreeProvider: QueryTreeProvider, +): vscode.Disposable { + return vscode.commands.registerCommand('mariadbProfiler.searchQuery', async () => { + const text = await vscode.window.showInputBox({ + prompt: 'Search queries by SQL text', + placeHolder: 'e.g. users, SELECT, WHERE id =', + value: '', + }); + + // User cancelled + if (text === undefined) { return; } + + if (text === '') { + queryTreeProvider.setSearchText(null); + } else { + queryTreeProvider.setSearchText(text); + } + + updateFilterContext(queryTreeProvider); + }); +} diff --git a/vscode-extension/src/command/startJob.ts b/vscode-extension/src/command/startJob.ts new file mode 100644 index 0000000..b9fe36a --- /dev/null +++ b/vscode-extension/src/command/startJob.ts @@ -0,0 +1,27 @@ +import * as vscode from 'vscode'; +import { JobManagerService } from '../service/JobManagerService'; + +export function registerStartJobCommand( + context: vscode.ExtensionContext, + jobManager: JobManagerService, + onJobsChanged: () => void, +): vscode.Disposable { + return vscode.commands.registerCommand('mariadbProfiler.startJob', async () => { + const jobKey = await vscode.window.showInputBox({ + prompt: 'Enter a job key (leave empty for auto-generated key)', + placeHolder: 'my-profiling-session', + }); + + // User cancelled + if (jobKey === undefined) { return; } + + try { + const key = await jobManager.startJob(jobKey || undefined); + vscode.window.showInformationMessage(`Profiling job '${key}' started`); + onJobsChanged(); + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : String(e); + vscode.window.showErrorMessage(`Failed to start job: ${msg}`); + } + }); +} diff --git a/vscode-extension/src/command/stopJob.ts b/vscode-extension/src/command/stopJob.ts new file mode 100644 index 0000000..064bb18 --- /dev/null +++ b/vscode-extension/src/command/stopJob.ts @@ -0,0 +1,45 @@ +import * as vscode from 'vscode'; +import { JobManagerService } from '../service/JobManagerService'; +import { JobInfo } from '../model/JobInfo'; + +export function registerStopJobCommand( + context: vscode.ExtensionContext, + jobManager: JobManagerService, + onJobsChanged: () => void, +): vscode.Disposable { + return vscode.commands.registerCommand('mariadbProfiler.stopJob', async (jobItem?: { job: JobInfo }) => { + let jobKey: string | undefined; + + if (jobItem?.job) { + jobKey = jobItem.job.key; + } else { + // Show picker for active jobs + const activeJobs = jobManager.getActiveJobs(); + if (activeJobs.length === 0) { + vscode.window.showInformationMessage('No active profiling jobs'); + return; + } + + const items = activeJobs.map(j => ({ + label: j.key, + description: `Started: ${new Date(j.startedAt * 1000).toLocaleString()}`, + })); + + const selected = await vscode.window.showQuickPick(items, { + placeHolder: 'Select a job to stop', + }); + + if (!selected) { return; } + jobKey = selected.label; + } + + try { + await jobManager.stopJob(jobKey); + vscode.window.showInformationMessage(`Profiling job '${jobKey}' stopped`); + onJobsChanged(); + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : String(e); + vscode.window.showErrorMessage(`Failed to stop job: ${msg}`); + } + }); +} diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts new file mode 100644 index 0000000..08bd1ec --- /dev/null +++ b/vscode-extension/src/extension.ts @@ -0,0 +1,239 @@ +import * as vscode from 'vscode'; +import { LogParserService } from './service/LogParserService'; +import { JobManagerService } from './service/JobManagerService'; +import { StatisticsService } from './service/StatisticsService'; +import { FileWatcherService } from './service/FileWatcherService'; +import { FrameResolverService } from './service/FrameResolverService'; +import { JobTreeProvider } from './provider/JobTreeProvider'; +import { QueryTreeProvider } from './provider/QueryTreeProvider'; +import { StatisticsTreeProvider } from './provider/StatisticsTreeProvider'; +import { QueryDocumentProvider } from './provider/QueryDocumentProvider'; +import { registerStartJobCommand } from './command/startJob'; +import { registerStopJobCommand } from './command/stopJob'; +import { registerOpenLogCommand } from './command/openLog'; +import { + registerFilterByTypeCommand, + registerFilterByTagCommand, + registerClearFilterCommand, + updateFilterContext, +} from './command/filterQueries'; +import { registerSearchQueryCommand } from './command/searchQueries'; +import { LiveTailManager, registerLiveTailCommands } from './command/liveTail'; +import { JobInfo } from './model/JobInfo'; + +export function activate(context: vscode.ExtensionContext): void { + // --- Output Channels --- + const errorChannel = vscode.window.createOutputChannel('MariaDB Profiler'); + const liveTailChannel = vscode.window.createOutputChannel('MariaDB Profiler Live Tail'); + context.subscriptions.push(errorChannel, liveTailChannel); + + // --- Services --- + const logParser = new LogParserService(errorChannel); + const jobManager = new JobManagerService(errorChannel); + const statisticsService = new StatisticsService(); + const fileWatcher = new FileWatcherService(); + const frameResolver = new FrameResolverService(errorChannel); + context.subscriptions.push(fileWatcher); + + // --- UI Providers --- + const jobTreeProvider = new JobTreeProvider(); + const queryTreeProvider = new QueryTreeProvider(); + const statisticsTreeProvider = new StatisticsTreeProvider(); + const queryDocumentProvider = new QueryDocumentProvider(); + + // Register TreeViews + const jobTreeView = vscode.window.createTreeView('mariadbProfiler.jobs', { + treeDataProvider: jobTreeProvider, + showCollapseAll: false, + }); + const queryTreeView = vscode.window.createTreeView('mariadbProfiler.queries', { + treeDataProvider: queryTreeProvider, + showCollapseAll: true, + }); + const statisticsTreeView = vscode.window.createTreeView('mariadbProfiler.statistics', { + treeDataProvider: statisticsTreeProvider, + showCollapseAll: true, + }); + context.subscriptions.push(jobTreeView, queryTreeView, statisticsTreeView); + + // Register Virtual Document Provider + context.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider( + QueryDocumentProvider.scheme, + queryDocumentProvider, + ), + ); + + // --- Live Tail --- + const liveTailManager = new LiveTailManager( + liveTailChannel, fileWatcher, logParser, jobManager, + ); + context.subscriptions.push(liveTailManager); + + // --- State --- + let selectedJobKey: string | null = null; + let jsonlOffset = 0; + let refreshTimer: ReturnType | undefined; + + // --- Helper: Refresh jobs list --- + function refreshJobs(): void { + const jobs = jobManager.loadJobs(); + jobTreeProvider.refresh(jobs); + } + + // --- Helper: Load queries for a job --- + function loadJobQueries(jobKey: string): void { + const jsonlPath = jobManager.getJsonlPath(jobKey); + const entries = logParser.parseJsonlFile(jsonlPath); + + // Resolve frames + for (let i = 0; i < entries.length; i++) { + const frameIndex = frameResolver.resolve(entries[i]); + if (frameIndex > 0) { + queryTreeProvider.setResolvedFrame(i, frameIndex); + } + } + + queryTreeProvider.loadEntries(entries); + jsonlOffset = entries.length > 0 + ? require('fs').statSync(jsonlPath).size + : 0; + + // Update statistics + const stats = statisticsService.computeStats(entries); + statisticsTreeProvider.updateStats(stats); + + // Update filter context + updateFilterContext(queryTreeProvider); + } + + // --- Helper: Incremental update for active job --- + function updateActiveJob(): void { + if (!selectedJobKey) { return; } + + const jsonlPath = jobManager.getJsonlPath(selectedJobKey); + const result = logParser.parseJsonlFileFromOffset(jsonlPath, jsonlOffset); + + if (result.entries.length > 0) { + // Resolve frames for new entries + const startIndex = queryTreeProvider.getEntries().length; + for (let i = 0; i < result.entries.length; i++) { + const frameIndex = frameResolver.resolve(result.entries[i]); + if (frameIndex > 0) { + queryTreeProvider.setResolvedFrame(startIndex + i, frameIndex); + } + } + + queryTreeProvider.appendEntries(result.entries); + jsonlOffset = result.newOffset; + + // Recompute statistics + const allEntries = queryTreeProvider.getEntries(); + const stats = statisticsService.computeStats(allEntries); + statisticsTreeProvider.updateStats(stats); + } + + // Also refresh jobs list to pick up status changes + refreshJobs(); + } + + // --- Helper: Start/stop refresh timer --- + function startRefreshTimer(): void { + stopRefreshTimer(); + const interval = vscode.workspace.getConfiguration('mariadbProfiler') + .get('refreshInterval', 5) * 1000; + + refreshTimer = setInterval(() => { + updateActiveJob(); + }, interval); + } + + function stopRefreshTimer(): void { + if (refreshTimer) { + clearInterval(refreshTimer); + refreshTimer = undefined; + } + } + + // --- Job Selection Handler --- + const selectJobCmd = vscode.commands.registerCommand( + 'mariadbProfiler.selectJob', + (job: JobInfo) => { + selectedJobKey = job.key; + loadJobQueries(job.key); + + // Set up auto-refresh for active jobs + if (job.isActive) { + startRefreshTimer(); + } else { + stopRefreshTimer(); + } + }, + ); + context.subscriptions.push(selectJobCmd); + + // --- Show Full SQL Command --- + const showSqlCmd = vscode.commands.registerCommand( + 'mariadbProfiler.showQuerySql', + (item: { entry?: import('./model/QueryEntry').QueryEntry; entryIndex?: number }) => { + if (item?.entry) { + queryDocumentProvider.showQueryDetail(item.entry, item.entryIndex ?? 0); + } + }, + ); + context.subscriptions.push(showSqlCmd); + + // --- Register Commands --- + context.subscriptions.push( + registerStartJobCommand(context, jobManager, refreshJobs), + registerStopJobCommand(context, jobManager, refreshJobs), + registerOpenLogCommand(context, jobManager, logParser, queryTreeProvider), + registerFilterByTypeCommand(context, queryTreeProvider), + registerFilterByTagCommand(context, queryTreeProvider), + registerClearFilterCommand(context, queryTreeProvider), + registerSearchQueryCommand(context, queryTreeProvider), + ...registerLiveTailCommands(context, liveTailManager, jobManager), + ); + + // --- Refresh Command --- + const refreshCmd = vscode.commands.registerCommand('mariadbProfiler.refresh', () => { + refreshJobs(); + if (selectedJobKey) { + loadJobQueries(selectedJobKey); + } + }); + context.subscriptions.push(refreshCmd); + + // --- Watch jobs.json for external changes --- + const jobsJsonPath = jobManager.getJobsJsonPath(); + fileWatcher.watchFile(jobsJsonPath, () => { + refreshJobs(); + }); + + // --- Configuration change handler --- + const configWatcher = vscode.workspace.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('mariadbProfiler.frameResolverScript')) { + frameResolver.invalidateCache(); + } + if (e.affectsConfiguration('mariadbProfiler.refreshInterval') && selectedJobKey) { + startRefreshTimer(); + } + }); + context.subscriptions.push(configWatcher); + + // --- Cleanup on deactivate --- + context.subscriptions.push({ + dispose: () => { + stopRefreshTimer(); + }, + }); + + // --- Initial Load --- + refreshJobs(); + + errorChannel.appendLine('[MariaDB Profiler] Extension activated'); +} + +export function deactivate(): void { + // Cleanup handled by disposables +} diff --git a/vscode-extension/src/model/JobInfo.ts b/vscode-extension/src/model/JobInfo.ts new file mode 100644 index 0000000..4b2d1ff --- /dev/null +++ b/vscode-extension/src/model/JobInfo.ts @@ -0,0 +1,82 @@ +export interface JobData { + started_at: number; + ended_at?: number; + query_count?: number; + parent?: string | null; +} + +export interface JobsFile { + active_jobs: Record; + completed_jobs: Record; +} + +export interface JobInfo { + key: string; + startedAt: number; + endedAt?: number; + queryCount?: number; + parent?: string | null; + isActive: boolean; +} + +export function parseJobsFile(content: string): JobsFile { + const raw = JSON.parse(content); + return { + active_jobs: normalizeJobMap(raw.active_jobs), + completed_jobs: normalizeJobMap(raw.completed_jobs), + }; +} + +// PHP encodes empty associative arrays as [] instead of {} +function normalizeJobMap(value: unknown): Record { + if (Array.isArray(value) && value.length === 0) { + return {}; + } + if (typeof value === 'object' && value !== null) { + return value as Record; + } + return {}; +} + +export function jobsFileToJobInfos(jobsFile: JobsFile): JobInfo[] { + const jobs: JobInfo[] = []; + + for (const [key, data] of Object.entries(jobsFile.active_jobs)) { + jobs.push({ + key, + startedAt: data.started_at, + endedAt: data.ended_at, + queryCount: data.query_count, + parent: data.parent, + isActive: true, + }); + } + + for (const [key, data] of Object.entries(jobsFile.completed_jobs)) { + jobs.push({ + key, + startedAt: data.started_at, + endedAt: data.ended_at, + queryCount: data.query_count, + parent: data.parent, + isActive: false, + }); + } + + // Sort: active first, then by startedAt descending + jobs.sort((a, b) => { + if (a.isActive !== b.isActive) { return a.isActive ? -1 : 1; } + return b.startedAt - a.startedAt; + }); + + return jobs; +} + +export function formatDuration(startedAt: number, endedAt?: number): string { + const end = endedAt ?? Date.now() / 1000; + const seconds = end - startedAt; + if (seconds < 60) { return `${seconds.toFixed(1)}s`; } + const minutes = Math.floor(seconds / 60); + const secs = seconds % 60; + return `${minutes}m${secs.toFixed(0)}s`; +} diff --git a/vscode-extension/src/model/QueryEntry.ts b/vscode-extension/src/model/QueryEntry.ts new file mode 100644 index 0000000..96bd5e5 --- /dev/null +++ b/vscode-extension/src/model/QueryEntry.ts @@ -0,0 +1,167 @@ +export interface BacktraceFrame { + call: string; + file: string; + line: number; + function?: string; + class_name?: string; +} + +export interface RawQueryEntry { + k: string; + q: string; + ts: number; + tag?: string; + s?: string; + params?: (string | null)[]; + trace?: BacktraceFrame[]; +} + +export interface QueryEntry { + jobKey: string; + query: string; + timestamp: number; + tag?: string; + status?: string; + params?: (string | null)[]; + trace?: BacktraceFrame[]; +} + +export type QueryType = 'SELECT' | 'INSERT' | 'UPDATE' | 'DELETE' | 'OTHER'; + +export function fromRaw(raw: RawQueryEntry): QueryEntry { + return { + jobKey: raw.k, + query: raw.q, + timestamp: raw.ts, + tag: raw.tag, + status: raw.s, + params: raw.params, + trace: raw.trace, + }; +} + +export function getQueryType(entry: QueryEntry): QueryType { + const trimmed = entry.query.trimStart().toUpperCase(); + if (trimmed.startsWith('SELECT')) { return 'SELECT'; } + if (trimmed.startsWith('INSERT')) { return 'INSERT'; } + if (trimmed.startsWith('UPDATE')) { return 'UPDATE'; } + if (trimmed.startsWith('DELETE')) { return 'DELETE'; } + return 'OTHER'; +} + +export function getBoundQuery(entry: QueryEntry): string { + if (!entry.params || entry.params.length === 0) { + return entry.query; + } + + let paramIndex = 0; + const params = entry.params; + let result = ''; + let i = 0; + const q = entry.query; + + while (i < q.length) { + // Skip string literals + if (q[i] === '\'' || q[i] === '"') { + const quote = q[i]; + result += q[i++]; + while (i < q.length) { + if (q[i] === '\\') { + result += q[i++]; + if (i < q.length) { result += q[i++]; } + } else if (q[i] === quote) { + result += q[i++]; + break; + } else { + result += q[i++]; + } + } + continue; + } + + // Skip backtick-quoted identifiers + if (q[i] === '`') { + result += q[i++]; + while (i < q.length && q[i] !== '`') { + result += q[i++]; + } + if (i < q.length) { result += q[i++]; } + continue; + } + + // Skip line comments + if (q[i] === '-' && i + 1 < q.length && q[i + 1] === '-') { + while (i < q.length && q[i] !== '\n') { + result += q[i++]; + } + continue; + } + if (q[i] === '#') { + while (i < q.length && q[i] !== '\n') { + result += q[i++]; + } + continue; + } + + // Skip block comments + if (q[i] === '/' && i + 1 < q.length && q[i + 1] === '*') { + result += q[i++]; + result += q[i++]; + while (i < q.length && !(q[i] === '*' && i + 1 < q.length && q[i + 1] === '/')) { + result += q[i++]; + } + if (i < q.length) { result += q[i++]; result += q[i++]; } + continue; + } + + // Replace placeholder + if (q[i] === '?' && paramIndex < params.length) { + const param = params[paramIndex++]; + result += param === null ? 'NULL' : `'${param}'`; + i++; + continue; + } + + result += q[i++]; + } + + return result; +} + +const TABLE_PATTERNS = [ + /\bFROM\s+`?(\w+)`?/gi, + /\bJOIN\s+`?(\w+)`?/gi, + /\bUPDATE\s+`?(\w+)`?/gi, + /\bINTO\s+`?(\w+)`?/gi, + /\bDELETE\s+FROM\s+`?(\w+)`?/gi, +]; + +export function getTables(entry: QueryEntry): string[] { + const tables = new Set(); + for (const pattern of TABLE_PATTERNS) { + pattern.lastIndex = 0; + let match; + while ((match = pattern.exec(entry.query)) !== null) { + tables.add(match[1].toLowerCase()); + } + } + return [...tables].sort(); +} + +export function getShortSql(entry: QueryEntry, maxLen: number = 60): string { + const sql = entry.query.replace(/\s+/g, ' ').trim(); + if (sql.length <= maxLen) { return sql; } + return sql.substring(0, maxLen - 3) + '...'; +} + +export function getSourceFile(entry: QueryEntry): string | null { + if (!entry.trace || entry.trace.length === 0) { return null; } + const frame = entry.trace[0]; + return `${frame.file}:${frame.line}`; +} + +export function formatTimestamp(ts: number): string { + const date = new Date(ts * 1000); + return date.toTimeString().split(' ')[0] + + '.' + String(date.getMilliseconds()).padStart(3, '0'); +} diff --git a/vscode-extension/src/provider/JobTreeProvider.ts b/vscode-extension/src/provider/JobTreeProvider.ts new file mode 100644 index 0000000..e29d52e --- /dev/null +++ b/vscode-extension/src/provider/JobTreeProvider.ts @@ -0,0 +1,63 @@ +import * as vscode from 'vscode'; +import { JobInfo, formatDuration } from '../model/JobInfo'; +import { shortKey } from '../util/queryUtils'; + +export class JobTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + private jobs: JobInfo[] = []; + private _onJobSelected = new vscode.EventEmitter(); + readonly onJobSelected = this._onJobSelected.event; + + refresh(jobs: JobInfo[]): void { + this.jobs = jobs; + this._onDidChangeTreeData.fire(); + } + + getTreeItem(element: JobTreeItem): vscode.TreeItem { + return element; + } + + getChildren(): JobTreeItem[] { + return this.jobs.map(job => new JobTreeItem(job)); + } + + selectJob(job: JobInfo): void { + this._onJobSelected.fire(job); + } +} + +export class JobTreeItem extends vscode.TreeItem { + constructor(public readonly job: JobInfo) { + super(shortKey(job.key), vscode.TreeItemCollapsibleState.None); + + const queryInfo = job.queryCount !== undefined ? `${job.queryCount} queries` : 'recording...'; + const duration = formatDuration(job.startedAt, job.endedAt); + this.description = `${queryInfo}, ${duration}`; + + this.iconPath = new vscode.ThemeIcon( + job.isActive ? 'circle-filled' : 'circle-outline', + job.isActive + ? new vscode.ThemeColor('charts.green') + : new vscode.ThemeColor('charts.gray'), + ); + + this.contextValue = job.isActive ? 'activeJob' : 'completedJob'; + + this.command = { + command: 'mariadbProfiler.selectJob', + title: 'Select Job', + arguments: [job], + }; + + this.tooltip = [ + `Job: ${job.key}`, + `Status: ${job.isActive ? 'Active' : 'Completed'}`, + `Started: ${new Date(job.startedAt * 1000).toLocaleString()}`, + job.endedAt ? `Ended: ${new Date(job.endedAt * 1000).toLocaleString()}` : '', + job.queryCount !== undefined ? `Queries: ${job.queryCount}` : '', + job.parent ? `Parent: ${job.parent}` : '', + ].filter(Boolean).join('\n'); + } +} diff --git a/vscode-extension/src/provider/QueryDocumentProvider.ts b/vscode-extension/src/provider/QueryDocumentProvider.ts new file mode 100644 index 0000000..334de14 --- /dev/null +++ b/vscode-extension/src/provider/QueryDocumentProvider.ts @@ -0,0 +1,85 @@ +import * as vscode from 'vscode'; +import { QueryEntry, getBoundQuery, getQueryType, getTables, formatTimestamp } from '../model/QueryEntry'; + +export class QueryDocumentProvider implements vscode.TextDocumentContentProvider { + static readonly scheme = 'mariadb-profiler'; + + private _onDidChange = new vscode.EventEmitter(); + readonly onDidChange = this._onDidChange.event; + + private documents = new Map(); + + provideTextDocumentContent(uri: vscode.Uri): string { + const entry = this.documents.get(uri.toString()); + if (!entry) { return '-- Query not found'; } + + return this.formatQueryDocument(entry); + } + + async showQueryDetail(entry: QueryEntry, index: number): Promise { + const qtype = getQueryType(entry); + const uri = vscode.Uri.parse( + `${QueryDocumentProvider.scheme}:query-${index}.sql?ts=${entry.timestamp}` + ); + + this.documents.set(uri.toString(), entry); + this._onDidChange.fire(uri); + + const doc = await vscode.workspace.openTextDocument(uri); + await vscode.window.showTextDocument(doc, { + preview: true, + viewColumn: vscode.ViewColumn.One, + }); + } + + private formatQueryDocument(entry: QueryEntry): string { + const lines: string[] = []; + + // Header metadata as SQL comments + lines.push(`-- Job: ${entry.jobKey}`); + lines.push(`-- Time: ${new Date(entry.timestamp * 1000).toISOString()}`); + lines.push(`-- Type: ${getQueryType(entry)}`); + + if (entry.tag) { + lines.push(`-- Tag: ${entry.tag}`); + } + if (entry.status) { + lines.push(`-- Status: ${entry.status}`); + } + + const tables = getTables(entry); + if (tables.length > 0) { + lines.push(`-- Tables: ${tables.join(', ')}`); + } + + lines.push(''); + + // Main SQL + lines.push(entry.query); + lines.push(''); + + // Bound parameters + if (entry.params && entry.params.length > 0) { + lines.push('-- Bound Parameters:'); + entry.params.forEach((param, i) => { + lines.push(`-- ?${i + 1} = ${param === null ? 'NULL' : param}`); + }); + lines.push(''); + + // Resolved query + lines.push('-- Resolved Query:'); + lines.push(`-- ${getBoundQuery(entry).replace(/\n/g, '\n-- ')}`); + lines.push(''); + } + + // Backtrace + if (entry.trace && entry.trace.length > 0) { + lines.push('-- Backtrace:'); + entry.trace.forEach((frame, i) => { + lines.push(`-- #${i} ${frame.file}:${frame.line} ${frame.call}`); + }); + } + + return lines.join('\n'); + } +} diff --git a/vscode-extension/src/provider/QueryTreeProvider.ts b/vscode-extension/src/provider/QueryTreeProvider.ts new file mode 100644 index 0000000..b555d85 --- /dev/null +++ b/vscode-extension/src/provider/QueryTreeProvider.ts @@ -0,0 +1,282 @@ +import * as vscode from 'vscode'; +import { + QueryEntry, + BacktraceFrame, + getQueryType, + getShortSql, + getTables, + getBoundQuery, + formatTimestamp, + QueryType, +} from '../model/QueryEntry'; +import { applyPathMappings } from '../util/pathMapping'; + +type QueryTreeItem = QueryEntryItem | QueryMetadataItem | BacktraceHeaderItem | BacktraceFrameItem; + +export class QueryTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + private allEntries: QueryEntry[] = []; + private filteredEntries: QueryEntry[] = []; + private typeFilter: string | null = null; + private tagFilter: string | null = null; + private searchText: string | null = null; + private resolvedFrameMap = new Map(); // entryIndex -> frameIndex + + loadEntries(entries: QueryEntry[]): void { + this.allEntries = entries; + this.applyFilters(); + } + + appendEntries(entries: QueryEntry[]): void { + this.allEntries.push(...entries); + this.applyFilters(); + } + + setFilter(queryType: string | null): void { + this.typeFilter = queryType; + this.applyFilters(); + } + + setTagFilter(tag: string | null): void { + this.tagFilter = tag; + this.applyFilters(); + } + + setSearchText(text: string | null): void { + this.searchText = text; + this.applyFilters(); + } + + clearFilters(): void { + this.typeFilter = null; + this.tagFilter = null; + this.searchText = null; + this.applyFilters(); + } + + hasActiveFilters(): boolean { + return this.typeFilter !== null || this.tagFilter !== null || this.searchText !== null; + } + + getFilterDescription(): string { + const parts: string[] = []; + if (this.typeFilter) { parts.push(this.typeFilter); } + if (this.tagFilter) { parts.push(`tag:${this.tagFilter}`); } + if (this.searchText) { parts.push(`"${this.searchText}"`); } + return parts.length > 0 ? parts.join(', ') : ''; + } + + getEntries(): QueryEntry[] { + return this.allEntries; + } + + getFilteredEntries(): QueryEntry[] { + return this.filteredEntries; + } + + setResolvedFrame(entryIndex: number, frameIndex: number): void { + this.resolvedFrameMap.set(entryIndex, frameIndex); + } + + getAllTags(): string[] { + const tags = new Set(); + for (const entry of this.allEntries) { + if (entry.tag) { tags.add(entry.tag); } + } + return [...tags].sort(); + } + + getTreeItem(element: QueryTreeItem): vscode.TreeItem { + return element; + } + + getChildren(element?: QueryTreeItem): QueryTreeItem[] { + if (!element) { + // Root level: filtered query entries + return this.filteredEntries.map((entry, index) => { + const globalIndex = this.allEntries.indexOf(entry); + return new QueryEntryItem(entry, globalIndex); + }); + } + + if (element instanceof QueryEntryItem) { + return this.getQueryChildren(element); + } + + if (element instanceof BacktraceHeaderItem) { + return this.getBacktraceChildren(element); + } + + return []; + } + + private getQueryChildren(item: QueryEntryItem): QueryTreeItem[] { + const entry = item.entry; + const children: QueryTreeItem[] = []; + + // Tables + const tables = getTables(entry); + if (tables.length > 0) { + children.push(new QueryMetadataItem(`Tables: ${tables.join(', ')}`, 'symbol-class')); + } + + // Tags + if (entry.tag) { + children.push(new QueryMetadataItem(`Tag: ${entry.tag}`, 'tag')); + } + + // Status + if (entry.status) { + const icon = entry.status === 'ok' ? 'check' : 'error'; + children.push(new QueryMetadataItem(`Status: ${entry.status}`, icon)); + } + + // Params + if (entry.params && entry.params.length > 0) { + const paramStr = entry.params.map((p, i) => `?${i + 1} = ${p === null ? 'NULL' : p}`).join(', '); + children.push(new QueryMetadataItem(`Params: ${paramStr}`, 'symbol-parameter')); + } + + // Backtrace + if (entry.trace && entry.trace.length > 0) { + children.push(new BacktraceHeaderItem(entry.trace, item.entryIndex, this.resolvedFrameMap.get(item.entryIndex))); + } + + return children; + } + + private getBacktraceChildren(item: BacktraceHeaderItem): QueryTreeItem[] { + return item.frames.map((frame, frameIndex) => { + const isResolved = item.resolvedFrameIndex === frameIndex; + return new BacktraceFrameItem(frame, isResolved); + }); + } + + private applyFilters(): void { + const maxQueries = vscode.workspace.getConfiguration('mariadbProfiler') + .get('maxQueries', 10000); + + let entries = this.allEntries; + + if (this.typeFilter) { + const filter = this.typeFilter; + entries = entries.filter(e => getQueryType(e) === filter); + } + + if (this.tagFilter) { + const tag = this.tagFilter; + entries = entries.filter(e => e.tag === tag); + } + + if (this.searchText) { + const search = this.searchText.toLowerCase(); + entries = entries.filter(e => e.query.toLowerCase().includes(search)); + } + + this.filteredEntries = entries.slice(0, maxQueries); + this._onDidChangeTreeData.fire(undefined); + } +} + +const QUERY_TYPE_ICONS: Record = { + SELECT: 'database', + INSERT: 'add', + UPDATE: 'edit', + DELETE: 'trash', + OTHER: 'question', +}; + +const QUERY_TYPE_COLORS: Record = { + SELECT: 'charts.blue', + INSERT: 'charts.green', + UPDATE: 'charts.orange', + DELETE: 'charts.red', + OTHER: 'charts.gray', +}; + +export class QueryEntryItem extends vscode.TreeItem { + readonly entry: QueryEntry; + readonly entryIndex: number; + + constructor(entry: QueryEntry, entryIndex: number) { + const qtype = getQueryType(entry); + const shortSql = getShortSql(entry, 50); + const label = `${qtype} ${shortSql}`; + + super(label, vscode.TreeItemCollapsibleState.Collapsed); + + this.entry = entry; + this.entryIndex = entryIndex; + this.contextValue = 'queryEntry'; + + // Description: [tag] HH:MM:SS.mmm + const parts: string[] = []; + if (entry.tag) { parts.push(`[${entry.tag}]`); } + parts.push(formatTimestamp(entry.timestamp)); + this.description = parts.join(' '); + + this.iconPath = new vscode.ThemeIcon( + QUERY_TYPE_ICONS[qtype], + new vscode.ThemeColor(QUERY_TYPE_COLORS[qtype]), + ); + + this.tooltip = new vscode.MarkdownString(); + this.tooltip.appendCodeblock(getBoundQuery(entry), 'sql'); + } +} + +export class QueryMetadataItem extends vscode.TreeItem { + constructor(label: string, icon: string) { + super(label, vscode.TreeItemCollapsibleState.None); + this.iconPath = new vscode.ThemeIcon(icon); + this.contextValue = 'queryMetadata'; + } +} + +export class BacktraceHeaderItem extends vscode.TreeItem { + readonly frames: BacktraceFrame[]; + readonly entryIndex: number; + readonly resolvedFrameIndex?: number; + + constructor(frames: BacktraceFrame[], entryIndex: number, resolvedFrameIndex?: number) { + super(`Backtrace (${frames.length} frames)`, vscode.TreeItemCollapsibleState.Collapsed); + this.frames = frames; + this.entryIndex = entryIndex; + this.resolvedFrameIndex = resolvedFrameIndex; + this.iconPath = new vscode.ThemeIcon('debug-stackframe'); + this.contextValue = 'backtraceHeader'; + } +} + +export class BacktraceFrameItem extends vscode.TreeItem { + constructor(frame: BacktraceFrame, isResolved: boolean) { + const fileName = frame.file.split('/').pop() || frame.file; + const label = `${fileName}:${frame.line}`; + + super(label, vscode.TreeItemCollapsibleState.None); + + this.description = frame.call; + this.contextValue = 'backtraceFrame'; + + this.iconPath = new vscode.ThemeIcon( + 'arrow-right', + isResolved ? new vscode.ThemeColor('charts.green') : undefined, + ); + + // Map path for Docker environments + const localPath = applyPathMappings(frame.file); + + this.command = { + command: 'vscode.open', + title: 'Open File', + arguments: [ + vscode.Uri.file(localPath), + { selection: new vscode.Range(frame.line - 1, 0, frame.line - 1, 0) } as vscode.TextDocumentShowOptions, + ], + }; + + this.tooltip = `${frame.file}:${frame.line}\n${frame.call}`; + } +} diff --git a/vscode-extension/src/provider/StatisticsTreeProvider.ts b/vscode-extension/src/provider/StatisticsTreeProvider.ts new file mode 100644 index 0000000..809c4ef --- /dev/null +++ b/vscode-extension/src/provider/StatisticsTreeProvider.ts @@ -0,0 +1,94 @@ +import * as vscode from 'vscode'; +import { QueryStats } from '../service/StatisticsService'; +import { generateBar, formatPercent } from '../util/queryUtils'; + +type StatTreeItem = StatHeaderItem | StatCategoryItem | StatBarItem; + +export class StatisticsTreeProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData = new vscode.EventEmitter(); + readonly onDidChangeTreeData = this._onDidChangeTreeData.event; + + private stats: QueryStats | null = null; + + updateStats(stats: QueryStats): void { + this.stats = stats; + this._onDidChangeTreeData.fire(); + } + + clear(): void { + this.stats = null; + this._onDidChangeTreeData.fire(); + } + + getTreeItem(element: StatTreeItem): vscode.TreeItem { + return element; + } + + getChildren(element?: StatTreeItem): StatTreeItem[] { + if (!this.stats) { + return [new StatHeaderItem('No job selected', 'info')]; + } + + if (!element) { + // Root level + return [ + new StatHeaderItem(`Total Queries: ${this.stats.totalQueries}`, 'pulse'), + new StatCategoryItem('Query Types', 'symbol-enum', this.stats.byType, this.stats.totalQueries), + new StatCategoryItem('Top Tables', 'symbol-class', this.stats.byTable, this.stats.totalQueries), + new StatCategoryItem('Top Tags', 'tag', this.stats.byTag, this.stats.totalQueries), + ]; + } + + if (element instanceof StatCategoryItem) { + return this.getCategoryChildren(element); + } + + return []; + } + + private getCategoryChildren(item: StatCategoryItem): StatBarItem[] { + const entries = Object.entries(item.data); + if (entries.length === 0) { + return [new StatBarItem('(none)', '', 0)]; + } + + const maxValue = entries.length > 0 ? entries[0][1] : 0; + + return entries.slice(0, 10).map(([key, value]) => { + const bar = generateBar(value, maxValue); + const pct = formatPercent(value, item.total); + return new StatBarItem(`${key} ${bar}`, `${value} (${pct})`, value); + }); + } +} + +class StatHeaderItem extends vscode.TreeItem { + constructor(label: string, icon: string) { + super(label, vscode.TreeItemCollapsibleState.None); + this.iconPath = new vscode.ThemeIcon(icon); + this.contextValue = 'statHeader'; + } +} + +class StatCategoryItem extends vscode.TreeItem { + readonly data: Record; + readonly total: number; + + constructor(label: string, icon: string, data: Record, total: number) { + const count = Object.keys(data).length; + super(label, count > 0 ? vscode.TreeItemCollapsibleState.Expanded : vscode.TreeItemCollapsibleState.None); + this.data = data; + this.total = total; + this.description = `(${count})`; + this.iconPath = new vscode.ThemeIcon(icon); + this.contextValue = 'statCategory'; + } +} + +class StatBarItem extends vscode.TreeItem { + constructor(label: string, description: string, _value: number) { + super(label, vscode.TreeItemCollapsibleState.None); + this.description = description; + this.contextValue = 'statBar'; + } +} diff --git a/vscode-extension/src/service/FileWatcherService.ts b/vscode-extension/src/service/FileWatcherService.ts new file mode 100644 index 0000000..86d3bcb --- /dev/null +++ b/vscode-extension/src/service/FileWatcherService.ts @@ -0,0 +1,71 @@ +import * as fs from 'fs'; +import * as vscode from 'vscode'; + +interface WatchEntry { + callback: () => void; + lastSize: number; + lastMtime: number; +} + +export class FileWatcherService implements vscode.Disposable { + private watchers = new Map(); + private timer: ReturnType | undefined; + private pollIntervalMs = 1000; + + constructor() { + this.timer = setInterval(() => this.poll(), this.pollIntervalMs); + } + + watchFile(filePath: string, onChange: () => void): void { + this.watchers.set(filePath, { + callback: onChange, + lastSize: this.getFileSize(filePath), + lastMtime: this.getFileMtime(filePath), + }); + } + + unwatchFile(filePath: string): void { + this.watchers.delete(filePath); + } + + dispose(): void { + if (this.timer) { + clearInterval(this.timer); + this.timer = undefined; + } + this.watchers.clear(); + } + + private poll(): void { + for (const [filePath, entry] of this.watchers) { + const size = this.getFileSize(filePath); + const mtime = this.getFileMtime(filePath); + + if (size !== entry.lastSize || mtime !== entry.lastMtime) { + entry.lastSize = size; + entry.lastMtime = mtime; + try { + entry.callback(); + } catch (e) { + // Ignore callback errors + } + } + } + } + + private getFileSize(filePath: string): number { + try { + return fs.statSync(filePath).size; + } catch { + return -1; + } + } + + private getFileMtime(filePath: string): number { + try { + return fs.statSync(filePath).mtimeMs; + } catch { + return -1; + } + } +} diff --git a/vscode-extension/src/service/FrameResolverService.ts b/vscode-extension/src/service/FrameResolverService.ts new file mode 100644 index 0000000..a3bf1b3 --- /dev/null +++ b/vscode-extension/src/service/FrameResolverService.ts @@ -0,0 +1,73 @@ +import * as vm from 'vm'; +import * as vscode from 'vscode'; +import { QueryEntry, BacktraceFrame } from '../model/QueryEntry'; + +export class FrameResolverService { + private cachedScript: vm.Script | null = null; + private cachedScriptText: string = ''; + private errorChannel: vscode.OutputChannel; + + constructor(errorChannel: vscode.OutputChannel) { + this.errorChannel = errorChannel; + } + + resolve(entry: QueryEntry): number { + if (!entry.trace || entry.trace.length === 0) { return 0; } + + const scriptText = vscode.workspace.getConfiguration('mariadbProfiler') + .get('frameResolverScript', ''); + + if (!scriptText) { return 0; } + + try { + const script = this.getScript(scriptText); + if (!script) { return 0; } + + const sandbox: Record = { + trace: entry.trace.map(f => ({ + file: f.file, + line: f.line, + call: f.call, + function: f.function || '', + class_name: f.class_name || '', + })), + tag: entry.tag || '', + query: entry.query, + }; + + const context = vm.createContext(sandbox); + const result = script.runInContext(context, { timeout: 1000 }); + + if (typeof result === 'number' && result >= 0 && result < entry.trace.length) { + return result; + } + + return 0; + } catch (e) { + this.errorChannel.appendLine(`[FrameResolver] Error: ${e}`); + return 0; + } + } + + invalidateCache(): void { + this.cachedScript = null; + this.cachedScriptText = ''; + } + + private getScript(scriptText: string): vm.Script | null { + if (this.cachedScriptText === scriptText && this.cachedScript) { + return this.cachedScript; + } + + try { + this.cachedScript = new vm.Script(scriptText, { filename: 'frameResolver.js' }); + this.cachedScriptText = scriptText; + return this.cachedScript; + } catch (e) { + this.errorChannel.appendLine(`[FrameResolver] Compile error: ${e}`); + this.cachedScript = null; + this.cachedScriptText = scriptText; + return null; + } + } +} diff --git a/vscode-extension/src/service/JobManagerService.ts b/vscode-extension/src/service/JobManagerService.ts new file mode 100644 index 0000000..54e9b3e --- /dev/null +++ b/vscode-extension/src/service/JobManagerService.ts @@ -0,0 +1,116 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { execFile } from 'child_process'; +import { JobInfo, JobsFile, parseJobsFile, jobsFileToJobInfos } from '../model/JobInfo'; + +export class JobManagerService { + private errorChannel: vscode.OutputChannel; + + constructor(errorChannel: vscode.OutputChannel) { + this.errorChannel = errorChannel; + } + + loadJobs(): JobInfo[] { + const jobsPath = this.getJobsJsonPath(); + if (!fs.existsSync(jobsPath)) { return []; } + + try { + const content = fs.readFileSync(jobsPath, 'utf-8'); + const jobsFile = parseJobsFile(content); + return jobsFileToJobInfos(jobsFile); + } catch (e) { + this.errorChannel.appendLine(`[JobManager] Failed to load jobs.json: ${e}`); + return []; + } + } + + getActiveJobs(): JobInfo[] { + return this.loadJobs().filter(j => j.isActive); + } + + getCompletedJobs(): JobInfo[] { + return this.loadJobs().filter(j => !j.isActive); + } + + async startJob(jobKey?: string): Promise { + const args = ['job', 'start']; + if (jobKey) { args.push(jobKey); } + + const output = await this.runCli(args); + // CLI outputs: "Job '{key}' started" + const match = output.match(/Job '([^']+)' started/); + if (match) { return match[1]; } + + // Fallback: return the key if provided, or extract from output + return jobKey || output.trim(); + } + + async stopJob(jobKey: string): Promise { + await this.runCli(['job', 'end', jobKey]); + } + + getJsonlPath(jobKey: string): string { + return path.join(this.getLogDir(), `${jobKey}.jsonl`); + } + + getRawLogPath(jobKey: string): string { + return path.join(this.getLogDir(), `${jobKey}.raw.log`); + } + + getJobsJsonPath(): string { + return path.join(this.getLogDir(), 'jobs.json'); + } + + getLogDir(): string { + return vscode.workspace.getConfiguration('mariadbProfiler') + .get('logDirectory', '/tmp/mariadb_profiler'); + } + + private getPhpPath(): string { + return vscode.workspace.getConfiguration('mariadbProfiler') + .get('phpPath', 'php'); + } + + private getCliScriptPath(): string { + const configured = vscode.workspace.getConfiguration('mariadbProfiler') + .get('cliScriptPath', ''); + + if (configured) { return configured; } + + // Auto-detect from workspace + const workspaceFolders = vscode.workspace.workspaceFolders; + if (workspaceFolders) { + for (const folder of workspaceFolders) { + const candidate = path.join(folder.uri.fsPath, 'cli', 'mariadb_profiler.php'); + if (fs.existsSync(candidate)) { return candidate; } + } + } + + return ''; + } + + private runCli(args: string[]): Promise { + return new Promise((resolve, reject) => { + const phpPath = this.getPhpPath(); + const scriptPath = this.getCliScriptPath(); + + if (!scriptPath) { + reject(new Error('CLI script path not configured and not found in workspace')); + return; + } + + const cliArgs = [scriptPath, `--log-dir=${this.getLogDir()}`, ...args]; + + execFile(phpPath, cliArgs, { timeout: 60000 }, (error, stdout, stderr) => { + if (error) { + this.errorChannel.appendLine(`[CLI] Error: ${error.message}`); + if (stderr) { this.errorChannel.appendLine(`[CLI] stderr: ${stderr}`); } + reject(error); + return; + } + resolve(stdout); + }); + }); + } +} diff --git a/vscode-extension/src/service/LogParserService.ts b/vscode-extension/src/service/LogParserService.ts new file mode 100644 index 0000000..4a9626f --- /dev/null +++ b/vscode-extension/src/service/LogParserService.ts @@ -0,0 +1,90 @@ +import * as fs from 'fs'; +import * as vscode from 'vscode'; +import { QueryEntry, RawQueryEntry, fromRaw } from '../model/QueryEntry'; + +export class LogParserService { + private errorChannel: vscode.OutputChannel; + + constructor(errorChannel: vscode.OutputChannel) { + this.errorChannel = errorChannel; + } + + parseJsonlFile(filePath: string): QueryEntry[] { + if (!fs.existsSync(filePath)) { return []; } + + const content = fs.readFileSync(filePath, 'utf-8'); + return this.parseJsonlContent(content); + } + + parseJsonlFileFromOffset(filePath: string, offset: number): { entries: QueryEntry[]; newOffset: number } { + if (!fs.existsSync(filePath)) { + return { entries: [], newOffset: offset }; + } + + const stat = fs.statSync(filePath); + if (stat.size <= offset) { + return { entries: [], newOffset: offset }; + } + + const fd = fs.openSync(filePath, 'r'); + try { + const bufSize = stat.size - offset; + const buffer = Buffer.alloc(bufSize); + fs.readSync(fd, buffer, 0, bufSize, offset); + const content = buffer.toString('utf-8'); + const entries = this.parseJsonlContent(content); + return { entries, newOffset: stat.size }; + } finally { + fs.closeSync(fd); + } + } + + readRawLogTail(filePath: string, maxLines: number = 500): string { + if (!fs.existsSync(filePath)) { return ''; } + + const content = fs.readFileSync(filePath, 'utf-8'); + const lines = content.split('\n'); + const tail = lines.slice(-maxLines); + return tail.join('\n'); + } + + tailRawLog(filePath: string, offset: number): { content: string; newOffset: number } { + if (!fs.existsSync(filePath)) { + return { content: '', newOffset: offset }; + } + + const stat = fs.statSync(filePath); + if (stat.size <= offset) { + return { content: '', newOffset: offset }; + } + + const fd = fs.openSync(filePath, 'r'); + try { + const bufSize = stat.size - offset; + const buffer = Buffer.alloc(bufSize); + fs.readSync(fd, buffer, 0, bufSize, offset); + return { content: buffer.toString('utf-8'), newOffset: stat.size }; + } finally { + fs.closeSync(fd); + } + } + + private parseJsonlContent(content: string): QueryEntry[] { + const entries: QueryEntry[] = []; + const lines = content.split('\n'); + + for (const line of lines) { + const trimmed = line.trim(); + if (!trimmed) { continue; } + + try { + const raw: RawQueryEntry = JSON.parse(trimmed); + entries.push(fromRaw(raw)); + } catch (e) { + this.errorChannel.appendLine(`[LogParser] Failed to parse line: ${trimmed.substring(0, 100)}`); + } + } + + return entries; + } +} diff --git a/vscode-extension/src/service/StatisticsService.ts b/vscode-extension/src/service/StatisticsService.ts new file mode 100644 index 0000000..facc2a6 --- /dev/null +++ b/vscode-extension/src/service/StatisticsService.ts @@ -0,0 +1,48 @@ +import { QueryEntry, getQueryType, getTables } from '../model/QueryEntry'; + +export interface QueryStats { + totalQueries: number; + byType: Record; + byTable: Record; + byTag: Record; +} + +export class StatisticsService { + computeStats(entries: QueryEntry[]): QueryStats { + const byType: Record = {}; + const byTable: Record = {}; + const byTag: Record = {}; + + for (const entry of entries) { + // By type + const qtype = getQueryType(entry); + byType[qtype] = (byType[qtype] || 0) + 1; + + // By table + for (const table of getTables(entry)) { + byTable[table] = (byTable[table] || 0) + 1; + } + + // By tag + if (entry.tag) { + byTag[entry.tag] = (byTag[entry.tag] || 0) + 1; + } + } + + return { + totalQueries: entries.length, + byType: sortByValueDesc(byType), + byTable: sortByValueDesc(byTable), + byTag: sortByValueDesc(byTag), + }; + } +} + +function sortByValueDesc(record: Record): Record { + const sorted: Record = {}; + const entries = Object.entries(record).sort((a, b) => b[1] - a[1]); + for (const [key, value] of entries) { + sorted[key] = value; + } + return sorted; +} diff --git a/vscode-extension/src/util/pathMapping.ts b/vscode-extension/src/util/pathMapping.ts new file mode 100644 index 0000000..d995cca --- /dev/null +++ b/vscode-extension/src/util/pathMapping.ts @@ -0,0 +1,14 @@ +import * as vscode from 'vscode'; + +export function applyPathMappings(containerPath: string): string { + const config = vscode.workspace.getConfiguration('mariadbProfiler'); + const mappings = config.get>('pathMappings', {}); + + for (const [from, to] of Object.entries(mappings)) { + if (containerPath.startsWith(from)) { + return containerPath.replace(from, to); + } + } + + return containerPath; +} diff --git a/vscode-extension/src/util/queryUtils.ts b/vscode-extension/src/util/queryUtils.ts new file mode 100644 index 0000000..9e714a6 --- /dev/null +++ b/vscode-extension/src/util/queryUtils.ts @@ -0,0 +1,15 @@ +export function generateBar(value: number, max: number, barWidth: number = 20): string { + if (max === 0) { return '\u2591'.repeat(barWidth); } + const filled = Math.round((value / max) * barWidth); + return '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled); +} + +export function formatPercent(value: number, total: number): string { + if (total === 0) { return '0%'; } + return `${Math.round((value / total) * 100)}%`; +} + +export function shortKey(key: string, maxLen: number = 12): string { + if (key.length <= maxLen) { return key; } + return key.substring(0, maxLen); +} diff --git a/vscode-extension/test/unit/JobInfo.test.ts b/vscode-extension/test/unit/JobInfo.test.ts new file mode 100644 index 0000000..8d61485 --- /dev/null +++ b/vscode-extension/test/unit/JobInfo.test.ts @@ -0,0 +1,106 @@ +import { describe, it, expect } from 'vitest'; +import { + parseJobsFile, + jobsFileToJobInfos, + formatDuration, +} from '../../src/model/JobInfo'; + +describe('parseJobsFile', () => { + it('should parse standard jobs.json', () => { + const json = JSON.stringify({ + active_jobs: { + 'abc-123': { started_at: 1705970401.0, parent: null }, + }, + completed_jobs: { + 'def-456': { started_at: 1705970300.0, ended_at: 1705970400.0, query_count: 42 }, + }, + }); + + const result = parseJobsFile(json); + expect(Object.keys(result.active_jobs)).toEqual(['abc-123']); + expect(Object.keys(result.completed_jobs)).toEqual(['def-456']); + expect(result.active_jobs['abc-123'].started_at).toBe(1705970401.0); + expect(result.completed_jobs['def-456'].query_count).toBe(42); + }); + + it('should handle PHP empty array as empty map', () => { + const json = JSON.stringify({ + active_jobs: [], + completed_jobs: { 'def-456': { started_at: 1705970300.0 } }, + }); + + const result = parseJobsFile(json); + expect(result.active_jobs).toEqual({}); + expect(Object.keys(result.completed_jobs)).toEqual(['def-456']); + }); + + it('should handle both maps empty (PHP format)', () => { + const json = JSON.stringify({ + active_jobs: [], + completed_jobs: [], + }); + + const result = parseJobsFile(json); + expect(result.active_jobs).toEqual({}); + expect(result.completed_jobs).toEqual({}); + }); + + it('should handle parent field', () => { + const json = JSON.stringify({ + active_jobs: {}, + completed_jobs: { + 'child-job': { started_at: 100, ended_at: 200, query_count: 5, parent: 'parent-job' }, + }, + }); + + const result = parseJobsFile(json); + expect(result.completed_jobs['child-job'].parent).toBe('parent-job'); + }); +}); + +describe('jobsFileToJobInfos', () => { + it('should convert to sorted JobInfo array', () => { + const json = JSON.stringify({ + active_jobs: { + 'active-1': { started_at: 100 }, + }, + completed_jobs: { + 'completed-1': { started_at: 50, ended_at: 80, query_count: 10 }, + 'completed-2': { started_at: 90, ended_at: 95, query_count: 5 }, + }, + }); + + const jobsFile = parseJobsFile(json); + const infos = jobsFileToJobInfos(jobsFile); + + // Active jobs first + expect(infos[0].key).toBe('active-1'); + expect(infos[0].isActive).toBe(true); + + // Completed sorted by startedAt descending + expect(infos[1].key).toBe('completed-2'); + expect(infos[1].isActive).toBe(false); + expect(infos[2].key).toBe('completed-1'); + expect(infos[2].isActive).toBe(false); + expect(infos[2].queryCount).toBe(10); + }); + + it('should handle empty jobs file', () => { + const jobsFile = parseJobsFile(JSON.stringify({ active_jobs: [], completed_jobs: [] })); + expect(jobsFileToJobInfos(jobsFile)).toEqual([]); + }); +}); + +describe('formatDuration', () => { + it('should format short duration in seconds', () => { + expect(formatDuration(100, 103.2)).toBe('3.2s'); + }); + + it('should format longer duration with minutes', () => { + expect(formatDuration(100, 225)).toBe('2m5s'); + }); + + it('should handle zero duration', () => { + expect(formatDuration(100, 100)).toBe('0.0s'); + }); +}); diff --git a/vscode-extension/test/unit/LogParserService.test.ts b/vscode-extension/test/unit/LogParserService.test.ts new file mode 100644 index 0000000..55292f1 --- /dev/null +++ b/vscode-extension/test/unit/LogParserService.test.ts @@ -0,0 +1,164 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; + +// Mock vscode module for LogParserService +import { vi } from 'vitest'; +vi.mock('vscode', () => ({ + window: { + createOutputChannel: () => ({ + appendLine: () => {}, + show: () => {}, + clear: () => {}, + dispose: () => {}, + }), + }, + workspace: { + getConfiguration: () => ({ + get: (_key: string, defaultValue: unknown) => defaultValue, + }), + }, +})); + +import { LogParserService } from '../../src/service/LogParserService'; + +describe('LogParserService', () => { + let tmpDir: string; + let service: LogParserService; + const mockChannel = { + appendLine: () => {}, + show: () => {}, + clear: () => {}, + dispose: () => {}, + } as any; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'logparser-test-')); + service = new LogParserService(mockChannel); + }); + + afterEach(() => { + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + describe('parseJsonlFile', () => { + it('should parse valid JSONL file', () => { + const filePath = path.join(tmpDir, 'test.jsonl'); + const lines = [ + '{"k":"job1","q":"SELECT 1","ts":100}', + '{"k":"job1","q":"INSERT INTO t VALUES (1)","ts":101,"tag":"api"}', + ]; + fs.writeFileSync(filePath, lines.join('\n')); + + const entries = service.parseJsonlFile(filePath); + expect(entries).toHaveLength(2); + expect(entries[0].jobKey).toBe('job1'); + expect(entries[0].query).toBe('SELECT 1'); + expect(entries[1].tag).toBe('api'); + }); + + it('should skip invalid lines', () => { + const filePath = path.join(tmpDir, 'test.jsonl'); + const lines = [ + '{"k":"job1","q":"SELECT 1","ts":100}', + 'INVALID JSON', + '{"k":"job1","q":"SELECT 2","ts":102}', + ]; + fs.writeFileSync(filePath, lines.join('\n')); + + const entries = service.parseJsonlFile(filePath); + expect(entries).toHaveLength(2); + }); + + it('should return empty for non-existent file', () => { + const entries = service.parseJsonlFile('/nonexistent/file.jsonl'); + expect(entries).toEqual([]); + }); + + it('should handle empty file', () => { + const filePath = path.join(tmpDir, 'empty.jsonl'); + fs.writeFileSync(filePath, ''); + const entries = service.parseJsonlFile(filePath); + expect(entries).toEqual([]); + }); + + it('should handle trailing newline', () => { + const filePath = path.join(tmpDir, 'test.jsonl'); + fs.writeFileSync(filePath, '{"k":"j","q":"SELECT 1","ts":0}\n'); + const entries = service.parseJsonlFile(filePath); + expect(entries).toHaveLength(1); + }); + }); + + describe('parseJsonlFileFromOffset', () => { + it('should read new entries from offset', () => { + const filePath = path.join(tmpDir, 'test.jsonl'); + const line1 = '{"k":"job1","q":"SELECT 1","ts":100}\n'; + const line2 = '{"k":"job1","q":"SELECT 2","ts":101}\n'; + fs.writeFileSync(filePath, line1); + + // First read + const result1 = service.parseJsonlFileFromOffset(filePath, 0); + expect(result1.entries).toHaveLength(1); + expect(result1.newOffset).toBe(Buffer.byteLength(line1)); + + // Append new data + fs.appendFileSync(filePath, line2); + + // Second read from offset + const result2 = service.parseJsonlFileFromOffset(filePath, result1.newOffset); + expect(result2.entries).toHaveLength(1); + expect(result2.entries[0].query).toBe('SELECT 2'); + }); + + it('should return empty when no new data', () => { + const filePath = path.join(tmpDir, 'test.jsonl'); + fs.writeFileSync(filePath, '{"k":"j","q":"SELECT 1","ts":0}\n'); + + const result1 = service.parseJsonlFileFromOffset(filePath, 0); + const result2 = service.parseJsonlFileFromOffset(filePath, result1.newOffset); + expect(result2.entries).toHaveLength(0); + expect(result2.newOffset).toBe(result1.newOffset); + }); + + it('should handle non-existent file', () => { + const result = service.parseJsonlFileFromOffset('/nonexistent', 0); + expect(result.entries).toEqual([]); + expect(result.newOffset).toBe(0); + }); + }); + + describe('readRawLogTail', () => { + it('should read tail of raw log', () => { + const filePath = path.join(tmpDir, 'test.raw.log'); + const lines = Array.from({ length: 10 }, (_, i) => `line ${i}`); + fs.writeFileSync(filePath, lines.join('\n')); + + const result = service.readRawLogTail(filePath, 3); + const resultLines = result.split('\n'); + expect(resultLines).toHaveLength(3); + expect(resultLines[0]).toBe('line 7'); + expect(resultLines[2]).toBe('line 9'); + }); + + it('should return empty for non-existent file', () => { + expect(service.readRawLogTail('/nonexistent')).toBe(''); + }); + }); + + describe('tailRawLog', () => { + it('should read new content from offset', () => { + const filePath = path.join(tmpDir, 'test.raw.log'); + fs.writeFileSync(filePath, 'line 1\n'); + + const r1 = service.tailRawLog(filePath, 0); + expect(r1.content).toBe('line 1\n'); + + fs.appendFileSync(filePath, 'line 2\n'); + + const r2 = service.tailRawLog(filePath, r1.newOffset); + expect(r2.content).toBe('line 2\n'); + }); + }); +}); diff --git a/vscode-extension/test/unit/QueryEntry.test.ts b/vscode-extension/test/unit/QueryEntry.test.ts new file mode 100644 index 0000000..3fe623f --- /dev/null +++ b/vscode-extension/test/unit/QueryEntry.test.ts @@ -0,0 +1,239 @@ +import { describe, it, expect } from 'vitest'; +import { + QueryEntry, + RawQueryEntry, + fromRaw, + getQueryType, + getBoundQuery, + getTables, + getShortSql, + getSourceFile, + formatTimestamp, +} from '../../src/model/QueryEntry'; + +describe('fromRaw', () => { + it('should convert raw JSONL entry to QueryEntry', () => { + const raw: RawQueryEntry = { + k: 'job1', + q: 'SELECT * FROM users', + ts: 1705970401.123, + tag: 'api', + s: 'ok', + params: ['42'], + trace: [{ call: 'UserController->index', file: '/app/UserController.php', line: 42 }], + }; + + const entry = fromRaw(raw); + + expect(entry.jobKey).toBe('job1'); + expect(entry.query).toBe('SELECT * FROM users'); + expect(entry.timestamp).toBe(1705970401.123); + expect(entry.tag).toBe('api'); + expect(entry.status).toBe('ok'); + expect(entry.params).toEqual(['42']); + expect(entry.trace).toHaveLength(1); + expect(entry.trace![0].file).toBe('/app/UserController.php'); + }); + + it('should handle minimal entry', () => { + const raw: RawQueryEntry = { k: 'j', q: 'SELECT 1', ts: 0 }; + const entry = fromRaw(raw); + + expect(entry.jobKey).toBe('j'); + expect(entry.query).toBe('SELECT 1'); + expect(entry.tag).toBeUndefined(); + expect(entry.status).toBeUndefined(); + expect(entry.params).toBeUndefined(); + expect(entry.trace).toBeUndefined(); + }); +}); + +describe('getQueryType', () => { + it('should detect SELECT', () => { + expect(getQueryType({ jobKey: '', query: 'SELECT * FROM users', timestamp: 0 })).toBe('SELECT'); + }); + + it('should detect INSERT', () => { + expect(getQueryType({ jobKey: '', query: 'INSERT INTO logs VALUES (1)', timestamp: 0 })).toBe('INSERT'); + }); + + it('should detect UPDATE', () => { + expect(getQueryType({ jobKey: '', query: 'UPDATE users SET name = ?', timestamp: 0 })).toBe('UPDATE'); + }); + + it('should detect DELETE', () => { + expect(getQueryType({ jobKey: '', query: 'DELETE FROM logs WHERE id = 1', timestamp: 0 })).toBe('DELETE'); + }); + + it('should detect OTHER for non-standard queries', () => { + expect(getQueryType({ jobKey: '', query: 'SHOW TABLES', timestamp: 0 })).toBe('OTHER'); + }); + + it('should handle leading whitespace', () => { + expect(getQueryType({ jobKey: '', query: ' SELECT 1', timestamp: 0 })).toBe('SELECT'); + }); + + it('should be case-insensitive', () => { + expect(getQueryType({ jobKey: '', query: 'select * from t', timestamp: 0 })).toBe('SELECT'); + }); +}); + +describe('getBoundQuery', () => { + it('should return original query when no params', () => { + const entry: QueryEntry = { jobKey: '', query: 'SELECT 1', timestamp: 0 }; + expect(getBoundQuery(entry)).toBe('SELECT 1'); + }); + + it('should return original query when params is empty', () => { + const entry: QueryEntry = { jobKey: '', query: 'SELECT 1', timestamp: 0, params: [] }; + expect(getBoundQuery(entry)).toBe('SELECT 1'); + }); + + it('should bind single parameter', () => { + const entry: QueryEntry = { + jobKey: '', query: 'SELECT * FROM users WHERE id = ?', timestamp: 0, + params: ['42'], + }; + expect(getBoundQuery(entry)).toBe("SELECT * FROM users WHERE id = '42'"); + }); + + it('should bind multiple parameters', () => { + const entry: QueryEntry = { + jobKey: '', query: 'SELECT * FROM users WHERE name = ? AND id = ?', timestamp: 0, + params: ['John', '42'], + }; + expect(getBoundQuery(entry)).toBe("SELECT * FROM users WHERE name = 'John' AND id = '42'"); + }); + + it('should handle NULL parameters', () => { + const entry: QueryEntry = { + jobKey: '', query: 'INSERT INTO t (a) VALUES (?)', timestamp: 0, + params: [null], + }; + expect(getBoundQuery(entry)).toBe('INSERT INTO t (a) VALUES (NULL)'); + }); + + it('should not replace ? inside string literals', () => { + const entry: QueryEntry = { + jobKey: '', query: "SELECT * FROM t WHERE a = '?' AND b = ?", timestamp: 0, + params: ['val'], + }; + expect(getBoundQuery(entry)).toBe("SELECT * FROM t WHERE a = '?' AND b = 'val'"); + }); + + it('should not replace ? inside backtick identifiers', () => { + const entry: QueryEntry = { + jobKey: '', query: 'SELECT `?` FROM t WHERE a = ?', timestamp: 0, + params: ['val'], + }; + expect(getBoundQuery(entry)).toBe("SELECT `?` FROM t WHERE a = 'val'"); + }); + + it('should skip ? in line comments', () => { + const entry: QueryEntry = { + jobKey: '', query: 'SELECT 1 -- ? placeholder\nWHERE a = ?', timestamp: 0, + params: ['val'], + }; + expect(getBoundQuery(entry)).toBe("SELECT 1 -- ? placeholder\nWHERE a = 'val'"); + }); + + it('should skip ? in block comments', () => { + const entry: QueryEntry = { + jobKey: '', query: 'SELECT /* ? */ * FROM t WHERE a = ?', timestamp: 0, + params: ['val'], + }; + expect(getBoundQuery(entry)).toBe("SELECT /* ? */ * FROM t WHERE a = 'val'"); + }); +}); + +describe('getTables', () => { + it('should extract tables from SELECT', () => { + const entry: QueryEntry = { + jobKey: '', query: 'SELECT * FROM users u JOIN posts p ON p.user_id = u.id', timestamp: 0, + }; + expect(getTables(entry)).toEqual(['posts', 'users']); + }); + + it('should extract tables from INSERT', () => { + const entry: QueryEntry = { + jobKey: '', query: 'INSERT INTO logs (msg) VALUES (?)', timestamp: 0, + }; + expect(getTables(entry)).toEqual(['logs']); + }); + + it('should extract tables from UPDATE', () => { + const entry: QueryEntry = { + jobKey: '', query: 'UPDATE users SET name = ?', timestamp: 0, + }; + expect(getTables(entry)).toEqual(['users']); + }); + + it('should extract tables from DELETE', () => { + const entry: QueryEntry = { + jobKey: '', query: 'DELETE FROM logs WHERE id = 1', timestamp: 0, + }; + expect(getTables(entry)).toEqual(['logs']); + }); + + it('should handle backtick-quoted table names', () => { + const entry: QueryEntry = { + jobKey: '', query: 'SELECT * FROM `users`', timestamp: 0, + }; + expect(getTables(entry)).toEqual(['users']); + }); + + it('should deduplicate tables', () => { + const entry: QueryEntry = { + jobKey: '', query: 'SELECT * FROM users u WHERE u.id IN (SELECT user_id FROM users)', timestamp: 0, + }; + expect(getTables(entry)).toEqual(['users']); + }); +}); + +describe('getShortSql', () => { + it('should return short SQL as-is', () => { + const entry: QueryEntry = { jobKey: '', query: 'SELECT 1', timestamp: 0 }; + expect(getShortSql(entry)).toBe('SELECT 1'); + }); + + it('should truncate long SQL', () => { + const longQuery = 'SELECT ' + 'a'.repeat(100) + ' FROM users'; + const entry: QueryEntry = { jobKey: '', query: longQuery, timestamp: 0 }; + const result = getShortSql(entry, 20); + expect(result.length).toBe(20); + expect(result.endsWith('...')).toBe(true); + }); + + it('should normalize whitespace', () => { + const entry: QueryEntry = { jobKey: '', query: 'SELECT\n *\n FROM\n users', timestamp: 0 }; + expect(getShortSql(entry)).toBe('SELECT * FROM users'); + }); +}); + +describe('getSourceFile', () => { + it('should return null when no trace', () => { + const entry: QueryEntry = { jobKey: '', query: 'SELECT 1', timestamp: 0 }; + expect(getSourceFile(entry)).toBeNull(); + }); + + it('should return null when trace is empty', () => { + const entry: QueryEntry = { jobKey: '', query: 'SELECT 1', timestamp: 0, trace: [] }; + expect(getSourceFile(entry)).toBeNull(); + }); + + it('should return first frame as file:line', () => { + const entry: QueryEntry = { + jobKey: '', query: 'SELECT 1', timestamp: 0, + trace: [{ call: 'test()', file: '/app/Test.php', line: 42 }], + }; + expect(getSourceFile(entry)).toBe('/app/Test.php:42'); + }); +}); + +describe('formatTimestamp', () => { + it('should format unix timestamp to HH:MM:SS.mmm', () => { + const result = formatTimestamp(1705970401.123); + // Just check format, not exact value (timezone dependent) + expect(result).toMatch(/^\d{2}:\d{2}:\d{2}\.\d{3}$/); + }); +}); diff --git a/vscode-extension/test/unit/StatisticsService.test.ts b/vscode-extension/test/unit/StatisticsService.test.ts new file mode 100644 index 0000000..c36e00e --- /dev/null +++ b/vscode-extension/test/unit/StatisticsService.test.ts @@ -0,0 +1,75 @@ +import { describe, it, expect } from 'vitest'; +import { StatisticsService } from '../../src/service/StatisticsService'; +import { QueryEntry } from '../../src/model/QueryEntry'; + +describe('StatisticsService', () => { + const service = new StatisticsService(); + + it('should compute empty stats for no entries', () => { + const stats = service.computeStats([]); + expect(stats.totalQueries).toBe(0); + expect(stats.byType).toEqual({}); + expect(stats.byTable).toEqual({}); + expect(stats.byTag).toEqual({}); + }); + + it('should count queries by type', () => { + const entries: QueryEntry[] = [ + { jobKey: 'j', query: 'SELECT 1', timestamp: 0 }, + { jobKey: 'j', query: 'SELECT 2', timestamp: 0 }, + { jobKey: 'j', query: 'INSERT INTO t VALUES (1)', timestamp: 0 }, + { jobKey: 'j', query: 'UPDATE t SET a = 1', timestamp: 0 }, + { jobKey: 'j', query: 'DELETE FROM t', timestamp: 0 }, + ]; + + const stats = service.computeStats(entries); + expect(stats.totalQueries).toBe(5); + expect(stats.byType['SELECT']).toBe(2); + expect(stats.byType['INSERT']).toBe(1); + expect(stats.byType['UPDATE']).toBe(1); + expect(stats.byType['DELETE']).toBe(1); + }); + + it('should count queries by table', () => { + const entries: QueryEntry[] = [ + { jobKey: 'j', query: 'SELECT * FROM users', timestamp: 0 }, + { jobKey: 'j', query: 'SELECT * FROM users JOIN posts ON 1=1', timestamp: 0 }, + { jobKey: 'j', query: 'INSERT INTO posts VALUES (1)', timestamp: 0 }, + ]; + + const stats = service.computeStats(entries); + expect(stats.byTable['users']).toBe(2); + expect(stats.byTable['posts']).toBe(2); + }); + + it('should count queries by tag', () => { + const entries: QueryEntry[] = [ + { jobKey: 'j', query: 'SELECT 1', timestamp: 0, tag: 'api' }, + { jobKey: 'j', query: 'SELECT 2', timestamp: 0, tag: 'api' }, + { jobKey: 'j', query: 'SELECT 3', timestamp: 0, tag: 'web' }, + { jobKey: 'j', query: 'SELECT 4', timestamp: 0 }, // no tag + ]; + + const stats = service.computeStats(entries); + expect(stats.byTag['api']).toBe(2); + expect(stats.byTag['web']).toBe(1); + expect(stats.byTag['']).toBeUndefined(); // no-tag entries not counted + }); + + it('should sort by value descending', () => { + const entries: QueryEntry[] = [ + { jobKey: 'j', query: 'SELECT 1', timestamp: 0, tag: 'rare' }, + { jobKey: 'j', query: 'SELECT 2', timestamp: 0, tag: 'common' }, + { jobKey: 'j', query: 'SELECT 3', timestamp: 0, tag: 'common' }, + { jobKey: 'j', query: 'SELECT 4', timestamp: 0, tag: 'common' }, + { jobKey: 'j', query: 'SELECT 5', timestamp: 0, tag: 'medium' }, + { jobKey: 'j', query: 'SELECT 6', timestamp: 0, tag: 'medium' }, + ]; + + const stats = service.computeStats(entries); + const tagKeys = Object.keys(stats.byTag); + expect(tagKeys[0]).toBe('common'); + expect(tagKeys[1]).toBe('medium'); + expect(tagKeys[2]).toBe('rare'); + }); +}); diff --git a/vscode-extension/tsconfig.json b/vscode-extension/tsconfig.json new file mode 100644 index 0000000..8ec7dad --- /dev/null +++ b/vscode-extension/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2022", + "lib": ["ES2022"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "test"] +} diff --git a/vscode-extension/vitest.config.ts b/vscode-extension/vitest.config.ts new file mode 100644 index 0000000..252c33f --- /dev/null +++ b/vscode-extension/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + include: ['test/**/*.test.ts'], + globals: true, + }, +}); From 2acf90e2adecacf9e6c83558b2906295d424dce5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 11:05:52 +0000 Subject: [PATCH 4/8] Add GitHub Actions workflow for VSCode extension Runs on pushes/PRs that touch vscode-extension/: - TypeScript type check (tsc --noEmit) - Unit tests (vitest) - Production build (esbuild) Mirrors the existing plugin-build.yml pattern for JetBrains plugin. https://claude.ai/code/session_01EabgSSMXSA25Pjf1aTEmB2 --- .github/workflows/vscode-build.yml | 49 ++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 .github/workflows/vscode-build.yml diff --git a/.github/workflows/vscode-build.yml b/.github/workflows/vscode-build.yml new file mode 100644 index 0000000..d55c890 --- /dev/null +++ b/.github/workflows/vscode-build.yml @@ -0,0 +1,49 @@ +name: Build VSCode Extension + +on: + push: + branches: ['**'] + paths: + - 'vscode-extension/**' + - '.github/workflows/vscode-build.yml' + pull_request: + branches: ['**'] + paths: + - 'vscode-extension/**' + - '.github/workflows/vscode-build.yml' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + if: github.event_name != 'pull_request' || !contains(github.event.pull_request.labels.*.name, 'no_run') + name: Build & Test Extension + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: vscode-extension/package-lock.json + + - name: Install dependencies + working-directory: vscode-extension + run: npm ci + + - name: Type check + working-directory: vscode-extension + run: npx tsc --noEmit + + - name: Run tests + working-directory: vscode-extension + run: npx vitest run + + - name: Build + working-directory: vscode-extension + run: node esbuild.mjs --production From 68bf1085ce006fc33a17501baa5bee53aeddef8f Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 11:07:02 +0000 Subject: [PATCH 5/8] Add VSCode extension build to release workflow - New build-vscode job: npm ci, tsc, vitest, esbuild, vsce package - Produces .vsix artifact included in GitHub Release assets - Release job now depends on build-vscode alongside existing jobs https://claude.ai/code/session_01EabgSSMXSA25Pjf1aTEmB2 --- .github/workflows/release.yml | 47 +++++++++++++++++++++++++++++++++-- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 87a0717..2aa9a9f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -193,12 +193,55 @@ jobs: path: jetbrains-plugin/build/distributions/*.zip if-no-files-found: error + # --------------------------------------------------------------------------- + # VSCode Extension build + # --------------------------------------------------------------------------- + build-vscode: + name: VSCode Extension + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: vscode-extension/package-lock.json + + - name: Install dependencies + working-directory: vscode-extension + run: npm ci + + - name: Type check + working-directory: vscode-extension + run: npx tsc --noEmit + + - name: Run tests + working-directory: vscode-extension + run: npx vitest run + + - name: Build + working-directory: vscode-extension + run: node esbuild.mjs --production + + - name: Package .vsix + working-directory: vscode-extension + run: npx @vscode/vsce package --no-dependencies -o mariadb-profiler-viewer.vsix + + - uses: actions/upload-artifact@v4 + with: + name: mariadb-profiler-viewer-vsix + path: vscode-extension/*.vsix + if-no-files-found: error + # --------------------------------------------------------------------------- # Create GitHub Release # --------------------------------------------------------------------------- release: name: Create Release - needs: [build-linux, build-windows, build-plugin] + needs: [build-linux, build-windows, build-plugin, build-vscode] runs-on: ubuntu-latest permissions: contents: write @@ -214,7 +257,7 @@ jobs: - name: Collect release assets run: | mkdir -p release - find artifacts -type f \( -name "*.so" -o -name "*.dll" -o -name "*.zip" \) -exec cp {} release/ \; + find artifacts -type f \( -name "*.so" -o -name "*.dll" -o -name "*.zip" -o -name "*.vsix" \) -exec cp {} release/ \; echo "=== Release assets ===" ls -lh release/ From 3ce14ca569fc843e6a806ba212a29a1a27624e49 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 11:09:59 +0000 Subject: [PATCH 6/8] Add .vsix artifact upload to vscode-build workflow Package and upload .vsix on every push/PR, matching plugin-build.yml which uploads the JetBrains plugin .zip. https://claude.ai/code/session_01EabgSSMXSA25Pjf1aTEmB2 --- .github/workflows/vscode-build.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/vscode-build.yml b/.github/workflows/vscode-build.yml index d55c890..0fcc8d0 100644 --- a/.github/workflows/vscode-build.yml +++ b/.github/workflows/vscode-build.yml @@ -47,3 +47,14 @@ jobs: - name: Build working-directory: vscode-extension run: node esbuild.mjs --production + + - name: Package .vsix + working-directory: vscode-extension + run: npx @vscode/vsce package --no-dependencies -o mariadb-profiler-viewer.vsix + + - name: Upload extension artifact + uses: actions/upload-artifact@v4 + with: + name: mariadb-profiler-viewer-vsix + path: vscode-extension/*.vsix + if-no-files-found: error From 94bf38fae173f77eca492518682e7566a75d617c Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 11:28:15 +0000 Subject: [PATCH 7/8] Fix 19 issues across VSCode extension - liveTail.ts: include qtype in output, fix trace frame indexing for duplicates - package.json: bump esbuild ^0.27.3 / vitest ^4.0.18, add untrustedWorkspaces capability - openLog.ts: add try/catch for parseJsonlFile, refresh filter context and statistics - extension.ts: replace require('fs') with proper import, safe statSync, re-watch jobsJsonPath on logDirectory change - JobInfo.ts: fix formatDuration "1m60s" rounding bug, show "running..." for active jobs, ms for <1s - QueryEntry.ts: escape single quotes in bound params, support schema.table in TABLE_PATTERNS - JobTreeProvider.ts: replace unsupported 'charts.gray' ThemeColor with 'disabledForeground' - QueryDocumentProvider.ts: add FIFO cap (50) to documents Map, remove unused qtype variable - FrameResolverService.ts: add Workspace Trust guard, enforce integer-only indices, cache failed compilations - JobManagerService.ts: sanitize fallback startJob output with strict regex - LogParserService.ts: fix TOCTOU race by using fstatSync on opened fd, use bytesRead from readSync - pathMapping.ts: use startsWith+slice instead of replace to avoid replacement pattern injection - queryUtils.ts: clamp generateBar filled value to prevent RangeError - vscode-build.yml: use npm exec instead of npx for reproducible packaging https://claude.ai/code/session_01EabgSSMXSA25Pjf1aTEmB2 --- .github/workflows/vscode-build.yml | 2 +- vscode-extension/package-lock.json | 1661 +++++------------ vscode-extension/package.json | 10 +- vscode-extension/src/command/liveTail.ts | 7 +- vscode-extension/src/command/openLog.ts | 19 +- vscode-extension/src/extension.ts | 21 +- vscode-extension/src/model/JobInfo.ts | 12 +- vscode-extension/src/model/QueryEntry.ts | 14 +- .../src/provider/JobTreeProvider.ts | 2 +- .../src/provider/QueryDocumentProvider.ts | 12 +- .../src/service/FrameResolverService.ts | 16 +- .../src/service/JobManagerService.ts | 7 +- .../src/service/LogParserService.ts | 42 +- vscode-extension/src/util/pathMapping.ts | 2 +- vscode-extension/src/util/queryUtils.ts | 2 +- vscode-extension/test/unit/JobInfo.test.ts | 2 +- 16 files changed, 572 insertions(+), 1259 deletions(-) diff --git a/.github/workflows/vscode-build.yml b/.github/workflows/vscode-build.yml index 0fcc8d0..e89f553 100644 --- a/.github/workflows/vscode-build.yml +++ b/.github/workflows/vscode-build.yml @@ -50,7 +50,7 @@ jobs: - name: Package .vsix working-directory: vscode-extension - run: npx @vscode/vsce package --no-dependencies -o mariadb-profiler-viewer.vsix + run: npm exec @vscode/vsce -- package --no-dependencies -o mariadb-profiler-viewer.vsix - name: Upload extension artifact uses: actions/upload-artifact@v4 diff --git a/vscode-extension/package-lock.json b/vscode-extension/package-lock.json index d1ebb91..37c0969 100644 --- a/vscode-extension/package-lock.json +++ b/vscode-extension/package-lock.json @@ -11,9 +11,9 @@ "@types/node": "^20.11.0", "@types/vscode": "^1.85.0", "@vscode/vsce": "^2.22.0", - "esbuild": "^0.19.0", + "esbuild": "^0.27.3", "typescript": "^5.3.0", - "vitest": "^1.2.0" + "vitest": "^4.0.18" }, "engines": { "vscode": "^1.85.0" @@ -189,9 +189,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz", - "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", + "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", "cpu": [ "ppc64" ], @@ -202,13 +202,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz", - "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", + "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", "cpu": [ "arm" ], @@ -219,13 +219,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz", - "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", + "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", "cpu": [ "arm64" ], @@ -236,13 +236,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz", - "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", + "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", "cpu": [ "x64" ], @@ -253,13 +253,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz", - "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", + "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", "cpu": [ "arm64" ], @@ -270,13 +270,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz", - "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", + "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", "cpu": [ "x64" ], @@ -287,13 +287,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz", - "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", + "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", "cpu": [ "arm64" ], @@ -304,13 +304,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz", - "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", + "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", "cpu": [ "x64" ], @@ -321,13 +321,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz", - "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", + "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", "cpu": [ "arm" ], @@ -338,13 +338,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz", - "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", + "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", "cpu": [ "arm64" ], @@ -355,13 +355,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz", - "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", + "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", "cpu": [ "ia32" ], @@ -372,13 +372,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz", - "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", + "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", "cpu": [ "loong64" ], @@ -389,13 +389,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz", - "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", + "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", "cpu": [ "mips64el" ], @@ -406,13 +406,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz", - "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", + "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", "cpu": [ "ppc64" ], @@ -423,13 +423,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz", - "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", + "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", "cpu": [ "riscv64" ], @@ -440,13 +440,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz", - "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", + "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", "cpu": [ "s390x" ], @@ -457,13 +457,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz", - "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", + "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", "cpu": [ "x64" ], @@ -474,13 +474,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", + "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz", - "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", + "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", "cpu": [ "x64" ], @@ -491,13 +508,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", + "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz", - "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", + "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", "cpu": [ "x64" ], @@ -508,13 +542,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", + "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz", - "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", + "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", "cpu": [ "x64" ], @@ -525,13 +576,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz", - "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", + "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", "cpu": [ "arm64" ], @@ -542,13 +593,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz", - "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", + "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", "cpu": [ "ia32" ], @@ -559,13 +610,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz", - "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", + "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", "cpu": [ "x64" ], @@ -576,20 +627,7 @@ "win32" ], "engines": { - "node": ">=12" - } - }, - "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@sinclair/typebox": "^0.27.8" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=18" } }, "node_modules/@jridgewell/sourcemap-codec": { @@ -949,10 +987,28 @@ "win32" ] }, - "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, @@ -996,74 +1052,111 @@ } }, "node_modules/@vitest/expect": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", - "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "chai": "^4.3.10" + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", - "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "1.6.1", - "p-limit": "^5.0.0", - "pathe": "^1.1.1" + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/snapshot": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", - "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", "dev": true, "license": "MIT", "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/spy": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", - "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", "dev": true, "license": "MIT", - "dependencies": { - "tinyspy": "^2.2.0" - }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/utils": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", - "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", "dev": true, "license": "MIT", "dependencies": { - "diff-sequences": "^29.6.3", - "estree-walker": "^3.0.3", - "loupe": "^2.3.7", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" }, "funding": { "url": "https://opencollective.com/vitest" @@ -1256,32 +1349,6 @@ "win32" ] }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -1313,13 +1380,13 @@ "license": "Python-2.0" }, "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { - "node": "*" + "node": ">=12" } }, "node_modules/asynckit": { @@ -1459,16 +1526,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cac": { - "version": "6.7.14", - "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", - "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "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", @@ -1501,22 +1558,13 @@ } }, "node_modules/chai": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", - "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, "license": "MIT", - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.1.0" - }, "engines": { - "node": ">=4" + "node": ">=18" } }, "node_modules/chalk": { @@ -1534,19 +1582,6 @@ "node": ">=4" } }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, "node_modules/cheerio": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz", @@ -1656,28 +1691,6 @@ "dev": true, "license": "MIT" }, - "node_modules/confbox": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", - "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", - "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", @@ -1743,19 +1756,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/deep-eql": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", - "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", @@ -1831,16 +1831,6 @@ "node": ">=8" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -1983,6 +1973,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -2013,9 +2010,9 @@ } }, "node_modules/esbuild": { - "version": "0.19.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", - "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", + "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2023,32 +2020,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.19.12", - "@esbuild/android-arm": "0.19.12", - "@esbuild/android-arm64": "0.19.12", - "@esbuild/android-x64": "0.19.12", - "@esbuild/darwin-arm64": "0.19.12", - "@esbuild/darwin-x64": "0.19.12", - "@esbuild/freebsd-arm64": "0.19.12", - "@esbuild/freebsd-x64": "0.19.12", - "@esbuild/linux-arm": "0.19.12", - "@esbuild/linux-arm64": "0.19.12", - "@esbuild/linux-ia32": "0.19.12", - "@esbuild/linux-loong64": "0.19.12", - "@esbuild/linux-mips64el": "0.19.12", - "@esbuild/linux-ppc64": "0.19.12", - "@esbuild/linux-riscv64": "0.19.12", - "@esbuild/linux-s390x": "0.19.12", - "@esbuild/linux-x64": "0.19.12", - "@esbuild/netbsd-x64": "0.19.12", - "@esbuild/openbsd-x64": "0.19.12", - "@esbuild/sunos-x64": "0.19.12", - "@esbuild/win32-arm64": "0.19.12", - "@esbuild/win32-ia32": "0.19.12", - "@esbuild/win32-x64": "0.19.12" + "@esbuild/aix-ppc64": "0.27.3", + "@esbuild/android-arm": "0.27.3", + "@esbuild/android-arm64": "0.27.3", + "@esbuild/android-x64": "0.27.3", + "@esbuild/darwin-arm64": "0.27.3", + "@esbuild/darwin-x64": "0.27.3", + "@esbuild/freebsd-arm64": "0.27.3", + "@esbuild/freebsd-x64": "0.27.3", + "@esbuild/linux-arm": "0.27.3", + "@esbuild/linux-arm64": "0.27.3", + "@esbuild/linux-ia32": "0.27.3", + "@esbuild/linux-loong64": "0.27.3", + "@esbuild/linux-mips64el": "0.27.3", + "@esbuild/linux-ppc64": "0.27.3", + "@esbuild/linux-riscv64": "0.27.3", + "@esbuild/linux-s390x": "0.27.3", + "@esbuild/linux-x64": "0.27.3", + "@esbuild/netbsd-arm64": "0.27.3", + "@esbuild/netbsd-x64": "0.27.3", + "@esbuild/openbsd-arm64": "0.27.3", + "@esbuild/openbsd-x64": "0.27.3", + "@esbuild/openharmony-arm64": "0.27.3", + "@esbuild/sunos-x64": "0.27.3", + "@esbuild/win32-arm64": "0.27.3", + "@esbuild/win32-ia32": "0.27.3", + "@esbuild/win32-x64": "0.27.3" } }, "node_modules/escape-string-regexp": { @@ -2071,30 +2071,6 @@ "@types/estree": "^1.0.0" } }, - "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", - "dev": true, - "license": "MIT", - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, - "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -2106,6 +2082,16 @@ "node": ">=6" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -2116,6 +2102,24 @@ "pend": "~1.2.0" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -2173,16 +2177,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-func-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", - "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2222,19 +2216,6 @@ "node": ">= 0.4" } }, - "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -2404,16 +2385,6 @@ "node": ">= 14" } }, - "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=16.17.0" - } - }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -2511,19 +2482,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "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", @@ -2540,20 +2498,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", - "dev": true, - "license": "MIT" - }, "node_modules/jsonc-parser": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", @@ -2640,23 +2584,6 @@ "uc.micro": "^1.0.1" } }, - "node_modules/local-pkg": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", - "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mlly": "^1.7.3", - "pkg-types": "^1.2.1" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", @@ -2706,16 +2633,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loupe": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", - "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-func-name": "^2.0.1" - } - }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -2783,13 +2700,6 @@ "dev": true, "license": "MIT" }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true, - "license": "MIT" - }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -2826,19 +2736,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "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", @@ -2885,26 +2782,6 @@ "license": "MIT", "optional": true }, - "node_modules/mlly": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", - "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.15.0", - "pathe": "^2.0.3", - "pkg-types": "^1.3.1", - "ufo": "^1.6.1" - } - }, - "node_modules/mlly/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -2968,35 +2845,6 @@ "license": "MIT", "optional": true }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -3023,6 +2871,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -3033,22 +2892,6 @@ "wrappy": "1" } }, - "node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-fn": "^4.0.0" - }, - "engines": { - "node": ">=12" - }, - "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", @@ -3068,22 +2911,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-limit": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", - "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/parse-semver": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", @@ -3167,33 +2994,13 @@ "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/pathe": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", - "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "dev": true, "license": "MIT" }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -3208,25 +3015,19 @@ "dev": true, "license": "ISC" }, - "node_modules/pkg-types": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", - "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "dependencies": { - "confbox": "^0.1.8", - "mlly": "^1.7.4", - "pathe": "^2.0.1" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pkg-types/node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, "node_modules/postcss": { "version": "8.5.6", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", @@ -3284,34 +3085,6 @@ "node": ">=10" } }, - "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/pump": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", @@ -3357,13 +3130,6 @@ "rc": "cli.js" } }, - "node_modules/react-is": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", - "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", - "dev": true, - "license": "MIT" - }, "node_modules/read": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", @@ -3502,29 +3268,6 @@ "node": ">=10" } }, - "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.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -3608,19 +3351,6 @@ "dev": true, "license": "ISC" }, - "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", @@ -3705,19 +3435,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -3729,19 +3446,6 @@ "node": ">=0.10.0" } }, - "node_modules/strip-literal": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", - "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^9.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -3794,20 +3498,37 @@ "dev": true, "license": "MIT" }, - "node_modules/tinypool": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", - "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", "dev": true, "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyspy": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", - "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", "dev": true, "license": "MIT", "engines": { @@ -3855,16 +3576,6 @@ "node": "*" } }, - "node_modules/type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/typed-rest-client": { "version": "1.8.11", "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.11.tgz", @@ -3898,13 +3609,6 @@ "dev": true, "license": "MIT" }, - "node_modules/ufo": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", - "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", - "dev": true, - "license": "MIT" - }, "node_modules/underscore": { "version": "1.13.7", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.7.tgz", @@ -3955,21 +3659,24 @@ } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -3978,19 +3685,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -4011,526 +3724,91 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, - "node_modules/vite-node": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", - "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "cac": "^6.7.14", - "debug": "^4.3.4", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "vite": "^5.0.0" + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" }, "bin": { - "vite-node": "vite-node.mjs" + "vitest": "vitest.mjs" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" }, "funding": { "url": "https://opencollective.com/vitest" - } - }, - "node_modules/vite/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/vitest": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", - "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "1.6.1", - "@vitest/runner": "1.6.1", - "@vitest/snapshot": "1.6.1", - "@vitest/spy": "1.6.1", - "@vitest/utils": "1.6.1", - "acorn-walk": "^8.3.2", - "chai": "^4.3.10", - "debug": "^4.3.4", - "execa": "^8.0.1", - "local-pkg": "^0.5.0", - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "picocolors": "^1.0.0", - "std-env": "^3.5.0", - "strip-literal": "^2.0.0", - "tinybench": "^2.5.1", - "tinypool": "^0.8.3", - "vite": "^5.0.0", - "vite-node": "1.6.1", - "why-is-node-running": "^2.2.2" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || >=20.0.0", - "@vitest/browser": "1.6.1", - "@vitest/ui": "1.6.1", - "happy-dom": "*", - "jsdom": "*" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - } + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } } }, "node_modules/whatwg-encoding": { @@ -4557,22 +3835,6 @@ "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/why-is-node-running": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", @@ -4664,19 +3926,6 @@ "dependencies": { "buffer-crc32": "~0.2.3" } - }, - "node_modules/yocto-queue": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", - "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } } } } diff --git a/vscode-extension/package.json b/vscode-extension/package.json index af0638f..69a1c6c 100644 --- a/vscode-extension/package.json +++ b/vscode-extension/package.json @@ -217,6 +217,12 @@ "description": "JavaScript code for custom frame resolution (receives trace, tag, query variables)" } } + }, + "capabilities": { + "untrustedWorkspaces": { + "supported": false, + "description": "This extension executes user-provided JavaScript for frame resolution and requires a trusted workspace." + } } }, "scripts": { @@ -233,8 +239,8 @@ "@types/node": "^20.11.0", "@types/vscode": "^1.85.0", "@vscode/vsce": "^2.22.0", - "esbuild": "^0.19.0", + "esbuild": "^0.27.3", "typescript": "^5.3.0", - "vitest": "^1.2.0" + "vitest": "^4.0.18" } } diff --git a/vscode-extension/src/command/liveTail.ts b/vscode-extension/src/command/liveTail.ts index b8988e9..2e4688c 100644 --- a/vscode-extension/src/command/liveTail.ts +++ b/vscode-extension/src/command/liveTail.ts @@ -90,13 +90,14 @@ export class LiveTailManager implements vscode.Disposable { const time = formatTimestamp(entry.timestamp); this.outputChannel.appendLine( - `[${time}] ${status.toUpperCase()}${tag} ${shortSql}` + `[${time}] ${qtype} ${status.toUpperCase()}${tag} ${shortSql}` ); // Show backtrace frames if (entry.trace) { - for (const frame of entry.trace.slice(0, 3)) { - this.outputChannel.appendLine(` #${entry.trace.indexOf(frame)} ${frame.file}:${frame.line}`); + for (let i = 0; i < Math.min(entry.trace.length, 3); i++) { + const frame = entry.trace[i]; + this.outputChannel.appendLine(` #${i} ${frame.file}:${frame.line}`); } if (entry.trace.length > 3) { this.outputChannel.appendLine(` ... (${entry.trace.length - 3} more frames)`); diff --git a/vscode-extension/src/command/openLog.ts b/vscode-extension/src/command/openLog.ts index aae6ed6..af6e89d 100644 --- a/vscode-extension/src/command/openLog.ts +++ b/vscode-extension/src/command/openLog.ts @@ -1,13 +1,18 @@ import * as vscode from 'vscode'; import { JobManagerService } from '../service/JobManagerService'; import { LogParserService } from '../service/LogParserService'; +import { StatisticsService } from '../service/StatisticsService'; import { QueryTreeProvider } from '../provider/QueryTreeProvider'; +import { StatisticsTreeProvider } from '../provider/StatisticsTreeProvider'; +import { updateFilterContext } from './filterQueries'; export function registerOpenLogCommand( context: vscode.ExtensionContext, jobManager: JobManagerService, logParser: LogParserService, queryTreeProvider: QueryTreeProvider, + statisticsService: StatisticsService, + statisticsTreeProvider: StatisticsTreeProvider, ): vscode.Disposable { return vscode.commands.registerCommand('mariadbProfiler.openLog', async () => { const uris = await vscode.window.showOpenDialog({ @@ -21,8 +26,20 @@ export function registerOpenLogCommand( if (!uris || uris.length === 0) { return; } const filePath = uris[0].fsPath; - const entries = logParser.parseJsonlFile(filePath); + + let entries; + try { + entries = logParser.parseJsonlFile(filePath); + } catch (e) { + vscode.window.showErrorMessage(`Failed to parse log file: ${e}`); + return; + } + queryTreeProvider.loadEntries(entries); + updateFilterContext(queryTreeProvider); + + const stats = statisticsService.computeStats(entries); + statisticsTreeProvider.updateStats(stats); vscode.window.showInformationMessage(`Loaded ${entries.length} queries from log file`); }); diff --git a/vscode-extension/src/extension.ts b/vscode-extension/src/extension.ts index 08bd1ec..13f9c66 100644 --- a/vscode-extension/src/extension.ts +++ b/vscode-extension/src/extension.ts @@ -1,3 +1,4 @@ +import * as fs from 'fs'; import * as vscode from 'vscode'; import { LogParserService } from './service/LogParserService'; import { JobManagerService } from './service/JobManagerService'; @@ -95,9 +96,11 @@ export function activate(context: vscode.ExtensionContext): void { } queryTreeProvider.loadEntries(entries); - jsonlOffset = entries.length > 0 - ? require('fs').statSync(jsonlPath).size - : 0; + try { + jsonlOffset = entries.length > 0 ? fs.statSync(jsonlPath).size : 0; + } catch { + jsonlOffset = 0; + } // Update statistics const stats = statisticsService.computeStats(entries); @@ -187,7 +190,7 @@ export function activate(context: vscode.ExtensionContext): void { context.subscriptions.push( registerStartJobCommand(context, jobManager, refreshJobs), registerStopJobCommand(context, jobManager, refreshJobs), - registerOpenLogCommand(context, jobManager, logParser, queryTreeProvider), + registerOpenLogCommand(context, jobManager, logParser, queryTreeProvider, statisticsService, statisticsTreeProvider), registerFilterByTypeCommand(context, queryTreeProvider), registerFilterByTagCommand(context, queryTreeProvider), registerClearFilterCommand(context, queryTreeProvider), @@ -205,7 +208,7 @@ export function activate(context: vscode.ExtensionContext): void { context.subscriptions.push(refreshCmd); // --- Watch jobs.json for external changes --- - const jobsJsonPath = jobManager.getJobsJsonPath(); + let jobsJsonPath = jobManager.getJobsJsonPath(); fileWatcher.watchFile(jobsJsonPath, () => { refreshJobs(); }); @@ -218,6 +221,14 @@ export function activate(context: vscode.ExtensionContext): void { if (e.affectsConfiguration('mariadbProfiler.refreshInterval') && selectedJobKey) { startRefreshTimer(); } + if (e.affectsConfiguration('mariadbProfiler.logDirectory')) { + fileWatcher.unwatchFile(jobsJsonPath); + jobsJsonPath = jobManager.getJobsJsonPath(); + fileWatcher.watchFile(jobsJsonPath, () => { + refreshJobs(); + }); + refreshJobs(); + } }); context.subscriptions.push(configWatcher); diff --git a/vscode-extension/src/model/JobInfo.ts b/vscode-extension/src/model/JobInfo.ts index 4b2d1ff..1571677 100644 --- a/vscode-extension/src/model/JobInfo.ts +++ b/vscode-extension/src/model/JobInfo.ts @@ -73,10 +73,12 @@ export function jobsFileToJobInfos(jobsFile: JobsFile): JobInfo[] { } export function formatDuration(startedAt: number, endedAt?: number): string { - const end = endedAt ?? Date.now() / 1000; - const seconds = end - startedAt; + if (endedAt === undefined) { return 'running...'; } + const seconds = endedAt - startedAt; + if (seconds < 1) { return `${Math.round(seconds * 1000)} ms`; } if (seconds < 60) { return `${seconds.toFixed(1)}s`; } - const minutes = Math.floor(seconds / 60); - const secs = seconds % 60; - return `${minutes}m${secs.toFixed(0)}s`; + const totalSeconds = Math.round(seconds); + const minutes = Math.floor(totalSeconds / 60); + const secs = totalSeconds % 60; + return `${minutes}m${secs}s`; } diff --git a/vscode-extension/src/model/QueryEntry.ts b/vscode-extension/src/model/QueryEntry.ts index 96bd5e5..087e56c 100644 --- a/vscode-extension/src/model/QueryEntry.ts +++ b/vscode-extension/src/model/QueryEntry.ts @@ -117,7 +117,7 @@ export function getBoundQuery(entry: QueryEntry): string { // Replace placeholder if (q[i] === '?' && paramIndex < params.length) { const param = params[paramIndex++]; - result += param === null ? 'NULL' : `'${param}'`; + result += param === null ? 'NULL' : `'${param.replace(/'/g, "''")}'`; i++; continue; } @@ -129,11 +129,11 @@ export function getBoundQuery(entry: QueryEntry): string { } const TABLE_PATTERNS = [ - /\bFROM\s+`?(\w+)`?/gi, - /\bJOIN\s+`?(\w+)`?/gi, - /\bUPDATE\s+`?(\w+)`?/gi, - /\bINTO\s+`?(\w+)`?/gi, - /\bDELETE\s+FROM\s+`?(\w+)`?/gi, + /\bFROM\s+(`[^`]+`\.`[^`]+`|`[^`]+`|[\w]+\.[\w]+|[\w]+)/gi, + /\bJOIN\s+(`[^`]+`\.`[^`]+`|`[^`]+`|[\w]+\.[\w]+|[\w]+)/gi, + /\bUPDATE\s+(`[^`]+`\.`[^`]+`|`[^`]+`|[\w]+\.[\w]+|[\w]+)/gi, + /\bINTO\s+(`[^`]+`\.`[^`]+`|`[^`]+`|[\w]+\.[\w]+|[\w]+)/gi, + /\bDELETE\s+FROM\s+(`[^`]+`\.`[^`]+`|`[^`]+`|[\w]+\.[\w]+|[\w]+)/gi, ]; export function getTables(entry: QueryEntry): string[] { @@ -142,7 +142,7 @@ export function getTables(entry: QueryEntry): string[] { pattern.lastIndex = 0; let match; while ((match = pattern.exec(entry.query)) !== null) { - tables.add(match[1].toLowerCase()); + tables.add(match[1].replace(/`/g, '').toLowerCase()); } } return [...tables].sort(); diff --git a/vscode-extension/src/provider/JobTreeProvider.ts b/vscode-extension/src/provider/JobTreeProvider.ts index e29d52e..719efe3 100644 --- a/vscode-extension/src/provider/JobTreeProvider.ts +++ b/vscode-extension/src/provider/JobTreeProvider.ts @@ -40,7 +40,7 @@ export class JobTreeItem extends vscode.TreeItem { job.isActive ? 'circle-filled' : 'circle-outline', job.isActive ? new vscode.ThemeColor('charts.green') - : new vscode.ThemeColor('charts.gray'), + : new vscode.ThemeColor('disabledForeground'), ); this.contextValue = job.isActive ? 'activeJob' : 'completedJob'; diff --git a/vscode-extension/src/provider/QueryDocumentProvider.ts b/vscode-extension/src/provider/QueryDocumentProvider.ts index 334de14..7daa390 100644 --- a/vscode-extension/src/provider/QueryDocumentProvider.ts +++ b/vscode-extension/src/provider/QueryDocumentProvider.ts @@ -1,6 +1,8 @@ import * as vscode from 'vscode'; import { QueryEntry, getBoundQuery, getQueryType, getTables, formatTimestamp } from '../model/QueryEntry'; +const MAX_DOCUMENTS = 50; + export class QueryDocumentProvider implements vscode.TextDocumentContentProvider { static readonly scheme = 'mariadb-profiler'; @@ -8,6 +10,7 @@ export class QueryDocumentProvider implements vscode.TextDocumentContentProvider readonly onDidChange = this._onDidChange.event; private documents = new Map(); + private insertionOrder: string[] = []; provideTextDocumentContent(uri: vscode.Uri): string { const entry = this.documents.get(uri.toString()); @@ -17,12 +20,17 @@ export class QueryDocumentProvider implements vscode.TextDocumentContentProvider } async showQueryDetail(entry: QueryEntry, index: number): Promise { - const qtype = getQueryType(entry); const uri = vscode.Uri.parse( `${QueryDocumentProvider.scheme}:query-${index}.sql?ts=${entry.timestamp}` ); - this.documents.set(uri.toString(), entry); + const key = uri.toString(); + if (this.documents.size >= MAX_DOCUMENTS && !this.documents.has(key)) { + const oldest = this.insertionOrder.shift(); + if (oldest) { this.documents.delete(oldest); } + } + this.documents.set(key, entry); + this.insertionOrder.push(key); this._onDidChange.fire(uri); const doc = await vscode.workspace.openTextDocument(uri); diff --git a/vscode-extension/src/service/FrameResolverService.ts b/vscode-extension/src/service/FrameResolverService.ts index a3bf1b3..a9086da 100644 --- a/vscode-extension/src/service/FrameResolverService.ts +++ b/vscode-extension/src/service/FrameResolverService.ts @@ -5,6 +5,7 @@ import { QueryEntry, BacktraceFrame } from '../model/QueryEntry'; export class FrameResolverService { private cachedScript: vm.Script | null = null; private cachedScriptText: string = ''; + private failedScripts = new Set(); private errorChannel: vscode.OutputChannel; constructor(errorChannel: vscode.OutputChannel) { @@ -19,6 +20,11 @@ export class FrameResolverService { if (!scriptText) { return 0; } + if (!vscode.workspace.isTrusted) { + this.errorChannel.appendLine('[FrameResolver] Workspace is not trusted, skipping script execution'); + return 0; + } + try { const script = this.getScript(scriptText); if (!script) { return 0; } @@ -38,7 +44,7 @@ export class FrameResolverService { const context = vm.createContext(sandbox); const result = script.runInContext(context, { timeout: 1000 }); - if (typeof result === 'number' && result >= 0 && result < entry.trace.length) { + if (Number.isInteger(result) && result >= 0 && result < entry.trace.length) { return result; } @@ -52,6 +58,7 @@ export class FrameResolverService { invalidateCache(): void { this.cachedScript = null; this.cachedScriptText = ''; + this.failedScripts.clear(); } private getScript(scriptText: string): vm.Script | null { @@ -59,14 +66,19 @@ export class FrameResolverService { return this.cachedScript; } + if (this.failedScripts.has(scriptText)) { + return null; + } + try { this.cachedScript = new vm.Script(scriptText, { filename: 'frameResolver.js' }); this.cachedScriptText = scriptText; return this.cachedScript; } catch (e) { this.errorChannel.appendLine(`[FrameResolver] Compile error: ${e}`); + this.failedScripts.add(scriptText); this.cachedScript = null; - this.cachedScriptText = scriptText; + this.cachedScriptText = ''; return null; } } diff --git a/vscode-extension/src/service/JobManagerService.ts b/vscode-extension/src/service/JobManagerService.ts index 54e9b3e..d09d809 100644 --- a/vscode-extension/src/service/JobManagerService.ts +++ b/vscode-extension/src/service/JobManagerService.ts @@ -42,8 +42,11 @@ export class JobManagerService { const match = output.match(/Job '([^']+)' started/); if (match) { return match[1]; } - // Fallback: return the key if provided, or extract from output - return jobKey || output.trim(); + // Fallback: return the key if provided, or extract a safe identifier from output + if (jobKey) { return jobKey; } + const safeMatch = output.trim().match(/^[A-Za-z0-9_-]+$/); + if (safeMatch) { return safeMatch[0]; } + throw new Error('Could not determine job key from CLI output'); } async stopJob(jobKey: string): Promise { diff --git a/vscode-extension/src/service/LogParserService.ts b/vscode-extension/src/service/LogParserService.ts index 4a9626f..d4ce707 100644 --- a/vscode-extension/src/service/LogParserService.ts +++ b/vscode-extension/src/service/LogParserService.ts @@ -17,23 +17,25 @@ export class LogParserService { } parseJsonlFileFromOffset(filePath: string, offset: number): { entries: QueryEntry[]; newOffset: number } { - if (!fs.existsSync(filePath)) { - return { entries: [], newOffset: offset }; - } - - const stat = fs.statSync(filePath); - if (stat.size <= offset) { + let fd: number; + try { + fd = fs.openSync(filePath, 'r'); + } catch { return { entries: [], newOffset: offset }; } - const fd = fs.openSync(filePath, 'r'); try { + const stat = fs.fstatSync(fd); + if (stat.size <= offset) { + return { entries: [], newOffset: offset }; + } + const bufSize = stat.size - offset; const buffer = Buffer.alloc(bufSize); - fs.readSync(fd, buffer, 0, bufSize, offset); - const content = buffer.toString('utf-8'); + const bytesRead = fs.readSync(fd, buffer, 0, bufSize, offset); + const content = buffer.slice(0, bytesRead).toString('utf-8'); const entries = this.parseJsonlContent(content); - return { entries, newOffset: stat.size }; + return { entries, newOffset: offset + bytesRead }; } finally { fs.closeSync(fd); } @@ -49,21 +51,23 @@ export class LogParserService { } tailRawLog(filePath: string, offset: number): { content: string; newOffset: number } { - if (!fs.existsSync(filePath)) { - return { content: '', newOffset: offset }; - } - - const stat = fs.statSync(filePath); - if (stat.size <= offset) { + let fd: number; + try { + fd = fs.openSync(filePath, 'r'); + } catch { return { content: '', newOffset: offset }; } - const fd = fs.openSync(filePath, 'r'); try { + const stat = fs.fstatSync(fd); + if (stat.size <= offset) { + return { content: '', newOffset: offset }; + } + const bufSize = stat.size - offset; const buffer = Buffer.alloc(bufSize); - fs.readSync(fd, buffer, 0, bufSize, offset); - return { content: buffer.toString('utf-8'), newOffset: stat.size }; + const bytesRead = fs.readSync(fd, buffer, 0, bufSize, offset); + return { content: buffer.slice(0, bytesRead).toString('utf-8'), newOffset: offset + bytesRead }; } finally { fs.closeSync(fd); } diff --git a/vscode-extension/src/util/pathMapping.ts b/vscode-extension/src/util/pathMapping.ts index d995cca..536ebc8 100644 --- a/vscode-extension/src/util/pathMapping.ts +++ b/vscode-extension/src/util/pathMapping.ts @@ -6,7 +6,7 @@ export function applyPathMappings(containerPath: string): string { for (const [from, to] of Object.entries(mappings)) { if (containerPath.startsWith(from)) { - return containerPath.replace(from, to); + return to + containerPath.slice(from.length); } } diff --git a/vscode-extension/src/util/queryUtils.ts b/vscode-extension/src/util/queryUtils.ts index 9e714a6..4c300a3 100644 --- a/vscode-extension/src/util/queryUtils.ts +++ b/vscode-extension/src/util/queryUtils.ts @@ -1,6 +1,6 @@ export function generateBar(value: number, max: number, barWidth: number = 20): string { if (max === 0) { return '\u2591'.repeat(barWidth); } - const filled = Math.round((value / max) * barWidth); + const filled = Math.max(0, Math.min(barWidth, Math.round((value / max) * barWidth))); return '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled); } diff --git a/vscode-extension/test/unit/JobInfo.test.ts b/vscode-extension/test/unit/JobInfo.test.ts index 8d61485..525e0ae 100644 --- a/vscode-extension/test/unit/JobInfo.test.ts +++ b/vscode-extension/test/unit/JobInfo.test.ts @@ -101,6 +101,6 @@ describe('formatDuration', () => { }); it('should handle zero duration', () => { - expect(formatDuration(100, 100)).toBe('0.0s'); + expect(formatDuration(100, 100)).toBe('0 ms'); }); }); From 560fb94f14366fe7d6a22a141d2e26f61a1ed877 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 18 Feb 2026 11:30:37 +0000 Subject: [PATCH 8/8] Show bound parameter values in query list and live tail Use getBoundQuery() instead of raw SQL for the tree view label and live tail output so users see actual parameter values (e.g. 'SELECT * FROM users WHERE id = 42') instead of placeholders ('SELECT * FROM users WHERE id = ?'). https://claude.ai/code/session_01EabgSSMXSA25Pjf1aTEmB2 --- vscode-extension/src/command/liveTail.ts | 5 +++-- vscode-extension/src/provider/QueryTreeProvider.ts | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vscode-extension/src/command/liveTail.ts b/vscode-extension/src/command/liveTail.ts index 2e4688c..851acfd 100644 --- a/vscode-extension/src/command/liveTail.ts +++ b/vscode-extension/src/command/liveTail.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import { JobManagerService } from '../service/JobManagerService'; import { LogParserService } from '../service/LogParserService'; import { FileWatcherService } from '../service/FileWatcherService'; -import { getQueryType, getShortSql, formatTimestamp } from '../model/QueryEntry'; +import { getQueryType, getBoundQuery, formatTimestamp } from '../model/QueryEntry'; export class LiveTailManager implements vscode.Disposable { private outputChannel: vscode.OutputChannel; @@ -84,7 +84,8 @@ export class LiveTailManager implements vscode.Disposable { for (const entry of result.entries) { const qtype = getQueryType(entry); - const shortSql = getShortSql(entry, 80); + const boundSql = getBoundQuery(entry).replace(/\s+/g, ' ').trim(); + const shortSql = boundSql.length <= 80 ? boundSql : boundSql.substring(0, 77) + '...'; const tag = entry.tag ? ` [${entry.tag}]` : ''; const status = entry.status || 'ok'; const time = formatTimestamp(entry.timestamp); diff --git a/vscode-extension/src/provider/QueryTreeProvider.ts b/vscode-extension/src/provider/QueryTreeProvider.ts index b555d85..9a2b7da 100644 --- a/vscode-extension/src/provider/QueryTreeProvider.ts +++ b/vscode-extension/src/provider/QueryTreeProvider.ts @@ -202,7 +202,8 @@ export class QueryEntryItem extends vscode.TreeItem { constructor(entry: QueryEntry, entryIndex: number) { const qtype = getQueryType(entry); - const shortSql = getShortSql(entry, 50); + const boundSql = getBoundQuery(entry).replace(/\s+/g, ' ').trim(); + const shortSql = boundSql.length <= 50 ? boundSql : boundSql.substring(0, 47) + '...'; const label = `${qtype} ${shortSql}`; super(label, vscode.TreeItemCollapsibleState.Collapsed);