diff --git a/.github/instructions/interactive.instructions.md b/.github/instructions/interactive.instructions.md new file mode 100644 index 0000000000000..21ed92f6460a9 --- /dev/null +++ b/.github/instructions/interactive.instructions.md @@ -0,0 +1,51 @@ +--- +applyTo: '**/interactive/**' +description: Architecture documentation for VS Code interactive window component +--- + +# Interactive Window + +The interactive window component enables extensions to offer REPL like experience to its users. VS Code provides the user interface and extensions provide the execution environment, code completions, execution results rendering and so on. + +The interactive window consists of notebook editor at the top and regular monaco editor at the bottom of the viewport. Extensions can extend the interactive window by leveraging the notebook editor API and text editor/document APIs: + +* Extensions register notebook controllers for the notebook document in the interactive window through `vscode.notebooks.createNotebookController`. The notebook document has a special notebook view type `interactive`, which is contributed by the core instead of extensions. The registered notebook controller is responsible for execution. +* Extensions register auto complete provider for the bottom text editor through `vscode.languages.registerCompletionItemProvider`. The resource scheme for the text editor is `interactive-input` and the language used in the editor is determined by the notebook controller contributed by extensions. + +Users can type in code in the text editor and after users pressing `Shift+Enter`, we will insert a new code cell into the notebook document with the content from the text editor. Then we will request execution for the newly inserted cell. The notebook controller will handle the execution just like it's in a normal notebook editor. + +## Interactive Window Registration + +Registering a new editor type in the workbench consists of two steps: + +* Register an editor input factory which is responsible for resolving resources with given `glob` patterns. Here we register an `InteractiveEditorInput` for all resources with `vscode-interactive` scheme. +* Register an editor pane factory for the given editor input type. Here we register `InteractiveEditor` for our own editor input `InteractiveEditorInput`. + +The workbench editor service is not aware of how models are resolved in `EditorInput`, neither how `EditorPane`s are rendered. It only cares about the common states and events on `EditorInput` or `EditorPane`, i.e., display name, capabilities (editable), content change, dirty state change. It's `EditorInput`/`EditorPane`'s responsibility to provide the right info and updates to the editor service. One major difference between Interactive Editor and other editor panes is Interactive Window is never dirty so users never see a dot on the editor title bar. + +![Editor Registration](./resources/interactive/interactive.editor.drawio.svg) + +## Interactive Window Editor Model Resolution + +The `Interactive.open` command will manually create an `InteractiveEditorInput` specific for the Interactive Window and resolving that Input will go through the following workflow: + +The `INotebookEditorModelResolverService` is used to resolve the notebook model. The `InteractiveEditorInput` wraps a `NotebookEditorInput` for the notebook document and manages a separate text model for the input editor. + +When the notebook model is resolved, the `INotebookEditorModelResolverService` uses the working copy infrastructure to create a `IResolvedNotebookEditorModel`. The content is passed through a `NotebookSerializer` from the `INotebookService` to construct a `NotebookTextModel`. + +![Model Resolution](./resources/interactive/interactive.model.resolution.drawio.svg) + +The `FileSystem` provider that is registered for `vscode-interactive` schema will always return an empty buffer for any read, and will drop all write requests as nothing is stored on disk for Interactive Window resources. The `NotebookSerializer` that is registered for the `interactive` viewtype knows to return an empty notebook data model when it deserializes an empty buffer when the model is being resolved. + +Restoring the interactive window happens through the editor serializer (`InteractiveEditorSerializer`), where the notebook data is stored, and can be used to repopulate the `InteractiveEditorInput` without needing to go through the full editor model resolution flow. + +## UI/EH Editor/Document Syncing + +`EditorInput` is responsible for resolving models for the given resources but in Interactive Window it's much simpler as we are not resolving models ourselves but delegating to Notebook and TextEditor. `InteractiveEditorInput` does the coordination job: + +- It wraps a `NotebookEditorInput` via `_notebookEditorInput` for the notebook document (history cells) +- It manages a separate text model via `ITextModelService` for the input editor at the bottom +- The `IInteractiveDocumentService` coordinates between these two parts +- The `IInteractiveHistoryService` manages command history for the input editor + +![Architecture](./resources/interactive/interactive.eh.drawio.svg) diff --git a/.github/instructions/notebook.instructions.md b/.github/instructions/notebook.instructions.md new file mode 100644 index 0000000000000..3d78e744d317a --- /dev/null +++ b/.github/instructions/notebook.instructions.md @@ -0,0 +1,109 @@ +--- +applyTo: '**/notebook/**' +description: Architecture documentation for VS Code notebook and interactive window components +--- + +# Notebook Architecture + +This document describes the internal architecture of VS Code's notebook implementation. + +## Model resolution + +Notebook model resolution is handled by `NotebookService`. It resolves notebook models from the file system or other sources. The notebook model is a tree of cells, where each cell has a type (code or markdown) and a list of outputs. + +## Viewport rendering (virtualization) + +The notebook viewport is virtualized to improve performance. Only visible cells are rendered, and cells outside the viewport are recycled. The viewport rendering is handled by `NotebookCellList` which extends `WorkbenchList`. + +![Viewport Rendering](./resources/notebook/viewport-rendering.drawio.svg) + +The rendering has the following steps: + +1. **Render Viewport** - Layout/render only the cells that are in the visible viewport +2. **Render Template** - Each cell type has a template (code cell, markdown cell) that is instantiated via `CodeCellRenderer` or `MarkupCellRenderer` +3. **Render Element** - The cell content is rendered into the template +4. **Get Dynamic Height** - Cell height is computed dynamically based on content (editor lines, outputs, etc.) +5. **Cell Parts Lifecycle** - Each cell has lifecycle parts that manage focus, selection, and other state + +### Cell resize above viewport + +When a cell above the viewport is resized (e.g., output grows), the viewport needs to be updated to maintain scroll position. This is handled by tracking scroll anchors. + +![Cell Resize Above Viewport](./resources/notebook/cell-resize-above-viewport.drawio.svg) + +## Cell Rendering + +The notebook editor renders cells through a contribution system. Cell parts are organized into two categories via `CellPartsCollection`: + +- **CellContentPart** - Non-floating elements rendered inside a cell synchronously to avoid flickering + - `prepareRenderCell()` - Prepare model (no DOM operations) + - `renderCell()` / `didRenderCell()` - Update DOM for the cell + - `unrenderCell()` - Cleanup when cell leaves viewport + - `updateInternalLayoutNow()` - Update layout per cell layout changes + - `updateState()` - Update per cell state change + - `updateForExecutionState()` - Update per execution state change + +- **CellOverlayPart** - Floating elements rendered on top, may be deferred to next animation frame + +Cell parts are located in `view/cellParts/` and contribute to different aspects: +- **Editor** - The Monaco editor for code cells +- **Outputs** - Rendered outputs from code execution +- **Toolbar** - Cell toolbar with actions +- **Status Bar** - Execution status, language info +- **Decorations** - Fold regions, diagnostics, etc. +- **Context Keys** - Cell-specific context key management +- **Drag and Drop** - Cell reordering via `CellDragAndDropController` + +## Focus Tracking + +Focus in the notebook editor is complex because there are multiple focusable elements: + +1. The notebook list itself (`NotebookCellList`) +2. Individual cell containers +3. Monaco editors within cells +4. Output elements (webviews via `BackLayerWebView`) + +The `NotebookEditorWidget` tracks focus state and provides APIs to manage focus across these components. Context keys like `NOTEBOOK_EDITOR_FOCUSED`, `NOTEBOOK_OUTPUT_FOCUSED`, and `NOTEBOOK_OUTPUT_INPUT_FOCUSED` are used to track focus state. + +## Optimizations + +### Output virtualization + +Large outputs are virtualized similar to cells. Only visible portions of outputs are rendered. + +### Cell DOM recycling + +Cell DOM elements are pooled and recycled to reduce DOM operations. When scrolling, cells that move out of the viewport have their templates returned to the pool. Editor instances are managed via `NotebookCellEditorPool`. + +### Webview reuse + +Output webviews are reused across cells when possible to reduce the overhead of creating new webview contexts. The `BackLayerWebView` manages the webview lifecycle. + +--- + +# Find in Notebook Outputs + +The notebook find feature supports searching in both text models and rendered outputs. The find functionality is implemented via `FindModel` and `CellFindMatchModel` classes. + +## Hybrid Find + +For rendered outputs (HTML, images with alt text, etc.), the find uses a hybrid approach: + +1. **Text model search** - Searches cell source code using standard text search via `FindMatch` +2. **DOM search in webview** - Uses `window.find()` to search rendered output content via `CellWebviewFindMatch` + +![Hybrid Find](./resources/notebook/hybrid-find.drawio.svg) + +The hybrid find works by: + +1. First finding matches in text models (cell inputs) - stored in `contentMatches` +2. Then finding matches in rendered outputs via webview - stored in `webviewMatches` +3. Mixing both match types into a unified result set via `CellFindMatchModel` +4. Navigating between matches reveals the appropriate editor or output + +### Implementation details + +- Uses `window.find()` for DOM searching in webview +- Uses `document.execCommand('hiliteColor')` to highlight matches +- Serializes `document.getSelection()` to get match positions +- Creates ranges for current match highlighting diff --git a/.github/instructions/resources/interactive/interactive.editor.drawio.svg b/.github/instructions/resources/interactive/interactive.editor.drawio.svg new file mode 100644 index 0000000000000..c6b947982800c --- /dev/null +++ b/.github/instructions/resources/interactive/interactive.editor.drawio.svg @@ -0,0 +1,331 @@ + + + + + + + + + + + +
+
+
+ Notebook Editor Widget +
+
+
+
+ + Notebook Editor Widget + +
+
+ + + + +
+
+
+ Code Editor Widget +
+
+
+
+ + Code Editor Widget + +
+
+ + + + +
+
+
+ Interactive Editor +
+
+
+
+ + Interactive Editor + +
+
+ + + + + +
+
+
+ NotebookService +
+
+
+
+ + NotebookService + +
+
+ + + + + +
+
+
+ TextModelResolverService +
+
+
+
+ + TextModelResolverService + +
+
+ + + + + + + + + + + +
+
+
+ NotebookTextModel +
+
+
+
+ + NotebookTextModel + +
+
+ + + + +
+
+
+ ITextModel +
+
+
+
+ + ITextModel + +
+
+ + + + +
+
+
+ Interactive Editor Input +
+
+
+
+ + Interactive Editor In... + +
+
+ + + + +
+
+
+ EditorPane +
+
+
+
+ + EditorPane + +
+
+ + + + +
+
+
+ EditorInput +
+
+
+
+ + EditorInput + +
+
+ + + + + + +
+
+
+ Editor Resolver Service +
+
+
+
+ + Editor Resolver Service + +
+
+ + + + +
+
+
+ EditorPane Registry +
+
+
+
+ + EditorPane Registry + +
+
+ + + + + + +
+
+
+ Registry Editor Pane +
+
+
+
+ + Registry Editor Pane + +
+
+ + + + + + + + +
+
+
+ + registerEditor + +
+
+
+
+ + registerEditor + +
+
+ + + + + + +
+
+
+ input: EditorInput +
+
+
+
+ + input: EditorInput + +
+
+ + + + + + +
+
+
+ group: IEditorGroup +
+
+
+
+ + group: IEditorGroup + +
+
+ + + + + + +
+
+
+ getControl: IEditorControl +
+
+
+
+ + getControl: IEditorControl + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/.github/instructions/resources/interactive/interactive.eh.drawio.svg b/.github/instructions/resources/interactive/interactive.eh.drawio.svg new file mode 100644 index 0000000000000..5f9f40795c2b1 --- /dev/null +++ b/.github/instructions/resources/interactive/interactive.eh.drawio.svg @@ -0,0 +1,244 @@ + + + + + + + + + +
+
+
+ + Ext Host + +
+
+
+
+ + Ext Host + +
+
+ + + + +
+
+
+ + UI + +
+
+
+
+ + UI + +
+
+ + + + + + + +
+
+
+ Notebook Editor +
+
+
+
+ + Notebook Editor + +
+
+ + + + +
+
+
+ Text Editor +
+
+
+
+ + Text Editor + +
+
+ + + + +
+
+
+ Interactive Editor +
+
+
+
+ + Interactive Editor + +
+
+ + + + + +
+
+
+ NotebookService +
+
+
+
+ + NotebookService + +
+
+ + + + + + + +
+
+
+ TextModelResolverService +
+
+
+
+ + TextModelResolverService + +
+
+ + + + + + + +
+
+
+ ExthostNotebook +
+
+
+
+ + ExthostNotebook + +
+
+ + + + + + + +
+
+
+ ExthostEditors +
+
+
+
+ + ExthostEditors + +
+
+ + + + + + + + +
+
+
+ ExthostInteractive +
+
+
+
+ + ExthostInteracti... + +
+
+ + + + + + +
+
+
+ NotebookEditor +
+
+
+
+ + NotebookE... + +
+
+ + + + + + +
+
+
+ Input +
+
+
+
+ + Input + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/.github/instructions/resources/interactive/interactive.model.resolution.drawio.svg b/.github/instructions/resources/interactive/interactive.model.resolution.drawio.svg new file mode 100644 index 0000000000000..61c91b981255e --- /dev/null +++ b/.github/instructions/resources/interactive/interactive.model.resolution.drawio.svg @@ -0,0 +1,352 @@ + + + + + + + + + + +
+
+
+ Read resource +
+
+
+
+ + Read resource + +
+
+ + + + + + +
+
+
+ Deserialize NotebookData +
+
+
+
+ + Deserialize NotebookData + +
+
+ + + + + + +
+
+
+ creates +
+
+
+
+ + creates + +
+
+ + + + + +
+
+
+ creates +
+
+
+
+ + creates + +
+
+ + + + + +
+
+
+ creates +
+
+
+
+ + creates + +
+
+ + + + + +
+
+
+ NotebookEditorModelResolverService +
+
+
+
+ + NotebookEditorMod... + +
+
+ + + + +
+
+
+ + SimpleNotebookEditorModel + +
+
+
+
+ + SimpleNoteboookEd... + +
+
+ + + + +
+
+
+ + NotebookFileWorkingCopyModelFactory + +
+
+
+
+ + NotebookFileWorki... + +
+
+ + + + +
+
+
+ + WorkingCopyManager + +
+
+
+
+ + WorkingCopyManager + +
+
+ + + + + +
+
+
+ creates +
+
+
+
+ + creates + +
+
+ + + + +
+
+
+ NotebookService +
+
+
+
+ + NotebookService + +
+
+ + + + +
+
+
+ FileService +
+
+
+
+ + FileService + +
+
+ + + + +
+
+
+ NotebookTextModel +
+
+
+
+ + NotebookTextModel + +
+
+ + + + + +
+
+
+ register FS provider +
+ for vscode-interactive schema +
+
+
+
+ + register FS provider... + +
+
+ + + + + +
+
+
+ register notebook serializer +
+ for interactive viewtype +
+
+
+
+ + register notebook serializer... + +
+
+ + + + +
+
+
+ interactive.contribution +
+ (startup) +
+
+
+
+ + interactive.contributio... + +
+
+ + + + + + + +
+
+
+ InteractiveEditor +
+
+
+
+ + InteractiveEditor + +
+
+ + + + + + + +
+
+
+ + NotebookEditorInput + +
+
+
+
+ + NotebookEditorInp... + +
+
+
+ + + + + Text is not SVG - cannot display + + + +
diff --git a/.github/instructions/resources/notebook/cell-resize-above-viewport.drawio.svg b/.github/instructions/resources/notebook/cell-resize-above-viewport.drawio.svg new file mode 100644 index 0000000000000..6a2f80fc98da0 --- /dev/null +++ b/.github/instructions/resources/notebook/cell-resize-above-viewport.drawio.svg @@ -0,0 +1,654 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ Notebook List View +
+
+
+
+ + Notebook List View + +
+
+ + + + +
+
+
+ Webview top -1000 +
+
+
+
+ + Webview top -1000 + +
+
+ + + + + + +
+
+
+ Viewport +
+
+
+
+ + Viewport + +
+
+ + + + + + + + + +
+
+
+ Notebook List View +
+
+
+
+ + Notebook List View + +
+
+ + + + + + + + + + + + + + + + +
+
+
+ Grow Height by 50 +
+
+
+
+ + Grow Height by 50 + +
+
+ + + + +
+
+
+ scrollTop 1000 +
+
+
+
+ + scrollTop 1000 + +
+
+ + + + +
+
+
+ scrollTop 1000 +
+
+
+
+ + scrollTop 1000 + +
+
+ + + + + + + + + + + + +
+
+
+ Notebook List View +
+
+
+
+ + Notebook List View + +
+
+ + + + + + + + + + + + + + + + +
+
+
+ scrollTop 1050 +
+
+
+
+ + scrollTop 1050 + +
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+ Notebook List View +
+
+
+
+ + Notebook List View + +
+
+ + + + + + + + + + + + + + + + +
+
+
+ scrollTop 1050 +
+
+
+
+ + scrollTop 1050 + +
+
+ + + + + + +
+
+
+ Adjust top +
+
+
+
+ + Adjust top + +
+
+ + + + + + +
+
+
+ UpdateScrollTop +
+
+
+
+ + UpdateScrollTop + +
+
+ + + + + + +
+
+
+ Webview top -1000 +
+
+
+
+ + Webview top -1000 + +
+
+ + + + +
+
+
+ Webview top -1000 +
+
+
+
+ + Webview top -1000 + +
+
+ + + + +
+
+
+ Webview top -1000 +
+
+
+
+ + Webview top -1000 + +
+
+ + + + + + + + + + + + + + +
+
+
+ Notebook List View +
+
+
+
+ + Notebook List View + +
+
+ + + + + + + + + + + + + + + + +
+
+
+ scrollTop 1050 +
+
+
+
+ + scrollTop 1050 + +
+
+ + + + +
+
+
+ Webview top -950 +
+
+
+
+ + Webview top -950 + +
+
+ + + + + + + + + + + + + + +
+
+
+ Notebook List View +
+
+
+
+ + Notebook List View + +
+
+ + + + + + + + + + + + + + + + +
+
+
+ scrollTop 1050 +
+
+
+
+ + scrollTop 1050 + +
+
+ + + + +
+
+
+ Webview top -950 +
+
+
+
+ + Webview top -950 + +
+
+ + + + + + +
+
+
+ Adjust top +
+
+
+
+ + Adjust top + +
+
+ + + + + + + + +
+
+
+ 1 +
+
+
+
+ + 1 + +
+
+ + + + +
+
+
+ 2 +
+
+
+
+ + 2 + +
+
+ + + + +
+
+
+ 3 +
+
+
+
+ + 3 + +
+
+ + + + +
+
+
+ 4 +
+
+
+
+ + 4 + +
+
+ + + + +
+
+
+ 4' +
+
+
+
+ + 4' + +
+
+ + + + +
+
+
+ 5 +
+
+
+
+ + 5 + +
+
+
+ + + + + Viewer does not support full SVG 1.1 + + + +
diff --git a/.github/instructions/resources/notebook/hybrid-find.drawio.svg b/.github/instructions/resources/notebook/hybrid-find.drawio.svg new file mode 100644 index 0000000000000..a2419b58fce4e --- /dev/null +++ b/.github/instructions/resources/notebook/hybrid-find.drawio.svg @@ -0,0 +1,327 @@ + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ window.find +
+
+
+
+ + window.find + +
+
+ + + + + + +
+
+
+ exec("hiliteColor") +
+ findMatchColor +
+
+
+
+ + exec("hiliteColor")... + +
+
+ + + + +
+
+
+ Serialize +
+ document.getSelection() +
+
+
+
+ + Serialize... + +
+
+ + + + + + + + + + +
+
+
+ Find in Rendered Outputs (Search in DOM) +
+
+
+
+ + Find in Rendered Out... + +
+
+ + + + +
+
+
+ Find +
+
+
+
+ + Find + +
+
+ + + + +
+
+
+ Find in Text Model +
+
+
+
+ + Find in Text Model + +
+
+ + + + +
+
+
+ Mix matches +
+
+
+
+ + Mix matches + +
+
+ + + + +
+
+
+ End of Doc +
+
+
+
+ + End of Doc + +
+
+ + + + + + +
+
+
+ Webview +
+
+
+
+ + Webview + +
+
+ + + + + + +
+
+
+ Find Next +
+
+
+
+ + Find Next + +
+
+ + + + + + +
+
+
+ Is Model Match +
+
+
+
+ + Is Model Match + +
+
+ + + + +
+
+
+ Reveal Editor +
+
+
+
+ + Reveal Editor + +
+
+ + + + +
+
+
+ Y +
+
+
+
+ + Y + +
+
+ + + + + + +
+
+
+ document create range +
+
+
+
+ + document create range + +
+
+ + + + + + +
+
+
+ "hiliteColor" +
+ currentFindMatchColor +
+
+
+
+ + "hiliteColor"... + +
+
+ + + + + + +
+
+
+ Find cell/output container +
+
+
+
+ + Find cell/output con... + +
+
+
+ + + + + Viewer does not support full SVG 1.1 + + + +
diff --git a/.github/instructions/resources/notebook/viewport-rendering.drawio.svg b/.github/instructions/resources/notebook/viewport-rendering.drawio.svg new file mode 100644 index 0000000000000..6a51b66c723b1 --- /dev/null +++ b/.github/instructions/resources/notebook/viewport-rendering.drawio.svg @@ -0,0 +1,521 @@ + + + + + + + + + + +
+
+
+ Render Viewport +
+
+
+
+ + Render Viewport + +
+
+ + + + +
+
+
+ Notebook List View +
+
+
+
+ + Notebook List View + +
+
+ + + + + + + + + +
+
+
+ Render Template +
+
+
+
+ + Render Template + +
+
+ + + + + + + + + +
+
+
+ Render Element +
+
+
+
+ + Render Element + +
+
+ + + + + +
+
+
+ Get Dynamic Height +
+
+
+
+ + Get Dynamic Height + +
+
+ + + + + + + + + +
+
+
+ Create Cell Templates/Parts +
+
+
+
+ + Create Cell Templates/Parts + +
+
+ + + + +
+
+
+ Toolbar +
+
+
+
+ + Toolbar + +
+
+ + + + +
+
+
+ Editor +
+
+
+
+ + Editor + +
+
+ + + + +
+
+
+ Statusbar +
+
+
+
+ + Statusbar + +
+
+ + + + + + + +
+
+
+ Code Cell +
+
+
+
+ + Code Cell + +
+
+ + + + + +
+
+
+ Render Cell Parts +
+
+
+
+ + Render Cell Parts + +
+
+ + + + + + + + + + + +
+
+
+ CellPart read DOM +
+
+
+
+ + CellPart read DOM + +
+
+ + + + +
+
+
+ Update layout info +
+
+
+
+ + Update layout info + +
+
+ + + + + + + +
+
+
+ Toolbar.renderCell +
+
+
+
+ + Toolbar.renderCell + +
+
+ + + + +
+
+
+ Toolbar.renderCell +
+
+
+
+ + Toolbar.renderCell + +
+
+ + + + +
+
+
+ Toolbar.didRenderCell +
+
+
+
+ + Toolbar.didRenderCell + +
+
+ + + + +
+
+
+ Toolbar.renderCell +
+
+
+
+ + Toolbar.renderCell + +
+
+ + + + +
+
+
+ Toolbar.renderCell +
+
+
+
+ + Toolbar.renderCell + +
+
+ + + + +
+
+
+ + Toolbar.prepareLayout + +
+
+
+
+ + Toolbar.prepareLay... + +
+
+ + + + + + + + + + + +
+
+
+ Cell Layout Change +
+
+
+
+ + Cell Layout Change + +
+
+ + + + + +
+
+
+ Cell Part updateInternalLayoutNow +
+
+
+
+ + Cell Part updateInternalLayoutNow + +
+
+ + + + +
+
+
+ Toolbar.renderCell +
+
+
+
+ + Toolbar.renderCell + +
+
+ + + + +
+
+
+ Toolbar.renderCell +
+
+
+
+ + Toolbar.renderCell + +
+
+ + + + +
+
+
+ + Toolbar.updateInternalLayoutNow + +
+
+
+
+ + Toolbar.updateInternalLayout... + +
+
+ + + + +
+
+
+ Next Frame +
+
+
+
+ + Next Frame + +
+
+ + + + +
+
+
+ + DOM Read + +
+
+
+
+ + DOM Read + +
+
+ + + + +
+
+
+ + DOM Write + +
+
+
+
+ + DOM Write + +
+
+
+ + + + + Viewer does not support full SVG 1.1 + + + +
diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 6ae56ad639e28..f601633b570d4 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -286,6 +286,23 @@ "cwd": "./build/vite/" }, "isBackground": true, + "problemMatcher": { + "owner": "vite", + "fileLocation": "absolute", + "pattern": { + "regexp": "^(.+?):(\\d+):(\\d+):\\s+(error|warning)\\s+(.*)$", + "file": 1, + "line": 2, + "column": 3, + "severity": 4, + "message": 5 + }, + "background": { + "activeOnStart": true, + "beginsPattern": ".*VITE.*", + "endsPattern": "(Local|Network):.*" + } + } }, { "label": "Launch MCP Server", diff --git a/build/azure-pipelines/common/sanity-tests.yml b/build/azure-pipelines/common/sanity-tests.yml new file mode 100644 index 0000000000000..4bb3b7e44a2eb --- /dev/null +++ b/build/azure-pipelines/common/sanity-tests.yml @@ -0,0 +1,61 @@ +parameters: + - name: commit + type: string + - name: quality + type: string + - name: poolName + type: string + - name: os + type: string + +jobs: + - job: ${{ parameters.os }} + displayName: ${{ parameters.os }} Sanity Tests + pool: + name: ${{ parameters.poolName }} + os: ${{ parameters.os }} + timeoutInMinutes: 30 + variables: + SANITY_TEST_LOGS: $(Build.SourcesDirectory)/.build/sanity-test-logs + templateContext: + outputs: + - output: pipelineArtifact + targetPath: $(SANITY_TEST_LOGS) + artifactName: sanity-test-logs-${{ lower(parameters.os) }}-$(System.JobAttempt) + displayName: Publish Sanity Test Logs + sbomEnabled: false + isProduction: false + condition: succeededOrFailed() + steps: + - checkout: self + + - task: NodeTool@0 + inputs: + versionSource: fromFile + versionFilePath: .nvmrc + displayName: Install Node.js + + - ${{ if eq(parameters.os, 'windows') }}: + - script: | + mkdir "$(SANITY_TEST_LOGS)" + displayName: Create Logs Directory + + - ${{ else }}: + - script: | + mkdir -p "$(SANITY_TEST_LOGS)" + displayName: Create Logs Directory + + - script: npm install + displayName: Install Dependencies + workingDirectory: $(Build.SourcesDirectory)/test/sanity + + - script: npm run sanity-test -- --commit ${{ parameters.commit }} --quality ${{ parameters.quality }} --verbose --test-results $(SANITY_TEST_LOGS)/sanity-test.xml + displayName: Run Sanity Tests + + - task: PublishTestResults@2 + inputs: + testResultsFormat: JUnit + testResultsFiles: $(SANITY_TEST_LOGS)/sanity-test.xml + testRunTitle: ${{ parameters.os }} Sanity Tests + condition: succeededOrFailed() + displayName: Publish Test Results diff --git a/build/azure-pipelines/product-sanity-tests.yml b/build/azure-pipelines/product-sanity-tests.yml new file mode 100644 index 0000000000000..79406964f3738 --- /dev/null +++ b/build/azure-pipelines/product-sanity-tests.yml @@ -0,0 +1,71 @@ +pr: none + +trigger: none + +parameters: + - name: commit + displayName: Commit + type: string + - name: quality + displayName: Quality + type: string + default: insider + values: + - exploration + - insider + - stable + +variables: + - name: skipComponentGovernanceDetection + value: true + - name: Codeql.SkipTaskAutoInjection + value: true + +name: "$(Date:yyyyMMdd).$(Rev:r) (${{ parameters.quality }})" + +resources: + repositories: + - repository: 1ESPipelines + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release + +extends: + template: v1/1ES.Official.PipelineTemplate.yml@1esPipelines + parameters: + sdl: + tsa: + enabled: false + codeql: + compiled: + enabled: false + justificationForDisabling: "Sanity tests only, no code compilation" + credscan: + suppressionsFile: $(Build.SourcesDirectory)/build/azure-pipelines/config/CredScanSuppressions.json + eslint: + enabled: false + sourceAnalysisPool: 1es-windows-2022-x64 + createAdoIssuesForJustificationsForDisablement: false + stages: + - stage: SanityTests + jobs: + - template: build/azure-pipelines/common/sanity-tests.yml@self + parameters: + commit: ${{ parameters.commit }} + quality: ${{ parameters.quality }} + poolName: 1es-windows-2022-x64 + os: windows + + - template: build/azure-pipelines/common/sanity-tests.yml@self + parameters: + commit: ${{ parameters.commit }} + quality: ${{ parameters.quality }} + poolName: 1es-ubuntu-22.04-x64 + os: linux + + - template: build/azure-pipelines/common/sanity-tests.yml@self + parameters: + commit: ${{ parameters.commit }} + quality: ${{ parameters.quality }} + poolName: AcesShared + os: macOS diff --git a/build/vite/tsconfig.json b/build/vite/tsconfig.json index 454dc14491fe5..c2f24a640981c 100644 --- a/build/vite/tsconfig.json +++ b/build/vite/tsconfig.json @@ -1,10 +1,11 @@ { "compilerOptions": { "allowImportingTsExtensions": true, + "checkJs": true, + "module": "preserve", "noEmit": true, "strict": true, - "forceConsistentCasingInFileNames": true, "experimentalDecorators": true, - }, - "include": ["**/*.ts"] + "types": ["vite/client"] + } } diff --git a/build/vite/vite.config.ts b/build/vite/vite.config.ts index 5736a474d6bab..4ccae2a2419b5 100644 --- a/build/vite/vite.config.ts +++ b/build/vite/vite.config.ts @@ -5,7 +5,6 @@ import { createLogger, defineConfig, Plugin } from 'vite'; import path, { join } from 'path'; -/// @ts-ignore import { urlToEsmPlugin } from './rollup-url-to-module-plugin/index.mjs'; import { statSync } from 'fs'; import { pathToFileURL } from 'url'; @@ -185,7 +184,6 @@ export default defineConfig({ fs: { allow: [ // To allow loading from sources, not needed when loading monaco-editor from npm package - /// @ts-ignore join(import.meta.dirname, '../../../') ] } diff --git a/package-lock.json b/package-lock.json index adcd484670c8a..d7d561c1ee5a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,7 +46,7 @@ "native-is-elevated": "0.8.0", "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", - "node-pty": "^1.1.0-beta43", + "node-pty": "^1.2.0-beta.3", "open": "^10.1.2", "tas-client": "0.3.1", "undici": "^7.9.0", @@ -3258,9 +3258,9 @@ } }, "node_modules/@vscode/policy-watcher": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/@vscode/policy-watcher/-/policy-watcher-1.3.5.tgz", - "integrity": "sha512-k1n9gaDBjyVRy5yJLABbZCnyFwgQ8OA4sR3vXmXnmB+mO9JA0nsl/XOXQfVCoLasBu3UHCOfAnDWGn2sRzCR+A==", + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@vscode/policy-watcher/-/policy-watcher-1.3.6.tgz", + "integrity": "sha512-YJA9+6M4s2SjChWczy3EdyhXNPWqNNU8O2jzlrsQz7za5Am5Vo+1Rrln4AQDSvo9aTCNlTwlTAhRVWvyGGaN8A==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -3320,9 +3320,9 @@ } }, "node_modules/@vscode/spdlog": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.4.tgz", - "integrity": "sha512-NmFasVWjn/6BjHMAjqalsbG2srQCt8yfC0EczP5wzNQFawv74rhvuarhWi44x3St9LB8bZBxrpbT7igPaTJwcw==", + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.5.tgz", + "integrity": "sha512-2eckivcs73OTnP+CwvJOQxluzT9tLqEH5Wl+rrv8bt5hVeXLdRYtihENFNSAYW099hL4/oyJ990KAssi7OxSWw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -3559,9 +3559,9 @@ } }, "node_modules/@vscode/windows-mutex": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@vscode/windows-mutex/-/windows-mutex-0.5.2.tgz", - "integrity": "sha512-O9CNYVl2GmFVbiHiz7tyFrKIdXVs3qf8HnyWlfxyuMaKzXd1L35jSTNCC1oAVwr8F0O2P4o3C/jOSIXulUCJ7w==", + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@vscode/windows-mutex/-/windows-mutex-0.5.3.tgz", + "integrity": "sha512-hWNmD+AzINR57jWuc/iW53kA+BghI4iOuicxhAEeeJLPOeMm9X5IUD0ttDwJFEib+D8H/2T9pT/8FeB/xcqbRw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -12818,9 +12818,9 @@ "license": "MIT" }, "node_modules/native-keymap": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/native-keymap/-/native-keymap-3.3.7.tgz", - "integrity": "sha512-07n5kF0L9ERC9pilqEFucnhs1XG4WttbHAMWhhOSqQYXhB8mMNTSCzP4psTaVgDSp6si2HbIPhTIHuxSia6NPQ==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/native-keymap/-/native-keymap-3.3.8.tgz", + "integrity": "sha512-JoNfN3hlYWSiCJDMep9adOjpOvq64orKNO8zIy0ns1EZJFUwnvgHRpD7T8eWm7SMlbn4X3fh5FkA7LKPtT/Niw==", "hasInstallScript": true, "license": "MIT" }, @@ -12949,9 +12949,9 @@ } }, "node_modules/node-pty": { - "version": "1.1.0-beta43", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0-beta43.tgz", - "integrity": "sha512-CYyIQogRs97Rfjo0WKyku8V56Bm4WyWUijrbWDs5LJ+ZmsUW2gqbVAEpD+1gtA7dEZ6v1A08GzfqsDuIl/eRqw==", + "version": "1.2.0-beta.3", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.2.0-beta.3.tgz", + "integrity": "sha512-SeAwG9LgWijWLtWldBWPwUUA1rAg2OKBG37dtSGOTYvBkUstWxAi2hXS0pX9JXbHPDxs0DnVc5tXrXsY821E+w==", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 19d570a512404..f33f6d0d96916 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.109.0", - "distro": "e5c6724cbb9375a8edee07cc94fa99aa31c4c680", + "distro": "66d6007ba3b8eff60c3026cb216e699981aca7ec", "author": { "name": "Microsoft Corporation" }, @@ -109,7 +109,7 @@ "native-is-elevated": "0.8.0", "native-keymap": "^3.3.5", "native-watchdog": "^1.4.1", - "node-pty": "^1.1.0-beta43", + "node-pty": "^1.2.0-beta.3", "open": "^10.1.2", "tas-client": "0.3.1", "undici": "^7.9.0", diff --git a/remote/package-lock.json b/remote/package-lock.json index 44adad2a8260f..138d0fda7d697 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -38,7 +38,7 @@ "kerberos": "2.1.1", "minimist": "^1.2.8", "native-watchdog": "^1.4.1", - "node-pty": "^1.1.0-beta43", + "node-pty": "^1.2.0-beta.3", "tas-client": "0.3.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", @@ -451,9 +451,9 @@ } }, "node_modules/@vscode/spdlog": { - "version": "0.15.4", - "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.4.tgz", - "integrity": "sha512-NmFasVWjn/6BjHMAjqalsbG2srQCt8yfC0EczP5wzNQFawv74rhvuarhWi44x3St9LB8bZBxrpbT7igPaTJwcw==", + "version": "0.15.5", + "resolved": "https://registry.npmjs.org/@vscode/spdlog/-/spdlog-0.15.5.tgz", + "integrity": "sha512-2eckivcs73OTnP+CwvJOQxluzT9tLqEH5Wl+rrv8bt5hVeXLdRYtihENFNSAYW099hL4/oyJ990KAssi7OxSWw==", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1051,9 +1051,9 @@ } }, "node_modules/node-pty": { - "version": "1.1.0-beta43", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0-beta43.tgz", - "integrity": "sha512-CYyIQogRs97Rfjo0WKyku8V56Bm4WyWUijrbWDs5LJ+ZmsUW2gqbVAEpD+1gtA7dEZ6v1A08GzfqsDuIl/eRqw==", + "version": "1.2.0-beta.3", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.2.0-beta.3.tgz", + "integrity": "sha512-SeAwG9LgWijWLtWldBWPwUUA1rAg2OKBG37dtSGOTYvBkUstWxAi2hXS0pX9JXbHPDxs0DnVc5tXrXsY821E+w==", "hasInstallScript": true, "license": "MIT", "dependencies": { diff --git a/remote/package.json b/remote/package.json index 479adcd5410f4..1e20aa81c44c4 100644 --- a/remote/package.json +++ b/remote/package.json @@ -33,7 +33,7 @@ "kerberos": "2.1.1", "minimist": "^1.2.8", "native-watchdog": "^1.4.1", - "node-pty": "^1.1.0-beta43", + "node-pty": "^1.2.0-beta.3", "tas-client": "0.3.1", "vscode-oniguruma": "1.7.0", "vscode-regexpp": "^3.1.0", diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index 5146046fe9991..288c77f7d5215 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -379,6 +379,7 @@ export class ToolBar extends Disposable { override dispose(): void { this.clear(); this.disposables.dispose(); + this.element.remove(); super.dispose(); } } diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index 772297def7f52..7973ab63893fa 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -148,6 +148,10 @@ export interface IObserver { handleChange(observable: IObservableWithChange, change: TChange): void; } +/** + * A reader allows code to track what it depends on, so the caller knows when the computed value or produced side-effect is no longer valid. + * Use `derived(reader => ...)` to turn code that needs a reader into an observable value. +*/ export interface IReader { /** * Reads the value of an observable and subscribes to it. diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index de827f77c3d73..4269ca6a4a818 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -777,7 +777,7 @@ configurationRegistry.registerConfiguration({ }, [ChatConfiguration.ThinkingStyle]: { type: 'string', - default: 'fixedScrolling', + default: 'collapsedPreview', enum: ['collapsed', 'collapsedPreview', 'fixedScrolling'], enumDescriptions: [ nls.localize('chat.agent.thinkingMode.collapsed', "Thinking parts will be collapsed by default."), diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatMcpAppModel.ts b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatMcpAppModel.ts index 3a80411b9b47c..c1cd9e500892e 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatMcpAppModel.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatContentParts/toolInvocationParts/chatMcpAppModel.ts @@ -62,6 +62,9 @@ export class ChatMcpAppModel extends Disposable { /** Whether ui/initialize has been called and capabilities announced */ private _announcedCapabilities = false; + /** Latest CSP used for the frame */ + private _latestCsp: McpApps.McpUiResourceCsp | undefined = undefined; + /** Current height of the webview */ private _height: number = 300; @@ -216,6 +219,7 @@ export class ChatMcpAppModel extends Disposable { // Reset the state this._announcedCapabilities = false; + this._latestCsp = resourceContent.csp; // Set the HTML content this._webview.setHtml(htmlWithCsp); @@ -257,9 +261,9 @@ export class ChatMcpAppModel extends Disposable { img-src 'self' data: ${cleanDomains(csp?.resourceDomains)}; font-src 'self' ${cleanDomains(csp?.resourceDomains)}; media-src 'self' data: ${cleanDomains(csp?.resourceDomains)}; - frame-src 'none'; + frame-src ${cleanDomains(csp?.frameDomains) || `'none'`}; object-src 'none'; - base-uri 'self'; + base-uri ${cleanDomains(csp?.baseUriDomains) || `'self'`}; `; const cspTag = ``; @@ -468,6 +472,10 @@ export class ChatMcpAppModel extends Disposable { serverTools: { listChanged: true }, serverResources: { listChanged: true }, logging: {}, + sandbox: { + csp: this._latestCsp, + permissions: { clipboardWrite: true }, + }, }, hostContext: this.hostContext.get(), } satisfies Required; diff --git a/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts index a367400ac87b4..da168a4805137 100644 --- a/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/widget/chatListRenderer.ts @@ -1548,6 +1548,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer('chat.agent.thinking.collapsedTools') === CollapsedToolsDisplayMode.Off) { + this.finalizeCurrentThinkingPart(context, templateData); + } + const codeBlockStartIndex = context.codeBlockStartIndex; const part = this.instantiationService.createInstance(ChatToolInvocationPart, toolInvocation, context, this.chatContentMarkdownRenderer, this._contentReferencesListPool, this._toolEditorPool, () => this._currentLayoutWidth.get(), this._toolInvocationCodeBlockCollection, this._announcedToolProgressKeys, codeBlockStartIndex); part.addDisposable(part.onDidChangeHeight(() => { diff --git a/src/vs/workbench/contrib/chat/common/model/chatModel.ts b/src/vs/workbench/contrib/chat/common/model/chatModel.ts index 53e4608e89080..1c917062110fb 100644 --- a/src/vs/workbench/contrib/chat/common/model/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/model/chatModel.ts @@ -26,14 +26,13 @@ import { ISelection } from '../../../../../editor/common/core/selection.js'; import { TextEdit } from '../../../../../editor/common/languages.js'; import { EditSuggestionId } from '../../../../../editor/common/textModelEditSource.js'; import { localize } from '../../../../../nls.js'; -import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js'; import { ILogService } from '../../../../../platform/log/common/log.js'; import { CellUri, ICellEditOperation } from '../../../notebook/common/notebookCommon.js'; import { ChatRequestToolReferenceEntry, IChatRequestVariableEntry } from '../attachments/chatVariableEntries.js'; import { migrateLegacyTerminalToolSpecificData } from '../chat.js'; import { ChatAgentVoteDirection, ChatAgentVoteDownReason, ChatResponseClearToPreviousToolInvocationReason, ElicitationState, IChatAgentMarkdownContentWithVulnerability, IChatClearToPreviousToolInvocation, IChatCodeCitation, IChatCommandButton, IChatConfirmation, IChatContentInlineReference, IChatContentReference, IChatEditingSessionAction, IChatElicitationRequest, IChatElicitationRequestSerialized, IChatExtensionsContent, IChatFollowup, IChatLocationData, IChatMarkdownContent, IChatMcpServersStarting, IChatModelReference, IChatMultiDiffData, IChatMultiDiffDataSerialized, IChatNotebookEdit, IChatPrepareToolInvocationPart, IChatProgress, IChatProgressMessage, IChatPullRequestContent, IChatResponseCodeblockUriPart, IChatResponseProgressFileTreeData, IChatService, IChatSessionContext, IChatSessionTiming, IChatTask, IChatTaskSerialized, IChatTextEdit, IChatThinkingPart, IChatToolInvocation, IChatToolInvocationSerialized, IChatTreeData, IChatUndoStop, IChatUsedContext, IChatWarningMessage, ResponseModelState, isIUsedContext } from '../chatService/chatService.js'; import { ChatAgentLocation, ChatModeKind } from '../constants.js'; -import { IChatEditingService, IChatEditingSession, ModifiedFileEntryState, editEntriesToMultiDiffData } from '../editing/chatEditingService.js'; +import { IChatEditingService, IChatEditingSession, ModifiedFileEntryState } from '../editing/chatEditingService.js'; import { ILanguageModelChatMetadata, ILanguageModelChatMetadataAndIdentifier } from '../languageModels.js'; import { IChatAgentCommand, IChatAgentData, IChatAgentResult, IChatAgentService, UserSelectedTools, reviveSerializedAgent } from '../participants/chatAgents.js'; import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from '../requestParser/chatParserTypes.js'; @@ -1757,7 +1756,6 @@ export class ChatModel extends Disposable implements IChatModel { constructor( initialData: ISerializableChatData | IExportableChatData | undefined, initialModelProps: { initialLocation: ChatAgentLocation; canUseTools: boolean; inputState?: ISerializableChatModelInputState; resource?: URI; sessionId?: string; disableBackgroundKeepAlive?: boolean }, - @IConfigurationService private readonly configurationService: IConfigurationService, @ILogService private readonly logService: ILogService, @IChatAgentService private readonly chatAgentService: IChatAgentService, @IChatEditingService private readonly chatEditingService: IChatEditingService, @@ -1814,16 +1812,6 @@ export class ChatModel extends Disposable implements IChatModel { return; } - if ( - request === this._requests.at(-1) && - request.session.sessionResource.scheme !== Schemas.vscodeLocalChatSession && - this.configurationService.getValue('chat.checkpoints.showFileChanges') === true && - this._editingSession.hasEditsInRequest(request.id) - ) { - const diffs = this._editingSession.getDiffsForFilesInRequest(request.id); - request.response?.updateContent(editEntriesToMultiDiffData(diffs), true); - } - this._onDidChange.fire({ kind: 'completedRequest', request }); })); })); diff --git a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts index b7a2dd243650b..0fb275283c0c9 100644 --- a/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts +++ b/src/vs/workbench/contrib/chat/common/promptSyntax/utils/promptFilesLocator.ts @@ -265,6 +265,7 @@ export class PromptFilesLocator { type: QueryType.File, shouldGlobMatchFilePattern: true, excludePattern: workspaceRoot ? getExcludePattern(workspaceRoot.uri) : undefined, + ignoreGlobCase: true, sortByScore: true, filePattern }; diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 7fbfacc1959a3..cc16e91151e6b 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -591,7 +591,7 @@ export class ExplorerFindProvider implements IAsyncFindProvider { } private async searchInWorkspace(patternLowercase: string, root: ExplorerItem, rootIndex: number, isFuzzyMatch: boolean, token: CancellationToken): Promise<{ explorerRoot: ExplorerItem; files: URI[]; directories: URI[]; hitMaxResults: boolean }> { - const segmentMatchPattern = caseInsensitiveGlobPattern(isFuzzyMatch ? fuzzyMatchingGlobPattern(patternLowercase) : continousMatchingGlobPattern(patternLowercase)); + const segmentMatchPattern = isFuzzyMatch ? fuzzyMatchingGlobPattern(patternLowercase) : continousMatchingGlobPattern(patternLowercase); const searchExcludePattern = getExcludes(this.configurationService.getValue({ resource: root.resource })) || {}; const searchOptions: IFileQuery = { @@ -603,6 +603,7 @@ export class ExplorerFindProvider implements IAsyncFindProvider { shouldGlobMatchFilePattern: true, cacheKey: `explorerfindprovider:${root.name}:${rootIndex}:${this.sessionId}`, excludePattern: searchExcludePattern, + ignoreGlobCase: true, }; let fileResults: ISearchComplete | undefined; @@ -652,7 +653,7 @@ function getMatchingDirectoriesFromFiles(resources: URI[], root: ExplorerItem, s for (const dirResource of uniqueDirectories) { const stats = dirResource.path.split('/'); const dirStat = stats[stats.length - 1]; - if (!dirStat || !glob.match(segmentMatchPattern, dirStat)) { + if (!dirStat || !glob.match(segmentMatchPattern, dirStat, { ignoreCase: true })) { continue; } @@ -698,19 +699,6 @@ function continousMatchingGlobPattern(pattern: string): string { return '*' + pattern + '*'; } -function caseInsensitiveGlobPattern(pattern: string): string { - let caseInsensitiveFilePattern = ''; - for (let i = 0; i < pattern.length; i++) { - const char = pattern[i]; - if (/[a-zA-Z]/.test(char)) { - caseInsensitiveFilePattern += `[${char.toLowerCase()}${char.toUpperCase()}]`; - } else { - caseInsensitiveFilePattern += char; - } - } - return caseInsensitiveFilePattern; -} - export interface ICompressedNavigationController { readonly current: ExplorerItem; readonly currentId: string; diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 41dfa8fc39e30..7491ecc8c1669 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -104,9 +104,6 @@ export class InlineChatController implements IEditorContribution { return editor.getContribution(InlineChatController.ID) ?? undefined; } - private static readonly _autoModel = 'copilot/auto'; - private static _lastSelectedModel: string | undefined; - private readonly _store = new DisposableStore(); private readonly _isActiveController = observableValue(this, false); private readonly _zone: Lazy; @@ -128,7 +125,7 @@ export class InlineChatController implements IEditorContribution { @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, @ICodeEditorService codeEditorService: ICodeEditorService, @IContextKeyService contextKeyService: IContextKeyService, - @IConfigurationService private readonly _configurationService: IConfigurationService, + @IConfigurationService configurationService: IConfigurationService, @ISharedWebContentExtractorService private readonly _webContentExtractorService: ISharedWebContentExtractorService, @IFileService private readonly _fileService: IFileService, @IChatAttachmentResolveService private readonly _chatAttachmentResolveService: IChatAttachmentResolveService, @@ -138,7 +135,7 @@ export class InlineChatController implements IEditorContribution { ) { const ctxInlineChatVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService); - const notebookAgentConfig = observableConfigValue(InlineChatConfigKeys.notebookAgent, false, this._configurationService); + const notebookAgentConfig = observableConfigValue(InlineChatConfigKeys.notebookAgent, false, configurationService); this._zone = new Lazy(() => { @@ -202,15 +199,8 @@ export class InlineChatController implements IEditorContribution { { editor: this._editor, notebookEditor }, () => Promise.resolve(), ); - result.domNode.classList.add('inline-chat-2'); - - this._store.add(result); - this._store.add(result.widget.chatWidget.input.onDidChangeCurrentLanguageModel(model => { - InlineChatController._lastSelectedModel = model.identifier !== InlineChatController._autoModel - ? model.identifier - : undefined; - })); + result.domNode.classList.add('inline-chat-2'); return result; }); @@ -437,14 +427,6 @@ export class InlineChatController implements IEditorContribution { const session = this._inlineChatSessionService.createSession(this._editor); - // Reset model to default if persistence is disabled - if (!this._configurationService.getValue(InlineChatConfigKeys.PersistModelChoice) && !InlineChatController._lastSelectedModel) { - const defaultModel = this._languageModelService.lookupLanguageModel(InlineChatController._autoModel); - if (defaultModel) { - this._zone.value.widget.chatWidget.input.setCurrentLanguageModel({ metadata: defaultModel, identifier: InlineChatController._autoModel }); - } - } - // ADD diagnostics const entries: IChatRequestVariableEntry[] = []; for (const [range, marker] of this._markerDecorationsService.getLiveMarkers(uri)) { diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index 156e3a2fae415..cc0bd191ff2e1 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -20,7 +20,6 @@ export const enum InlineChatConfigKeys { /** @deprecated do not read on client */ EnableV2 = 'inlineChat.enableV2', notebookAgent = 'inlineChat.notebookAgent', - PersistModelChoice = 'inlineChat.persistModelChoice', } Registry.as(Extensions.Configuration).registerConfiguration({ @@ -53,14 +52,6 @@ Registry.as(Extensions.Configuration).registerConfigurat experiment: { mode: 'startup' } - }, - [InlineChatConfigKeys.PersistModelChoice]: { - description: localize('persistModelChoice', "Whether inline chat remembers the last selected model."), - default: false, - type: 'boolean', - experiment: { - mode: 'auto' - } } } }); diff --git a/src/vs/workbench/contrib/mcp/browser/mcpToolCallUI.ts b/src/vs/workbench/contrib/mcp/browser/mcpToolCallUI.ts index e18a34d2e12ee..7667d5b2670aa 100644 --- a/src/vs/workbench/contrib/mcp/browser/mcpToolCallUI.ts +++ b/src/vs/workbench/contrib/mcp/browser/mcpToolCallUI.ts @@ -217,7 +217,7 @@ export class McpToolCallUI extends Disposable { throw new Error('UI resource has no content'); } - const meta = resourceResult._meta?.ui as McpApps.McpUiResourceMeta | undefined; + const meta = content._meta?.ui as McpApps.McpUiResourceMeta | undefined; return { ...meta, diff --git a/src/vs/workbench/contrib/mcp/common/modelContextProtocolApps.ts b/src/vs/workbench/contrib/mcp/common/modelContextProtocolApps.ts index 225615b8ecb4f..dd2f5f7003530 100644 --- a/src/vs/workbench/contrib/mcp/common/modelContextProtocolApps.ts +++ b/src/vs/workbench/contrib/mcp/common/modelContextProtocolApps.ts @@ -61,7 +61,7 @@ export namespace McpApps { * ⚠️ Do not edit within `namespace` manually except to update schema versions ⚠️ */ export namespace McpApps { - /* + /** * Current protocol version supported by this SDK. * * The SDK automatically handles version negotiation during initialization. @@ -262,12 +262,9 @@ export namespace McpApps { /** @description Optional override for the inner iframe's sandbox attribute. */ sandbox?: string; /** @description CSP configuration from resource metadata. */ - csp?: { - /** @description Origins for network requests (fetch/XHR/WebSocket). */ - connectDomains?: string[]; - /** @description Origins for static resources (scripts, images, styles, fonts). */ - resourceDomains?: string[]; - }; + csp?: McpUiResourceCsp; + /** @description Sandbox permissions from resource metadata. */ + permissions?: McpUiResourcePermissions; }; } @@ -356,7 +353,7 @@ export namespace McpApps { /** @description Metadata of the tool call that instantiated this App. */ toolInfo?: { /** @description JSON-RPC id of the tools/call request. */ - id: RequestId; + id?: RequestId; /** @description Tool definition including name, inputSchema, etc. */ tool: Tool; }; @@ -471,6 +468,13 @@ export namespace McpApps { }; /** @description Host accepts log messages. */ logging?: {}; + /** @description Sandbox configuration applied by the host. */ + sandbox?: { + /** @description Permissions granted by the host (camera, microphone, geolocation). */ + permissions?: McpUiResourcePermissions; + /** @description CSP domains approved by the host. */ + csp?: McpUiResourceCsp; + }; } /** @@ -540,6 +544,26 @@ export namespace McpApps { connectDomains?: string[]; /** @description Origins for static resources (scripts, images, styles, fonts). */ resourceDomains?: string[]; + /** @description Origins for nested iframes (frame-src directive). */ + frameDomains?: string[]; + /** @description Allowed base URIs for the document (base-uri directive). */ + baseUriDomains?: string[]; + } + + /** + * @description Sandbox permissions requested by the UI resource. + * Hosts MAY honor these by setting appropriate iframe `allow` attributes. + * Apps SHOULD NOT assume permissions are granted; use JS feature detection as fallback. + */ + export interface McpUiResourcePermissions { + /** @description Request camera access (Permission Policy `camera` feature). */ + camera?: {}; + /** @description Request microphone access (Permission Policy `microphone` feature). */ + microphone?: {}; + /** @description Request geolocation access (Permission Policy `geolocation` feature). */ + geolocation?: {}; + /** @description Request clipboard write access (Permission Policy `clipboard-write` feature). */ + clipboardWrite?: {}; } /** @@ -548,6 +572,8 @@ export namespace McpApps { export interface McpUiResourceMeta { /** @description Content Security Policy configuration. */ csp?: McpUiResourceCsp; + /** @description Sandbox permissions requested by the UI. */ + permissions?: McpUiResourcePermissions; /** @description Dedicated origin for widget sandbox. */ domain?: string; /** @description Visual boundary preference - true if UI prefers a visible border. */ @@ -592,12 +618,12 @@ export namespace McpApps { */ export interface McpUiToolMeta { /** - * URI of the UI resource to display for this tool. + * URI of the UI resource to display for this tool, if any. * This is converted to `_meta["ui/resourceUri"]`. * * @example "ui://weather/widget.html" */ - resourceUri: string; + resourceUri?: string; /** * @description Who can access this tool. Default: ["model", "app"] * - "model": Tool visible to and callable by the agent @@ -605,4 +631,47 @@ export namespace McpApps { */ visibility?: McpUiToolVisibility[]; } + + /** + * Method string constants for MCP Apps protocol messages. + * + * These constants provide a type-safe way to check message methods without + * accessing internal Zod schema properties. External libraries should use + * these constants instead of accessing `schema.shape.method._def.values[0]`. + * + * @example + * ```typescript + * import { SANDBOX_PROXY_READY_METHOD } from '@modelcontextprotocol/ext-apps'; + * + * if (event.data.method === SANDBOX_PROXY_READY_METHOD) { + * // Handle sandbox proxy ready notification + * } + * ``` + */ + export const OPEN_LINK_METHOD: McpUiOpenLinkRequest["method"] = "ui/open-link"; + export const MESSAGE_METHOD: McpUiMessageRequest["method"] = "ui/message"; + export const SANDBOX_PROXY_READY_METHOD: McpUiSandboxProxyReadyNotification["method"] = + "ui/notifications/sandbox-proxy-ready"; + export const SANDBOX_RESOURCE_READY_METHOD: McpUiSandboxResourceReadyNotification["method"] = + "ui/notifications/sandbox-resource-ready"; + export const SIZE_CHANGED_METHOD: McpUiSizeChangedNotification["method"] = + "ui/notifications/size-changed"; + export const TOOL_INPUT_METHOD: McpUiToolInputNotification["method"] = + "ui/notifications/tool-input"; + export const TOOL_INPUT_PARTIAL_METHOD: McpUiToolInputPartialNotification["method"] = + "ui/notifications/tool-input-partial"; + export const TOOL_RESULT_METHOD: McpUiToolResultNotification["method"] = + "ui/notifications/tool-result"; + export const TOOL_CANCELLED_METHOD: McpUiToolCancelledNotification["method"] = + "ui/notifications/tool-cancelled"; + export const HOST_CONTEXT_CHANGED_METHOD: McpUiHostContextChangedNotification["method"] = + "ui/notifications/host-context-changed"; + export const RESOURCE_TEARDOWN_METHOD: McpUiResourceTeardownRequest["method"] = + "ui/resource-teardown"; + export const INITIALIZE_METHOD: McpUiInitializeRequest["method"] = + "ui/initialize"; + export const INITIALIZED_METHOD: McpUiInitializedNotification["method"] = + "ui/notifications/initialized"; + export const REQUEST_DISPLAY_MODE_METHOD: McpUiRequestDisplayModeRequest["method"] = + "ui/request-display-mode"; } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 73a4877c05d79..5b21ceec1d3b6 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -29,7 +29,7 @@ import { CancellationToken } from '../../../../base/common/cancellation.js'; import { Color, RGBA } from '../../../../base/common/color.js'; import { onUnexpectedError } from '../../../../base/common/errors.js'; import { Emitter, Event } from '../../../../base/common/event.js'; -import { combinedDisposable, Disposable, DisposableStore, dispose } from '../../../../base/common/lifecycle.js'; +import { combinedDisposable, Disposable, DisposableStore, dispose, MutableDisposable } from '../../../../base/common/lifecycle.js'; import { setTimeout0 } from '../../../../base/common/platform.js'; import { extname, isEqual } from '../../../../base/common/resources.js'; import { URI } from '../../../../base/common/uri.js'; @@ -1516,16 +1516,17 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD })); let hasPendingChangeContentHeight = false; + const renderScrollHeightDisposable = this._localStore.add(new MutableDisposable()); this._localStore.add(this._list.onDidChangeContentHeight(() => { if (hasPendingChangeContentHeight) { return; } hasPendingChangeContentHeight = true; - this._localStore.add(DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this.getDomNode()), () => { + renderScrollHeightDisposable.value = DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this.getDomNode()), () => { hasPendingChangeContentHeight = false; this._updateScrollHeight(); - }, 100)); + }, 100); })); this._localStore.add(this._list.onDidRemoveOutputs(outputs => { diff --git a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 350fa0ad053ad..2383e0c60f57c 100644 --- a/src/vs/workbench/contrib/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -228,6 +228,7 @@ export class IncludePatternInputWidget extends PatternInputWidget { this.inputBox.focus(); } })); + controlsDiv.appendChild(this.useSearchInEditorsBox.domNode); super.renderSubcontrols(controlsDiv); } diff --git a/src/vs/workbench/contrib/search/browser/searchChatContext.ts b/src/vs/workbench/contrib/search/browser/searchChatContext.ts index 8a5f8d560c636..1a463744b4cd8 100644 --- a/src/vs/workbench/contrib/search/browser/searchChatContext.ts +++ b/src/vs/workbench/contrib/search/browser/searchChatContext.ts @@ -236,7 +236,7 @@ export async function searchFilesAndFolders( configurationService: IConfigurationService, searchService: ISearchService ): Promise<{ folders: URI[]; files: URI[] }> { - const segmentMatchPattern = caseInsensitiveGlobPattern(fuzzyMatch ? fuzzyMatchingGlobPattern(pattern) : continousMatchingGlobPattern(pattern)); + const segmentMatchPattern = fuzzyMatch ? fuzzyMatchingGlobPattern(pattern) : continousMatchingGlobPattern(pattern); const searchExcludePattern = getExcludes(configurationService.getValue({ resource: workspace })) || {}; const searchOptions: IFileQuery = { @@ -249,6 +249,7 @@ export async function searchFilesAndFolders( cacheKey, excludePattern: searchExcludePattern, sortByScore: true, + ignoreGlobCase: true, }; let searchResult: ISearchComplete | undefined; @@ -284,19 +285,6 @@ function continousMatchingGlobPattern(pattern: string): string { return '*' + pattern + '*'; } -function caseInsensitiveGlobPattern(pattern: string): string { - let caseInsensitiveFilePattern = ''; - for (let i = 0; i < pattern.length; i++) { - const char = pattern[i]; - if (/[a-zA-Z]/.test(char)) { - caseInsensitiveFilePattern += `[${char.toLowerCase()}${char.toUpperCase()}]`; - } else { - caseInsensitiveFilePattern += char; - } - } - return caseInsensitiveFilePattern; -} - // TODO: remove this and have support from the search service function getMatchingFoldersFromFiles(resources: URI[], workspace: URI, segmentMatchPattern: string): URI[] { const uniqueFolders = new ResourceSet(); @@ -318,7 +306,7 @@ function getMatchingFoldersFromFiles(resources: URI[], workspace: URI, segmentMa for (const folderResource of uniqueFolders) { const stats = folderResource.path.split('/'); const dirStat = stats[stats.length - 1]; - if (!dirStat || !glob.match(segmentMatchPattern, dirStat)) { + if (!dirStat || !glob.match(segmentMatchPattern, dirStat, { ignoreCase: true })) { continue; } diff --git a/src/vs/workbench/contrib/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts index fa7b0da0b11a3..fb52bbb7553fd 100644 --- a/src/vs/workbench/contrib/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -14,6 +14,7 @@ import * as errors from '../../../../base/common/errors.js'; import { Event } from '../../../../base/common/event.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js'; +import { isLinux } from '../../../../base/common/platform.js'; import * as strings from '../../../../base/common/strings.js'; import { URI } from '../../../../base/common/uri.js'; import * as network from '../../../../base/common/network.js'; @@ -1627,6 +1628,7 @@ export class SearchView extends ViewPane { maxResults: this.searchConfig.maxResults ?? undefined, disregardIgnoreFiles: !useExcludesAndIgnoreFiles || undefined, disregardExcludeSettings: !useExcludesAndIgnoreFiles || undefined, + ignoreGlobCase: !isLinux || undefined, onlyOpenEditors: onlySearchInOpenEditors, excludePattern, includePattern, diff --git a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts index 528341bae7403..d42b08000d26e 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskTerminalStatus.ts @@ -5,7 +5,7 @@ import * as nls from '../../../../nls.js'; import { Codicon } from '../../../../base/common/codicons.js'; -import { Disposable, IDisposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js'; +import { Disposable, DisposableMap, DisposableStore, IDisposable } from '../../../../base/common/lifecycle.js'; import Severity from '../../../../base/common/severity.js'; import { AbstractProblemCollector, StartStopProblemCollector } from '../common/problemCollectors.js'; import { ITaskGeneralEvent, ITaskProcessEndedEvent, ITaskProcessStartedEvent, TaskEventKind, TaskRunType } from '../common/tasks.js'; @@ -17,13 +17,12 @@ import type { IMarker } from '@xterm/xterm'; import { AccessibilitySignal, IAccessibilitySignalService } from '../../../../platform/accessibilitySignal/browser/accessibilitySignalService.js'; import { ITerminalStatus } from '../../terminal/common/terminal.js'; -interface ITerminalData { +interface ITerminalData extends IDisposable { terminal: ITerminalInstance; task: Task; status: ITerminalStatus; problemMatcher: AbstractProblemCollector; taskRunEnded: boolean; - disposeListener?: MutableDisposable; } const TASK_TERMINAL_STATUS_ID = 'task_terminal_status'; @@ -38,7 +37,7 @@ const INFO_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: C const INFO_INACTIVE_TASK_STATUS: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, icon: Codicon.info, severity: Severity.Info, tooltip: nls.localize('taskTerminalStatus.infosInactive', "Task has infos and is waiting...") }; export class TaskTerminalStatus extends Disposable { - private terminalMap: Map = new Map(); + private terminalMap: DisposableMap = this._register(new DisposableMap()); private _marker: IMarker | undefined; constructor(@ITaskService taskService: ITaskService, @IAccessibilitySignalService private readonly _accessibilitySignalService: IAccessibilitySignalService) { super(); @@ -50,34 +49,42 @@ export class TaskTerminalStatus extends Disposable { case TaskEventKind.ProcessEnded: this.eventEnd(event); break; } })); - this._register(toDisposable(() => { - for (const terminalData of this.terminalMap.values()) { - terminalData.disposeListener?.dispose(); - } - this.terminalMap.clear(); - })); } addTerminal(task: Task, terminal: ITerminalInstance, problemMatcher: AbstractProblemCollector) { const status: ITerminalStatus = { id: TASK_TERMINAL_STATUS_ID, severity: Severity.Info }; terminal.statusList.add(status); - this._register(problemMatcher.onDidFindFirstMatch(() => { + const store = new DisposableStore(); + store.add(problemMatcher.onDidFindFirstMatch(() => { this._marker = terminal.registerMarker(); if (this._marker) { - this._register(this._marker); + store.add(this._marker); } })); - this._register(problemMatcher.onDidFindErrors(() => { + store.add(problemMatcher.onDidFindErrors(() => { if (this._marker) { terminal.addBufferMarker({ marker: this._marker, hoverMessage: nls.localize('task.watchFirstError', "Beginning of detected errors for this run"), disableCommandStorage: true }); } })); - this._register(problemMatcher.onDidRequestInvalidateLastMarker(() => { + store.add(problemMatcher.onDidRequestInvalidateLastMarker(() => { this._marker?.dispose(); this._marker = undefined; })); - this.terminalMap.set(terminal.instanceId, { terminal, task, status, problemMatcher, taskRunEnded: false }); + store.add(terminal.onDisposed(() => { + this.terminalMap.deleteAndDispose(terminal.instanceId); + })); + + this.terminalMap.set(terminal.instanceId, { + terminal, + task, + status, + problemMatcher, + taskRunEnded: false, + dispose() { + store.dispose(); + }, + }); } private terminalFromEvent(event: { terminalId: number | undefined }): ITerminalData | undefined { @@ -138,16 +145,6 @@ export class TaskTerminalStatus extends Disposable { if (!terminalData) { return; } - if (!terminalData.disposeListener) { - terminalData.disposeListener = this._register(new MutableDisposable()); - terminalData.disposeListener.value = terminalData.terminal.onDisposed(() => { - if (!event.terminalId) { - return; - } - this.terminalMap.delete(event.terminalId); - terminalData.disposeListener?.dispose(); - }); - } terminalData.taskRunEnded = false; terminalData.terminal.statusList.remove(terminalData.status); // We don't want to show an infinite status for a background task that doesn't have a problem matcher. diff --git a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts index 0e183c912cb0e..f6b40bdb34421 100644 --- a/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts +++ b/src/vs/workbench/contrib/tasks/test/browser/taskTerminalStatus.test.ts @@ -43,6 +43,10 @@ class TestTerminal extends Disposable implements Partial { override dispose(): void { super.dispose(); } + + private readonly _onDisposed = this._register(new Emitter()); + readonly onDisposed = this._onDisposed.event; + } class TestTask extends CommonTask { diff --git a/src/vs/workbench/services/search/common/fileSearchManager.ts b/src/vs/workbench/services/search/common/fileSearchManager.ts index bd539edb41d1e..1a101615d35ee 100644 --- a/src/vs/workbench/services/search/common/fileSearchManager.ts +++ b/src/vs/workbench/services/search/common/fileSearchManager.ts @@ -6,6 +6,7 @@ import * as path from '../../../../base/common/path.js'; import { CancellationToken, CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { toErrorMessage } from '../../../../base/common/errorMessage.js'; +import * as strings from '../../../../base/common/strings.js'; import * as glob from '../../../../base/common/glob.js'; import * as resources from '../../../../base/common/resources.js'; import { StopWatch } from '../../../../base/common/stopwatch.js'; @@ -56,12 +57,13 @@ class FileSearchEngine { constructor(private config: IFileQuery, private provider: FileSearchProvider2, private sessionLifecycle?: SessionLifecycle) { this.filePattern = config.filePattern; - this.includePattern = config.includePattern && glob.parse(config.includePattern); + const globOptions = config.ignoreGlobCase ? { ignoreCase: true } : undefined; + this.includePattern = config.includePattern && glob.parse(config.includePattern, globOptions); this.maxResults = config.maxResults || undefined; this.exists = config.exists; this.activeCancellationTokens = new Set(); - this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern); + this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern, globOptions); } cancel(): void { @@ -224,7 +226,7 @@ class FileSearchEngine { private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: URI, relativeFile: string, onResult: (result: IInternalFileMatch) => void) { // Support relative paths to files from a root resource (ignores excludes) - if (relativeFile === this.filePattern) { + if (this.filePattern && strings.equals(relativeFile, this.filePattern, this.config.ignoreGlobCase)) { const basename = path.basename(this.filePattern); this.matchFile(onResult, { base: base, relativePath: this.filePattern, basename }); } @@ -250,6 +252,7 @@ class FileSearchEngine { private matchDirectoryTree({ rootEntries, pathToEntries }: IDirectoryTree, queryTester: QueryGlobTester, onResult: (result: IInternalFileMatch) => void) { const self = this; const filePattern = this.filePattern; + const ignoreGlobCase = this.config.ignoreGlobCase; function matchDirectory(entries: IDirectoryEntry[]) { const hasSibling = hasSiblingFn(() => entries.map(entry => entry.basename)); for (let i = 0, n = entries.length; i < n; i++) { @@ -260,7 +263,7 @@ class FileSearchEngine { // If the user searches for the exact file name, we adjust the glob matching // to ignore filtering by siblings because the user seems to know what they // are searching for and we want to include the result in that case anyway - if (queryTester.matchesExcludesSync(relativePath, basename, filePattern !== basename ? hasSibling : undefined)) { + if (queryTester.matchesExcludesSync(relativePath, basename, !strings.equals(filePattern, basename, ignoreGlobCase) ? hasSibling : undefined)) { continue; } @@ -268,7 +271,7 @@ class FileSearchEngine { if (sub) { matchDirectory(sub); } else { - if (relativePath === filePattern) { + if (strings.equals(relativePath, filePattern, ignoreGlobCase)) { continue; // ignore file if its path matches with the file pattern because that is already matched above } diff --git a/src/vs/workbench/services/search/common/queryBuilder.ts b/src/vs/workbench/services/search/common/queryBuilder.ts index 68487ac7bf645..e2beb5012b69b 100644 --- a/src/vs/workbench/services/search/common/queryBuilder.ts +++ b/src/vs/workbench/services/search/common/queryBuilder.ts @@ -91,6 +91,7 @@ interface ICommonQueryBuilderOptions { disregardExcludeSettings?: boolean; disregardSearchExcludeSettings?: boolean; ignoreSymlinks?: boolean; + ignoreGlobCase?: boolean; onlyOpenEditors?: boolean; onlyFileScheme?: boolean; } @@ -269,6 +270,7 @@ export class QueryBuilder { excludePattern: excludeSearchPathsInfo.pattern, includePattern: includeSearchPathsInfo.pattern, + ignoreGlobCase: options.ignoreGlobCase, onlyOpenEditors: options.onlyOpenEditors, maxResults: options.maxResults, onlyFileScheme: options.onlyFileScheme @@ -617,6 +619,7 @@ export class QueryBuilder { disregardGlobalIgnoreFiles: typeof options.disregardGlobalIgnoreFiles === 'boolean' ? options.disregardGlobalIgnoreFiles : !folderConfig.search.useGlobalIgnoreFiles, disregardParentIgnoreFiles: typeof options.disregardParentIgnoreFiles === 'boolean' ? options.disregardParentIgnoreFiles : !folderConfig.search.useParentIgnoreFiles, ignoreSymlinks: typeof options.ignoreSymlinks === 'boolean' ? options.ignoreSymlinks : !folderConfig.search.followSymlinks, + ignoreGlobCase: options.ignoreGlobCase, }; } } diff --git a/src/vs/workbench/services/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts index 980e30227a643..fd1743ceb68ab 100644 --- a/src/vs/workbench/services/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -81,6 +81,7 @@ export interface IFolderQuery { folderName?: string; excludePattern?: ExcludeGlobPattern[]; includePattern?: glob.IExpression; + ignoreGlobCase?: boolean; fileEncoding?: string; disregardIgnoreFiles?: boolean; disregardGlobalIgnoreFiles?: boolean; @@ -97,6 +98,7 @@ export interface ICommonQueryProps { // Note that this will override any ignore files if applicable. includePattern?: glob.IExpression; excludePattern?: glob.IExpression; + ignoreGlobCase?: boolean; extraFileResources?: U[]; onlyOpenEditors?: boolean; @@ -505,12 +507,13 @@ export function getExcludes(configuration: ISearchConfiguration, includeSearchEx } export function pathIncludedInQuery(queryProps: ICommonQueryProps, fsPath: string): boolean { - if (queryProps.excludePattern && glob.match(queryProps.excludePattern, fsPath)) { + const globOptions = queryProps.ignoreGlobCase ? { ignoreCase: true } : undefined; + if (queryProps.excludePattern && glob.match(queryProps.excludePattern, fsPath, globOptions)) { return false; } if (queryProps.includePattern || queryProps.usingSearchPaths) { - if (queryProps.includePattern && glob.match(queryProps.includePattern, fsPath)) { + if (queryProps.includePattern && glob.match(queryProps.includePattern, fsPath, globOptions)) { return true; } @@ -518,9 +521,9 @@ export function pathIncludedInQuery(queryProps: ICommonQueryProps, fsPath: if (queryProps.usingSearchPaths) { return !!queryProps.folderQueries && queryProps.folderQueries.some(fq => { const searchPath = fq.folder.fsPath; - if (extpath.isEqualOrParent(fsPath, searchPath)) { + if (extpath.isEqualOrParent(fsPath, searchPath, queryProps.ignoreGlobCase)) { const relPath = paths.relative(searchPath, fsPath); - return !fq.includePattern || !!glob.match(fq.includePattern, relPath); + return !fq.includePattern || !!glob.match(fq.includePattern, relPath, globOptions); } else { return false; } @@ -708,6 +711,8 @@ export class QueryGlobTester { private _parsedIncludeExpression: glob.ParsedExpression | null = null; constructor(config: ISearchQuery, folderQuery: IFolderQuery) { + const globOptions = config.ignoreGlobCase || folderQuery.ignoreGlobCase ? { ignoreCase: true } : undefined; + // todo: try to incorporate folderQuery.excludePattern.folder if available this._excludeExpression = folderQuery.excludePattern?.map(excludePattern => { return { @@ -721,7 +726,7 @@ export class QueryGlobTester { this._excludeExpression = [config.excludePattern || {}]; } - this._parsedExcludeExpression = this._excludeExpression.map(e => glob.parse(e)); + this._parsedExcludeExpression = this._excludeExpression.map(e => glob.parse(e, globOptions)); // Empty includeExpression means include nothing, so no {} shortcuts let includeExpression: glob.IExpression | undefined = config.includePattern; @@ -737,7 +742,7 @@ export class QueryGlobTester { } if (includeExpression) { - this._parsedIncludeExpression = glob.parse(includeExpression); + this._parsedIncludeExpression = glob.parse(includeExpression, globOptions); } } diff --git a/src/vs/workbench/services/search/common/searchExtTypes.ts b/src/vs/workbench/services/search/common/searchExtTypes.ts index 595b0014095a8..69053d310052a 100644 --- a/src/vs/workbench/services/search/common/searchExtTypes.ts +++ b/src/vs/workbench/services/search/common/searchExtTypes.ts @@ -137,6 +137,11 @@ export interface TextSearchProviderFolderOptions { */ excludes: GlobPattern[]; + /** + * Whether to ignore case for glob patterns. + */ + ignoreGlobCase?: boolean; + /** * Whether symlinks should be followed while searching. * For more info, see the setting description for `search.followSymlinks`. diff --git a/src/vs/workbench/services/search/common/textSearchManager.ts b/src/vs/workbench/services/search/common/textSearchManager.ts index 10fc87c384687..9c7ad25a87980 100644 --- a/src/vs/workbench/services/search/common/textSearchManager.ts +++ b/src/vs/workbench/services/search/common/textSearchManager.ts @@ -222,6 +222,7 @@ export class TextSearchManager { }, followSymlinks: !fq.ignoreSymlinks, encoding: (fq.fileEncoding && this.fileUtils.toCanonicalName(fq.fileEncoding)) ?? '', + ignoreGlobCase: this.query.ignoreGlobCase || fq.ignoreGlobCase, }; return options; } @@ -259,6 +260,7 @@ export class TextSearchResultsCollector { if (!this._currentFileMatch) { this._currentFolderIdx = folderIdx; + this._currentUri = data.uri; this._currentFileMatch = { resource: data.uri, results: [] diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index c23475cb647e3..d850308c70c0e 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -64,7 +64,8 @@ export class FileWalker { constructor(config: IFileQuery) { this.config = config; this.filePattern = config.filePattern || ''; - this.includePattern = config.includePattern && glob.parse(config.includePattern); + const globOptions = config.ignoreGlobCase ? { ignoreCase: true } : undefined; + this.includePattern = config.includePattern && glob.parse(config.includePattern, globOptions); this.maxResults = config.maxResults || null; this.exists = !!config.exists; this.walkedPaths = Object.create(null); @@ -78,7 +79,7 @@ export class FileWalker { this.normalizedFilePatternLowercase = config.shouldGlobMatchFilePattern ? null : prepareQuery(this.filePattern).normalizedLowercase; } - this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern); + this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern, globOptions); this.folderExcludePatterns = new Map(); config.folderQueries.forEach(folderQuery => { @@ -99,12 +100,12 @@ export class FileWalker { .filter(rootFolder => rootFolder !== fqPath) .forEach(otherRootFolder => { // Exclude nested root folders - if (isEqualOrParent(otherRootFolder, fqPath)) { + if (isEqualOrParent(otherRootFolder, fqPath, config.ignoreGlobCase)) { folderExcludeExpression[path.relative(fqPath, otherRootFolder)] = true; } }); - this.folderExcludePatterns.set(fqPath, new AbsoluteAndRelativeParsedExpression(folderExcludeExpression, fqPath)); + this.folderExcludePatterns.set(fqPath, new AbsoluteAndRelativeParsedExpression(folderExcludeExpression, fqPath, config.ignoreGlobCase)); }); } @@ -395,11 +396,12 @@ export class FileWalker { private addDirectoryEntries(folderQuery: IFolderQuery, { pathToEntries }: IDirectoryTree, base: string, relativeFiles: string[], onResult: (result: IRawFileMatch) => void) { // Support relative paths to files from a root resource (ignores excludes) - if (relativeFiles.indexOf(this.filePattern) !== -1) { + const filePatternMatch = this.filePattern && relativeFiles.find(f => strings.equals(f, this.filePattern, this.config.ignoreGlobCase)); + if (filePatternMatch) { this.matchFile(onResult, { base, - relativePath: this.filePattern, - searchPath: this.getSearchPath(folderQuery, this.filePattern) + relativePath: filePatternMatch, + searchPath: this.getSearchPath(folderQuery, filePatternMatch) }); } @@ -425,6 +427,7 @@ export class FileWalker { const self = this; const excludePattern = this.folderExcludePatterns.get(rootFolder)!; const filePattern = this.filePattern; + const ignoreGlobCase = this.config.ignoreGlobCase; function matchDirectory(entries: IDirectoryEntry[]) { self.directoriesWalked++; const hasSibling = hasSiblingFn(() => entries.map(entry => entry.basename)); @@ -436,7 +439,7 @@ export class FileWalker { // If the user searches for the exact file name, we adjust the glob matching // to ignore filtering by siblings because the user seems to know what they // are searching for and we want to include the result in that case anyway - if (excludePattern.test(relativePath, basename, filePattern !== basename ? hasSibling : undefined)) { + if (excludePattern.test(relativePath, basename, !strings.equals(filePattern, basename, ignoreGlobCase) ? hasSibling : undefined)) { continue; } @@ -445,7 +448,7 @@ export class FileWalker { matchDirectory(sub); } else { self.filesWalked++; - if (relativePath === filePattern) { + if (strings.equals(relativePath, filePattern, ignoreGlobCase)) { continue; // ignore file if its path matches with the file pattern because that is already matched above } @@ -487,7 +490,7 @@ export class FileWalker { // to ignore filtering by siblings because the user seems to know what they // are searching for and we want to include the result in that case anyway const currentRelativePath = relativeParentPath ? [relativeParentPath, file].join(path.sep) : file; - if (this.folderExcludePatterns.get(folderQuery.folder.fsPath)!.test(currentRelativePath, file, this.config.filePattern !== file ? hasSibling : undefined)) { + if (this.folderExcludePatterns.get(folderQuery.folder.fsPath)!.test(currentRelativePath, file, !strings.equals(this.config.filePattern, file, this.config.ignoreGlobCase) ? hasSibling : undefined)) { return clb(null); } @@ -539,7 +542,7 @@ export class FileWalker { // File: Check for match on file pattern and include pattern else { this.filesWalked++; - if (currentRelativePath === this.filePattern) { + if (strings.equals(currentRelativePath, this.filePattern, this.config.ignoreGlobCase)) { return clb(null, undefined); // ignore file if its path matches with the file pattern because checkFilePatternRelativeMatch() takes care of those } @@ -670,7 +673,7 @@ class AbsoluteAndRelativeParsedExpression { private absoluteParsedExpr: glob.ParsedExpression | undefined; private relativeParsedExpr: glob.ParsedExpression | undefined; - constructor(public expression: glob.IExpression, private root: string) { + constructor(public expression: glob.IExpression, private root: string, private ignoreCase?: boolean) { this.init(expression); } @@ -692,8 +695,9 @@ class AbsoluteAndRelativeParsedExpression { } }); - this.absoluteParsedExpr = absoluteGlobExpr && glob.parse(absoluteGlobExpr, { trimForExclusions: true }); - this.relativeParsedExpr = relativeGlobExpr && glob.parse(relativeGlobExpr, { trimForExclusions: true }); + const globOptions = { trimForExclusions: true, ignoreCase: this.ignoreCase }; + this.absoluteParsedExpr = absoluteGlobExpr && glob.parse(absoluteGlobExpr, globOptions); + this.relativeParsedExpr = relativeGlobExpr && glob.parse(relativeGlobExpr, globOptions); } test(_path: string, basename?: string, hasSibling?: (name: string) => boolean | Promise): string | Promise | undefined | null { diff --git a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts index 29921d5465a63..1bb31f33dcd14 100644 --- a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts @@ -32,6 +32,11 @@ export function spawnRipgrepCmd(config: IFileQuery, folderQuery: IFolderQuery, i function getRgArgs(config: IFileQuery, folderQuery: IFolderQuery, includePattern?: glob.IExpression, excludePattern?: glob.IExpression, numThreads?: number) { const args = ['--files', '--hidden', '--case-sensitive', '--no-require-git']; + if (config.ignoreGlobCase || folderQuery.ignoreGlobCase) { + args.push('--glob-case-insensitive'); + args.push('--ignore-file-case-insensitive'); + } + // includePattern can't have siblingClauses foldersToIncludeGlobs([folderQuery], includePattern, false).forEach(globArg => { const inclusion = anchorGlob(globArg); diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index e28971303c896..5d89c3cab3cc3 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -408,6 +408,11 @@ export function getRgArgs(query: TextSearchQuery2, options: RipgrepTextSearchOpt const args = ['--hidden', '--no-require-git']; args.push(query.isCaseSensitive ? '--case-sensitive' : '--ignore-case'); + if (options.folderOptions.ignoreGlobCase) { + args.push('--glob-case-insensitive'); + args.push('--ignore-file-case-insensitive'); + } + const { doubleStarIncludes, otherIncludes } = groupBy( options.folderOptions.includes, (include: string) => include.startsWith('**') ? 'doubleStarIncludes' : 'otherIncludes'); diff --git a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts index da33ca6c13203..a9519ff4aea80 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -346,12 +346,12 @@ flakySuite('TextSearch-integration', function () { }; return doSearchTest(config, 3).then(results => { - assert.strictEqual(results.length, 3); + assert.strictEqual(results.length, 1); + assert.strictEqual(results[0].results!.length, 3); assert.strictEqual((results[0].results![0]).lineNumber, 24); assert.strictEqual((results[0].results![0]).text, ' compiler.addUnit(prog,"input.ts");'); - // assert.strictEqual((results[1].results[0]).preview.text, ' compiler.typeCheck();\n'); // See https://github.com/BurntSushi/ripgrep/issues/1095 - assert.strictEqual((results[2].results![0]).lineNumber, 26); - assert.strictEqual((results[2].results![0]).text, ' compiler.emit();'); + assert.strictEqual((results[0].results![2]).lineNumber, 26); + assert.strictEqual((results[0].results![2]).text, ' compiler.emit();'); }); }); diff --git a/src/vs/workbench/services/search/worker/localFileSearch.ts b/src/vs/workbench/services/search/worker/localFileSearch.ts index b01a1cea40c04..74f4ede4c1dfe 100644 --- a/src/vs/workbench/services/search/worker/localFileSearch.ts +++ b/src/vs/workbench/services/search/worker/localFileSearch.ts @@ -175,7 +175,9 @@ export class LocalFileSearchWorker implements ILocalFileSearchWorker, IWebWorker private async walkFolderQuery(handle: IWorkerFileSystemDirectoryHandle, queryProps: ICommonQueryProps, folderQuery: IFolderQuery, extUri: ExtUri, onFile: (file: FileNode) => Promise | unknown, token: CancellationToken): Promise { - const folderExcludes = folderQuery.excludePattern?.map(excludePattern => glob.parse(excludePattern.pattern ?? {}, { trimForExclusions: true }) as glob.ParsedExpression); + const ignoreGlobCase = queryProps.ignoreGlobCase || folderQuery.ignoreGlobCase; + const globOptions = { trimForExclusions: true, ignoreCase: ignoreGlobCase }; + const folderExcludes = folderQuery.excludePattern?.map(excludePattern => glob.parse(excludePattern.pattern ?? {}, globOptions) as glob.ParsedExpression); const evalFolderExcludes = (path: string, basename: string, hasSibling: (query: string) => boolean) => { return folderExcludes?.some(folderExclude => { @@ -231,7 +233,7 @@ export class LocalFileSearchWorker implements ILocalFileSearchWorker, IWebWorker if (!file) { return; } const ignoreContents = new TextDecoder('utf8').decode(new Uint8Array(await (await file.getFile()).arrayBuffer())); - ignoreFile = new IgnoreFile(ignoreContents, prior, ignoreFile); + ignoreFile = new IgnoreFile(ignoreContents, prior, ignoreFile, ignoreGlobCase); })); } @@ -326,19 +328,21 @@ function reviveQueryProps(queryProps: ICommonQueryProps): ICommon function pathExcludedInQuery(queryProps: ICommonQueryProps, fsPath: string): boolean { - if (queryProps.excludePattern && glob.match(queryProps.excludePattern, fsPath)) { + const globOptions = queryProps.ignoreGlobCase ? { ignoreCase: true } : undefined; + if (queryProps.excludePattern && glob.match(queryProps.excludePattern, fsPath, globOptions)) { return true; } return false; } function pathIncludedInQuery(queryProps: ICommonQueryProps, path: string, extUri: ExtUri): boolean { - if (queryProps.excludePattern && glob.match(queryProps.excludePattern, path)) { + const globOptions = queryProps.ignoreGlobCase ? { ignoreCase: true } : undefined; + if (queryProps.excludePattern && glob.match(queryProps.excludePattern, path, globOptions)) { return false; } if (queryProps.includePattern || queryProps.usingSearchPaths) { - if (queryProps.includePattern && glob.match(queryProps.includePattern, path)) { + if (queryProps.includePattern && glob.match(queryProps.includePattern, path, globOptions)) { return true; } @@ -350,7 +354,7 @@ function pathIncludedInQuery(queryProps: ICommonQueryProps, path: string, e const uri = URI.file(path); if (extUri.isEqualOrParent(uri, searchPath)) { const relPath = paths.relative(searchPath.path, uri.path); - return !fq.includePattern || !!glob.match(fq.includePattern, relPath); + return !fq.includePattern || !!glob.match(fq.includePattern, relPath, globOptions); } else { return false; } diff --git a/test/sanity/package-lock.json b/test/sanity/package-lock.json index 13c973d806187..1c246d774bedb 100644 --- a/test/sanity/package-lock.json +++ b/test/sanity/package-lock.json @@ -7,8 +7,10 @@ "": { "name": "code-oss-dev-sanity-test", "version": "0.1.0", + "hasInstallScript": true, "license": "MIT", "dependencies": { + "mocha-junit-reporter": "^2.2.1", "node-fetch": "^3.3.2", "playwright": "^1.57.0" }, @@ -16,6 +18,35 @@ "@types/node": "22.x" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@types/node": { "version": "22.19.1", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.1.tgz", @@ -26,6 +57,256 @@ "undici-types": "~6.21.0" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0", + "peer": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "peer": true + }, + "node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "license": "ISC", + "peer": true + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "peer": true, + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "peer": true + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT", + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", + "license": "BSD-3-Clause", + "engines": { + "node": "*" + } + }, "node_modules/data-uri-to-buffer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", @@ -35,6 +316,83 @@ "node": ">= 12" } }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", + "license": "BSD-3-Clause", + "peer": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT", + "peer": true + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT", + "peer": true + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", @@ -58,6 +416,50 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "peer": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "license": "ISC", + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/formdata-polyfill": { "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", @@ -84,6 +486,314 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "peer": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "license": "ISC", + "peer": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "license": "MIT", + "peer": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC", + "peer": true + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC", + "peer": true + }, + "node_modules/md5": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", + "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", + "license": "BSD-3-Clause", + "dependencies": { + "charenc": "0.0.2", + "crypt": "0.0.2", + "is-buffer": "~1.1.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "license": "MIT", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mocha": { + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "license": "MIT", + "peer": true, + "dependencies": { + "browser-stdout": "^1.3.1", + "chokidar": "^4.0.1", + "debug": "^4.3.5", + "diff": "^7.0.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^10.4.5", + "he": "^1.2.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^9.0.5", + "ms": "^2.1.3", + "picocolors": "^1.1.1", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/mocha-junit-reporter": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/mocha-junit-reporter/-/mocha-junit-reporter-2.2.1.tgz", + "integrity": "sha512-iDn2tlKHn8Vh8o4nCzcUVW4q7iXp7cC4EB78N0cDHIobLymyHNwe0XG8HEHHjc3hJlXm0Vy6zcrxaIhnI2fWmw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.4", + "md5": "^2.3.0", + "mkdirp": "^3.0.0", + "strip-ansi": "^6.0.1", + "xml": "^1.0.1" + }, + "peerDependencies": { + "mocha": ">=2.2.5" + } + }, + "node_modules/mocha-junit-reporter/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/mocha-junit-reporter/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -122,6 +832,89 @@ "url": "https://opencollective.com/node-fetch" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0", + "peer": true + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "peer": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC", + "peer": true + }, "node_modules/playwright": { "version": "1.57.0", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", @@ -152,6 +945,240 @@ "node": ">=18" } }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "peer": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "peer": true + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -167,6 +1194,243 @@ "engines": { "node": ">= 8" } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workerpool": { + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "peer": true + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "peer": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "license": "MIT", + "peer": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "peer": true + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/test/sanity/package.json b/test/sanity/package.json index 1402946cba950..a1974fc790643 100644 --- a/test/sanity/package.json +++ b/test/sanity/package.json @@ -9,6 +9,7 @@ "start": "node ./out/index.js" }, "dependencies": { + "mocha-junit-reporter": "^2.2.1", "node-fetch": "^3.3.2", "playwright": "^1.57.0" }, diff --git a/test/sanity/src/context.ts b/test/sanity/src/context.ts index 48f57c5be4d99..39f3e85438aad 100644 --- a/test/sanity/src/context.ts +++ b/test/sanity/src/context.ts @@ -34,6 +34,7 @@ export class TestContext { private readonly tempDirs = new Set(); private readonly logFile: string; + private _currentTest?: Mocha.Test & { consoleOutputs?: string[] }; public constructor( public readonly quality: 'stable' | 'insider' | 'exploration', @@ -47,6 +48,14 @@ export class TestContext { console.log(`Log file: ${this.logFile}`); } + /** + * Sets the current test for log capturing. + */ + public set currentTest(test: Mocha.Test) { + this._currentTest = test; + this._currentTest.consoleOutputs ||= []; + } + /** * Returns the current platform in the format -. */ @@ -58,10 +67,11 @@ export class TestContext { * Logs a message with a timestamp. */ public log(message: string) { - const line = `[${new Date().toISOString()}] ${message}\n`; - fs.appendFileSync(this.logFile, line); + const line = `[${new Date().toISOString()}] ${message}`; + fs.appendFileSync(this.logFile, line + '\n'); + this._currentTest?.consoleOutputs?.push(line); if (this.verbose) { - console.log(line.trimEnd()); + console.log(line); } } @@ -69,9 +79,10 @@ export class TestContext { * Logs an error message and throws an Error. */ public error(message: string): never { - const line = `[${new Date().toISOString()}] ERROR: ${message}\n`; - fs.appendFileSync(this.logFile, line); - console.error(line.trimEnd()); + const line = `[${new Date().toISOString()}] ERROR: ${message}`; + fs.appendFileSync(this.logFile, line + '\n'); + this._currentTest?.consoleOutputs?.push(line); + console.error(line); throw new Error(message); } diff --git a/test/sanity/src/index.ts b/test/sanity/src/index.ts index 96330a22afebe..8ce74cae96c9c 100644 --- a/test/sanity/src/index.ts +++ b/test/sanity/src/index.ts @@ -7,9 +7,9 @@ import minimist from 'minimist'; import Mocha, { MochaOptions } from 'mocha'; const options = minimist(process.argv.slice(2), { - string: ['fgrep', 'grep'], + string: ['fgrep', 'grep', 'test-results'], boolean: ['help'], - alias: { fgrep: 'f', grep: 'g', help: 'h' }, + alias: { fgrep: 'f', grep: 'g', help: 'h', 'test-results': 't' }, }); if (options.help) { @@ -21,17 +21,21 @@ if (options.help) { console.info(' --no-signing-check Skip Authenticode and codesign signature checks'); console.info(' --grep, -g Only run tests matching the given '); console.info(' --fgrep, -f Only run tests containing the given '); + console.info(' --test-results, -t Output test results in JUnit format to the specified path'); console.info(' --verbose, -v Enable verbose logging'); console.info(' --help, -h Show this help message'); process.exit(0); } +const testResults = options['test-results']; const mochaOptions: MochaOptions = { color: true, timeout: 5 * 60 * 1000, slow: 3 * 60 * 1000, grep: options.grep, fgrep: options.fgrep, + reporter: testResults ? 'mocha-junit-reporter' : undefined, + reporterOptions: testResults ? { mochaFile: testResults, outputs: true } : undefined, }; const mocha = new Mocha(mochaOptions); diff --git a/test/sanity/src/main.ts b/test/sanity/src/main.ts index f78fad78d4e05..e522356bc85a6 100644 --- a/test/sanity/src/main.ts +++ b/test/sanity/src/main.ts @@ -28,7 +28,8 @@ if (!options.quality) { const context = new TestContext(options.quality, options.commit, options.verbose, !options['signing-check']); describe('VS Code Sanity Tests', () => { - beforeEach(() => { + beforeEach(function () { + context.currentTest = this.currentTest!; const cwd = context.createTempDir(); process.chdir(cwd); context.log(`Changed working directory to: ${cwd}`);