diff --git a/extension.ts b/extension.ts index ab1872a..8e15bca 100644 --- a/extension.ts +++ b/extension.ts @@ -31,8 +31,42 @@ export interface BookmarkFolder { createdAt: number; order?: number; parentId?: string | null; + /** A codicon id (e.g. `server`), shown instead of the default folder icon. */ + icon?: string; + /** A theme color id (e.g. `charts.red`) tinting the folder's icon. */ + color?: string; } +/** Codicons offered when customizing a folder's icon. */ +export const FOLDER_ICON_CHOICES: ReadonlyArray<{ id: string; label: string }> = [ + { id: 'folder', label: 'Folder' }, + { id: 'server', label: 'Server' }, + { id: 'database', label: 'Database' }, + { id: 'beaker', label: 'Beaker' }, + { id: 'bug', label: 'Bug' }, + { id: 'checklist', label: 'Checklist' }, + { id: 'eye', label: 'In Review' }, + { id: 'flame', label: 'Flame' }, + { id: 'gear', label: 'Gear' }, + { id: 'globe', label: 'Globe' }, + { id: 'heart', label: 'Heart' }, + { id: 'lock', label: 'Lock' }, + { id: 'rocket', label: 'Rocket' }, + { id: 'star-full', label: 'Star' }, + { id: 'tag', label: 'Tag' }, + { id: 'tools', label: 'Tools' }, +]; + +/** Theme colors offered when customizing a folder's icon color. */ +export const FOLDER_COLOR_CHOICES: ReadonlyArray<{ id: string; label: string }> = [ + { id: 'charts.red', label: 'Red' }, + { id: 'charts.orange', label: 'Orange' }, + { id: 'charts.yellow', label: 'Yellow' }, + { id: 'charts.green', label: 'Green' }, + { id: 'charts.blue', label: 'Blue' }, + { id: 'charts.purple', label: 'Purple' }, +]; + /** Folders may nest up to this many levels deep (root-level folders are depth 1). */ export const MAX_FOLDER_DEPTH = 3; @@ -208,6 +242,7 @@ export function activate(context: vscode.ExtensionContext): ExtensionApi { const treeView = vscode.window.createTreeView('workspace-file-bookmarks-view', { treeDataProvider: provider, showCollapseAll: true, + canSelectMany: true, dragAndDropController: provider, }); @@ -243,7 +278,8 @@ export function activate(context: vscode.ExtensionContext): ExtensionApi { ), vscode.commands.registerCommand( 'workspace-file-bookmarks.removeBookmark', - (item: BookmarkTreeItem) => store.removeBookmark(item.bookmark.id), + (item: BookmarkTreeItem, selection?: readonly TreeNode[]) => + store.removeBookmarks(resolveBookmarkTargets(item, selection).map((b) => b.id)), ), vscode.commands.registerCommand( 'workspace-file-bookmarks.renameBookmark', @@ -265,7 +301,10 @@ export function activate(context: vscode.ExtensionContext): ExtensionApi { provider.setSearchFilter(null), ), vscode.commands.registerCommand('workspace-file-bookmarks.openBookmark', (bookmark: Bookmark) => - openBookmark(bookmark), + openBookmark(bookmark, store), + ), + vscode.commands.registerCommand('workspace-file-bookmarks.removeBrokenBookmarks', () => + removeBrokenBookmarks(store), ), vscode.commands.registerCommand('workspace-file-bookmarks.createFolder', () => createFolder(store), @@ -282,13 +321,18 @@ export function activate(context: vscode.ExtensionContext): ExtensionApi { 'workspace-file-bookmarks.renameFolder', (item: FolderGroupItem) => renameFolder(store, item), ), + vscode.commands.registerCommand( + 'workspace-file-bookmarks.setFolderAppearance', + (item: FolderGroupItem) => setFolderAppearance(store, item), + ), vscode.commands.registerCommand( 'workspace-file-bookmarks.deleteFolder', (item: FolderGroupItem) => deleteFolder(store, item), ), vscode.commands.registerCommand( 'workspace-file-bookmarks.moveToFolder', - (item: BookmarkTreeItem) => moveToFolder(store, item), + (item: BookmarkTreeItem, selection?: readonly TreeNode[]) => + moveToFolder(store, item, selection), ), vscode.commands.registerCommand( 'workspace-file-bookmarks.openAllInFolder', @@ -309,6 +353,22 @@ export function activate(context: vscode.ExtensionContext): ExtensionApi { vscode.commands.registerCommand('workspace-file-bookmarks.goToBookmark', () => goToBookmark(store), ), + vscode.commands.registerCommand( + 'workspace-file-bookmarks.copyBookmarkPath', + (item: BookmarkTreeItem) => copyBookmarkPath(item.bookmark), + ), + vscode.commands.registerCommand( + 'workspace-file-bookmarks.copyBookmarkRelativePath', + (item: BookmarkTreeItem) => copyBookmarkRelativePath(item.bookmark), + ), + vscode.commands.registerCommand( + 'workspace-file-bookmarks.revealBookmarkInExplorer', + (item: BookmarkTreeItem) => revealBookmarkInExplorer(item.bookmark), + ), + vscode.commands.registerCommand( + 'workspace-file-bookmarks.revealBookmarkInFileManager', + (item: BookmarkTreeItem) => revealBookmarkInFileManager(item.bookmark), + ), ); return { store, provider }; @@ -320,8 +380,40 @@ export class BookmarkStore { private readonly _onDidChange = new vscode.EventEmitter(); readonly onDidChange = this._onDidChange.event; + /** Ids of bookmarks whose target was found missing. In-memory only: re-derived lazily, never persisted. */ + private staleIds = new Set(); + constructor(private readonly context: vscode.ExtensionContext) {} + isStale(id: string): boolean { + return this.staleIds.has(id); + } + + /** Replaces the set of stale bookmark ids and refreshes the tree if it changed. */ + setStaleIds(ids: Iterable) { + const next = new Set(ids); + if (next.size === this.staleIds.size && [...next].every((id) => this.staleIds.has(id))) { + return; + } + this.staleIds = next; + this._onDidChange.fire(); + } + + /** Removes every bookmark currently marked stale, in a single write. */ + removeStaleBookmarks() { + const stale = this.staleIds; + this.staleIds = new Set(); + this.setBookmarks(this.getAllBookmarks().filter((b) => !stale.has(b.id))); + } + + markStale(id: string) { + this.setStaleIds([...this.staleIds, id]); + } + + clearStale(id: string) { + this.setStaleIds([...this.staleIds].filter((staleId) => staleId !== id)); + } + getAllBookmarks(): Bookmark[] { return this.context.workspaceState.get(STORAGE_KEY_BOOKMARKS, []); } @@ -350,8 +442,22 @@ export class BookmarkStore { this.setBookmarks(this.getAllBookmarks().filter((b) => b.id !== id)); } + /** Removes several bookmarks in one write, so the tree refreshes once. */ + removeBookmarks(ids: string[]) { + const idSet = new Set(ids); + this.setBookmarks(this.getAllBookmarks().filter((b) => !idSet.has(b.id))); + } + moveBookmarkToFolder(id: string, folderId: string | null) { - this.setBookmarks(this.getAllBookmarks().map((b) => (b.id === id ? { ...b, folderId } : b))); + this.moveBookmarksToFolder([id], folderId); + } + + /** Moves several bookmarks to a folder (or the root) in one write. */ + moveBookmarksToFolder(ids: string[], folderId: string | null) { + const idSet = new Set(ids); + this.setBookmarks( + this.getAllBookmarks().map((b) => (idSet.has(b.id) ? { ...b, folderId } : b)), + ); } renameBookmark(id: string, label: string) { @@ -408,6 +514,27 @@ export class BookmarkStore { this.setFolders(this.getAllFolders().map((f) => (f.id === id ? { ...f, name } : f))); } + /** Sets a folder's icon and/or color; `undefined` for either clears it back to the default. */ + setFolderAppearance(id: string, appearance: { icon?: string; color?: string }) { + this.setFolders( + this.getAllFolders().map((f) => { + if (f.id !== id) { + return f; + } + const updated: BookmarkFolder = { ...f }; + delete updated.icon; + delete updated.color; + if (appearance.icon) { + updated.icon = appearance.icon; + } + if (appearance.color) { + updated.color = appearance.color; + } + return updated; + }), + ); + } + moveFolderToParent(id: string, parentId: string | null) { this.setFolders(this.getAllFolders().map((f) => (f.id === id ? { ...f, parentId } : f))); } @@ -486,7 +613,10 @@ export class FolderGroupItem extends vscode.TreeItem { ) { super(folder.name, vscode.TreeItemCollapsibleState.Expanded); this.contextValue = 'bookmarkFolder'; - this.iconPath = new vscode.ThemeIcon('folder'); + this.iconPath = new vscode.ThemeIcon( + folder.icon ?? 'folder', + folder.color ? new vscode.ThemeColor(folder.color) : undefined, + ); this.description = childFolders.length > 0 ? `${bookmarks.length} • ${childFolders.length} subfolder${childFolders.length === 1 ? '' : 's'}` @@ -498,14 +628,18 @@ export class BookmarkTreeItem extends vscode.TreeItem { constructor( public readonly bookmark: Bookmark, descriptionParts: string[], + public readonly stale = false, ) { super(bookmark.label, vscode.TreeItemCollapsibleState.None); - this.description = descriptionParts.join(' • '); - this.tooltip = + this.description = (stale ? ['missing', ...descriptionParts] : descriptionParts).join(' • '); + const location = bookmark.lineStart !== undefined ? `${bookmark.relativePath}:${formatLineRange({ lineStart: bookmark.lineStart, lineEnd: bookmark.lineEnd })}` : bookmark.relativePath; - this.iconPath = new vscode.ThemeIcon('bookmark'); + this.tooltip = stale ? `${location}\nThis file no longer exists.` : location; + this.iconPath = stale + ? new vscode.ThemeIcon('warning', new vscode.ThemeColor('list.warningForeground')) + : new vscode.ThemeIcon('bookmark'); this.contextValue = 'bookmarkItem'; this.command = { command: 'workspace-file-bookmarks.openBookmark', @@ -587,16 +721,19 @@ export class BookmarksTreeProvider bookmarks = bookmarks.filter((b) => matchesSearchFilter(b, searchFilter)); } + const makeItem = (b: Bookmark, folderName: string | null) => + new BookmarkTreeItem( + b, + describeBookmark(b, isMultiRoot, folderName), + this.store.isStale(b.id), + ); + if (this.viewMode === 'list') { if (element) { return []; } - return sortByOrder(bookmarks, bookmarkFallbackCompare).map( - (b) => - new BookmarkTreeItem( - b, - describeBookmark(b, isMultiRoot, folderBreadcrumb(folders, b.folderId ?? '') || null), - ), + return sortByOrder(bookmarks, bookmarkFallbackCompare).map((b) => + makeItem(b, folderBreadcrumb(folders, b.folderId ?? '') || null), ); } @@ -611,8 +748,8 @@ export class BookmarksTreeProvider const childFolderNodes = sortByOrder(element.childFolders, folderFallbackCompare).map( buildFolderNode, ); - const childBookmarkNodes = sortByOrder(element.bookmarks, bookmarkFallbackCompare).map( - (b) => new BookmarkTreeItem(b, describeBookmark(b, isMultiRoot, null)), + const childBookmarkNodes = sortByOrder(element.bookmarks, bookmarkFallbackCompare).map((b) => + makeItem(b, null), ); return [...childFolderNodes, ...childBookmarkNodes]; } @@ -629,7 +766,7 @@ export class BookmarksTreeProvider const ungrouped = sortByOrder( bookmarks.filter((b) => !b.folderId), bookmarkFallbackCompare, - ).map((b) => new BookmarkTreeItem(b, describeBookmark(b, isMultiRoot, null))); + ).map((b) => makeItem(b, null)); return [...folderNodes, ...ungrouped]; } @@ -771,7 +908,22 @@ export function addBookmarksForUris( } } -export async function openBookmark(bookmark: Bookmark) { +/** Whether the file a bookmark points at still exists (a lightweight `fs.stat`, no content read). */ +export async function bookmarkTargetExists(bookmark: Bookmark): Promise { + try { + await vscode.workspace.fs.stat(vscode.Uri.parse(bookmark.uri)); + return true; + } catch { + return false; + } +} + +/** + * Opens a bookmark. When a `store` is given, a failed open whose target no longer exists marks the + * bookmark stale (greyed with a warning icon in the tree), and a successful open clears any stale + * mark. A failure for another reason (e.g. a binary file) is reported but not marked stale. + */ +export async function openBookmark(bookmark: Bookmark, store?: BookmarkStore) { try { const uri = vscode.Uri.parse(bookmark.uri); const document = await vscode.workspace.openTextDocument(uri); @@ -783,20 +935,79 @@ export async function openBookmark(bookmark: Bookmark) { editor.selection = new vscode.Selection(range.start, range.end); editor.revealRange(range, vscode.TextEditorRevealType.InCenter); } + store?.clearStale(bookmark.id); } catch { + if (store && !(await bookmarkTargetExists(bookmark))) { + store.markStale(bookmark.id); + } vscode.window.showErrorMessage( `Could not open "${bookmark.relativePath}". The file may have been moved or deleted.`, ); } } +/** + * Sweeps every bookmark with one `fs.stat` per distinct file, marks the missing ones stale, and — + * after a confirmation showing the count — removes them. Declining leaves them marked so they + * stay visible in the tree. On-demand only; nothing polls in the background. + */ +export async function removeBrokenBookmarks(store: BookmarkStore): Promise { + const bookmarks = store.getAllBookmarks(); + const existsByUri = new Map(); + await Promise.all( + [...new Set(bookmarks.map((b) => b.uri))].map(async (uri) => { + const exists = await bookmarkTargetExists(bookmarks.find((b) => b.uri === uri) as Bookmark); + existsByUri.set(uri, exists); + }), + ); + + const broken = bookmarks.filter((b) => existsByUri.get(b.uri) === false); + store.setStaleIds(broken.map((b) => b.id)); + + if (broken.length === 0) { + vscode.window.showInformationMessage('No broken bookmarks found.'); + return; + } + + const confirm = await vscode.window.showWarningMessage( + `Remove ${broken.length} broken bookmark(s) whose files no longer exist?`, + { modal: true }, + 'Remove', + ); + if (confirm !== 'Remove') { + return; + } + store.removeStaleBookmarks(); + vscode.window.showInformationMessage(`Removed ${broken.length} broken bookmark(s).`); +} + +/** Copies the bookmarked file's absolute path to the clipboard. */ +export async function copyBookmarkPath(bookmark: Bookmark): Promise { + await vscode.env.clipboard.writeText(vscode.Uri.parse(bookmark.uri).fsPath); +} + +/** Copies the bookmarked file's workspace-relative path to the clipboard. */ +export async function copyBookmarkRelativePath(bookmark: Bookmark): Promise { + await vscode.env.clipboard.writeText(bookmark.relativePath); +} + +/** Reveals the bookmarked file in VS Code's Explorer view. */ +export async function revealBookmarkInExplorer(bookmark: Bookmark): Promise { + await vscode.commands.executeCommand('revealInExplorer', vscode.Uri.parse(bookmark.uri)); +} + +/** Reveals the bookmarked file in the OS file manager. */ +export async function revealBookmarkInFileManager(bookmark: Bookmark): Promise { + await vscode.commands.executeCommand('revealFileInOS', vscode.Uri.parse(bookmark.uri)); +} + export async function openAllInFolder(store: BookmarkStore, item: FolderGroupItem) { if (item.bookmarks.length === 0) { vscode.window.showInformationMessage(`"${item.folder.name}" has no bookmarks.`); return; } for (const bookmark of item.bookmarks) { - await openBookmark(bookmark); + await openBookmark(bookmark, store); } } @@ -856,6 +1067,38 @@ export async function renameFolder(store: BookmarkStore, item: FolderGroupItem) } } +const DEFAULT_APPEARANCE_PICK = '$(discard) Default'; + +/** Two-step quick-pick (icon, then color) that stores the chosen look on the folder. Cancelling either step leaves the folder unchanged. */ +export async function setFolderAppearance(store: BookmarkStore, item: FolderGroupItem) { + const iconItems = [ + { label: DEFAULT_APPEARANCE_PICK, iconId: undefined as string | undefined }, + ...FOLDER_ICON_CHOICES.map((c) => ({ label: `$(${c.id}) ${c.label}`, iconId: c.id })), + ]; + const pickedIcon = await vscode.window.showQuickPick(iconItems, { + placeHolder: `Icon for "${item.folder.name}"`, + }); + if (!pickedIcon) { + return; + } + + const colorItems = [ + { label: DEFAULT_APPEARANCE_PICK, colorId: undefined as string | undefined }, + ...FOLDER_COLOR_CHOICES.map((c) => ({ label: c.label, colorId: c.id })), + ]; + const pickedColor = await vscode.window.showQuickPick(colorItems, { + placeHolder: `Color for "${item.folder.name}"`, + }); + if (!pickedColor) { + return; + } + + store.setFolderAppearance(item.folder.id, { + icon: pickedIcon.iconId, + color: pickedColor.colorId, + }); +} + export async function renameBookmark(store: BookmarkStore, item: BookmarkTreeItem) { const label = await vscode.window.showInputBox({ prompt: 'Rename bookmark', @@ -1136,12 +1379,41 @@ export function dropBookmark( store.reorderBookmarks(computeReorderedIds(scope, draggedId, beforeId, bookmarkFallbackCompare)); } -export async function moveToFolder(store: BookmarkStore, item: BookmarkTreeItem) { - const folderId = await pickFolder(store, `Move "${item.bookmark.label}" to...`); +/** + * The bookmarks a tree command should act on. When the command is invoked from a multi-selected + * context menu, VS Code passes the clicked item plus the whole selection; if the clicked item is + * part of that selection the action applies to every selected bookmark (folders in the selection + * are ignored), otherwise just to the clicked item. + */ +export function resolveBookmarkTargets( + item: BookmarkTreeItem, + selection?: readonly TreeNode[], +): Bookmark[] { + const selected = (selection ?? []).filter((node): node is BookmarkTreeItem => 'bookmark' in node); + if (selected.length > 1 && selected.some((n) => n.bookmark.id === item.bookmark.id)) { + return selected.map((n) => n.bookmark); + } + return [item.bookmark]; +} + +export async function moveToFolder( + store: BookmarkStore, + item: BookmarkTreeItem, + selection?: readonly TreeNode[], +) { + const targets = resolveBookmarkTargets(item, selection); + const placeHolder = + targets.length > 1 + ? `Move ${targets.length} bookmarks to...` + : `Move "${item.bookmark.label}" to...`; + const folderId = await pickFolder(store, placeHolder); if (folderId === undefined) { return; } - store.moveBookmarkToFolder(item.bookmark.id, folderId); + store.moveBookmarksToFolder( + targets.map((b) => b.id), + folderId, + ); } /** Writes all bookmarks and folders to a JSON file the user picks via a save dialog. */ @@ -1283,6 +1555,6 @@ export async function goToBookmark(store: BookmarkStore): Promise { placeHolder: 'Go to bookmark', }); if (picked) { - await openBookmark(picked.bookmark); + await openBookmark(picked.bookmark, store); } } diff --git a/package-lock.json b/package-lock.json index dbf6575..4dae0fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,11 @@ "@eslint/js": "^10.0.1", "@types/mocha": "^10.0.10", "@types/node": "^26.6.6", - "@types/vscode": "^1.145.0", - "@typescript/native": "npm:typescript@^7.1.0-dev.20261108.1", + "@types/vscode": "^1.146.0", + "@typescript-eslint/eslint-plugin": "^8.72.2", + "@typescript-eslint/parser": "^8.72.2", + "@typescript-eslint/utils": "^8.72.2", + "@typescript/native": "npm:typescript@^7.1.0-dev.20261111.1", "@vitest/coverage-istanbul": "^5.1.0", "@vitest/coverage-v8": "^5.1.0", "@vitest/eslint-plugin": "^1.6.28", @@ -26,10 +29,10 @@ "eslint": "^10.14.0", "eslint-config-prettier": "^10.1.8", "lefthook": "^2.2.2", - "prettier": "^3.9.10", + "prettier": "^3.10.0", "typescript": "npm:@typescript/typescript6@^6.0.2", - "typescript-eslint": "^8.72.1", - "vite": "^8.3.6", + "typescript-eslint": "^8.72.2", + "vite": "^8.3.8", "vitest": "^5.1.0" }, "engines": { @@ -94,9 +97,9 @@ } }, "node_modules/@azure/core-rest-pipeline": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.0.tgz", - "integrity": "sha512-bMs8ekJLjX8wPV+9IPBges1SLPyuDtE9g5gLDWOpxzKcoOFQnpLGkbcT1tdw3FaAmDS1gnPmMmJ6y/T5B96kIA==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.25.1.tgz", + "integrity": "sha512-fTIA3CAm/cdODqiXkO6jYOIkU2Ylld7vq65KGc70gTwl12sLQsC8v7I5t6tp2N2CfUONzZBXyeFw7KEu6Ks2zg==", "dev": true, "license": "MIT", "dependencies": { @@ -105,7 +108,7 @@ "@azure/core-tracing": "^1.3.0", "@azure/core-util": "^1.13.0", "@azure/logger": "^1.3.0", - "@typespec/ts-http-runtime": "^0.3.4", + "@typespec/ts-http-runtime": "^0.3.10", "tslib": "^2.6.2" }, "engines": { @@ -1384,9 +1387,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.157.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.157.0.tgz", - "integrity": "sha512-95iTox1qdt6WGWce3xwNOVayByOfLaYAq+qGPpub6NWbBh9sv0GG3+zyfh9ZBCO/jVu5A0EWI+qQI7g6fevjxg==", + "version": "0.158.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.158.0.tgz", + "integrity": "sha512-xZ66qA4WmLbTLlxH3mqMd8f9UKbaOzOT0ATwFf4DH/14pMgpUrtqtjyYzOQmgfl2a0iRAAMbQ9G1th/67jRzGw==", "dev": true, "license": "MIT", "funding": { @@ -1412,9 +1415,9 @@ "license": "MIT" }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.3.0.tgz", - "integrity": "sha512-zjq+pUSE36RJG9nJXqrRfMCOxekzVhlaYMtFELHaOcRH4UqTnkWhbTbEixKEawsb0ZTVMRbn8RLYcuU4DYk8qg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.3.2.tgz", + "integrity": "sha512-kqR2xcygEx3WwDY69LsKeyf7SVmx4yzMtKZRknyaHieVJdRvvnAM97L0Gq6tUFc9T2YXcW/Ghij2+Um6He0LTw==", "cpu": [ "arm" ], @@ -1429,9 +1432,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.3.0.tgz", - "integrity": "sha512-HdnCiSmMGgEILGVLU1o4dcOblLGj8sMnqEFrDY/S7KRti1nTQHAqGGs2AniPspbK90AFS3pM23K/R0J3hPQXYQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.3.2.tgz", + "integrity": "sha512-gXM4GAAvvIZA7qUuSCQS+Zz91H2fagKa5MNppUdjzysJ8LXMWm9D/gQj4nfOKo6okquDQ5pkjsuo2SUMXNW0Rw==", "cpu": [ "arm64" ], @@ -1446,9 +1449,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.3.0.tgz", - "integrity": "sha512-TkQ2JGC8/9UxoCeH5dSJj1gxuFhzw200QTJJd2YNPRXznSSM4XvpKV2Cs+RM2BcM8sa6xa4rmXNuiJ64+1993g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.3.2.tgz", + "integrity": "sha512-OBs5hp3W+LwMEUmDMMMnu08ncG0t6DDgnFU+6TAucKtAfNgerYLZSr2jtREKb/JuPQ1J7bJnWIgKKkxvjuAw1g==", "cpu": [ "arm64" ], @@ -1463,9 +1466,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.3.0.tgz", - "integrity": "sha512-Gr+iZW50RJZo4BPUazaDh/Azx7W0Rl04sy2MkXD+H4RkxzUX5hIrjaVw1BK+XHsccKExQAmCy6DhvRLlUzaYUg==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.3.2.tgz", + "integrity": "sha512-X0VGYd4v/gdjfa/IP6uHuMhofPoUDYvK/byk3bC0jQ1pKKgt6RZ4B4puMqP0AHkjmIHAsxA471mM6AxZ39IaTA==", "cpu": [ "x64" ], @@ -1480,9 +1483,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.3.0.tgz", - "integrity": "sha512-RwAtx8peSqUoBf8pxE0L62xKXBNnA3BtdFV+07t13t0TQoASCtV9tnU67aKKMRZSiD06SU07chB5B8fWFdW3gQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.3.2.tgz", + "integrity": "sha512-mr04K4cp9J/fj9fknrX8e4QvI/V48Sp+4v64PoLwhoirIH8b8L8EBrF/0bTjV1It2IL91hoNG13GblnWLglkxA==", "cpu": [ "x64" ], @@ -1497,9 +1500,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.3.0.tgz", - "integrity": "sha512-ow8fq6kYaIx75D0EVT5GeF4DcawcEYYXJ2CvEaOzyygW1jtVDQU/9o5YYidctHBRDUgPzGaTNB6n/VMTmd1ymw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.3.2.tgz", + "integrity": "sha512-VgWy02HU9Tiy6tU8U+kF3Fd4TQwTNdIBvs2MlgViP1ksMtAnQQTIPAj0ZsJzIQXKIOugqQSpqxQgELJ3fkuL5w==", "cpu": [ "arm" ], @@ -1514,9 +1517,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.0.tgz", - "integrity": "sha512-PLOWuiz+rdqSgiLgN7bxvy6DjXLj8gnJxzRS243LNnLi96VeyB7VFsC3eblcKv3Xwd3jQqJfbCWRhLijTIZyvQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.3.2.tgz", + "integrity": "sha512-0wloJFYqV844gv6boiERbHpErxbxHbmlgnMX5VxP1Ux+DLBqGrUOn0cIpBmCZEcTITes3QUelxya4u8c9ziQvQ==", "cpu": [ "arm64" ], @@ -1534,9 +1537,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.3.0.tgz", - "integrity": "sha512-CQ/k85cc342j5SnZ/gT9p132NXZRGTgLRhfItdYqUg+jpvL5HYFve0GB+zQDCYGwDjtDWIoqKSOitkmZMZc/tw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.3.2.tgz", + "integrity": "sha512-Kys8wgFQy8H8vua0MNpC0akMBFoNO8455RHk2yycBM0iayMIAtVAL6xmV9TWAkehFgrrlImTL2fZdX5LXfYtAA==", "cpu": [ "arm64" ], @@ -1554,9 +1557,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.3.0.tgz", - "integrity": "sha512-wk+ZG7xDP2px3cQRFoNoNp52/w1O5hTTtciHxuastreMOp8isYUTCpcG/rEYnSQBI+nycN+B9d9pBGua+qvV7w==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.3.2.tgz", + "integrity": "sha512-L8eej7+/StRxUHHOPsOVl7Fzx/3tumuGB4hGQZKT73xRI16KZhaSJr0gi3C3WBjNRxeJRKKps2UD4lQukP+oBw==", "cpu": [ "ppc64" ], @@ -1574,9 +1577,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.3.0.tgz", - "integrity": "sha512-sEgyZt72Rj7V5q6OZ7A4U2I5v4EMGEv3Fp5QmoncmKbUMTrGmpyH7MwENfERGbfUYjxdBmktw4JJLys5I1aQNQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.3.2.tgz", + "integrity": "sha512-MV5CEb/nV8neQOj7l6945q1vy62HXtWO8zPDvfySPusoUzdBoLoJ/DfXXwaYThGTXZCniF+5bCf253WTPPK7Yw==", "cpu": [ "s390x" ], @@ -1594,9 +1597,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.0.tgz", - "integrity": "sha512-PvL0BoBUwVugK1pFd+zyVk1L9F6ucmQ7oF2dpy8zKPHNlbW2zrU9Jst3fWYfG7z3owjxqhwAvi5G4o0AikrJMw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.3.2.tgz", + "integrity": "sha512-3qssvvVvkwQookZ1/1avlKWc5orAR85vgKdzDd5noZS8nnHiI/ejOAEt0xfNYvtBBF7/DZmP8dANWkRBJVOBCA==", "cpu": [ "x64" ], @@ -1614,9 +1617,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.3.0.tgz", - "integrity": "sha512-5N4DdzAmLtnyuh027H6ViC99BeoA4T0RwKS6qUrl6zY70AKRsNDrnEZeWnqrKmDRFxXmv8zf9yhF2DxN/SfyGQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.3.2.tgz", + "integrity": "sha512-rJqoFTm/bCZFKKgaT4NzWn/TuJkFDToZ/rzYFnYVBL28jH1nIfg6snx2mDri30nal9H+wcYfKpoPR9jrMQgQQg==", "cpu": [ "x64" ], @@ -1634,9 +1637,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.3.0.tgz", - "integrity": "sha512-Tz+2b+yF5eVRGX3DWebn2JIvZkNYI1gkafI+nF2fau2zeQDOpBAS8w1PxHi4ke//NIOE6+2DNYyq1veIjzAXqQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.3.2.tgz", + "integrity": "sha512-ozdbCjHfh0N//wJNExjJlSlcDaeIKo8k1ktzkLUyESr2Gu/Qw7nBYxKe3jgtoaxda570vg10WdYosre82263hQ==", "cpu": [ "arm64" ], @@ -1651,9 +1654,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.3.0.tgz", - "integrity": "sha512-ntK6OFor3nQtS1TenZP5wX59oPpkKEmour3AipGHB32u+Z8QXxjVdG+7vNSvgerRWIj6vjg4XpKu42slb0vlXQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.3.2.tgz", + "integrity": "sha512-p5VA6Vk/2cXZPVWf+dDbuE0dkhrcoAo6pISTJmzUnsSdQqNJdusyR8lZgHku095+r27zrYISNLZnJoqrcePW0A==", "cpu": [ "wasm32" ], @@ -1670,9 +1673,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.3.0.tgz", - "integrity": "sha512-ldH38lBlOpS+b4fbSG9euQjhYXLE18PIz5hyvf6gvFfyVLodRE/Tsu4jV8UmPqjjce6lotmUUwscxPuer4lczQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.3.2.tgz", + "integrity": "sha512-XDb2U14Orf4r76SwzvJ7avePL00w4gHBmi1Ig4B/itYFEUTmYMNDHVU0dcYuBa0LgNBqyREoJOrQKxQlyYJA7A==", "cpu": [ "arm64" ], @@ -1687,9 +1690,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.3.0.tgz", - "integrity": "sha512-HglhUzUPuXNTGFt5ZwJalSEvavhFSl+nxpQM9urkzkt+2oGI1ZSwgyytk9aGw15/0kk1TCHPc57nv3DdiG8/yQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.3.2.tgz", + "integrity": "sha512-lEsKzKJqIUDZVCBhS4lkZuyQUhV1oRg5UHkI72qlOAqJn7qEXcoVdTridFS7PHNWdZknBl95EAF/4P1Xv0K1eQ==", "cpu": [ "x64" ], @@ -1704,9 +1707,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.1.0.tgz", + "integrity": "sha512-K+NU2HDCEem4sClgHH4ZZXwdJeNFkJo+hRk479DIwdIviAPN2hFJH1KHHwg8CxHQXvi0M5XY4deTXINfIiSG6w==", "dev": true, "license": "MIT" }, @@ -1855,24 +1858,24 @@ } }, "node_modules/@types/vscode": { - "version": "1.145.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.145.0.tgz", - "integrity": "sha512-mADZK4SF/j1n0+Qk4bH8NT1qTQGYDAsbhb5hStbzuB3hKP3mRH7xja5yUaIm7ExCh65Ff+C9XGwxcBvSlTCvkQ==", + "version": "1.146.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.146.0.tgz", + "integrity": "sha512-OtU25faeM997QQsW+D07FN9cX06j+EEI4aQaPw/UO3HWlCM39N1W7fG8ewhdZKluut31KffyMsmDzJzgMu6t8Q==", "dev": true, "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.72.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.1.tgz", - "integrity": "sha512-5CokTWXpmbylfFabsbvHC0CAw3XLaGnoa+nFJX5FR592h/LoZsENUE6p/rg5xvkKrFEXz6pYmgI29AgXc2N3BQ==", + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.2.tgz", + "integrity": "sha512-222PKFXKihmFpi/pS5njoBgdAqsPhj2/GBmFbrNw8Tq8gEG9Sv+kSlKFy4u2q+mJv9ZzJgM2Gkv/bUzRZwZw5g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.72.1", - "@typescript-eslint/type-utils": "8.72.1", - "@typescript-eslint/utils": "8.72.1", - "@typescript-eslint/visitor-keys": "8.72.1", + "@typescript-eslint/scope-manager": "8.72.2", + "@typescript-eslint/type-utils": "8.72.2", + "@typescript-eslint/utils": "8.72.2", + "@typescript-eslint/visitor-keys": "8.72.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1885,32 +1888,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.72.1", + "@typescript-eslint/parser": "^8.72.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.14", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.14.tgz", - "integrity": "sha512-wCBiZ77u1tuWde6G/nfOtLaCTYcFDU5yCzARzK10y+rLhzYoL7a7yEemcopcc2Mzow7gZOIjnV2ym1NYmdiJOQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, "node_modules/@typescript-eslint/parser": { - "version": "8.72.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.1.tgz", - "integrity": "sha512-ZGwZP4OCfyA0AQTpEAMWz+rtX01DRhQWW5hB0ucKDEIX5U7jdmPSaMx94rQfaPAQzIYz2BimOgKOJtjfpRFQ9g==", + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.2.tgz", + "integrity": "sha512-llP+T1YuJOLlxEciP6OyQGtz5SMxyn+9LA2BvZw5RmToGVa1y4beEDVQL6Gje7T5WFYh19WREjDCN9Ev4gm5AA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.72.1", - "@typescript-eslint/types": "8.72.1", - "@typescript-eslint/typescript-estree": "8.72.1", - "@typescript-eslint/visitor-keys": "8.72.1", + "@typescript-eslint/scope-manager": "8.72.2", + "@typescript-eslint/types": "8.72.2", + "@typescript-eslint/typescript-estree": "8.72.2", + "@typescript-eslint/visitor-keys": "8.72.2", "debug": "^4.4.3" }, "engines": { @@ -1926,14 +1919,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.72.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.1.tgz", - "integrity": "sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==", + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.2.tgz", + "integrity": "sha512-6y5dLTZdHhI8Eh3yIQ/bfQhRKwn5e+TpJj64o18Cngc0iW/BCrVvrr6zGYqXPnYH31u6YXAYiFh0+qxX5mybJw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.72.1", - "@typescript-eslint/types": "^8.72.1", + "@typescript-eslint/tsconfig-utils": "^8.72.2", + "@typescript-eslint/types": "^8.72.2", "debug": "^4.4.3" }, "engines": { @@ -1948,14 +1941,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.72.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.1.tgz", - "integrity": "sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==", + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.2.tgz", + "integrity": "sha512-iIoMJ6/8c1/QpFNyFjU0d1YzPco4MsZjQw0k3V+vfzNWAFmUmvhlyMoYEoYYDQZXc+J6Gi1X5XcxlzW2taIIGQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.72.1", - "@typescript-eslint/visitor-keys": "8.72.1" + "@typescript-eslint/types": "8.72.2", + "@typescript-eslint/visitor-keys": "8.72.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -1966,9 +1959,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.72.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.1.tgz", - "integrity": "sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==", + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.2.tgz", + "integrity": "sha512-+h3iCgZXuTNtoLXcnnjP12ZUHLMqoUcIHPfQrFEXTUqvb9dF8QK9b5nv5VtSo5fLkdHuks8ehbTzZj3rImAm9w==", "dev": true, "license": "MIT", "engines": { @@ -1983,15 +1976,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.72.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.72.1.tgz", - "integrity": "sha512-iY2wlmyrXJmx4yxUGP3aYCFF3cA/6ShddOKmLU2dx7LnEykbAM8WThlxZEfd1NddqceF6eJmZUayxx9oKIYNlg==", + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.72.2.tgz", + "integrity": "sha512-STfkR38qIdhfkLpgtXnUjAzsNy4xouqiBO0lrVc/Wpk9Kem5/aHXUx1J8dGC16zpdYN1+yc2ILQ28er1kXX7ag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.72.1", - "@typescript-eslint/typescript-estree": "8.72.1", - "@typescript-eslint/utils": "8.72.1", + "@typescript-eslint/types": "8.72.2", + "@typescript-eslint/typescript-estree": "8.72.2", + "@typescript-eslint/utils": "8.72.2", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2008,9 +2001,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.72.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.1.tgz", - "integrity": "sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==", + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.2.tgz", + "integrity": "sha512-Xfn37OLZx03+PCU7qWx9DTv2FCRh1E6fk2+wONeNY15HXIwtcwuveaSwVpGAUptkNmPQegRxW5JyRel9T7wrog==", "dev": true, "license": "MIT", "engines": { @@ -2022,16 +2015,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.72.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.1.tgz", - "integrity": "sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==", + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.2.tgz", + "integrity": "sha512-pQEGqIK/cqEvKivVWaFLYpe7XYejFxfuAOi4mu1oAWFvIo36aRHI9esRFTUxcIy/z8N482RJyH+LpZB0ZIU7Ow==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.72.1", - "@typescript-eslint/tsconfig-utils": "8.72.1", - "@typescript-eslint/types": "8.72.1", - "@typescript-eslint/visitor-keys": "8.72.1", + "@typescript-eslint/project-service": "8.72.2", + "@typescript-eslint/tsconfig-utils": "8.72.2", + "@typescript-eslint/types": "8.72.2", + "@typescript-eslint/visitor-keys": "8.72.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2050,16 +2043,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.72.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.1.tgz", - "integrity": "sha512-5v5liypurJBFHCBNqh/RjsymAD+VWcE0djuGY/BUqp5fC3Qn+luWDEksjuTtWjPn6i5zQlUtkZCRG/CYG0yCkA==", + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.2.tgz", + "integrity": "sha512-hv3eRYbpPbbpezuax1EfGrzpa3C4ySgNZkPUkLOWOAjU8HsOKowEDAr8C7nsNl1C+SBSKDpvno6VKHnlhvp9iA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.72.1", - "@typescript-eslint/types": "8.72.1", - "@typescript-eslint/typescript-estree": "8.72.1" + "@typescript-eslint/scope-manager": "8.72.2", + "@typescript-eslint/types": "8.72.2", + "@typescript-eslint/typescript-estree": "8.72.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2074,13 +2067,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.72.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.1.tgz", - "integrity": "sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==", + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.2.tgz", + "integrity": "sha512-NAGrjn0Pitjqzq/GkgMC4kBYm233PQ8GffyQ9+kTA3AlNQYEzOf25EIM02BAHZv5FRJSpHpJk+OGUxVkOB93GQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/types": "8.72.2", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2106,9 +2099,9 @@ }, "node_modules/@typescript/native": { "name": "typescript", - "version": "7.1.0-dev.20261108.1", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.1.0-dev.20261108.1.tgz", - "integrity": "sha512-AeosmVVqQg0Hyn/UmAu9RmKTE28xmi124lful0Be7lufu7H0cSRar1W3gUj+wIUEVoJ6Tu/PHNbT6HvXYMwhHA==", + "version": "7.1.0-dev.20261111.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.1.0-dev.20261111.1.tgz", + "integrity": "sha512-LYXsJx16DRxvSjFPyHP3J2d2bPvjItOgCEIe2kYxEyzaMIoWK17ioC+UME0Qj5cZTb7VPcHjVdEJEdGLW56f7g==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2118,13 +2111,13 @@ "node": ">=16.20.0" }, "optionalDependencies": { - "@typescript/typescript-darwin-arm64": "7.1.0-dev.20261108.1", - "@typescript/typescript-darwin-x64": "7.1.0-dev.20261108.1", - "@typescript/typescript-linux-arm": "7.1.0-dev.20261108.1", - "@typescript/typescript-linux-arm64": "7.1.0-dev.20261108.1", - "@typescript/typescript-linux-x64": "7.1.0-dev.20261108.1", - "@typescript/typescript-win32-arm64": "7.1.0-dev.20261108.1", - "@typescript/typescript-win32-x64": "7.1.0-dev.20261108.1" + "@typescript/typescript-darwin-arm64": "7.1.0-dev.20261111.1", + "@typescript/typescript-darwin-x64": "7.1.0-dev.20261111.1", + "@typescript/typescript-linux-arm": "7.1.0-dev.20261111.1", + "@typescript/typescript-linux-arm64": "7.1.0-dev.20261111.1", + "@typescript/typescript-linux-x64": "7.1.0-dev.20261111.1", + "@typescript/typescript-win32-arm64": "7.1.0-dev.20261111.1", + "@typescript/typescript-win32-x64": "7.1.0-dev.20261111.1" } }, "node_modules/@typescript/old": { @@ -2143,9 +2136,9 @@ } }, "node_modules/@typescript/typescript-darwin-arm64": { - "version": "7.1.0-dev.20261108.1", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.1.0-dev.20261108.1.tgz", - "integrity": "sha512-JwAKQ4zgIFCJgxOCdpcCFI32ZjMefFadg49nskmmP5OwJXTU/p6GWU3MImCnx9Ym4lWU+PEBkzSymn1itWhx1A==", + "version": "7.1.0-dev.20261111.1", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.1.0-dev.20261111.1.tgz", + "integrity": "sha512-JSndMjPkbtfCA3TSDKTg1TCrsVYeadNZrFN6AQu2Ljhwn+NQe3CQmmBIpoY7ECmA/g6R1Q2INnjKcuCoFL5kKw==", "cpu": [ "arm64" ], @@ -2160,9 +2153,9 @@ } }, "node_modules/@typescript/typescript-darwin-x64": { - "version": "7.1.0-dev.20261108.1", - "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.1.0-dev.20261108.1.tgz", - "integrity": "sha512-90XVBdVae2CsPPeTXYk/BGbxXy7X9iGYAzz5u4ZPLxXA4U6oXg4Wyd8YOwJAR5SAG1BZs6qA5bBgB3T4w8g9yw==", + "version": "7.1.0-dev.20261111.1", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.1.0-dev.20261111.1.tgz", + "integrity": "sha512-+QlSEMG9ye8cS/KTPi7xjS+0yM7Hm8drrrGshrFgN93TKgv1kZWe3fKJ07lY/o+9O2cJJ14XSNX1YPJiJEwN7Q==", "cpu": [ "x64" ], @@ -2177,9 +2170,9 @@ } }, "node_modules/@typescript/typescript-linux-arm": { - "version": "7.1.0-dev.20261108.1", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.1.0-dev.20261108.1.tgz", - "integrity": "sha512-jQjAZlaxG0KWKh8+cYvpgoBXlg/9nj1XjcJNpih8tbCDCPrzvFkoXjAfs+By9lLJm9OeHT5iD8ITuIdgjn/PLw==", + "version": "7.1.0-dev.20261111.1", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.1.0-dev.20261111.1.tgz", + "integrity": "sha512-9tRpV6YMXyRre9aLCy1HINJjCvw0Qr+IwWWex+3899WmG6jnLEVPZsFaVZI1zRTRxzn5CU1A4DFsfKsHG96ZyA==", "cpu": [ "arm" ], @@ -2194,9 +2187,9 @@ } }, "node_modules/@typescript/typescript-linux-arm64": { - "version": "7.1.0-dev.20261108.1", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.1.0-dev.20261108.1.tgz", - "integrity": "sha512-1h6sqGqeuagdgQfBKAbTrdX6FD0hKmF5W7XyZG6Om8osLTEXRIyY3pp7Y0vhhk4u2phwYJpGGP7lSJ8g1YhaAg==", + "version": "7.1.0-dev.20261111.1", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.1.0-dev.20261111.1.tgz", + "integrity": "sha512-BLwcZlBTW147/QOMbKTJ7VBzsvistqMOpYEF6ry0PTBt47vgTWSOO+UE/8pxlvM6/nKs2eu29vHHmuFdYZWC4g==", "cpu": [ "arm64" ], @@ -2211,9 +2204,9 @@ } }, "node_modules/@typescript/typescript-linux-x64": { - "version": "7.1.0-dev.20261108.1", - "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.1.0-dev.20261108.1.tgz", - "integrity": "sha512-cDO4IXh1H1pyNQF6D+YdgoR9lM+0rmgN7TaX8q+2M4reityriC33eYQ+pOBj5J7LBPMmjFI0iXbCOpn8025hNQ==", + "version": "7.1.0-dev.20261111.1", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.1.0-dev.20261111.1.tgz", + "integrity": "sha512-aINpUZoSfYmWAZ5NwMceDF0MHHNBg6d0WbU1ZRTfUrFvEJ7H4FpUpS2fOihPIMFH3UV4SPIkFabyQk4t8uADZg==", "cpu": [ "x64" ], @@ -2228,9 +2221,9 @@ } }, "node_modules/@typescript/typescript-win32-arm64": { - "version": "7.1.0-dev.20261108.1", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.1.0-dev.20261108.1.tgz", - "integrity": "sha512-/fOEEd3/C44J6UNR6UEgaLUUbj6wUR1/KyWVATrkzVj+ueTo821KbLMFC3P525HyGK1xelYrtppjXczTpthd4w==", + "version": "7.1.0-dev.20261111.1", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.1.0-dev.20261111.1.tgz", + "integrity": "sha512-Gz66d/XJZH5cQKcUzKKiFyj7G4jN92fQfOIIJdN4eEQHhEY/4UchSoPbop9Hh6I5Tzrk2v8ZPQCTe3utiiPP5g==", "cpu": [ "arm64" ], @@ -2245,9 +2238,9 @@ } }, "node_modules/@typescript/typescript-win32-x64": { - "version": "7.1.0-dev.20261108.1", - "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.1.0-dev.20261108.1.tgz", - "integrity": "sha512-/KwlIseOmDtie6BbiqIPHP3HJhdW/twO6jZnE3+fjxukW/psJdeJUvAjj2y9wzHScGrRUHLVaHVL5XY6RVi/fA==", + "version": "7.1.0-dev.20261111.1", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.1.0-dev.20261111.1.tgz", + "integrity": "sha512-nAHHwBOSAaQrIrSHL+8Nkz6pzALcBlHIlGJfUNjmGIoDpCpFe+maaolvstrnCemFReeXvLtTiMDEUFbXrSPQAQ==", "cpu": [ "x64" ], @@ -3042,9 +3035,9 @@ "license": "BSD-2-Clause" }, "node_modules/brace-expansion": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", - "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.13.tgz", + "integrity": "sha512-Vh3dmFbc4wZeox7M9I/0L9GfFd83Su4+yCck/OF9Gi/P/dAHn0RT7X4K9R7gMNMhDck3DvhR6QXsnQdjjgwNvQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3868,6 +3861,16 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/espree": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", @@ -4360,9 +4363,9 @@ } }, "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.14.tgz", + "integrity": "sha512-wCBiZ77u1tuWde6G/nfOtLaCTYcFDU5yCzARzK10y+rLhzYoL7a7yEemcopcc2Mzow7gZOIjnV2ym1NYmdiJOQ==", "dev": true, "license": "MIT", "engines": { @@ -5406,9 +5409,9 @@ "license": "MIT" }, "node_modules/marked": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/marked/-/marked-18.1.3.tgz", - "integrity": "sha512-9ChMH8EVVgjgID49nsL6IWxWk5c6GtAsq/xw7U1ggkjkU5AGuv68oLTVlFRtvcUYZtpbNyjyvGo3YDu0fGapiw==", + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-18.2.0.tgz", + "integrity": "sha512-KaeL+tAbtvI930q7HE2PN5lYXYdT8EiSmxHlN6F3rsfb57wd5mp5W5TLazpdj4inTQBJdgjYwbs40W/0VUGU+Q==", "dev": true, "license": "MIT", "bin": { @@ -5535,9 +5538,9 @@ "license": "MIT" }, "node_modules/mocha/node_modules/brace-expansion": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", - "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.8.tgz", + "integrity": "sha512-BnlDzDYQoplTsZSvPi86CNzmbcb07X8ufR8fk1zivaiEJlXJkiq1MQyDCVmlWcsWbj6aWXqHnrwwevbrm/Saig==", "dev": true, "license": "MIT", "dependencies": { @@ -6157,9 +6160,9 @@ } }, "node_modules/prettier": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", - "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.10.0.tgz", + "integrity": "sha512-mBi4765LgjO1PFjlEs7pUeK0nb1bw71c+N4/ItfeUWNdY5cV2pRKaFpbbFRYmDJMO+5ZwzGxHRqHxFZna7iv5w==", "dev": true, "license": "MIT", "bin": { @@ -6361,13 +6364,13 @@ } }, "node_modules/rolldown": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.3.0.tgz", - "integrity": "sha512-7j1jdVlzWzbRat9EDMsB/QmyMcc6hOO0vbcma8LGjBTmKlpS5Hem+5fpntc/HoRV5tLrG+Qo6I1ZHV/ZuL5S8g==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.3.2.tgz", + "integrity": "sha512-EFHqbVkXiYZA6akmYm2bvJhNbEGdUwAResRhYwl/BO8QhB5KxsG+t3RVHJSIOy5jBEb0XCACvrIGnnl6Z8sMGA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.157.0", + "@oxc-project/types": "=0.158.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6377,22 +6380,22 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.3.0", - "@rolldown/binding-android-arm64": "1.3.0", - "@rolldown/binding-darwin-arm64": "1.3.0", - "@rolldown/binding-darwin-x64": "1.3.0", - "@rolldown/binding-freebsd-x64": "1.3.0", - "@rolldown/binding-linux-arm-gnueabihf": "1.3.0", - "@rolldown/binding-linux-arm64-gnu": "1.3.0", - "@rolldown/binding-linux-arm64-musl": "1.3.0", - "@rolldown/binding-linux-ppc64-gnu": "1.3.0", - "@rolldown/binding-linux-s390x-gnu": "1.3.0", - "@rolldown/binding-linux-x64-gnu": "1.3.0", - "@rolldown/binding-linux-x64-musl": "1.3.0", - "@rolldown/binding-openharmony-arm64": "1.3.0", - "@rolldown/binding-wasm32-wasi": "1.3.0", - "@rolldown/binding-win32-arm64-msvc": "1.3.0", - "@rolldown/binding-win32-x64-msvc": "1.3.0" + "@rolldown/binding-android-arm-eabi": "1.3.2", + "@rolldown/binding-android-arm64": "1.3.2", + "@rolldown/binding-darwin-arm64": "1.3.2", + "@rolldown/binding-darwin-x64": "1.3.2", + "@rolldown/binding-freebsd-x64": "1.3.2", + "@rolldown/binding-linux-arm-gnueabihf": "1.3.2", + "@rolldown/binding-linux-arm64-gnu": "1.3.2", + "@rolldown/binding-linux-arm64-musl": "1.3.2", + "@rolldown/binding-linux-ppc64-gnu": "1.3.2", + "@rolldown/binding-linux-s390x-gnu": "1.3.2", + "@rolldown/binding-linux-x64-gnu": "1.3.2", + "@rolldown/binding-linux-x64-musl": "1.3.2", + "@rolldown/binding-openharmony-arm64": "1.3.2", + "@rolldown/binding-wasm32-wasi": "1.3.2", + "@rolldown/binding-win32-arm64-msvc": "1.3.2", + "@rolldown/binding-win32-x64-msvc": "1.3.2" } }, "node_modules/run-applescript": { @@ -7011,16 +7014,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.72.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.72.1.tgz", - "integrity": "sha512-3Li9FfPmQp45nxEtFD1ZCrYFI3cYK5kpyNtmm1H7lMagm8u9sy6PTjy3ytwQZs7KNukOyKFHAZ6z6GjYFeaeXw==", + "version": "8.72.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.72.2.tgz", + "integrity": "sha512-c/+AKMW1KB81r28Sp0AAdcyXj5dzNmAsZFcsrxKbZTKmcBK2xIwBR31dcSYSDu3bKmzXe8GKrSxO+eDQ401Uzg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.72.1", - "@typescript-eslint/parser": "8.72.1", - "@typescript-eslint/typescript-estree": "8.72.1", - "@typescript-eslint/utils": "8.72.1" + "@typescript-eslint/eslint-plugin": "8.72.2", + "@typescript-eslint/parser": "8.72.2", + "@typescript-eslint/typescript-estree": "8.72.2", + "@typescript-eslint/utils": "8.72.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7101,16 +7104,16 @@ } }, "node_modules/vite": { - "version": "8.3.6", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.6.tgz", - "integrity": "sha512-UI7dK29NnS54Fk+mmo7o+jI6U8Fp7hDl+X5Q1k45WH+d4dSlbLVUETGQ+hfkNgvLQGyoElPj837nZyv4MBc0VA==", + "version": "8.3.8", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.8.tgz", + "integrity": "sha512-LzOn3h9rG/0vhLdXrV4yJ4QFA9eEoqCl+Yx+kGJd7aB9w6nfNgVHKSYwLPZUHrz4ZGa+t+kXjV9itvcHDLQouA==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.33.0", "picomatch": "^4.0.7", "postcss": "^8.5.32", - "rolldown": "~1.2.16", + "rolldown": "~1.2.18", "tinyglobby": "^0.2.17" }, "bin": { @@ -7189,9 +7192,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz", - "integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.19.tgz", + "integrity": "sha512-e+Ebe0WT+9tbXmNKPIiC8YrWAHYwug3m6i4p3rZGGulfZri1jtI4HslT+OZnFf/OIHowWF71DTlHwXBP2SiA7A==", "cpu": [ "arm" ], @@ -7206,9 +7209,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz", - "integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.19.tgz", + "integrity": "sha512-hOombrTJ+szt2ciEzS7oCA78lMPBgF5UMQWCJIyNfnrI7GLl8HYoFqCnpL2gcPOaXMqryGCU5ExdQKyDDcC+yA==", "cpu": [ "arm64" ], @@ -7223,9 +7226,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz", - "integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.19.tgz", + "integrity": "sha512-XoMA2pwCKGTQzUjPgtRwkedH4WmXhdIPKT6l6oOxd1PUbNodGI6DcBekgN7EC//+x3pfBYX/qL1gNmZAqivw5g==", "cpu": [ "arm64" ], @@ -7240,9 +7243,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz", - "integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.19.tgz", + "integrity": "sha512-zo8EsSSwMZCY/e+fRnGaX02YGgvJD0yvhNi/9hpGPA2MJ9ZF8dPCw75UGfpI6OpQ/myR/dIYmogRvqiZD0TrXg==", "cpu": [ "x64" ], @@ -7257,9 +7260,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz", - "integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.19.tgz", + "integrity": "sha512-O8ISAOfI1nj7AuBPd30FSF47z7f7lzlk5poQVGgiikvQuGEBFYvW8ki+UEnT+YJPnNGjQzn304klt4O9FUE7mA==", "cpu": [ "x64" ], @@ -7274,9 +7277,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz", - "integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.19.tgz", + "integrity": "sha512-/lYCba5NO0yq4S0jGMH8QTBtkvQxoXjoQWzF5BcWp9MjB9jxVRD1GQDNofkVjcOj30DWyhLIN/4Oe60JKzaONw==", "cpu": [ "arm" ], @@ -7291,9 +7294,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz", - "integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.19.tgz", + "integrity": "sha512-pm/apFMBnSbFb3O6nG5pHO1iqHhZrjGDFEjSm+ot9J17XXPvH9L90y5h+jgfHCE+Qdlsvy8qKEcU4VKxgjD+vA==", "cpu": [ "arm64" ], @@ -7311,9 +7314,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz", - "integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.19.tgz", + "integrity": "sha512-K1SmQp3FFichn8oqrNIwvFoZ5wcvjC7goBzpaMNvLf9FZc+QB/QvLmnpJZgAAGHiD980tP8FZZnXk2K94rvlxg==", "cpu": [ "arm64" ], @@ -7331,9 +7334,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz", - "integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.19.tgz", + "integrity": "sha512-I0xwD57jP/3A7+5UPZin2w8Y7XHEmEM5zqpYZOcQ45MlNW6tUJUziymV3yRr7rL4vwyfEcRsRksC4a7Kc08p5Q==", "cpu": [ "ppc64" ], @@ -7351,9 +7354,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz", - "integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.19.tgz", + "integrity": "sha512-BrtEHT24SNXj9HiXqy+s/kXiSiQGt6EKCPUlRirAoIioVxKGnbXBUEkbZASAei7iZJB0/Ynm+0JP00Ipx7AnSw==", "cpu": [ "s390x" ], @@ -7371,9 +7374,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz", - "integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.19.tgz", + "integrity": "sha512-rm75/9DkHKeU2WnA8w4X94WGYgLlDCFCfkQC+4bguVJmC/jvDRv2B+2wLOYhyE0fVuv7//Ckk39OoOufyCu76Q==", "cpu": [ "x64" ], @@ -7391,9 +7394,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz", - "integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.19.tgz", + "integrity": "sha512-irh/4+iYFM4GAzpluDErgjz89bj6NDtqqMAG/6O1LG38Ed3NpyebzCpcVNkFW8uhy+/3/BocUvnXDR33CXWe9A==", "cpu": [ "x64" ], @@ -7411,9 +7414,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz", - "integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.19.tgz", + "integrity": "sha512-+KWTWNoPt+CfSq94CiceWu3yPE+QzkaG+phPyz9G60tmZLwfQkcA4AeksafzvJAw4pvg1mcd5a1TkUqVv0s4JA==", "cpu": [ "arm64" ], @@ -7427,10 +7430,29 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/vite/node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.2.19.tgz", + "integrity": "sha512-7EH+skOsl5qpWnK0vzamgJFhOv1cwxQpsxm8tI883K9fur5c27p8q87fzEO6BbBPabA2/+s/zR63/pLc137iOg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "2.0.0-beta.2", + "@emnapi/runtime": "2.0.0-beta.2", + "@napi-rs/wasm-runtime": "1.2.6" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + } + }, "node_modules/vite/node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz", - "integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.19.tgz", + "integrity": "sha512-3Q1Yg1Q7tEvBWoZf5rFvG/GKZQPgwfru+p4wBajDfJFBs0SHCpL++dfTjW9gyAKd1T7ZpIYvdaUax7Pa/IhFVQ==", "cpu": [ "arm64" ], @@ -7445,9 +7467,9 @@ } }, "node_modules/vite/node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz", - "integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.19.tgz", + "integrity": "sha512-y/sqRRc6d7hECzOFc5n3LSeqctmIp9mMjY1Kq4s14uLPd/LZ4z7i1zrW6fMtp2mPeuQIC4e6ULMcwQzMomqQag==", "cpu": [ "x64" ], @@ -7462,9 +7484,9 @@ } }, "node_modules/vite/node_modules/rolldown": { - "version": "1.2.16", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz", - "integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==", + "version": "1.2.19", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.19.tgz", + "integrity": "sha512-QIiV1J9d0pzN4jeeTH4sjPy0AZ6cAy2cG0ExeoE1+U8Rvj+hR3gYkokx0YbeUNGlZqiQJJ2utcETe0oKXwzsuA==", "dev": true, "license": "MIT", "dependencies": { @@ -7478,21 +7500,22 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.16", - "@rolldown/binding-android-arm64": "1.2.16", - "@rolldown/binding-darwin-arm64": "1.2.16", - "@rolldown/binding-darwin-x64": "1.2.16", - "@rolldown/binding-freebsd-x64": "1.2.16", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.16", - "@rolldown/binding-linux-arm64-gnu": "1.2.16", - "@rolldown/binding-linux-arm64-musl": "1.2.16", - "@rolldown/binding-linux-ppc64-gnu": "1.2.16", - "@rolldown/binding-linux-s390x-gnu": "1.2.16", - "@rolldown/binding-linux-x64-gnu": "1.2.16", - "@rolldown/binding-linux-x64-musl": "1.2.16", - "@rolldown/binding-openharmony-arm64": "1.2.16", - "@rolldown/binding-win32-arm64-msvc": "1.2.16", - "@rolldown/binding-win32-x64-msvc": "1.2.16" + "@rolldown/binding-android-arm-eabi": "1.2.19", + "@rolldown/binding-android-arm64": "1.2.19", + "@rolldown/binding-darwin-arm64": "1.2.19", + "@rolldown/binding-darwin-x64": "1.2.19", + "@rolldown/binding-freebsd-x64": "1.2.19", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.19", + "@rolldown/binding-linux-arm64-gnu": "1.2.19", + "@rolldown/binding-linux-arm64-musl": "1.2.19", + "@rolldown/binding-linux-ppc64-gnu": "1.2.19", + "@rolldown/binding-linux-s390x-gnu": "1.2.19", + "@rolldown/binding-linux-x64-gnu": "1.2.19", + "@rolldown/binding-linux-x64-musl": "1.2.19", + "@rolldown/binding-openharmony-arm64": "1.2.19", + "@rolldown/binding-wasm32-wasi": "1.2.19", + "@rolldown/binding-win32-arm64-msvc": "1.2.19", + "@rolldown/binding-win32-x64-msvc": "1.2.19" } }, "node_modules/vitest": { diff --git a/package.json b/package.json index 3d6ea6e..b8be975 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ }, "homepage": "https://github.com/Wikid82/Workspace-File-Bookmarks#readme", "engines": { - "vscode": "^1.145.0" + "vscode": "^1.146.0" }, "activationEvents": [], "main": "./dist/extension.cjs", @@ -148,6 +148,11 @@ "title": "Rename Folder", "category": "Workspace File Bookmarks" }, + { + "command": "workspace-file-bookmarks.setFolderAppearance", + "title": "Set Folder Icon and Color...", + "category": "Workspace File Bookmarks" + }, { "command": "workspace-file-bookmarks.deleteFolder", "title": "Delete Folder", @@ -177,6 +182,12 @@ "category": "Workspace File Bookmarks", "icon": "$(list-tree)" }, + { + "command": "workspace-file-bookmarks.removeBrokenBookmarks", + "title": "Remove Broken Bookmarks...", + "category": "Workspace File Bookmarks", + "icon": "$(warning)" + }, { "command": "workspace-file-bookmarks.exportBookmarks", "title": "Export Bookmarks...", @@ -194,6 +205,26 @@ "title": "Go to Bookmark...", "category": "Workspace File Bookmarks", "icon": "$(go-to-file)" + }, + { + "command": "workspace-file-bookmarks.copyBookmarkPath", + "title": "Copy Path", + "category": "Workspace File Bookmarks" + }, + { + "command": "workspace-file-bookmarks.copyBookmarkRelativePath", + "title": "Copy Relative Path", + "category": "Workspace File Bookmarks" + }, + { + "command": "workspace-file-bookmarks.revealBookmarkInExplorer", + "title": "Reveal in Explorer", + "category": "Workspace File Bookmarks" + }, + { + "command": "workspace-file-bookmarks.revealBookmarkInFileManager", + "title": "Reveal in File Manager", + "category": "Workspace File Bookmarks" } ], "menus": { @@ -243,6 +274,11 @@ "when": "view == workspace-file-bookmarks-view && workspace-file-bookmarks.viewMode == list", "group": "navigation@5" }, + { + "command": "workspace-file-bookmarks.removeBrokenBookmarks", + "when": "view == workspace-file-bookmarks-view", + "group": "8_maintenance@1" + }, { "command": "workspace-file-bookmarks.exportBookmarks", "when": "view == workspace-file-bookmarks-view", @@ -275,6 +311,26 @@ "when": "view == workspace-file-bookmarks-view && viewItem == bookmarkItem", "group": "1_modification" }, + { + "command": "workspace-file-bookmarks.revealBookmarkInExplorer", + "when": "view == workspace-file-bookmarks-view && viewItem == bookmarkItem", + "group": "2_reveal@1" + }, + { + "command": "workspace-file-bookmarks.revealBookmarkInFileManager", + "when": "view == workspace-file-bookmarks-view && viewItem == bookmarkItem", + "group": "2_reveal@2" + }, + { + "command": "workspace-file-bookmarks.copyBookmarkPath", + "when": "view == workspace-file-bookmarks-view && viewItem == bookmarkItem", + "group": "3_copy@1" + }, + { + "command": "workspace-file-bookmarks.copyBookmarkRelativePath", + "when": "view == workspace-file-bookmarks-view && viewItem == bookmarkItem", + "group": "3_copy@2" + }, { "command": "workspace-file-bookmarks.openAllInFolder", "when": "view == workspace-file-bookmarks-view && viewItem == bookmarkFolder", @@ -299,6 +355,11 @@ "command": "workspace-file-bookmarks.moveFolderToParent", "when": "view == workspace-file-bookmarks-view && viewItem == bookmarkFolder", "group": "1_modification" + }, + { + "command": "workspace-file-bookmarks.setFolderAppearance", + "when": "view == workspace-file-bookmarks-view && viewItem == bookmarkFolder", + "group": "1_modification" } ], "editor/title": [ @@ -360,8 +421,11 @@ "@eslint/js": "^10.0.1", "@types/mocha": "^10.0.10", "@types/node": "^26.6.6", - "@types/vscode": "^1.145.0", - "@typescript/native": "npm:typescript@^7.1.0-dev.20261108.1", + "@types/vscode": "^1.146.0", + "@typescript-eslint/eslint-plugin": "^8.72.2", + "@typescript-eslint/parser": "^8.72.2", + "@typescript-eslint/utils": "^8.72.2", + "@typescript/native": "npm:typescript@^7.1.0-dev.20261111.1", "@vitest/coverage-istanbul": "^5.1.0", "@vitest/coverage-v8": "^5.1.0", "@vitest/eslint-plugin": "^1.6.28", @@ -374,10 +438,10 @@ "eslint": "^10.14.0", "eslint-config-prettier": "^10.1.8", "lefthook": "^2.2.2", - "prettier": "^3.9.10", + "prettier": "^3.10.0", "typescript": "npm:@typescript/typescript6@^6.0.2", - "typescript-eslint": "^8.72.1", - "vite": "^8.3.6", + "typescript-eslint": "^8.72.2", + "vite": "^8.3.8", "vitest": "^5.1.0" }, "allowScripts": { diff --git a/scripts/npm/prettier.sh b/scripts/npm/prettier.sh new file mode 100755 index 0000000..a335370 --- /dev/null +++ b/scripts/npm/prettier.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../" && pwd)" + +NPM_MODULES=( + "$REPO_ROOT" + ) + +for MODULE in "${NPM_MODULES[@]}"; do + echo "============================================================================" + echo "Updating: $MODULE" + echo "============================================================================" + + cd "$MODULE" || exit 1 + rm -rf node_modules package-lock.json + npm install prettier --save-dev +done diff --git a/scripts/npm/typescript-eslint.sh b/scripts/npm/typescript-eslint.sh new file mode 100644 index 0000000..69c9c5f --- /dev/null +++ b/scripts/npm/typescript-eslint.sh @@ -0,0 +1,18 @@ +#!/bin/bash +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" + +NPM_MODULES=( + "$REPO_ROOT" +) + +for MODULE in "${NPM_MODULES[@]}"; do + echo "============================================================================" + echo "Updating: $MODULE" + echo "============================================================================" + + cd "$MODULE" || exit 1 + rm -rf node_modules package-lock.json + npm install typescript-eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser @typescript-eslint/utils --save-dev +done diff --git a/test/activate.test.ts b/test/activate.test.ts index 562b776..473758a 100644 --- a/test/activate.test.ts +++ b/test/activate.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from 'vitest'; import { activate, deactivate } from '../extension'; import { createFakeContext } from './fakeContext'; -import { commands, Position, Selection, Uri, window } from './vscode-mock'; +import { commands, Position, Selection, Uri, window, workspace } from './vscode-mock'; describe('activate', () => { it('creates the tree view and registers every command', () => { @@ -11,8 +11,12 @@ describe('activate', () => { activate(context as any); expect(window.createTreeView).toHaveBeenCalledOnce(); - expect(commands.registerCommand).toHaveBeenCalledTimes(24); - expect((context as any).subscriptions).toHaveLength(25); // tree view + 24 commands + expect(window.createTreeView).toHaveBeenCalledWith( + 'workspace-file-bookmarks-view', + expect.objectContaining({ canSelectMany: true }), + ); + expect(commands.registerCommand).toHaveBeenCalledTimes(30); + expect((context as any).subscriptions).toHaveLength(31); // tree view + 30 commands }); it('exercises every registered command handler at least once', async () => { @@ -57,6 +61,8 @@ describe('activate', () => { await handlers.get('workspace-file-bookmarks.createSubfolder')?.({ folder }); await handlers.get('workspace-file-bookmarks.moveFolderToParent')?.({ folder }); await handlers.get('workspace-file-bookmarks.renameFolder')?.({ folder }); + window.showQuickPick.mockResolvedValue(undefined); + await handlers.get('workspace-file-bookmarks.setFolderAppearance')?.({ folder }); await handlers.get('workspace-file-bookmarks.deleteFolder')?.({ folder, bookmarks: [], @@ -66,14 +72,21 @@ describe('activate', () => { await handlers.get('workspace-file-bookmarks.openAllInFolder')?.({ folder, bookmarks: [] }); await handlers.get('workspace-file-bookmarks.setViewModeList')?.(); await handlers.get('workspace-file-bookmarks.setViewModeTree')?.(); + workspace.fs.stat.mockResolvedValue({}); + await handlers.get('workspace-file-bookmarks.removeBrokenBookmarks')?.(); window.showSaveDialog.mockResolvedValue(undefined); await handlers.get('workspace-file-bookmarks.exportBookmarks')?.(); window.showOpenDialog.mockResolvedValue(undefined); await handlers.get('workspace-file-bookmarks.importBookmarks')?.(); window.showQuickPick.mockResolvedValue(undefined); await handlers.get('workspace-file-bookmarks.goToBookmark')?.(); + await handlers.get('workspace-file-bookmarks.copyBookmarkPath')?.({ bookmark }); + await handlers.get('workspace-file-bookmarks.copyBookmarkRelativePath')?.({ bookmark }); + await handlers.get('workspace-file-bookmarks.revealBookmarkInExplorer')?.({ bookmark }); + await handlers.get('workspace-file-bookmarks.revealBookmarkInFileManager')?.({ bookmark }); - expect(handlers.size).toBe(24); + expect(commands.registerCommand).toHaveBeenCalledTimes(30); + expect((context as any).subscriptions).toHaveLength(31); // tree view + 30 commands }); }); diff --git a/test/bookmarkStore.test.ts b/test/bookmarkStore.test.ts index 08f1e56..6cf73ff 100644 --- a/test/bookmarkStore.test.ts +++ b/test/bookmarkStore.test.ts @@ -481,3 +481,108 @@ describe('mergeImport', () => { expect(store.getAllBookmarks()).toHaveLength(1); }); }); + +describe('stale bookmark tracking', () => { + it('only fires a change event when the stale set actually changes', () => { + const store = newStore(); + store.addBookmark(makeBookmark({ id: 'a' })); + let changes = 0; + store.onDidChange(() => changes++); + + store.markStale('a'); + store.markStale('a'); + expect(changes).toBe(1); + + store.clearStale('a'); + expect(store.isStale('a')).toBe(false); + expect(changes).toBe(2); + }); + + it('removeStaleBookmarks drops only stale bookmarks and resets the stale set', () => { + const store = newStore(); + store.addBookmark(makeBookmark({ id: 'a', uri: 'file:///repo/a.ts' })); + store.addBookmark(makeBookmark({ id: 'b', uri: 'file:///repo/b.ts' })); + store.markStale('a'); + + store.removeStaleBookmarks(); + + expect(store.getAllBookmarks().map((b) => b.id)).toEqual(['b']); + expect(store.isStale('a')).toBe(false); + }); +}); + +describe('bulk bookmark operations', () => { + it('removeBookmarks removes only the named bookmarks in a single change event', () => { + const store = newStore(); + for (const id of ['a', 'b', 'c']) { + store.addBookmark(makeBookmark({ id, uri: `file:///repo/${id}.ts` })); + } + let changes = 0; + store.onDidChange(() => changes++); + + store.removeBookmarks(['a', 'c']); + + expect(store.getAllBookmarks().map((b) => b.id)).toEqual(['b']); + expect(changes).toBe(1); + }); + + it('moveBookmarksToFolder moves only the named bookmarks in a single change event', () => { + const store = newStore(); + for (const id of ['a', 'b', 'c']) { + store.addBookmark(makeBookmark({ id, uri: `file:///repo/${id}.ts` })); + } + const folder = store.createFolder('Backend'); + let changes = 0; + store.onDidChange(() => changes++); + + store.moveBookmarksToFolder(['a', 'b'], folder.id); + + const byId = new Map(store.getAllBookmarks().map((b) => [b.id, b.folderId])); + expect(byId.get('a')).toBe(folder.id); + expect(byId.get('b')).toBe(folder.id); + expect(byId.get('c')).toBeNull(); + expect(changes).toBe(1); + }); +}); + +describe('setFolderAppearance', () => { + it('sets icon and color on only the targeted folder', () => { + const store = newStore(); + const a = store.createFolder('A'); + const b = store.createFolder('B'); + + store.setFolderAppearance(a.id, { icon: 'bug', color: 'charts.green' }); + + const folders = store.getAllFolders(); + expect(folders.find((f) => f.id === a.id)).toMatchObject({ + icon: 'bug', + color: 'charts.green', + }); + expect(folders.find((f) => f.id === b.id)).not.toHaveProperty('icon'); + }); + + it('clears icon and color when called with no values', () => { + const store = newStore(); + const a = store.createFolder('A'); + store.setFolderAppearance(a.id, { icon: 'bug', color: 'charts.green' }); + + store.setFolderAppearance(a.id, {}); + + expect(store.getAllFolders()[0]).toEqual( + expect.not.objectContaining({ icon: expect.anything(), color: expect.anything() }), + ); + expect(store.getAllFolders()[0]).not.toHaveProperty('icon'); + expect(store.getAllFolders()[0]).not.toHaveProperty('color'); + }); + + it('preserves icon and color through mergeImport', () => { + const store = newStore(); + + store.mergeImport( + [], + [{ id: 'x', name: 'Imported', createdAt: 0, icon: 'rocket', color: 'charts.blue' }], + ); + + expect(store.getAllFolders()[0]).toMatchObject({ icon: 'rocket', color: 'charts.blue' }); + }); +}); diff --git a/test/commands.test.ts b/test/commands.test.ts index 527c4d4..0cdef40 100644 --- a/test/commands.test.ts +++ b/test/commands.test.ts @@ -5,6 +5,8 @@ import { addActiveSelectionBookmark, addBookmarksForUris, addBookmarksToFolder, + copyBookmarkPath, + copyBookmarkRelativePath, createFolder, createSubfolder, deleteFolder, @@ -21,12 +23,18 @@ import { pickFolder, pickParentFolder, renameBookmark, + removeBrokenBookmarks, renameFolder, + resolveBookmarkTargets, + revealBookmarkInExplorer, + revealBookmarkInFileManager, + setFolderAppearance, + FolderGroupItem, type Bookmark, type BookmarkFolder, } from '../extension'; import { createFakeContext } from './fakeContext'; -import { Position, Selection, Uri, window, workspace } from './vscode-mock'; +import { Position, Selection, Uri, commands, env, window, workspace } from './vscode-mock'; import type { InputBox } from './vscode-mock'; function newStore() { @@ -191,6 +199,44 @@ describe('openBookmark', () => { expect(window.showErrorMessage).toHaveBeenCalledOnce(); }); + it('marks the bookmark stale when the open fails because the file is gone', async () => { + const store = newStore(); + const bookmark = makeBookmark(); + store.addBookmark(bookmark); + workspace.openTextDocument.mockRejectedValue(new Error('not found')); + workspace.fs.stat.mockRejectedValue(new Error('ENOENT')); + + await openBookmark(bookmark, store); + + expect(store.isStale(bookmark.id)).toBe(true); + expect(window.showErrorMessage).toHaveBeenCalledOnce(); + }); + + it('does not mark the bookmark stale when the open fails but the file still exists', async () => { + const store = newStore(); + const bookmark = makeBookmark(); + store.addBookmark(bookmark); + workspace.openTextDocument.mockRejectedValue(new Error('binary file')); + workspace.fs.stat.mockResolvedValue({}); + + await openBookmark(bookmark, store); + + expect(store.isStale(bookmark.id)).toBe(false); + expect(window.showErrorMessage).toHaveBeenCalledOnce(); + }); + + it('clears a stale mark when the file opens successfully', async () => { + const store = newStore(); + const bookmark = makeBookmark(); + store.addBookmark(bookmark); + store.markStale(bookmark.id); + workspace.openTextDocument.mockResolvedValue({}); + + await openBookmark(bookmark, store); + + expect(store.isStale(bookmark.id)).toBe(false); + }); + it('reveals and selects the line range for a line/selection bookmark', async () => { const document = {}; workspace.openTextDocument.mockResolvedValue(document); @@ -737,6 +783,58 @@ describe('moveToFolder', () => { }); }); +describe('resolveBookmarkTargets', () => { + const nodeFor = (id: string) => ({ bookmark: makeBookmark({ id }) }) as any; + + it('returns just the clicked item when there is no selection', () => { + expect(resolveBookmarkTargets(nodeFor('a')).map((b) => b.id)).toEqual(['a']); + }); + + it('returns just the clicked item when the selection is only that item', () => { + const a = nodeFor('a'); + expect(resolveBookmarkTargets(a, [a]).map((b) => b.id)).toEqual(['a']); + }); + + it('returns every selected bookmark when the clicked item is part of a multi-selection', () => { + const [a, b, c] = [nodeFor('a'), nodeFor('b'), nodeFor('c')]; + expect(resolveBookmarkTargets(b, [a, b, c]).map((x) => x.id)).toEqual(['a', 'b', 'c']); + }); + + it('ignores folders in the selection', () => { + const [a, b] = [nodeFor('a'), nodeFor('b')]; + const folderNode = { folder: makeFolder() } as any; + expect(resolveBookmarkTargets(a, [a, folderNode, b]).map((x) => x.id)).toEqual(['a', 'b']); + }); + + it('acts only on the clicked item when it is outside the selection', () => { + const [a, b, c] = [nodeFor('a'), nodeFor('b'), nodeFor('c')]; + expect(resolveBookmarkTargets(c, [a, b]).map((x) => x.id)).toEqual(['c']); + }); +}); + +describe('moveToFolder with a multi-selection', () => { + it('moves every selected bookmark and words the prompt for the count', async () => { + const store = newStore(); + for (const id of ['a', 'b', 'c']) { + store.addBookmark(makeBookmark({ id, uri: `file:///repo/${id}.ts` })); + } + const folder = store.createFolder('Backend'); + window.showQuickPick.mockResolvedValue('Backend'); + const nodeFor = (id: string) => ({ bookmark: makeBookmark({ id }) }) as any; + const [a, b] = [nodeFor('a'), nodeFor('b')]; + + await moveToFolder(store, a, [a, b]); + + const byId = new Map(store.getAllBookmarks().map((x) => [x.id, x.folderId])); + expect(byId.get('a')).toBe(folder.id); + expect(byId.get('b')).toBe(folder.id); + expect(byId.get('c')).toBeNull(); + expect(window.showQuickPick.mock.calls[0][1]).toMatchObject({ + placeHolder: 'Move 2 bookmarks to...', + }); + }); +}); + describe('addBookmarksToFolder', () => { it('adds every target uri into the picked folder', async () => { const store = newStore(); @@ -953,3 +1051,139 @@ describe('goToBookmark', () => { expect(workspace.openTextDocument).not.toHaveBeenCalled(); }); }); + +describe('removeBrokenBookmarks', () => { + function seed(store: BookmarkStore) { + store.addBookmark(makeBookmark({ id: 'ok', uri: 'file:///repo/ok.ts' })); + store.addBookmark(makeBookmark({ id: 'gone1', uri: 'file:///repo/gone.ts' })); + store.addBookmark( + makeBookmark({ id: 'gone2', uri: 'file:///repo/gone.ts', lineStart: 3, lineEnd: 3 }), + ); + workspace.fs.stat.mockImplementation(async (uri: Uri) => { + if (uri.fsPath.endsWith('gone.ts')) { + throw new Error('ENOENT'); + } + return {}; + }); + } + + it('stats each distinct file once, marks missing bookmarks, and removes them after confirmation', async () => { + const store = newStore(); + seed(store); + window.showWarningMessage.mockResolvedValue('Remove'); + + await removeBrokenBookmarks(store); + + expect(workspace.fs.stat).toHaveBeenCalledTimes(2); + expect(window.showWarningMessage.mock.calls[0][0]).toContain('2 broken bookmark'); + expect(store.getAllBookmarks().map((b) => b.id)).toEqual(['ok']); + }); + + it('keeps the broken bookmarks, marked stale, when the user declines', async () => { + const store = newStore(); + seed(store); + window.showWarningMessage.mockResolvedValue(undefined); + + await removeBrokenBookmarks(store); + + expect(store.getAllBookmarks()).toHaveLength(3); + expect(store.isStale('gone1')).toBe(true); + expect(store.isStale('gone2')).toBe(true); + expect(store.isStale('ok')).toBe(false); + }); + + it('reports when nothing is broken and does not prompt', async () => { + const store = newStore(); + store.addBookmark(makeBookmark()); + workspace.fs.stat.mockResolvedValue({}); + + await removeBrokenBookmarks(store); + + expect(window.showInformationMessage).toHaveBeenCalledWith('No broken bookmarks found.'); + expect(window.showWarningMessage).not.toHaveBeenCalled(); + }); +}); + +describe('copy path / reveal actions', () => { + it('copies the absolute path of the bookmarked file', async () => { + await copyBookmarkPath(makeBookmark({ uri: 'file:///repo/src/a.ts' })); + + expect(env.clipboard.writeText).toHaveBeenCalledWith('/repo/src/a.ts'); + }); + + it('copies the workspace-relative path of the bookmarked file', async () => { + await copyBookmarkRelativePath(makeBookmark({ relativePath: 'src/a.ts' })); + + expect(env.clipboard.writeText).toHaveBeenCalledWith('src/a.ts'); + }); + + it("reveals the bookmarked file in VS Code's Explorer", async () => { + await revealBookmarkInExplorer(makeBookmark({ uri: 'file:///repo/src/a.ts' })); + + expect(commands.executeCommand).toHaveBeenCalledWith( + 'revealInExplorer', + expect.objectContaining({ fsPath: '/repo/src/a.ts' }), + ); + }); + + it('reveals the bookmarked file in the OS file manager', async () => { + await revealBookmarkInFileManager(makeBookmark({ uri: 'file:///repo/src/a.ts' })); + + expect(commands.executeCommand).toHaveBeenCalledWith( + 'revealFileInOS', + expect.objectContaining({ fsPath: '/repo/src/a.ts' }), + ); + }); +}); + +describe('setFolderAppearance', () => { + it('stores the chosen icon and color on the folder', async () => { + const store = newStore(); + const folder = store.createFolder('Backend'); + window.showQuickPick + .mockResolvedValueOnce({ label: '$(server) Server', iconId: 'server' }) + .mockResolvedValueOnce({ label: 'Red', colorId: 'charts.red' }); + + await setFolderAppearance(store, new FolderGroupItem(folder, [])); + + expect(store.getAllFolders()[0]).toMatchObject({ icon: 'server', color: 'charts.red' }); + }); + + it('clears the appearance when Default is chosen for both steps', async () => { + const store = newStore(); + const folder = store.createFolder('Backend'); + store.setFolderAppearance(folder.id, { icon: 'server', color: 'charts.red' }); + window.showQuickPick + .mockResolvedValueOnce({ label: 'Default', iconId: undefined }) + .mockResolvedValueOnce({ label: 'Default', colorId: undefined }); + + await setFolderAppearance(store, new FolderGroupItem(store.getAllFolders()[0], [])); + + const stored = store.getAllFolders()[0]; + expect(stored).not.toHaveProperty('icon'); + expect(stored).not.toHaveProperty('color'); + }); + + it('leaves the folder untouched when the icon pick is cancelled', async () => { + const store = newStore(); + const folder = store.createFolder('Backend'); + window.showQuickPick.mockResolvedValueOnce(undefined); + + await setFolderAppearance(store, new FolderGroupItem(folder, [])); + + expect(window.showQuickPick).toHaveBeenCalledOnce(); + expect(store.getAllFolders()[0]).not.toHaveProperty('icon'); + }); + + it('leaves the folder untouched when the color pick is cancelled', async () => { + const store = newStore(); + const folder = store.createFolder('Backend'); + window.showQuickPick + .mockResolvedValueOnce({ label: '$(server) Server', iconId: 'server' }) + .mockResolvedValueOnce(undefined); + + await setFolderAppearance(store, new FolderGroupItem(folder, [])); + + expect(store.getAllFolders()[0]).not.toHaveProperty('icon'); + }); +}); diff --git a/test/e2e/extension.e2e.test.ts b/test/e2e/extension.e2e.test.ts index 98af04d..ec101ba 100644 --- a/test/e2e/extension.e2e.test.ts +++ b/test/e2e/extension.e2e.test.ts @@ -60,14 +60,20 @@ describe('Workspace File Bookmarks (e2e)', () => { 'workspace-file-bookmarks.createSubfolder', 'workspace-file-bookmarks.moveFolderToParent', 'workspace-file-bookmarks.renameFolder', + 'workspace-file-bookmarks.setFolderAppearance', 'workspace-file-bookmarks.deleteFolder', 'workspace-file-bookmarks.moveToFolder', 'workspace-file-bookmarks.openAllInFolder', 'workspace-file-bookmarks.setViewModeList', 'workspace-file-bookmarks.setViewModeTree', + 'workspace-file-bookmarks.removeBrokenBookmarks', 'workspace-file-bookmarks.exportBookmarks', 'workspace-file-bookmarks.importBookmarks', 'workspace-file-bookmarks.goToBookmark', + 'workspace-file-bookmarks.copyBookmarkPath', + 'workspace-file-bookmarks.copyBookmarkRelativePath', + 'workspace-file-bookmarks.revealBookmarkInExplorer', + 'workspace-file-bookmarks.revealBookmarkInFileManager', ]) { assert.ok(commands.includes(id), `expected command "${id}" to be registered`); } @@ -297,4 +303,132 @@ describe('Workspace File Bookmarks (e2e)', () => { vscode.window.showQuickPick = originalShowQuickPick; } }); + + it('marks a bookmark to a deleted file stale and sweeps it away with removeBrokenBookmarks', async () => { + const { store, provider } = await getApi(); + const document = await vscode.workspace.openTextDocument(fixtureUri('src/sample-a.ts')); + await vscode.window.showTextDocument(document); + await vscode.commands.executeCommand('workspace-file-bookmarks.addBookmark'); + store.addBookmark({ + id: 'missing-1', + uri: fixtureUri('src/does-not-exist.ts').toString(), + label: 'does-not-exist.ts', + relativePath: 'src/does-not-exist.ts', + workspaceFolderName: 'workspace', + folderId: null, + createdAt: Date.now(), + }); + + const originalShowWarningMessage = vscode.window.showWarningMessage; + try { + (vscode.window as any).showWarningMessage = async () => undefined; + await vscode.commands.executeCommand('workspace-file-bookmarks.removeBrokenBookmarks'); + const stale = (provider.getChildren() as BookmarkTreeItem[]).filter( + (n) => isBookmarkNode(n) && n.stale, + ); + assert.deepEqual( + stale.map((n) => n.bookmark.id), + ['missing-1'], + ); + assert.equal(store.getAllBookmarks().length, 2, 'declining must not remove anything'); + + (vscode.window as any).showWarningMessage = async () => 'Remove'; + await vscode.commands.executeCommand('workspace-file-bookmarks.removeBrokenBookmarks'); + } finally { + vscode.window.showWarningMessage = originalShowWarningMessage; + } + + assert.deepEqual( + store.getAllBookmarks().map((b) => b.relativePath), + ['src/sample-a.ts'], + ); + }); + + it('removes and moves every bookmark in a multi-selection via the tree commands', async () => { + const { store, provider } = await getApi(); + for (const file of ['src/sample-a.ts', 'src/sample-b.ts']) { + const document = await vscode.workspace.openTextDocument(fixtureUri(file)); + await vscode.window.showTextDocument(document); + await vscode.commands.executeCommand('workspace-file-bookmarks.addBookmark'); + } + assert.equal(store.getAllBookmarks().length, 2); + const folder = store.createFolder('Batch'); + const items = (provider.getChildren() as Array).filter( + isBookmarkNode, + ); + assert.equal(items.length, 2); + + const originalShowQuickPick = vscode.window.showQuickPick; + try { + (vscode.window as any).showQuickPick = async () => 'Batch'; + await vscode.commands.executeCommand( + 'workspace-file-bookmarks.moveToFolder', + items[0], + items, + ); + } finally { + vscode.window.showQuickPick = originalShowQuickPick; + } + assert.ok(store.getAllBookmarks().every((b) => b.folderId === folder.id)); + + await vscode.commands.executeCommand( + 'workspace-file-bookmarks.removeBookmark', + items[0], + items, + ); + assert.equal(store.getAllBookmarks().length, 0); + }); + + it('copies the absolute and relative path of a bookmark to the clipboard', async () => { + const { store } = await getApi(); + const document = await vscode.workspace.openTextDocument(fixtureUri('src/sample-a.ts')); + await vscode.window.showTextDocument(document); + await vscode.commands.executeCommand('workspace-file-bookmarks.addBookmark'); + const item = { bookmark: store.getAllBookmarks()[0] }; + + await vscode.commands.executeCommand('workspace-file-bookmarks.copyBookmarkPath', item); + assert.equal(await vscode.env.clipboard.readText(), document.uri.fsPath); + + await vscode.commands.executeCommand('workspace-file-bookmarks.copyBookmarkRelativePath', item); + assert.equal(await vscode.env.clipboard.readText(), 'src/sample-a.ts'); + }); + + it("reveals a bookmark in VS Code's Explorer without error", async () => { + const { store } = await getApi(); + const document = await vscode.workspace.openTextDocument(fixtureUri('src/sample-a.ts')); + await vscode.window.showTextDocument(document); + await vscode.commands.executeCommand('workspace-file-bookmarks.addBookmark'); + const item = { bookmark: store.getAllBookmarks()[0] }; + + await vscode.commands.executeCommand('workspace-file-bookmarks.revealBookmarkInExplorer', item); + }); + + it('applies a custom icon and color to a folder via setFolderAppearance and the real tree', async () => { + const { store, provider } = await getApi(); + const folder = store.createFolder('Backend'); + const [node] = provider.getChildren() as FolderGroupItem[]; + + const originalShowQuickPick = vscode.window.showQuickPick; + try { + let call = 0; + (vscode.window as any).showQuickPick = async (items: any[]) => + call++ === 0 + ? items.find((i) => i.iconId === 'server') + : items.find((i) => i.colorId === 'charts.red'); + + await vscode.commands.executeCommand('workspace-file-bookmarks.setFolderAppearance', node); + } finally { + vscode.window.showQuickPick = originalShowQuickPick; + } + + assert.deepEqual( + { icon: store.getAllFolders()[0].icon, color: store.getAllFolders()[0].color }, + { icon: 'server', color: 'charts.red' }, + ); + const [updated] = provider.getChildren() as FolderGroupItem[]; + const icon = updated.iconPath as vscode.ThemeIcon; + assert.equal(icon.id, 'server'); + assert.equal((icon.color as vscode.ThemeColor).id, 'charts.red'); + assert.equal(updated.folder.id, folder.id); + }); }); diff --git a/test/treeProvider.test.ts b/test/treeProvider.test.ts index 58d7a4f..cd18e9a 100644 --- a/test/treeProvider.test.ts +++ b/test/treeProvider.test.ts @@ -313,6 +313,41 @@ describe('BookmarksTreeProvider', () => { expect(node.description).toBe('1 • 1 subfolder'); }); + it('marks stale bookmarks with a warning icon and "missing" description in list and tree views', () => { + const { store, provider } = newProvider(); + store.addBookmark(makeBookmark({ id: 'a', uri: 'file:///repo/a.ts' })); + store.addBookmark(makeBookmark({ id: 'b', uri: 'file:///repo/b.ts' })); + store.markStale('a'); + + for (const mode of ['tree', 'list'] as const) { + provider.setViewMode(mode); + const items = provider.getChildren() as BookmarkTreeItem[]; + const stale = items.find((i) => i.bookmark.id === 'a') as BookmarkTreeItem; + const fresh = items.find((i) => i.bookmark.id === 'b') as BookmarkTreeItem; + expect(stale.stale).toBe(true); + expect(stale.description).toMatch(/^missing/); + expect(stale.iconPath).toMatchObject({ id: 'warning' }); + expect(stale.tooltip).toContain('no longer exists'); + expect(fresh.stale).toBe(false); + expect(fresh.iconPath).toMatchObject({ id: 'bookmark' }); + } + }); + + it('uses the default folder icon when no icon or color is set', () => { + const node = new FolderGroupItem({ id: 'f', name: 'Backend', createdAt: 0 }, []); + + expect(node.iconPath).toMatchObject({ id: 'folder', color: undefined }); + }); + + it('applies a custom icon and theme color to the folder', () => { + const node = new FolderGroupItem( + { id: 'f', name: 'Backend', createdAt: 0, icon: 'server', color: 'charts.red' }, + [], + ); + + expect(node.iconPath).toMatchObject({ id: 'server', color: { id: 'charts.red' } }); + }); + it('uses the plural "subfolders" when there is more than one', () => { const parent = { id: 'p', name: 'Backend', createdAt: 0, parentId: null }; const node = new FolderGroupItem( diff --git a/test/vscode-mock.ts b/test/vscode-mock.ts index 110ab5d..4c7c49d 100644 --- a/test/vscode-mock.ts +++ b/test/vscode-mock.ts @@ -77,10 +77,17 @@ export class TreeItem { ) {} } -export class ThemeIcon { +export class ThemeColor { constructor(public id: string) {} } +export class ThemeIcon { + constructor( + public id: string, + public color?: ThemeColor, + ) {} +} + export class DataTransferItem { constructor(private readonly value: string) {} @@ -165,6 +172,7 @@ export const workspace = { fs: { readFile: vi.fn(), writeFile: vi.fn(), + stat: vi.fn(), }, }; @@ -172,3 +180,9 @@ export const commands = { registerCommand: vi.fn(() => ({ dispose: () => {} })), executeCommand: vi.fn(), }; + +export const env = { + clipboard: { + writeText: vi.fn(() => Promise.resolve()), + }, +};