From b1045fefd0bcd8f96197029c55e47edc574e5199 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Tue, 14 Jan 2025 21:46:03 +0100 Subject: [PATCH 01/27] chore: Differentiate __repr__ and help for report and accessors (#1113) closes #1103 As suggested in #1103, let's differentiate the output of `help` with the output of the `repr`. Let's have a concise output for `repr` to show where to get the information. --- skore/src/skore/sklearn/_base.py | 11 +++++++++-- .../skore/sklearn/_estimator/metrics_accessor.py | 14 ++++++++++++++ skore/src/skore/sklearn/_estimator/report.py | 6 ++++++ skore/tests/unit/sklearn/test_estimator.py | 9 ++++++--- 4 files changed, 35 insertions(+), 5 deletions(-) diff --git a/skore/src/skore/sklearn/_base.py b/skore/src/skore/sklearn/_base.py index f09410f6a..bd043023b 100644 --- a/skore/src/skore/sklearn/_base.py +++ b/skore/src/skore/sklearn/_base.py @@ -70,10 +70,17 @@ def help(self): console.print(self._create_help_panel()) - def __repr__(self): + def _rich_repr(self, class_name, help_method_name): """Return a string representation using rich.""" console = Console(file=StringIO(), force_terminal=False) - console.print(self._create_help_panel()) + console.print( + Panel( + f"Get guidance using the {help_method_name} method", + title=f"[cyan]{class_name}[/cyan]", + border_style="orange1", + expand=False, + ) + ) return console.file.getvalue() diff --git a/skore/src/skore/sklearn/_estimator/metrics_accessor.py b/skore/src/skore/sklearn/_estimator/metrics_accessor.py index 1dd3bf695..fc741b672 100644 --- a/skore/src/skore/sklearn/_estimator/metrics_accessor.py +++ b/skore/src/skore/sklearn/_estimator/metrics_accessor.py @@ -819,6 +819,13 @@ def _get_help_legend(self): def _get_help_tree_title(self): return "[bold cyan]reporter.metrics[/bold cyan]" + def __repr__(self): + """Return a string representation using rich.""" + return self._rich_repr( + class_name="skore.EstimatorReport.metrics", + help_method_name="reporter.metrics.help()", + ) + ######################################################################################## # Sub-accessors @@ -1096,3 +1103,10 @@ def _get_help_panel_title(self): def _get_help_tree_title(self): return "[bold cyan]reporter.metrics.plot[/bold cyan]" + + def __repr__(self): + """Return a string representation using rich.""" + return self._rich_repr( + class_name="skore.EstimatorReport.metrics.plot", + help_method_name="reporter.metrics.plot.help()", + ) diff --git a/skore/src/skore/sklearn/_estimator/report.py b/skore/src/skore/sklearn/_estimator/report.py index 2b977715a..fdd19b84a 100644 --- a/skore/src/skore/sklearn/_estimator/report.py +++ b/skore/src/skore/sklearn/_estimator/report.py @@ -255,3 +255,9 @@ def estimator_name(self): else: name = self._estimator.__class__.__name__ return name + + def __repr__(self): + """Return a string representation using rich.""" + return self._rich_repr( + class_name="skore.EstimatorReport", help_method_name="reporter.help()" + ) diff --git a/skore/tests/unit/sklearn/test_estimator.py b/skore/tests/unit/sklearn/test_estimator.py index 4e0e6e4c2..785d137e2 100644 --- a/skore/tests/unit/sklearn/test_estimator.py +++ b/skore/tests/unit/sklearn/test_estimator.py @@ -285,7 +285,8 @@ def test_estimator_report_repr(binary_classification_data): report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) repr_str = repr(report) - assert f"📓 Tools to diagnose estimator {estimator.__class__.__name__}" in repr_str + assert "skore.EstimatorReport" in repr_str + assert "reporter.help()" in repr_str @pytest.mark.parametrize( @@ -344,7 +345,8 @@ def test_estimator_report_plot_repr(binary_classification_data): report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) repr_str = repr(report.metrics.plot) - assert "🎨 Available plot methods" in repr_str + assert "skore.EstimatorReport.metrics.plot" in repr_str + assert "reporter.metrics.plot.help()" in repr_str def test_estimator_report_plot_roc(binary_classification_data): @@ -483,7 +485,8 @@ def test_estimator_report_metrics_repr(binary_classification_data): report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) repr_str = repr(report.metrics) - assert "📏 Available metrics methods" in repr_str + assert "skore.EstimatorReport.metrics" in repr_str + assert "reporter.metrics.help()" in repr_str @pytest.mark.parametrize( From 7518d1c90c9c651b77cb2e1a34979950a0ee5b78 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Tue, 14 Jan 2025 21:46:40 +0100 Subject: [PATCH 02/27] chore: Rename clean_cache to clear_cache (#1112) fixes #1102 This PR renames `EstimatorReport.clean_cache` to `EstimatorReport.clear_cache`. The name is aligned with `joblib.Memory` and sounds more correct. --- examples/model_evaluation/plot_estimator_report.py | 6 +++--- skore/src/skore/sklearn/_estimator/report.py | 2 +- skore/src/skore/sklearn/_estimator/report.pyi | 2 +- skore/tests/unit/sklearn/test_estimator.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/model_evaluation/plot_estimator_report.py b/examples/model_evaluation/plot_estimator_report.py index fb8fd30f2..33929d497 100644 --- a/examples/model_evaluation/plot_estimator_report.py +++ b/examples/model_evaluation/plot_estimator_report.py @@ -154,7 +154,7 @@ # # We can show that without initial cache, it would have taken more time to compute # the log loss. -reporter.clean_cache() +reporter.clear_cache() start = time.time() log_loss = reporter.metrics.log_loss() @@ -266,7 +266,7 @@ def operational_decision_cost(y_true, y_pred, amount): # %% # # Let's now clean the cache and see if it is faster. -reporter.clean_cache() +reporter.clear_cache() # %% start = time.time() @@ -370,7 +370,7 @@ def operational_decision_cost(y_true, y_pred, amount): # %% # # Now, let's clean the cache and check if we get a slowdown. -reporter.clean_cache() +reporter.clear_cache() # %% start = time.time() diff --git a/skore/src/skore/sklearn/_estimator/report.py b/skore/src/skore/sklearn/_estimator/report.py index fdd19b84a..a1cce9f74 100644 --- a/skore/src/skore/sklearn/_estimator/report.py +++ b/skore/src/skore/sklearn/_estimator/report.py @@ -137,7 +137,7 @@ def _initialize_state(self): # For the moment, we do not allow to alter the estimator and the training data. # For the validation set, we allow it and we invalidate the cache. - def clean_cache(self): + def clear_cache(self): """Clean the cache.""" self._cache = {} diff --git a/skore/src/skore/sklearn/_estimator/report.pyi b/skore/src/skore/sklearn/_estimator/report.pyi index 0b30e4b38..b54fb1593 100644 --- a/skore/src/skore/sklearn/_estimator/report.pyi +++ b/skore/src/skore/sklearn/_estimator/report.pyi @@ -34,7 +34,7 @@ class EstimatorReport(_HelpMixin): y_test: Optional[np.ndarray] = None, ) -> None: ... def _initialize_state(self) -> None: ... - def clean_cache(self) -> None: ... + def clear_cache(self) -> None: ... def cache_predictions( self, response_methods: Union[Literal["auto"], list[str]] = "auto", diff --git a/skore/tests/unit/sklearn/test_estimator.py b/skore/tests/unit/sklearn/test_estimator.py index 785d137e2..05d642926 100644 --- a/skore/tests/unit/sklearn/test_estimator.py +++ b/skore/tests/unit/sklearn/test_estimator.py @@ -509,7 +509,7 @@ def test_estimator_report_metrics_binary_classification( # check that something was written to the cache assert report._cache != {} - report.clean_cache() + report.clear_cache() # check that passing using data outside from the report works and that we they # don't come from the cache @@ -535,7 +535,7 @@ def test_estimator_report_metrics_regression(regression_data, metric): # check that something was written to the cache assert report._cache != {} - report.clean_cache() + report.clear_cache() # check that passing using data outside from the report works and that we they # don't come from the cache From 418b72ca6711ef467c95f67416f2162382c645a7 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Tue, 14 Jan 2025 23:36:48 +0100 Subject: [PATCH 03/27] fix: Dissociate help() and repr in displays (#1116) This PR is similar to https://github.com/probabl-ai/skore/pull/1113 by splitting `help` from `__repr__` to have a more compact `repr` redirecting to `help` Here we tackle the displays specifically. --- skore/src/skore/sklearn/_plot/utils.py | 9 ++++++++- skore/tests/unit/sklearn/plot/test_common.py | 3 ++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/skore/src/skore/sklearn/_plot/utils.py b/skore/src/skore/sklearn/_plot/utils.py index 2b995b1bb..c8bc39f1f 100644 --- a/skore/src/skore/sklearn/_plot/utils.py +++ b/skore/src/skore/sklearn/_plot/utils.py @@ -82,7 +82,14 @@ def help(self): def __repr__(self): """Return a string representation using rich.""" console = Console(file=StringIO(), force_terminal=False) - console.print(self._create_help_panel()) + console.print( + Panel( + "Get guidance using the display.help() method", + title=f"[cyan]{self.__class__.__name__}[/cyan]", + border_style="orange1", + expand=False, + ) + ) return console.file.getvalue() diff --git a/skore/tests/unit/sklearn/plot/test_common.py b/skore/tests/unit/sklearn/plot/test_common.py index 8384f1538..26d595777 100644 --- a/skore/tests/unit/sklearn/plot/test_common.py +++ b/skore/tests/unit/sklearn/plot/test_common.py @@ -52,7 +52,8 @@ def test_display_repr(pyplot, plot_func, estimator, dataset): display = getattr(report.metrics.plot, plot_func)() repr_str = repr(display) - assert f"📊 {display.__class__.__name__}" in repr_str + assert f"{display.__class__.__name__}" in repr_str + assert "display.help()" in repr_str @pytest.mark.parametrize( From 6a688147d462c7e79726d2a49ea48560955c6058 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Tue, 14 Jan 2025 23:57:33 +0100 Subject: [PATCH 04/27] fix: Remove icon from rich representation to have a cleaner display (#1115) This PR is removing the icon used in the different `help` method such that we don't have issue with spacing. https://github.com/probabl-ai/skore/issues/1104 will provide a richer output where we can leverage those types of icons instead to purely rely on textual that are grant in terminal mainly. --- skore/src/skore/sklearn/_base.py | 15 +++++---------- skore/src/skore/sklearn/_base.pyi | 3 +-- .../skore/sklearn/_estimator/metrics_accessor.py | 10 +++++----- skore/src/skore/sklearn/_estimator/report.py | 5 +---- skore/src/skore/sklearn/_plot/utils.py | 2 +- skore/tests/unit/sklearn/plot/test_common.py | 2 +- skore/tests/unit/sklearn/test_base.py | 10 +++++----- skore/tests/unit/sklearn/test_estimator.py | 8 +++----- 8 files changed, 22 insertions(+), 33 deletions(-) diff --git a/skore/src/skore/sklearn/_base.py b/skore/src/skore/sklearn/_base.py index bd043023b..a9a262a9e 100644 --- a/skore/src/skore/sklearn/_base.py +++ b/skore/src/skore/sklearn/_base.py @@ -87,7 +87,7 @@ def _rich_repr(self, class_name, help_method_name): class _BaseReport(_HelpMixin): def _get_help_panel_title(self): return ( - f"[bold cyan]:notebook: Tools to diagnose estimator " + f"[bold cyan]Tools to diagnose estimator " f"{self.estimator_name}[/bold cyan]" ) @@ -132,9 +132,7 @@ def _create_help_tree(self): # Add accessor methods first for accessor_attr, config in self._ACCESSOR_CONFIG.items(): accessor = getattr(self, accessor_attr) - branch = tree.add( - f"[bold cyan].{config['name']} {config['icon']}[/bold cyan]" - ) + branch = tree.add(f"[bold cyan].{config['name']}[/bold cyan]") # Add main accessor methods first methods = accessor._get_methods_for_help() @@ -149,9 +147,7 @@ def _create_help_tree(self): # Add sub-accessors after main methods for sub_attr, sub_obj in inspect.getmembers(accessor): if isinstance(sub_obj, _BaseAccessor) and not sub_attr.startswith("_"): - sub_branch = branch.add( - f"[bold cyan].{sub_attr} {sub_obj._icon}[/bold cyan]" - ) + sub_branch = branch.add(f"[bold cyan].{sub_attr}[/bold cyan]") # Add sub-accessor methods sub_methods = sub_obj._get_methods_for_help() @@ -183,13 +179,12 @@ def _create_help_tree(self): class _BaseAccessor(_HelpMixin): """Base class for all accessors.""" - def __init__(self, parent, icon): + def __init__(self, parent): self._parent = parent - self._icon = icon def _get_help_panel_title(self): name = self.__class__.__name__.replace("_", "").replace("Accessor", "").lower() - return f"{self._icon} Available {name} methods" + return f"Available {name} methods" def _create_help_tree(self): """Create a rich Tree with the available methods.""" diff --git a/skore/src/skore/sklearn/_base.pyi b/skore/src/skore/sklearn/_base.pyi index d1c7fc826..155db3d05 100644 --- a/skore/src/skore/sklearn/_base.pyi +++ b/skore/src/skore/sklearn/_base.pyi @@ -20,9 +20,8 @@ class _HelpMixin: class _BaseAccessor(_HelpMixin): _parent: Any - _icon: str - def __init__(self, parent: Any, icon: str) -> None: ... + def __init__(self, parent: Any) -> None: ... def _get_help_panel_title(self) -> str: ... def _create_help_tree(self) -> Tree: ... def _get_X_y_and_data_source_hash( diff --git a/skore/src/skore/sklearn/_estimator/metrics_accessor.py b/skore/src/skore/sklearn/_estimator/metrics_accessor.py index fc741b672..0a84379bf 100644 --- a/skore/src/skore/sklearn/_estimator/metrics_accessor.py +++ b/skore/src/skore/sklearn/_estimator/metrics_accessor.py @@ -42,7 +42,7 @@ class _MetricsAccessor(_BaseAccessor, DirNamesMixin): } def __init__(self, parent): - super().__init__(parent, icon=":straight_ruler:") + super().__init__(parent) # TODO: should build on the `add_scorers` function def report_metrics( @@ -797,7 +797,7 @@ def _create_help_tree(self): tree = super()._create_help_tree() # Add plot methods in a separate branch - plot_branch = tree.add("[bold cyan].plot :art:[/bold cyan]") + plot_branch = tree.add("[bold cyan].plot[/bold cyan]") plot_methods = self.plot._get_methods_for_help() plot_methods = self.plot._sort_methods_for_help(plot_methods) @@ -809,7 +809,7 @@ def _create_help_tree(self): return tree def _get_help_panel_title(self): - return f"[bold cyan]{self._icon} Available metrics methods[/bold cyan]" + return "[bold cyan]Available metrics methods[/bold cyan]" def _get_help_legend(self): return ( @@ -837,7 +837,7 @@ class _PlotMetricsAccessor(_BaseAccessor): """Plotting methods for the metrics accessor.""" def __init__(self, parent): - super().__init__(parent._parent, icon=":art:") + super().__init__(parent._parent) self._metrics_parent = parent def _get_display( @@ -1099,7 +1099,7 @@ def prediction_error( ) def _get_help_panel_title(self): - return f"[bold cyan]{self._icon} Available plot methods[/bold cyan]" + return "[bold cyan]Available plot methods[/bold cyan]" def _get_help_tree_title(self): return "[bold cyan]reporter.metrics.plot[/bold cyan]" diff --git a/skore/src/skore/sklearn/_estimator/report.py b/skore/src/skore/sklearn/_estimator/report.py index a1cce9f74..3e71f7986 100644 --- a/skore/src/skore/sklearn/_estimator/report.py +++ b/skore/src/skore/sklearn/_estimator/report.py @@ -64,10 +64,7 @@ class EstimatorReport(_BaseReport, DirNamesMixin): """ _ACCESSOR_CONFIG = { - "metrics": {"icon": ":straight_ruler:", "name": "metrics"}, - # Add other accessors as they're implemented - # "inspection": {"icon": ":magnifying_glass:", "name": "inspection"}, - # "linting": {"icon": ":check:", "name": "linting"}, + "metrics": {"name": "metrics"}, } @staticmethod diff --git a/skore/src/skore/sklearn/_plot/utils.py b/skore/src/skore/sklearn/_plot/utils.py index c8bc39f1f..7aa4b1e0b 100644 --- a/skore/src/skore/sklearn/_plot/utils.py +++ b/skore/src/skore/sklearn/_plot/utils.py @@ -66,7 +66,7 @@ def _create_help_panel(self): return Panel( self._create_help_tree(), title=( - f"[bold cyan]:bar_chart: {self.__class__.__name__} for " + f"[bold cyan]{self.__class__.__name__} for " f"{self.estimator_name}[/bold cyan]" ), border_style="orange1", diff --git a/skore/tests/unit/sklearn/plot/test_common.py b/skore/tests/unit/sklearn/plot/test_common.py index 26d595777..9f0d33824 100644 --- a/skore/tests/unit/sklearn/plot/test_common.py +++ b/skore/tests/unit/sklearn/plot/test_common.py @@ -28,7 +28,7 @@ def test_display_help(pyplot, capsys, plot_func, estimator, dataset): display.help() captured = capsys.readouterr() - assert f"📊 {display.__class__.__name__}" in captured.out + assert f"{display.__class__.__name__}" in captured.out @pytest.mark.parametrize( diff --git a/skore/tests/unit/sklearn/test_base.py b/skore/tests/unit/sklearn/test_base.py index a183b2922..0f6b12b91 100644 --- a/skore/tests/unit/sklearn/test_base.py +++ b/skore/tests/unit/sklearn/test_base.py @@ -213,7 +213,7 @@ def test_base_accessor_get_X_y_and_data_source_hash_error(): estimator = LogisticRegression().fit(X_train, y_train) report = MockReport(estimator, X_train=None, y_train=None, X_test=None, y_test=None) - accessor = _BaseAccessor(parent=report, icon="") + accessor = _BaseAccessor(parent=report) err_msg = re.escape( "Invalid data source: unknown. Possible values are: " "test, train, X_y." @@ -234,7 +234,7 @@ def test_base_accessor_get_X_y_and_data_source_hash_error(): report = MockReport( estimator, X_train=X_train, y_train=y_train, X_test=X_test, y_test=y_test ) - accessor = _BaseAccessor(parent=report, icon="") + accessor = _BaseAccessor(parent=report) for data_source in ("train", "test"): err_msg = f"X and y must be None when data_source is {data_source}." @@ -251,13 +251,13 @@ def test_base_accessor_get_X_y_and_data_source_hash_error(): # use `custom_metric` for them. estimator = KMeans(n_clusters=2).fit(X_train) report = MockReport(estimator, X_test=X_test) - accessor = _BaseAccessor(parent=report, icon="") + accessor = _BaseAccessor(parent=report) err_msg = "X must be provided." with pytest.raises(ValueError, match=err_msg): accessor._get_X_y_and_data_source_hash(data_source="X_y") report = MockReport(estimator) - accessor = _BaseAccessor(parent=report, icon="") + accessor = _BaseAccessor(parent=report) for data_source in ("train", "test"): err_msg = re.escape( f"No {data_source} data (i.e. X_{data_source}) were provided when " @@ -279,7 +279,7 @@ def test_base_accessor_get_X_y_and_data_source_hash(data_source): report = MockReport( estimator, X_train=X_train, y_train=y_train, X_test=X_test, y_test=y_test ) - accessor = _BaseAccessor(parent=report, icon="") + accessor = _BaseAccessor(parent=report) kwargs = {"X": X_test, "y": y_test} if data_source == "X_y" else {} X, y, data_source_hash = accessor._get_X_y_and_data_source_hash( data_source=data_source, **kwargs diff --git a/skore/tests/unit/sklearn/test_estimator.py b/skore/tests/unit/sklearn/test_estimator.py index 05d642926..fa55dc2de 100644 --- a/skore/tests/unit/sklearn/test_estimator.py +++ b/skore/tests/unit/sklearn/test_estimator.py @@ -274,9 +274,7 @@ def test_estimator_report_help(capsys, binary_classification_data): report.help() captured = capsys.readouterr() - assert ( - f"📓 Tools to diagnose estimator {estimator.__class__.__name__}" in captured.out - ) + assert f"Tools to diagnose estimator {estimator.__class__.__name__}" in captured.out def test_estimator_report_repr(binary_classification_data): @@ -336,7 +334,7 @@ def test_estimator_report_plot_help(capsys, binary_classification_data): report.metrics.plot.help() captured = capsys.readouterr() - assert "🎨 Available plot methods" in captured.out + assert "Available plot methods" in captured.out def test_estimator_report_plot_repr(binary_classification_data): @@ -476,7 +474,7 @@ def test_estimator_report_metrics_help(capsys, binary_classification_data): report.metrics.help() captured = capsys.readouterr() - assert "📏 Available metrics methods" in captured.out + assert "Available metrics methods" in captured.out def test_estimator_report_metrics_repr(binary_classification_data): From 15055aac3dd7794012a6a2f9a55555efe8a6fcda Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Wed, 15 Jan 2025 15:53:52 +0100 Subject: [PATCH 05/27] docs: Add examples in docstring of the EstimatorReport methods (#1117) closes #1078 Add examples for all the methods exposed in `EstimatorReport`. --------- Co-authored-by: Auguste Baum <52001167+augustebaum@users.noreply.github.com> Co-authored-by: Sylvain Combettes <48064216+sylvaincom@users.noreply.github.com> --- skore/pyproject.toml | 1 + .../sklearn/_estimator/metrics_accessor.py | 277 ++++++++++++++++++ skore/src/skore/sklearn/_estimator/report.py | 50 +++- 3 files changed, 326 insertions(+), 2 deletions(-) diff --git a/skore/pyproject.toml b/skore/pyproject.toml index 37d8124af..0af5534b3 100644 --- a/skore/pyproject.toml +++ b/skore/pyproject.toml @@ -111,6 +111,7 @@ addopts = [ "--ignore=examples", "--ignore=notebooks", ] +doctest_optionflags = ["ELLIPSIS", "NORMALIZE_WHITESPACE"] [tool.coverage.run] branch = true diff --git a/skore/src/skore/sklearn/_estimator/metrics_accessor.py b/skore/src/skore/sklearn/_estimator/metrics_accessor.py index 0a84379bf..7b2b0e8fb 100644 --- a/skore/src/skore/sklearn/_estimator/metrics_accessor.py +++ b/skore/src/skore/sklearn/_estimator/metrics_accessor.py @@ -93,6 +93,27 @@ def report_metrics( ------- pd.DataFrame The statistics for the metrics. + + Examples + -------- + >>> from sklearn.datasets import load_breast_cancer + >>> from sklearn.linear_model import LogisticRegression + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_breast_cancer(return_X_y=True), random_state=0 + ... ) + >>> classifier = LogisticRegression(max_iter=10_000) + >>> reporter = EstimatorReport( + ... classifier, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.metrics.report_metrics(pos_label=1) + Metric Precision (↗︎) Recall (↗︎) ROC AUC (↗︎) Brier score (↘︎) + LogisticRegression 0.98... 0.93... 0.99... 0.03... """ if scoring is None: # Equivalent to _get_scorers_to_add @@ -296,6 +317,27 @@ def accuracy(self, *, data_source="test", X=None, y=None): ------- pd.DataFrame The accuracy score. + + Examples + -------- + >>> from sklearn.datasets import load_breast_cancer + >>> from sklearn.linear_model import LogisticRegression + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_breast_cancer(return_X_y=True), random_state=0 + ... ) + >>> classifier = LogisticRegression(max_iter=10_000) + >>> reporter = EstimatorReport( + ... classifier, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.metrics.accuracy() + Metric Accuracy (↗︎) + LogisticRegression 0.95... """ return self._compute_metric_scores( metrics.accuracy_score, @@ -365,6 +407,27 @@ def precision( ------- pd.DataFrame The precision score. + + Examples + -------- + >>> from sklearn.datasets import load_breast_cancer + >>> from sklearn.linear_model import LogisticRegression + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_breast_cancer(return_X_y=True), random_state=0 + ... ) + >>> classifier = LogisticRegression(max_iter=10_000) + >>> reporter = EstimatorReport( + ... classifier, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.metrics.precision(pos_label=1) + Metric Precision (↗︎) + LogisticRegression 0.98... """ if self._parent._ml_task == "binary-classification" and pos_label is not None: # if `pos_label` is specified by our user, then we can safely report only @@ -442,6 +505,27 @@ def recall( ------- pd.DataFrame The recall score. + + Examples + -------- + >>> from sklearn.datasets import load_breast_cancer + >>> from sklearn.linear_model import LogisticRegression + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_breast_cancer(return_X_y=True), random_state=0 + ... ) + >>> classifier = LogisticRegression(max_iter=10_000) + >>> reporter = EstimatorReport( + ... classifier, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.metrics.recall(pos_label=1) + Metric Recall (↗︎) + LogisticRegression 0.93... """ if self._parent._ml_task == "binary-classification" and pos_label is not None: # if `pos_label` is specified by our user, then we can safely report only @@ -486,6 +570,27 @@ def brier_score(self, *, data_source="test", X=None, y=None): ------- pd.DataFrame The Brier score. + + Examples + -------- + >>> from sklearn.datasets import load_breast_cancer + >>> from sklearn.linear_model import LogisticRegression + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_breast_cancer(return_X_y=True), random_state=0 + ... ) + >>> classifier = LogisticRegression(max_iter=10_000) + >>> reporter = EstimatorReport( + ... classifier, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.metrics.brier_score() + Metric Brier score (↘︎) + LogisticRegression 0.03... """ # The Brier score in scikit-learn request `pos_label` to ensure that the # integral encoding of `y_true` corresponds to the probabilities of the @@ -566,6 +671,27 @@ def roc_auc( ------- pd.DataFrame The ROC AUC score. + + Examples + -------- + >>> from sklearn.datasets import load_breast_cancer + >>> from sklearn.linear_model import LogisticRegression + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_breast_cancer(return_X_y=True), random_state=0 + ... ) + >>> classifier = LogisticRegression(max_iter=10_000) + >>> reporter = EstimatorReport( + ... classifier, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.metrics.roc_auc() + Metric ROC AUC (↗︎) + LogisticRegression 0.99... """ return self._compute_metric_scores( metrics.roc_auc_score, @@ -600,6 +726,27 @@ def log_loss(self, *, data_source="test", X=None, y=None): ------- pd.DataFrame The log-loss. + + Examples + -------- + >>> from sklearn.datasets import load_breast_cancer + >>> from sklearn.linear_model import LogisticRegression + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_breast_cancer(return_X_y=True), random_state=0 + ... ) + >>> classifier = LogisticRegression(max_iter=10_000) + >>> reporter = EstimatorReport( + ... classifier, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.metrics.log_loss() + Metric Log loss (↘︎) + LogisticRegression 0.10... """ return self._compute_metric_scores( metrics.log_loss, @@ -638,6 +785,27 @@ def r2(self, *, data_source="test", X=None, y=None, multioutput="raw_values"): ------- pd.DataFrame The R² score. + + Examples + -------- + >>> from sklearn.datasets import load_diabetes + >>> from sklearn.linear_model import Ridge + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_diabetes(return_X_y=True), random_state=0 + ... ) + >>> regressor = Ridge() + >>> reporter = EstimatorReport( + ... regressor, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.metrics.r2() + Metric R² (↗︎) + Ridge 0.35... """ return self._compute_metric_scores( metrics.r2_score, @@ -677,6 +845,27 @@ def rmse(self, *, data_source="test", X=None, y=None, multioutput="raw_values"): ------- pd.DataFrame The root mean squared error. + + Examples + -------- + >>> from sklearn.datasets import load_diabetes + >>> from sklearn.linear_model import Ridge + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_diabetes(return_X_y=True), random_state=0 + ... ) + >>> regressor = Ridge() + >>> reporter = EstimatorReport( + ... regressor, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.metrics.rmse() + Metric RMSE (↘︎) + Ridge 56.5... """ return self._compute_metric_scores( metrics.root_mean_squared_error, @@ -740,6 +929,32 @@ def custom_metric( ------- pd.DataFrame The custom metric. + + Examples + -------- + >>> from sklearn.datasets import load_diabetes + >>> from sklearn.linear_model import Ridge + >>> from sklearn.metrics import mean_absolute_error + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_diabetes(return_X_y=True), random_state=0 + ... ) + >>> regressor = Ridge() + >>> reporter = EstimatorReport( + ... regressor, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.metrics.custom_metric( + ... metric_function=mean_absolute_error, + ... response_method="predict", + ... metric_name="MAE (↗︎)", + ... ) + Metric MAE (↗︎) + Ridge 44.9... """ return self._compute_metric_scores( metric_function, @@ -957,6 +1172,26 @@ def roc(self, *, data_source="test", X=None, y=None, pos_label=None, ax=None): ------- RocCurveDisplay The ROC curve display. + + Examples + -------- + >>> from sklearn.datasets import load_breast_cancer + >>> from sklearn.linear_model import LogisticRegression + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_breast_cancer(return_X_y=True), random_state=0 + ... ) + >>> classifier = LogisticRegression(max_iter=10_000) + >>> reporter = EstimatorReport( + ... classifier, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> display = reporter.metrics.plot.roc() + >>> display.plot(roc_curve_kwargs={"color": "tab:red"}) """ response_method = ("predict_proba", "decision_function") display_kwargs = {"pos_label": pos_label} @@ -1014,6 +1249,26 @@ def precision_recall( ------- PrecisionRecallCurveDisplay The precision-recall curve display. + + Examples + -------- + >>> from sklearn.datasets import load_breast_cancer + >>> from sklearn.linear_model import LogisticRegression + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_breast_cancer(return_X_y=True), random_state=0 + ... ) + >>> classifier = LogisticRegression(max_iter=10_000) + >>> reporter = EstimatorReport( + ... classifier, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> display = reporter.metrics.plot.precision_recall() + >>> display.plot(pr_curve_kwargs={"color": "tab:red"}) """ response_method = ("predict_proba", "decision_function") display_kwargs = {"pos_label": pos_label} @@ -1085,6 +1340,28 @@ def prediction_error( ------- PredictionErrorDisplay The prediction error display. + + Examples + -------- + >>> from sklearn.datasets import load_diabetes + >>> from sklearn.linear_model import Ridge + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_diabetes(return_X_y=True), random_state=0 + ... ) + >>> regressor = Ridge() + >>> reporter = EstimatorReport( + ... regressor, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> display = reporter.metrics.plot.prediction_error( + ... kind="actual_vs_predicted" + ... ) + >>> display.plot(line_kwargs={"color": "tab:red"}) """ display_kwargs = {"kind": kind, "subsample": subsample} display_plot_kwargs = {"ax": ax} diff --git a/skore/src/skore/sklearn/_estimator/report.py b/skore/src/skore/sklearn/_estimator/report.py index 3e71f7986..19e398b05 100644 --- a/skore/src/skore/sklearn/_estimator/report.py +++ b/skore/src/skore/sklearn/_estimator/report.py @@ -135,11 +135,35 @@ def _initialize_state(self): # For the validation set, we allow it and we invalidate the cache. def clear_cache(self): - """Clean the cache.""" + """Clear the cache. + + Examples + -------- + >>> from sklearn.datasets import load_breast_cancer + >>> from sklearn.linear_model import LogisticRegression + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_breast_cancer(return_X_y=True), random_state=0 + ... ) + >>> classifier = LogisticRegression(max_iter=10_000) + >>> reporter = EstimatorReport( + ... classifier, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.cache_predictions() + Caching predictions ... + >>> reporter.clear_cache() + >>> reporter._cache + {} + """ self._cache = {} def cache_predictions(self, response_methods="auto", n_jobs=None): - """Force caching of estimator's predictions. + """Cache estimator's predictions. Parameters ---------- @@ -152,6 +176,28 @@ def cache_predictions(self, response_methods="auto", n_jobs=None): n_jobs : int or None, default=None The number of jobs to run in parallel. None means 1 unless in a joblib.parallel_backend context. -1 means using all processors. + + Examples + -------- + >>> from sklearn.datasets import load_breast_cancer + >>> from sklearn.linear_model import LogisticRegression + >>> from sklearn.model_selection import train_test_split + >>> from skore import EstimatorReport + >>> X_train, X_test, y_train, y_test = train_test_split( + ... *load_breast_cancer(return_X_y=True), random_state=0 + ... ) + >>> classifier = LogisticRegression(max_iter=10_000) + >>> reporter = EstimatorReport( + ... classifier, + ... X_train=X_train, + ... y_train=y_train, + ... X_test=X_test, + ... y_test=y_test, + ... ) + >>> reporter.cache_predictions() + Caching predictions ... + >>> reporter._cache + {...} """ if self._ml_task in ("binary-classification", "multiclass-classification"): if response_methods == "auto": From 2cb5450d90f30c9e63ec383b37488075b6647747 Mon Sep 17 00:00:00 2001 From: Sylvain Combettes <48064216+sylvaincom@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:09:55 +0100 Subject: [PATCH 06/27] docs: Keep only the GitHub and Discord icons (#1123) Fix #1122 --- sphinx/conf.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/sphinx/conf.py b/sphinx/conf.py index d1a3ec83e..af94d4f2c 100644 --- a/sphinx/conf.py +++ b/sphinx/conf.py @@ -142,26 +142,6 @@ def reset_mpl(gallery_conf, fname): "url": "https://discord.gg/scBZerAGwW", "icon": "fa-brands fa-discord", }, - { - "name": "YouTube", - "url": "https://www.youtube.com/@probabl_ai", - "icon": "fa-brands fa-youtube", - }, - { - "name": "LinkedIn", - "url": "https://www.linkedin.com/company/probabl/", - "icon": "fa-brands fa-linkedin-in", - }, - { - "name": "Bluesky", - "url": "https://bsky.app/profile/probabl.ai", - "icon": "fa-brands fa-bluesky", - }, - { - "name": "X (ex-Twitter)", - "url": "https://x.com/probabl_ai", - "icon": "fa-brands fa-x-twitter", - }, ], "switcher": { "json_url": "https://skore.probabl.ai/versions.json", From 3891654b8abe3e2af9701327d8bf365019256f37 Mon Sep 17 00:00:00 2001 From: Sylvain Combettes <48064216+sylvaincom@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:11:00 +0100 Subject: [PATCH 07/27] docs: Minor changes in the doc (tracking items example) (#1125) Fix #1124 Just a couple of minor changes in variables naming, etc --- .../plot_skore_product_tour.py | 2 ++ .../getting_started/plot_tracking_items.py | 26 ++++++++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/examples/getting_started/plot_skore_product_tour.py b/examples/getting_started/plot_skore_product_tour.py index 8ec66bdcf..6d7b9bf01 100644 --- a/examples/getting_started/plot_skore_product_tour.py +++ b/examples/getting_started/plot_skore_product_tour.py @@ -192,8 +192,10 @@ # import time # # my_project.put("my_int", 4) +# # time.sleep(0.1) # my_project.put("my_int", 9) +# # time.sleep(0.1) # my_project.put("my_int", 16) # diff --git a/examples/getting_started/plot_tracking_items.py b/examples/getting_started/plot_tracking_items.py index 11bfe30aa..27a50404b 100644 --- a/examples/getting_started/plot_tracking_items.py +++ b/examples/getting_started/plot_tracking_items.py @@ -44,8 +44,10 @@ import time my_project.put("my_int", 4) + time.sleep(0.1) my_project.put("my_int", 9) + time.sleep(0.1) my_project.put("my_int", 16) @@ -73,17 +75,17 @@ # We retrieve the history of the ``my_int`` item: # %% -item_histories = my_project.get_item_versions("my_int") +item_versions = my_project.get_item_versions("my_int") # %% -# We can print the first history (first iteration) of this item: +# We can print the details of the first version of this item: # %% -passed_item = item_histories[0] -print(passed_item) -print(passed_item.primitive) -print(passed_item.created_at) -print(passed_item.updated_at) +first_item = item_versions[0] +print(first_item) +print(first_item.primitive) +print(first_item.created_at) +print(first_item.updated_at) # %% # Let us construct a dataframe with the values and last updated times: @@ -93,7 +95,7 @@ import pandas as pd list_primitive, list_created_at, list_updated_at = zip( - *[(elem.primitive, elem.created_at, elem.updated_at) for elem in item_histories] + *[(elem.primitive, elem.created_at, elem.updated_at) for elem in item_versions] ) df_track = pd.DataFrame( @@ -103,7 +105,7 @@ "updated_at": list_updated_at, } ) -df_track.insert(0, "iteration_number", np.arange(len(df_track))) +df_track.insert(0, "version_number", np.arange(len(df_track))) df_track # %% @@ -123,7 +125,7 @@ fig = px.line( df_track, - x="iteration_number", + x="version_number", y="primitive", hover_data=df_track.columns, markers=True, @@ -137,8 +139,8 @@ # example. # %% -# Here, we focused on `how` to use skore's tracking of history of items. -# But `why` track items? +# Here, we focused on *how* to use skore's tracking of history of items. +# But *why* track items? # # * We could track some items such as machine learning scores over time to better # understand which feature engineering works best. From 47775c36ce6127070b66938fd5ae052e4d9fcf3b Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Wed, 15 Jan 2025 16:11:37 +0100 Subject: [PATCH 08/27] feat: Private API to expose `data_source_hash` when computing metric (#1109) This PR exposes private metrics to expose the `data_source_hash` such we can compute it once and pass it around whenever possible. It is particularly interested because the hash might be expensive to compute on huge dataset. --- .../sklearn/_estimator/metrics_accessor.py | 313 ++++++++++++++++-- skore/tests/unit/sklearn/test_estimator.py | 118 ++++++- 2 files changed, 396 insertions(+), 35 deletions(-) diff --git a/skore/src/skore/sklearn/_estimator/metrics_accessor.py b/skore/src/skore/sklearn/_estimator/metrics_accessor.py index 7b2b0e8fb..941e3d5e7 100644 --- a/skore/src/skore/sklearn/_estimator/metrics_accessor.py +++ b/skore/src/skore/sklearn/_estimator/metrics_accessor.py @@ -115,18 +115,29 @@ def report_metrics( Metric Precision (↗︎) Recall (↗︎) ROC AUC (↗︎) Brier score (↘︎) LogisticRegression 0.98... 0.93... 0.99... 0.03... """ + if data_source == "X_y": + # optimization of the hash computation to avoid recomputing it + # FIXME: we are still recomputing the hash for all the metrics that we + # support in the report because we don't call `_compute_metric_scores` + # here. We should fix it. + X, y, data_source_hash = self._get_X_y_and_data_source_hash( + data_source=data_source, X=X, y=y + ) + else: + data_source_hash = None + if scoring is None: # Equivalent to _get_scorers_to_add if self._parent._ml_task == "binary-classification": - scoring = ["precision", "recall", "roc_auc"] + scoring = ["_precision", "_recall", "_roc_auc"] if hasattr(self._parent._estimator, "predict_proba"): - scoring.append("brier_score") + scoring.append("_brier_score") elif self._parent._ml_task == "multiclass-classification": - scoring = ["precision", "recall"] + scoring = ["_precision", "_recall"] if hasattr(self._parent._estimator, "predict_proba"): - scoring += ["roc_auc", "log_loss"] + scoring += ["_roc_auc", "_log_loss"] else: - scoring = ["r2", "rmse"] + scoring = ["_r2", "_rmse"] scores = [] @@ -136,18 +147,36 @@ def report_metrics( if isinstance(metric, _BaseScorer): # scorers have the advantage to have scoped defined kwargs metric_fn = partial( - self.custom_metric, + self._custom_metric, metric_function=metric._score_func, response_method=metric._response_method, ) # forward the additional parameters specific to the scorer metrics_kwargs = {**metric._kwargs} + metrics_kwargs["data_source_hash"] = data_source_hash + metrics_params = inspect.signature(metric._score_func).parameters + if "pos_label" in metrics_params: + metrics_kwargs["pos_label"] = pos_label elif isinstance(metric, str) or callable(metric): if isinstance(metric, str): + err_msg = ( + f"Invalid metric: {metric!r}. Please use a valid metric" + " from the list of supported metrics: " + f"{list(self._SCORE_OR_LOSS_ICONS.keys())}" + ) + if ( + metric.startswith("_") + and metric[1:] not in self._SCORE_OR_LOSS_ICONS + ): + raise ValueError(err_msg) + if not metric.startswith("_"): + if metric not in self._SCORE_OR_LOSS_ICONS: + raise ValueError(err_msg) + metric = f"_{metric}" metric_fn = getattr(self, metric) - metrics_kwargs = {} + metrics_kwargs = {"data_source_hash": data_source_hash} else: - metric_fn = partial(self.custom_metric, metric_function=metric) + metric_fn = partial(self._custom_metric, metric_function=metric) if scoring_kwargs is None: metrics_kwargs = {} else: @@ -159,6 +188,7 @@ def report_metrics( for param in metric_callable_params if param in scoring_kwargs } + metrics_kwargs["data_source_hash"] = data_source_hash metrics_params = inspect.signature(metric_fn).parameters if scoring_kwargs is not None: for param in metrics_params: @@ -172,7 +202,12 @@ def report_metrics( ) scores.append( - metric_fn(data_source=data_source, X=X, y=y, **metrics_kwargs) + metric_fn( + data_source=data_source, + X=X, + y=y, + **metrics_kwargs, + ) ) has_multilevel = any( @@ -205,25 +240,17 @@ def _compute_metric_scores( y_true, *, data_source="test", + data_source_hash=None, response_method, pos_label=None, metric_name=None, **metric_kwargs, ): - X, y_true, data_source_hash = self._get_X_y_and_data_source_hash( - data_source=data_source, X=X, y=y_true - ) + if data_source_hash is None: + X, y_true, data_source_hash = self._get_X_y_and_data_source_hash( + data_source=data_source, X=X, y=y_true + ) - y_pred = _get_cached_response_values( - cache=self._parent._cache, - estimator_hash=self._parent._hash, - estimator=self._parent.estimator, - X=X, - response_method=response_method, - pos_label=pos_label, - data_source=data_source, - data_source_hash=data_source_hash, - ) cache_key = (self._parent._hash, metric_fn.__name__, data_source) if data_source_hash: cache_key += (data_source_hash,) @@ -252,6 +279,17 @@ def _compute_metric_scores( if "pos_label" in metric_params: kwargs.update(pos_label=pos_label) + y_pred = _get_cached_response_values( + cache=self._parent._cache, + estimator_hash=self._parent._hash, + estimator=self._parent.estimator, + X=X, + response_method=response_method, + pos_label=pos_label, + data_source=data_source, + data_source_hash=data_source_hash, + ) + score = metric_fn(y_true, y_pred, **kwargs) self._parent._cache[cache_key] = score @@ -283,9 +321,8 @@ def _compute_metric_scores( names=["Metric", "Output"], ) score = score.reshape(1, -1) - else: - # FIXME: clusterer would fall here. - columns = None + else: # unknown task - try our best + columns = [metric_name] if score.shape[0] == 1 else None return pd.DataFrame(score, columns=columns, index=[self._parent.estimator_name]) @available_if( @@ -339,11 +376,21 @@ def accuracy(self, *, data_source="test", X=None, y=None): Metric Accuracy (↗︎) LogisticRegression 0.95... """ + return self._accuracy(data_source=data_source, data_source_hash=None, X=X, y=y) + + def _accuracy(self, *, data_source="test", data_source_hash=None, X=None, y=None): + """Private interface of `accuracy` to be able to pass `data_source_hash`. + + `data_source_hash` is either an `int` when we already computed the hash + and are able to pass it around or `None` and thus trigger its computation + in the underlying process. + """ return self._compute_metric_scores( metrics.accuracy_score, X=X, y_true=y, data_source=data_source, + data_source_hash=data_source_hash, response_method="predict", metric_name=f"Accuracy {self._SCORE_OR_LOSS_ICONS['accuracy']}", ) @@ -429,6 +476,31 @@ def precision( Metric Precision (↗︎) LogisticRegression 0.98... """ + return self._precision( + data_source=data_source, + data_source_hash=None, + X=X, + y=y, + average=average, + pos_label=pos_label, + ) + + def _precision( + self, + *, + data_source="test", + data_source_hash=None, + X=None, + y=None, + average=None, + pos_label=None, + ): + """Private interface of `precision` to be able to pass `data_source_hash`. + + `data_source_hash` is either an `int` when we already computed the hash + and are able to pass it around or `None` and thus trigger its computation + in the underlying process. + """ if self._parent._ml_task == "binary-classification" and pos_label is not None: # if `pos_label` is specified by our user, then we can safely report only # the statistics of the positive class @@ -439,6 +511,7 @@ def precision( X=X, y_true=y, data_source=data_source, + data_source_hash=data_source_hash, response_method="predict", pos_label=pos_label, metric_name=f"Precision {self._SCORE_OR_LOSS_ICONS['precision']}", @@ -527,6 +600,31 @@ def recall( Metric Recall (↗︎) LogisticRegression 0.93... """ + return self._recall( + data_source=data_source, + data_source_hash=None, + X=X, + y=y, + average=average, + pos_label=pos_label, + ) + + def _recall( + self, + *, + data_source="test", + data_source_hash=None, + X=None, + y=None, + average=None, + pos_label=None, + ): + """Private interface of `recall` to be able to pass `data_source_hash`. + + `data_source_hash` is either an `int` when we already computed the hash + and are able to pass it around or `None` and thus trigger its computation + in the underlying process. + """ if self._parent._ml_task == "binary-classification" and pos_label is not None: # if `pos_label` is specified by our user, then we can safely report only # the statistics of the positive class @@ -537,6 +635,7 @@ def recall( X=X, y_true=y, data_source=data_source, + data_source_hash=data_source_hash, response_method="predict", pos_label=pos_label, metric_name=f"Recall {self._SCORE_OR_LOSS_ICONS['recall']}", @@ -592,6 +691,22 @@ def brier_score(self, *, data_source="test", X=None, y=None): Metric Brier score (↘︎) LogisticRegression 0.03... """ + return self._brier_score( + data_source=data_source, + data_source_hash=None, + X=X, + y=y, + ) + + def _brier_score( + self, *, data_source="test", data_source_hash=None, X=None, y=None + ): + """Private interface of `brier_score` to be able to pass `data_source_hash`. + + `data_source_hash` is either an `int` when we already computed the hash + and are able to pass it around or `None` and thus trigger its computation + in the underlying process. + """ # The Brier score in scikit-learn request `pos_label` to ensure that the # integral encoding of `y_true` corresponds to the probabilities of the # `pos_label`. Since we get the predictions with `get_response_method`, we @@ -601,6 +716,7 @@ def brier_score(self, *, data_source="test", X=None, y=None): X=X, y_true=y, data_source=data_source, + data_source_hash=data_source_hash, response_method="predict_proba", metric_name=f"Brier score {self._SCORE_OR_LOSS_ICONS['brier_score']}", pos_label=self._parent._estimator.classes_[-1], @@ -693,11 +809,37 @@ def roc_auc( Metric ROC AUC (↗︎) LogisticRegression 0.99... """ + return self._roc_auc( + data_source=data_source, + data_source_hash=None, + X=X, + y=y, + average=average, + multi_class=multi_class, + ) + + def _roc_auc( + self, + *, + data_source="test", + data_source_hash=None, + X=None, + y=None, + average=None, + multi_class="ovr", + ): + """Private interface of `roc_auc` to be able to pass `data_source_hash`. + + `data_source_hash` is either an `int` when we already computed the hash + and are able to pass it around or `None` and thus trigger its computation + in the underlying process. + """ return self._compute_metric_scores( metrics.roc_auc_score, X=X, y_true=y, data_source=data_source, + data_source_hash=data_source_hash, response_method=["predict_proba", "decision_function"], metric_name=f"ROC AUC {self._SCORE_OR_LOSS_ICONS['roc_auc']}", average=average, @@ -748,11 +890,33 @@ def log_loss(self, *, data_source="test", X=None, y=None): Metric Log loss (↘︎) LogisticRegression 0.10... """ + return self._log_loss( + data_source=data_source, + data_source_hash=None, + X=X, + y=y, + ) + + def _log_loss( + self, + *, + data_source="test", + data_source_hash=None, + X=None, + y=None, + ): + """Private interface of `log_loss` to be able to pass `data_source_hash`. + + `data_source_hash` is either an `int` when we already computed the hash + and are able to pass it around or `None` and thus trigger its computation + in the underlying process. + """ return self._compute_metric_scores( metrics.log_loss, X=X, y_true=y, data_source=data_source, + data_source_hash=data_source_hash, response_method="predict_proba", metric_name=f"Log loss {self._SCORE_OR_LOSS_ICONS['log_loss']}", ) @@ -763,6 +927,13 @@ def r2(self, *, data_source="test", X=None, y=None, multioutput="raw_values"): Parameters ---------- + data_source : {"test", "train", "X_y"}, default="test" + The data source to use. + + - "test" : use the test set provided when creating the reporter. + - "train" : use the train set provided when creating the reporter. + - "X_y" : use the provided `X` and `y` to compute the metric. + X : array-like of shape (n_samples, n_features), default=None New data on which to compute the metric. By default, we use the validation set provided when creating the reporter. @@ -807,11 +978,35 @@ def r2(self, *, data_source="test", X=None, y=None, multioutput="raw_values"): Metric R² (↗︎) Ridge 0.35... """ + return self._r2( + data_source=data_source, + data_source_hash=None, + X=X, + y=y, + multioutput=multioutput, + ) + + def _r2( + self, + *, + data_source="test", + data_source_hash=None, + X=None, + y=None, + multioutput="raw_values", + ): + """Private interface of `r2` to be able to pass `data_source_hash`. + + `data_source_hash` is either an `int` when we already computed the hash + and are able to pass it around or `None` and thus trigger its computation + in the underlying process. + """ return self._compute_metric_scores( metrics.r2_score, X=X, y_true=y, data_source=data_source, + data_source_hash=data_source_hash, response_method="predict", metric_name=f"R² {self._SCORE_OR_LOSS_ICONS['r2']}", multioutput=multioutput, @@ -823,6 +1018,13 @@ def rmse(self, *, data_source="test", X=None, y=None, multioutput="raw_values"): Parameters ---------- + data_source : {"test", "train", "X_y"}, default="test" + The data source to use. + + - "test" : use the test set provided when creating the reporter. + - "train" : use the train set provided when creating the reporter. + - "X_y" : use the provided `X` and `y` to compute the metric. + X : array-like of shape (n_samples, n_features), default=None New data on which to compute the metric. By default, we use the validation set provided when creating the reporter. @@ -867,11 +1069,35 @@ def rmse(self, *, data_source="test", X=None, y=None, multioutput="raw_values"): Metric RMSE (↘︎) Ridge 56.5... """ + return self._rmse( + data_source=data_source, + data_source_hash=None, + X=X, + y=y, + multioutput=multioutput, + ) + + def _rmse( + self, + *, + data_source="test", + data_source_hash=None, + X=None, + y=None, + multioutput="raw_values", + ): + """Private interface of `rmse` to be able to pass `data_source_hash`. + + `data_source_hash` is either an `int` when we already computed the hash + and are able to pass it around or `None` and thus trigger its computation + in the underlying process. + """ return self._compute_metric_scores( metrics.root_mean_squared_error, X=X, y_true=y, data_source=data_source, + data_source_hash=data_source_hash, response_method="predict", metric_name=f"RMSE {self._SCORE_OR_LOSS_ICONS['rmse']}", multioutput=multioutput, @@ -914,6 +1140,13 @@ def custom_metric( The name of the metric. If not provided, it will be inferred from the metric function. + data_source : {"test", "train", "X_y"}, default="test" + The data source to use. + + - "test" : use the test set provided when creating the reporter. + - "train" : use the train set provided when creating the reporter. + - "X_y" : use the provided `X` and `y` to compute the metric. + X : array-like of shape (n_samples, n_features), default=None New data on which to compute the metric. By default, we use the validation set provided when creating the reporter. @@ -956,11 +1189,41 @@ def custom_metric( Metric MAE (↗︎) Ridge 44.9... """ + return self._custom_metric( + data_source=data_source, + data_source_hash=None, + X=X, + y=y, + metric_function=metric_function, + response_method=response_method, + metric_name=metric_name, + **kwargs, + ) + + def _custom_metric( + self, + *, + data_source="test", + data_source_hash=None, + X=None, + y=None, + metric_function=None, + response_method=None, + metric_name=None, + **kwargs, + ): + """Private interface of `custom_metric` to be able to pass `data_source_hash`. + + `data_source_hash` is either an `int` when we already computed the hash + and are able to pass it around or `None` and thus trigger its computation + in the underlying process. + """ return self._compute_metric_scores( metric_function, X=X, y_true=y, data_source=data_source, + data_source_hash=data_source_hash, response_method=response_method, metric_name=metric_name, **kwargs, diff --git a/skore/tests/unit/sklearn/test_estimator.py b/skore/tests/unit/sklearn/test_estimator.py index fa55dc2de..1459396ff 100644 --- a/skore/tests/unit/sklearn/test_estimator.py +++ b/skore/tests/unit/sklearn/test_estimator.py @@ -10,7 +10,14 @@ from sklearn.datasets import make_classification, make_regression from sklearn.ensemble import RandomForestClassifier from sklearn.linear_model import LinearRegression, LogisticRegression -from sklearn.metrics import make_scorer, median_absolute_error, r2_score, rand_score +from sklearn.metrics import ( + accuracy_score, + f1_score, + make_scorer, + median_absolute_error, + r2_score, + rand_score, +) from sklearn.model_selection import train_test_split from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler @@ -573,8 +580,13 @@ def _check_results_report_metrics(result, expected_metrics, expected_nb_stats): @pytest.mark.parametrize("pos_label, nb_stats", [(None, 2), (1, 1)]) +@pytest.mark.parametrize("data_source", ["test", "X_y"]) def test_estimator_report_report_metrics_binary( - binary_classification_data, binary_classification_data_svc, pos_label, nb_stats + binary_classification_data, + binary_classification_data_svc, + pos_label, + nb_stats, + data_source, ): """Check the behaviour of the `report_metrics` method with binary classification. We test both with an SVC that does not support `predict_proba` and a @@ -582,7 +594,10 @@ def test_estimator_report_report_metrics_binary( """ estimator, X_test, y_test = binary_classification_data report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) - result = report.metrics.report_metrics(pos_label=pos_label) + kwargs = {"X": X_test, "y": y_test} if data_source == "X_y" else {} + result = report.metrics.report_metrics( + pos_label=pos_label, data_source=data_source, **kwargs + ) expected_metrics = ("precision", "recall", "roc_auc", "brier_score") # depending on `pos_label`, we report a stats for each class or not for # precision and recall @@ -596,7 +611,10 @@ def test_estimator_report_report_metrics_binary( y_test = target_names[y_test] estimator = clone(estimator).fit(X_test, y_test) report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) - result = report.metrics.report_metrics(pos_label=pos_label_name) + kwargs = {"X": X_test, "y": y_test} if data_source == "X_y" else {} + result = report.metrics.report_metrics( + pos_label=pos_label_name, data_source=data_source, **kwargs + ) expected_metrics = ("precision", "recall", "roc_auc", "brier_score") # depending on `pos_label`, we report a stats for each class or not for # precision and recall @@ -605,7 +623,10 @@ def test_estimator_report_report_metrics_binary( estimator, X_test, y_test = binary_classification_data_svc report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) - result = report.metrics.report_metrics(pos_label=pos_label) + kwargs = {"X": X_test, "y": y_test} if data_source == "X_y" else {} + result = report.metrics.report_metrics( + pos_label=pos_label, data_source=data_source, **kwargs + ) expected_metrics = ("precision", "recall", "roc_auc") # depending on `pos_label`, we report a stats for each class or not for # precision and recall @@ -613,15 +634,17 @@ def test_estimator_report_report_metrics_binary( _check_results_report_metrics(result, expected_metrics, expected_nb_stats) +@pytest.mark.parametrize("data_source", ["test", "X_y"]) def test_estimator_report_report_metrics_multiclass( - multiclass_classification_data, multiclass_classification_data_svc + multiclass_classification_data, multiclass_classification_data_svc, data_source ): """Check the behaviour of the `report_metrics` method with multiclass classification. """ estimator, X_test, y_test = multiclass_classification_data report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) - result = report.metrics.report_metrics() + kwargs = {"X": X_test, "y": y_test} if data_source == "X_y" else {} + result = report.metrics.report_metrics(data_source=data_source, **kwargs) expected_metrics = ("precision", "recall", "roc_auc", "log_loss") # since we are not averaging by default, we report 3 statistics for # precision, recall and roc_auc @@ -630,7 +653,8 @@ def test_estimator_report_report_metrics_multiclass( estimator, X_test, y_test = multiclass_classification_data_svc report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) - result = report.metrics.report_metrics() + kwargs = {"X": X_test, "y": y_test} if data_source == "X_y" else {} + result = report.metrics.report_metrics(data_source=data_source, **kwargs) expected_metrics = ("precision", "recall") # since we are not averaging by default, we report 3 statistics for # precision and recall @@ -638,11 +662,13 @@ def test_estimator_report_report_metrics_multiclass( _check_results_report_metrics(result, expected_metrics, expected_nb_stats) -def test_estimator_report_report_metrics_regression(regression_data): +@pytest.mark.parametrize("data_source", ["test", "X_y"]) +def test_estimator_report_report_metrics_regression(regression_data, data_source): """Check the behaviour of the `report_metrics` method with regression.""" estimator, X_test, y_test = regression_data + kwargs = {"X": X_test, "y": y_test} if data_source == "X_y" else {} report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) - result = report.metrics.report_metrics() + result = report.metrics.report_metrics(data_source=data_source, **kwargs) expected_metrics = ("r2", "rmse") _check_results_report_metrics(result, expected_metrics, len(expected_metrics)) @@ -753,6 +779,18 @@ def custom_metric(y_true, y_pred, threshold=0.5): ) +@pytest.mark.parametrize("scoring", ["public_metric", "_private_metric"]) +def test_estimator_report_report_metrics_error_scoring_strings( + regression_data, scoring +): + """Check that we raise an error if a scoring string is not a valid metric.""" + estimator, X_test, y_test = regression_data + report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) + err_msg = re.escape(f"Invalid metric: {scoring!r}.") + with pytest.raises(ValueError, match=err_msg): + report.metrics.report_metrics(scoring=[scoring]) + + def test_estimator_report_custom_function_kwargs_numpy_array(regression_data): """Check that we are able to store a hash of a numpy array in the cache when they are passed as kwargs. @@ -845,6 +883,66 @@ def custom_metric(y_true, y_pred, some_weights): ) +def test_estimator_report_custom_metric_compatible_estimator( + binary_classification_data, +): + """Check that the estimator report still works if an estimator has a compatible + scikit-learn API. + """ + _, X_test, y_test = binary_classification_data + + class CompatibleEstimator: + """Estimator exposing only a predict method but it should be enough for the + reporters. + """ + + def fit(self, X, y): + self.fitted_ = True + return self + + def predict(self, X): + return np.ones(X.shape[0]) + + estimator = CompatibleEstimator() + report = EstimatorReport(estimator, fit=False, X_test=X_test, y_test=y_test) + result = report.metrics.custom_metric( + metric_function=lambda y_true, y_pred: 1, + metric_name="Custom Metric", + response_method="predict", + ) + assert result.columns.tolist() == ["Custom Metric"] + assert result.to_numpy()[0, 0] == 1 + + +def test_estimator_report_report_metrics_with_scorer_binary_classification( + binary_classification_data, +): + """Check that we can pass scikit-learn scorer with different parameters to + the `report_metrics` method.""" + estimator, X_test, y_test = binary_classification_data + report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) + + f1_scorer = make_scorer( + f1_score, response_method="predict", average="macro", pos_label=1 + ) + result = report.metrics.report_metrics( + scoring=["accuracy", accuracy_score, f1_scorer], + ) + assert result.shape == (1, 3) + np.testing.assert_allclose( + result.to_numpy(), + [ + [ + accuracy_score(y_test, estimator.predict(X_test)), + accuracy_score(y_test, estimator.predict(X_test)), + f1_score( + y_test, estimator.predict(X_test), average="macro", pos_label=1 + ), + ] + ], + ) + + def test_estimator_report_report_metrics_invalid_metric_type(regression_data): """Check that we raise the expected error message if an invalid metric is passed.""" estimator, X_test, y_test = regression_data From 707525226497456cc4cbd35ba2b2b1f6eb3bef96 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Wed, 15 Jan 2025 16:15:06 +0100 Subject: [PATCH 09/27] feat: Allow for nested progress bar (#1106) This PR is in anticipation of #1091 but is already in use and tested: - we use a progress in standalone mode in `EstimatorReport.cache_predictions` - we test the error handling and nested mode in a specific tests (and also standalone) The nested mode will come when cross-validation is in. --------- Co-authored-by: Auguste Baum <52001167+augustebaum@users.noreply.github.com> --- skore/src/skore/__init__.py | 46 ++++++ skore/src/skore/sklearn/_estimator/report.py | 18 ++- skore/src/skore/utils/_progress_bar.py | 74 +++++++++ skore/tests/unit/utils/test_progress_bar.py | 155 +++++++++++++++++++ 4 files changed, 285 insertions(+), 8 deletions(-) create mode 100644 skore/src/skore/utils/_progress_bar.py create mode 100644 skore/tests/unit/utils/test_progress_bar.py diff --git a/skore/src/skore/__init__.py b/skore/src/skore/__init__.py index 135bd0947..d63f16905 100644 --- a/skore/src/skore/__init__.py +++ b/skore/src/skore/__init__.py @@ -1,7 +1,9 @@ """Configure logging and global settings.""" import logging +import os +from rich import jupyter from rich.console import Console from rich.theme import Theme @@ -18,6 +20,50 @@ "train_test_split", ] +######################################################################################## +# FIXME: This is to make sure that the CSS style exposed by jupyter notebook and used +# by ipywidgets is applied to rich output. +# Currently, the VS Code extension does not support the CSS style exposed by jupyter +# notebook and used by ipywidgets. +# We should track the progress in the following issue: +# https://github.com/microsoft/vscode-jupyter/issues/7161 +######################################################################################## + +# Store the original display function +original_display = jupyter.display + + +def patched_display(segments, text): + """Patched version of rich.jupyter.display that includes VS Code styling.""" + # Call the original display function first + original_display(segments, text) + + # Apply VS Code styling if we're in VS Code + if "VSCODE_PID" in os.environ: + from IPython.display import HTML, display + + css = """ + + """ + display(HTML(css)) + + +# Patch the display function +jupyter.display = patched_display + +######################################################################################## +# End of the temporary patch +######################################################################################## + + logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) # Default to no output logger.setLevel(logging.INFO) diff --git a/skore/src/skore/sklearn/_estimator/report.py b/skore/src/skore/sklearn/_estimator/report.py index 19e398b05..7bf3f75cf 100644 --- a/skore/src/skore/sklearn/_estimator/report.py +++ b/skore/src/skore/sklearn/_estimator/report.py @@ -5,7 +5,6 @@ import joblib import numpy as np -from rich.progress import track from sklearn.base import clone from sklearn.exceptions import NotFittedError from sklearn.pipeline import Pipeline @@ -15,6 +14,7 @@ from skore.externals._sklearn_compat import is_clusterer from skore.sklearn._base import _BaseReport, _get_cached_response_values from skore.sklearn.find_ml_task import _find_ml_task +from skore.utils._progress_bar import progress_decorator class EstimatorReport(_BaseReport, DirNamesMixin): @@ -101,6 +101,8 @@ def __init__( X_test=None, y_test=None, ): + self._parent_progress = None # used to display progress bar + if fit == "auto": try: check_is_fitted(estimator) @@ -162,6 +164,7 @@ def clear_cache(self): """ self._cache = {} + @progress_decorator(description="Caching predictions") def cache_predictions(self, response_methods="auto", n_jobs=None): """Cache estimator's predictions. @@ -232,13 +235,12 @@ def cache_predictions(self, response_methods="auto", n_jobs=None): ) ) # trigger the computation - list( - track( - generator, - total=len(response_methods) * len(pos_labels) * len(data_sources), - description="Caching predictions", - ) - ) + progress = self._progress_info["current_progress"] + task = self._progress_info["current_task"] + total_iterations = len(response_methods) * len(pos_labels) * len(data_sources) + progress.update(task, total=total_iterations) + for _ in generator: + progress.update(task, advance=1, refresh=True) @property def estimator(self): diff --git a/skore/src/skore/utils/_progress_bar.py b/skore/src/skore/utils/_progress_bar.py new file mode 100644 index 000000000..aac71343f --- /dev/null +++ b/skore/src/skore/utils/_progress_bar.py @@ -0,0 +1,74 @@ +from functools import wraps + +from rich.progress import ( + BarColumn, + Progress, + SpinnerColumn, + TextColumn, +) + + +def progress_decorator(description): + """Decorate class methods to add a progress bar. + + Parameters + ---------- + description : str or callable + The description of the progress bar. If a callable, it should take the + self object as an argument and return a string. + + Returns + ------- + decorator : function + A decorator that wraps the input function and adds a progress bar to it. + """ + + def decorator(func): + @wraps(func) + def wrapper(*args, **kwargs): + self_obj = args[0] + + desc = description(self_obj) if callable(description) else description + + if getattr(self_obj, "_parent_progress", None) is not None: + progress = self_obj._parent_progress + else: + progress = Progress( + SpinnerColumn(), + TextColumn("[bold cyan]{task.description}"), + BarColumn( + complete_style="dark_orange", + finished_style="dark_orange", + pulse_style="orange1", + ), + TextColumn("[orange1]{task.percentage:>3.0f}%"), + expand=False, + ) + progress.start() + + task = progress.add_task(desc, total=None) + self_obj._progress_info = { + "current_progress": progress, + "current_task": task, + } + has_errored = False + try: + result = func(*args, **kwargs) + progress.update( + task, completed=progress.tasks[task].total, refresh=True + ) + return result + except Exception: + has_errored = True + raise + finally: + if self_obj._parent_progress is None: + if not has_errored: + progress.update( + task, completed=progress.tasks[task].total, refresh=True + ) + progress.stop() + + return wrapper + + return decorator diff --git a/skore/tests/unit/utils/test_progress_bar.py b/skore/tests/unit/utils/test_progress_bar.py new file mode 100644 index 000000000..0de7b5f45 --- /dev/null +++ b/skore/tests/unit/utils/test_progress_bar.py @@ -0,0 +1,155 @@ +import pytest +from rich.progress import Progress +from skore.utils._progress_bar import progress_decorator + + +def test_standalone_progress(): + """Check the general behavior of the progress bar when used standalone.""" + + class StandaloneTask: + def __init__(self): + self._parent_progress = None + self._progress_info = None + + @progress_decorator("Standalone Task") + def run(self, iterations=5): + progress = self._progress_info["current_progress"] + task = self._progress_info["current_task"] + progress.update(task, total=iterations) + + for _ in range(iterations): + progress.update(task, advance=1) + return "done" + + task = StandaloneTask() + result = task.run() + + assert result == "done" + assert task._progress_info is not None + assert isinstance(task._progress_info["current_progress"], Progress) + assert task._parent_progress is None + assert ( + task._progress_info["current_progress"] + .tasks[task._progress_info["current_task"]] + .completed + == 5 + ) + + +def test_nested_progress(): + """Check that we can nest progress bars.""" + + class ParentTask: + def __init__(self): + self._parent_progress = None + self._progress_info = None + + @progress_decorator("Parent Task") + def run(self, iterations=3): + progress = self._progress_info["current_progress"] + task = self._progress_info["current_task"] + progress.update(task, total=iterations) + + child = ChildTask(self._progress_info["current_progress"]) + for _ in range(iterations): + child.run() + progress.update(task, advance=1) + return "done" + + class ChildTask: + def __init__(self, parent_progress): + self._parent_progress = parent_progress + self._progress_info = None + + @progress_decorator("Child Task") + def run(self, iterations=2): + progress = self._progress_info["current_progress"] + task = self._progress_info["current_task"] + progress.update(task, total=iterations) + + for _ in range(iterations): + progress.update(task, advance=1) + return "done" + + parent = ParentTask() + result = parent.run() + + assert result == "done" + assert parent._progress_info is not None + assert isinstance(parent._progress_info["current_progress"], Progress) + assert ( + parent._progress_info["current_progress"] + .tasks[parent._progress_info["current_task"]] + .completed + == 3 + ) + + +def test_dynamic_description(): + """Check that we can pass a dynamic description using `self` when calling the + decorator.""" + + class DynamicTask: + def __init__(self, name): + self._parent_progress = None + self._progress_info = None + self.name = name + + @progress_decorator(lambda self: f"Processing {self.name}") + def run(self, iterations=4): + progress = self._progress_info["current_progress"] + task = self._progress_info["current_task"] + progress.update(task, total=iterations) + + for _ in range(iterations): + progress.update(task, advance=1) + return self.name + + task = DynamicTask("test_task") + result = task.run() + + assert result == "test_task" + assert task._progress_info is not None + assert ( + task._progress_info["current_progress"] + .tasks[task._progress_info["current_task"]] + .description + == "Processing test_task" + ) + assert ( + task._progress_info["current_progress"] + .tasks[task._progress_info["current_task"]] + .completed + == 4 + ) + + +def test_exception_handling(): + """Check that the progress bar stops during an exception but in a clean way.""" + + class ErrorTask: + def __init__(self): + self._parent_progress = None + self._progress_info = None + + @progress_decorator("Error Task") + def run(self): + progress = self._progress_info["current_progress"] + task = self._progress_info["current_task"] + progress.update(task, total=3) + + progress.update(task, advance=1) + raise ValueError("Test error") + + task = ErrorTask() + with pytest.raises(ValueError, match="Test error"): + task.run() + + # Verify progress bar was cleaned up + assert task._progress_info is not None + assert ( + task._progress_info["current_progress"] + .tasks[task._progress_info["current_task"]] + .completed + == 1 + ) From 07d188f913a4479180ddba23e2c9b62350264a35 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Wed, 15 Jan 2025 19:45:43 +0100 Subject: [PATCH 10/27] fix: Support passing pos_label as scorer param or as method param (#1126) This PR makes sure that we can pass `pos_label` either as a parameter to a scikit-learn scorer or as a parameter of the skore function. If both are provided, we just make sure that the values of both input are coherent. Otherwise, we raise an informative error. --- .../model_evaluation/plot_estimator_report.py | 5 +- .../sklearn/_estimator/metrics_accessor.py | 11 ++++- skore/tests/unit/sklearn/test_estimator.py | 49 ++++++++++++++++--- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/examples/model_evaluation/plot_estimator_report.py b/examples/model_evaluation/plot_estimator_report.py index 33929d497..044ec1f85 100644 --- a/examples/model_evaluation/plot_estimator_report.py +++ b/examples/model_evaluation/plot_estimator_report.py @@ -306,10 +306,7 @@ def operational_decision_cost(y_true, y_pred, amount): from sklearn.metrics import make_scorer, f1_score f1_scorer = make_scorer( - f1_score, - response_method="predict", - metric_name="F1 Score", - pos_label=pos_label, + f1_score, response_method="predict", metric_name="F1 Score", pos_label=pos_label ) operational_decision_cost_scorer = make_scorer( operational_decision_cost, diff --git a/skore/src/skore/sklearn/_estimator/metrics_accessor.py b/skore/src/skore/sklearn/_estimator/metrics_accessor.py index 941e3d5e7..0c2841baa 100644 --- a/skore/src/skore/sklearn/_estimator/metrics_accessor.py +++ b/skore/src/skore/sklearn/_estimator/metrics_accessor.py @@ -156,7 +156,16 @@ def report_metrics( metrics_kwargs["data_source_hash"] = data_source_hash metrics_params = inspect.signature(metric._score_func).parameters if "pos_label" in metrics_params: - metrics_kwargs["pos_label"] = pos_label + if pos_label is not None and "pos_label" in metrics_kwargs: + if pos_label != metrics_kwargs["pos_label"]: + raise ValueError( + "`pos_label` is passed both in the scorer and to the " + "`report_metrics` method. Please provide a consistent " + "`pos_label` or only pass it whether in the scorer or " + "to the `report_metrics` method." + ) + elif pos_label is not None: + metrics_kwargs["pos_label"] = pos_label elif isinstance(metric, str) or callable(metric): if isinstance(metric, str): err_msg = ( diff --git a/skore/tests/unit/sklearn/test_estimator.py b/skore/tests/unit/sklearn/test_estimator.py index 1459396ff..bd8336d19 100644 --- a/skore/tests/unit/sklearn/test_estimator.py +++ b/skore/tests/unit/sklearn/test_estimator.py @@ -914,19 +914,38 @@ def predict(self, X): assert result.to_numpy()[0, 0] == 1 +@pytest.mark.parametrize( + "scorer, pos_label", + [ + ( + make_scorer( + f1_score, response_method="predict", average="macro", pos_label=1 + ), + 1, + ), + ( + make_scorer( + f1_score, response_method="predict", average="macro", pos_label=1 + ), + None, + ), + (make_scorer(f1_score, response_method="predict", average="macro"), 1), + ], +) def test_estimator_report_report_metrics_with_scorer_binary_classification( - binary_classification_data, + binary_classification_data, scorer, pos_label ): """Check that we can pass scikit-learn scorer with different parameters to - the `report_metrics` method.""" + the `report_metrics` method. + + We also check that we can pass `pos_label` whether to the scorer or to the + `report_metrics` method or consistently to both. + """ estimator, X_test, y_test = binary_classification_data report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) - f1_scorer = make_scorer( - f1_score, response_method="predict", average="macro", pos_label=1 - ) result = report.metrics.report_metrics( - scoring=["accuracy", accuracy_score, f1_scorer], + scoring=["accuracy", accuracy_score, scorer], ) assert result.shape == (1, 3) np.testing.assert_allclose( @@ -943,6 +962,24 @@ def test_estimator_report_report_metrics_with_scorer_binary_classification( ) +def test_estimator_report_report_metrics_with_scorer_pos_label_error( + binary_classification_data, +): + """Check that we raise an error when pos_label is passed both in the scorer and + globally conducting to a mismatch.""" + estimator, X_test, y_test = binary_classification_data + report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) + + f1_scorer = make_scorer( + f1_score, response_method="predict", average="macro", pos_label=1 + ) + err_msg = re.escape( + "`pos_label` is passed both in the scorer and to the `report_metrics` method." + ) + with pytest.raises(ValueError, match=err_msg): + report.metrics.report_metrics(scoring=[f1_scorer], pos_label=0) + + def test_estimator_report_report_metrics_invalid_metric_type(regression_data): """Check that we raise the expected error message if an invalid metric is passed.""" estimator, X_test, y_test = regression_data From e9b34a6634bbf72a501400388af596306fad0250 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Wed, 15 Jan 2025 20:16:23 +0100 Subject: [PATCH 11/27] feat: Add scoring_names to overwrite the column of the score dataframe (#1114) closes #1111 Add a `scoring_names` parameter allowing to overwrite the columns of dataframe returned by the `report_metrics`. Build upon #1109 that expose some private functions for our internal metrics. It is nice because we can therefore expose as well the `metric_name` consistently in both `_custom_metric` and our defined metric. --- .../sklearn/_estimator/metrics_accessor.py | 109 ++++++++++++++++-- skore/tests/unit/sklearn/test_estimator.py | 40 +++++++ 2 files changed, 137 insertions(+), 12 deletions(-) diff --git a/skore/src/skore/sklearn/_estimator/metrics_accessor.py b/skore/src/skore/sklearn/_estimator/metrics_accessor.py index 0c2841baa..934fc4fcd 100644 --- a/skore/src/skore/sklearn/_estimator/metrics_accessor.py +++ b/skore/src/skore/sklearn/_estimator/metrics_accessor.py @@ -52,6 +52,7 @@ def report_metrics( X=None, y=None, scoring=None, + scoring_names=None, pos_label=None, scoring_kwargs=None, ): @@ -83,6 +84,10 @@ def report_metrics( same parameter name with different values), you can use scikit-learn scorers as provided by :func:`sklearn.metrics.make_scorer`. + scoring_names : list of str, default=None + Used to overwrite the default scoring names in the report. It should be of + the same length as the `scoring` parameter. + pos_label : int, float, bool or str, default=None The positive class. @@ -126,6 +131,7 @@ def report_metrics( else: data_source_hash = None + scoring_was_none = scoring is None if scoring is None: # Equivalent to _get_scorers_to_add if self._parent._ml_task == "binary-classification": @@ -139,9 +145,26 @@ def report_metrics( else: scoring = ["_r2", "_rmse"] - scores = [] + if scoring_names is not None and len(scoring_names) != len(scoring): + if scoring_was_none: + # we raise a better error message since we decide the default scores + raise ValueError( + "The `scoring_names` parameter should be of the same length as " + "the `scoring` parameter. In your case, `scoring` was set to None " + f"and you are using our default scores that are {len(scoring)}. " + "The list is the following: {scoring}." + ) + else: + raise ValueError( + "The `scoring_names` parameter should be of the same length as " + f"the `scoring` parameter. Got {len(scoring_names)} names for " + f"{len(scoring)} scoring functions." + ) + elif scoring_names is None: + scoring_names = [None] * len(scoring) - for metric in scoring: + scores = [] + for metric_name, metric in zip(scoring_names, scoring): # NOTE: we have to check specifically for `_BaseScorer` first because this # is also a callable but it has a special private API that we can leverage if isinstance(metric, _BaseScorer): @@ -210,6 +233,8 @@ def report_metrics( f"Invalid type of metric: {type(metric)} for {metric!r}" ) + if metric_name is not None: + metrics_kwargs["metric_name"] = metric_name scores.append( metric_fn( data_source=data_source, @@ -387,13 +412,26 @@ def accuracy(self, *, data_source="test", X=None, y=None): """ return self._accuracy(data_source=data_source, data_source_hash=None, X=X, y=y) - def _accuracy(self, *, data_source="test", data_source_hash=None, X=None, y=None): + def _accuracy( + self, + *, + data_source="test", + data_source_hash=None, + X=None, + y=None, + metric_name=None, + ): """Private interface of `accuracy` to be able to pass `data_source_hash`. `data_source_hash` is either an `int` when we already computed the hash and are able to pass it around or `None` and thus trigger its computation in the underlying process. + + `metric_name` allows to overwrite the default name of the metric in the report. """ + if metric_name is None: + metric_name = f"Accuracy {self._SCORE_OR_LOSS_ICONS['accuracy']}" + return self._compute_metric_scores( metrics.accuracy_score, X=X, @@ -401,7 +439,7 @@ def _accuracy(self, *, data_source="test", data_source_hash=None, X=None, y=None data_source=data_source, data_source_hash=data_source_hash, response_method="predict", - metric_name=f"Accuracy {self._SCORE_OR_LOSS_ICONS['accuracy']}", + metric_name=metric_name, ) @available_if( @@ -501,6 +539,7 @@ def _precision( data_source_hash=None, X=None, y=None, + metric_name=None, average=None, pos_label=None, ): @@ -509,12 +548,17 @@ def _precision( `data_source_hash` is either an `int` when we already computed the hash and are able to pass it around or `None` and thus trigger its computation in the underlying process. + + `metric_name` allows to overwrite the default name of the metric in the report. """ if self._parent._ml_task == "binary-classification" and pos_label is not None: # if `pos_label` is specified by our user, then we can safely report only # the statistics of the positive class average = "binary" + if metric_name is None: + metric_name = f"Precision {self._SCORE_OR_LOSS_ICONS['precision']}" + return self._compute_metric_scores( metrics.precision_score, X=X, @@ -523,7 +567,7 @@ def _precision( data_source_hash=data_source_hash, response_method="predict", pos_label=pos_label, - metric_name=f"Precision {self._SCORE_OR_LOSS_ICONS['precision']}", + metric_name=metric_name, average=average, ) @@ -625,6 +669,7 @@ def _recall( data_source_hash=None, X=None, y=None, + metric_name=None, average=None, pos_label=None, ): @@ -633,12 +678,17 @@ def _recall( `data_source_hash` is either an `int` when we already computed the hash and are able to pass it around or `None` and thus trigger its computation in the underlying process. + + `metric_name` allows to overwrite the default name of the metric in the report. """ if self._parent._ml_task == "binary-classification" and pos_label is not None: # if `pos_label` is specified by our user, then we can safely report only # the statistics of the positive class average = "binary" + if metric_name is None: + metric_name = f"Recall {self._SCORE_OR_LOSS_ICONS['recall']}" + return self._compute_metric_scores( metrics.recall_score, X=X, @@ -647,7 +697,7 @@ def _recall( data_source_hash=data_source_hash, response_method="predict", pos_label=pos_label, - metric_name=f"Recall {self._SCORE_OR_LOSS_ICONS['recall']}", + metric_name=metric_name, average=average, ) @@ -708,18 +758,29 @@ def brier_score(self, *, data_source="test", X=None, y=None): ) def _brier_score( - self, *, data_source="test", data_source_hash=None, X=None, y=None + self, + *, + data_source="test", + data_source_hash=None, + X=None, + y=None, + metric_name=None, ): """Private interface of `brier_score` to be able to pass `data_source_hash`. `data_source_hash` is either an `int` when we already computed the hash and are able to pass it around or `None` and thus trigger its computation in the underlying process. + + `metric_name` allows to overwrite the default name of the metric in the report. """ # The Brier score in scikit-learn request `pos_label` to ensure that the # integral encoding of `y_true` corresponds to the probabilities of the # `pos_label`. Since we get the predictions with `get_response_method`, we # can pass any `pos_label`, they will lead to the same result. + if metric_name is None: + metric_name = f"Brier score {self._SCORE_OR_LOSS_ICONS['brier_score']}" + return self._compute_metric_scores( metrics.brier_score_loss, X=X, @@ -727,7 +788,7 @@ def _brier_score( data_source=data_source, data_source_hash=data_source_hash, response_method="predict_proba", - metric_name=f"Brier score {self._SCORE_OR_LOSS_ICONS['brier_score']}", + metric_name=metric_name, pos_label=self._parent._estimator.classes_[-1], ) @@ -834,6 +895,7 @@ def _roc_auc( data_source_hash=None, X=None, y=None, + metric_name=None, average=None, multi_class="ovr", ): @@ -842,7 +904,12 @@ def _roc_auc( `data_source_hash` is either an `int` when we already computed the hash and are able to pass it around or `None` and thus trigger its computation in the underlying process. + + `metric_name` allows to overwrite the default name of the metric in the report. """ + if metric_name is None: + metric_name = f"ROC AUC {self._SCORE_OR_LOSS_ICONS['roc_auc']}" + return self._compute_metric_scores( metrics.roc_auc_score, X=X, @@ -850,7 +917,7 @@ def _roc_auc( data_source=data_source, data_source_hash=data_source_hash, response_method=["predict_proba", "decision_function"], - metric_name=f"ROC AUC {self._SCORE_OR_LOSS_ICONS['roc_auc']}", + metric_name=metric_name, average=average, multi_class=multi_class, ) @@ -913,13 +980,19 @@ def _log_loss( data_source_hash=None, X=None, y=None, + metric_name=None, ): """Private interface of `log_loss` to be able to pass `data_source_hash`. `data_source_hash` is either an `int` when we already computed the hash and are able to pass it around or `None` and thus trigger its computation in the underlying process. + + `metric_name` allows to overwrite the default name of the metric in the report. """ + if metric_name is None: + metric_name = f"Log loss {self._SCORE_OR_LOSS_ICONS['log_loss']}" + return self._compute_metric_scores( metrics.log_loss, X=X, @@ -927,7 +1000,7 @@ def _log_loss( data_source=data_source, data_source_hash=data_source_hash, response_method="predict_proba", - metric_name=f"Log loss {self._SCORE_OR_LOSS_ICONS['log_loss']}", + metric_name=metric_name, ) @available_if(_check_supported_ml_task(supported_ml_tasks=["regression"])) @@ -1002,6 +1075,7 @@ def _r2( data_source_hash=None, X=None, y=None, + metric_name=None, multioutput="raw_values", ): """Private interface of `r2` to be able to pass `data_source_hash`. @@ -1009,7 +1083,12 @@ def _r2( `data_source_hash` is either an `int` when we already computed the hash and are able to pass it around or `None` and thus trigger its computation in the underlying process. + + `metric_name` allows to overwrite the default name of the metric in the report. """ + if metric_name is None: + metric_name = f"R² {self._SCORE_OR_LOSS_ICONS['r2']}" + return self._compute_metric_scores( metrics.r2_score, X=X, @@ -1017,7 +1096,7 @@ def _r2( data_source=data_source, data_source_hash=data_source_hash, response_method="predict", - metric_name=f"R² {self._SCORE_OR_LOSS_ICONS['r2']}", + metric_name=metric_name, multioutput=multioutput, ) @@ -1093,6 +1172,7 @@ def _rmse( data_source_hash=None, X=None, y=None, + metric_name=None, multioutput="raw_values", ): """Private interface of `rmse` to be able to pass `data_source_hash`. @@ -1100,7 +1180,12 @@ def _rmse( `data_source_hash` is either an `int` when we already computed the hash and are able to pass it around or `None` and thus trigger its computation in the underlying process. + + `metric_name` allows to overwrite the default name of the metric in the report. """ + if metric_name is None: + metric_name = f"RMSE {self._SCORE_OR_LOSS_ICONS['rmse']}" + return self._compute_metric_scores( metrics.root_mean_squared_error, X=X, @@ -1108,7 +1193,7 @@ def _rmse( data_source=data_source, data_source_hash=data_source_hash, response_method="predict", - metric_name=f"RMSE {self._SCORE_OR_LOSS_ICONS['rmse']}", + metric_name=metric_name, multioutput=multioutput, ) diff --git a/skore/tests/unit/sklearn/test_estimator.py b/skore/tests/unit/sklearn/test_estimator.py index bd8336d19..55adf5341 100644 --- a/skore/tests/unit/sklearn/test_estimator.py +++ b/skore/tests/unit/sklearn/test_estimator.py @@ -694,6 +694,46 @@ def test_estimator_report_report_metrics_scoring_kwargs( assert result.columns.names == ["Metric", "Class label"] +@pytest.mark.parametrize( + "fixture_name, scoring_names, expected_columns", + [ + ("regression_data", ["R2", "RMSE"], ["R2", "RMSE"]), + ( + "multiclass_classification_data", + ["Precision", "Recall", "ROC AUC", "Log Loss"], + [ + "Precision", + "Precision", + "Precision", + "Recall", + "Recall", + "Recall", + "ROC AUC", + "ROC AUC", + "ROC AUC", + "Log Loss", + ], + ), + ], +) +def test_estimator_report_report_metrics_overwrite_scoring_names( + request, fixture_name, scoring_names, expected_columns +): + """Test that we can overwrite the scoring names in report_metrics.""" + estimator, X_test, y_test = request.getfixturevalue(fixture_name) + report = EstimatorReport(estimator, X_test=X_test, y_test=y_test) + result = report.metrics.report_metrics(scoring_names=scoring_names) + assert result.shape == (1, len(expected_columns)) + + # Get level 0 names if MultiIndex, otherwise get column names + result_columns = ( + result.columns.get_level_values(0).tolist() + if isinstance(result.columns, pd.MultiIndex) + else result.columns.tolist() + ) + assert result_columns == expected_columns + + def test_estimator_report_interaction_cache_metrics(regression_multioutput_data): """Check that the cache take into account the 'kwargs' of a metric.""" estimator, X_test, y_test = regression_multioutput_data From 8293adb505acf322fdee8ab45f806a25375282cd Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Thu, 16 Jan 2025 10:37:48 +0100 Subject: [PATCH 12/27] chore: Move CSS patch for ipywidget in VSCode in its module (#1127) This PR takes into account the [comment](https://github.com/probabl-ai/skore/pull/1106#discussion_r1916804468) of @rouk1 and make the patching more explicit and contained by having its own module. --- skore/src/skore/__init__.py | 50 +++------------------------------ skore/src/skore/utils/_patch.py | 42 +++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 46 deletions(-) create mode 100644 skore/src/skore/utils/_patch.py diff --git a/skore/src/skore/__init__.py b/skore/src/skore/__init__.py index d63f16905..7a5bf2e85 100644 --- a/skore/src/skore/__init__.py +++ b/skore/src/skore/__init__.py @@ -1,14 +1,13 @@ """Configure logging and global settings.""" import logging -import os -from rich import jupyter from rich.console import Console from rich.theme import Theme from skore.project import Project, open from skore.sklearn import CrossValidationReporter, EstimatorReport, train_test_split +from skore.utils._patch import setup_jupyter_display from skore.utils._show_versions import show_versions __all__ = [ @@ -20,54 +19,13 @@ "train_test_split", ] -######################################################################################## -# FIXME: This is to make sure that the CSS style exposed by jupyter notebook and used -# by ipywidgets is applied to rich output. -# Currently, the VS Code extension does not support the CSS style exposed by jupyter -# notebook and used by ipywidgets. -# We should track the progress in the following issue: -# https://github.com/microsoft/vscode-jupyter/issues/7161 -######################################################################################## - -# Store the original display function -original_display = jupyter.display - - -def patched_display(segments, text): - """Patched version of rich.jupyter.display that includes VS Code styling.""" - # Call the original display function first - original_display(segments, text) - - # Apply VS Code styling if we're in VS Code - if "VSCODE_PID" in os.environ: - from IPython.display import HTML, display - - css = """ - - """ - display(HTML(css)) - - -# Patch the display function -jupyter.display = patched_display - -######################################################################################## -# End of the temporary patch -######################################################################################## - - logger = logging.getLogger(__name__) logger.addHandler(logging.NullHandler()) # Default to no output logger.setLevel(logging.INFO) +# Configure jupyter display for VS Code compatibility +setup_jupyter_display() + skore_console_theme = Theme( { "repr.str": "cyan", diff --git a/skore/src/skore/utils/_patch.py b/skore/src/skore/utils/_patch.py new file mode 100644 index 000000000..765f8d884 --- /dev/null +++ b/skore/src/skore/utils/_patch.py @@ -0,0 +1,42 @@ +import os + +from rich import jupyter + +original_display = jupyter.display + + +def patched_display(segments, text): + """Patched version of rich.jupyter.display that includes VS Code styling. + + This is to make sure that the CSS style exposed by jupyter notebook and used + by ipywidgets is applied to rich output. + Currently, the VS Code extension does not support the CSS style exposed by jupyter + notebook and used by ipywidgets. + We should track the progress in the following issue: + https://github.com/microsoft/vscode-jupyter/issues/7161 + + """ + # Call the original display function first + original_display(segments, text) + + # Apply VS Code styling if we're in VS Code + if "VSCODE_PID" in os.environ: + from IPython.display import HTML, display + + css = """ + + """ + display(HTML(css)) + + +def setup_jupyter_display(): + """Configure the jupyter display to work properly in VS Code.""" + jupyter.display = patched_display From 848a915265987eb388d15f64cb94935b3f7b8a2e Mon Sep 17 00:00:00 2001 From: Auguste Baum <52001167+augustebaum@users.noreply.github.com> Date: Thu, 16 Jan 2025 10:49:11 +0100 Subject: [PATCH 13/27] fix(notes api): Make ItemRepository save notes properly (#1130) The storage API was not used correctly which resulted in not saving the notes properly; this affected `set_note` and `delete_note`. The issue was not detected in unit tests because they relied on an in-memory project, not a "real" on-disk project. --- skore/src/skore/item/item_repository.py | 12 ++- skore/tests/conftest.py | 7 ++ skore/tests/unit/project/test_notes.py | 119 ++++++++++++++---------- 3 files changed, 84 insertions(+), 54 deletions(-) diff --git a/skore/src/skore/item/item_repository.py b/skore/src/skore/item/item_repository.py index 96df231cb..b00d481ca 100644 --- a/skore/src/skore/item/item_repository.py +++ b/skore/src/skore/item/item_repository.py @@ -180,7 +180,9 @@ def set_item_note(self, key: str, message: str, *, version=-1): raise TypeError(f"Message should be a string; got {type(message)}") try: - self.storage[key][version]["item"]["note"] = message + old = self.storage[key] + old[version]["item"]["note"] = message + self.storage[key] = old except IndexError as e: raise KeyError((key, version)) from e @@ -202,7 +204,7 @@ def get_item_note(self, key: str, *, version=-1) -> Union[str, None]: Raises ------ KeyError - If no note is attached to the ``(key, version)`` couple. + If the ``(key, version)`` couple does not exist. """ try: return self.storage[key][version]["item"]["note"] @@ -223,9 +225,11 @@ def delete_item_note(self, key: str, *, version=-1): Raises ------ KeyError - If no note is attached to the ``(key, version)`` couple. + If the ``(key, version)`` couple does not exist. """ try: - self.storage[key][version]["item"]["note"] = None + old = self.storage[key] + old[version]["item"]["note"] = None + self.storage[key] = old except IndexError as e: raise KeyError((key, version)) from e diff --git a/skore/tests/conftest.py b/skore/tests/conftest.py index 82f8a2b48..3260b03bb 100644 --- a/skore/tests/conftest.py +++ b/skore/tests/conftest.py @@ -1,6 +1,7 @@ from datetime import datetime, timezone import pytest +import skore from skore.item.item_repository import ItemRepository from skore.persistence.in_memory_storage import InMemoryStorage from skore.project import Project @@ -46,6 +47,12 @@ def in_memory_project(): ) +@pytest.fixture +def on_disk_project(tmp_path): + project = skore.open(tmp_path / "project") + return project + + @pytest.fixture(scope="function") def pyplot(): """Setup and teardown fixture for matplotlib. diff --git a/skore/tests/unit/project/test_notes.py b/skore/tests/unit/project/test_notes.py index b885b6189..e2b3929dd 100644 --- a/skore/tests/unit/project/test_notes.py +++ b/skore/tests/unit/project/test_notes.py @@ -3,76 +3,95 @@ import pytest -def test_set_note(in_memory_project): - in_memory_project.put("key", "hello") - in_memory_project.set_note("key", "message") - assert in_memory_project.get_note("key") == "message" +@pytest.mark.parametrize( + "project_fixture", + [ + "in_memory_project", + "on_disk_project", + ], +) +class TestNotes: + def test_set_note(self, project_fixture, request): + project = request.getfixturevalue(project_fixture) + project.put("key", "hello") + project.set_note("key", "message") + assert project.get_note("key") == "message" -def test_set_note_version(in_memory_project): - """By default, `set_note` only attaches a message to the latest version - of a key.""" - in_memory_project.put("key", "hello") - in_memory_project.put("key", "goodbye") - in_memory_project.set_note("key", "message") - assert in_memory_project.get_note("key", version=-1) == "message" - assert in_memory_project.get_note("key", version=0) is None + def test_set_note_version(self, project_fixture, request): + """By default, `set_note` only attaches a message to the latest version + of a key.""" + project = request.getfixturevalue(project_fixture) + project.put("key", "hello") + project.put("key", "goodbye") + project.set_note("key", "message") + assert project.get_note("key", version=-1) == "message" + assert project.get_note("key", version=0) is None -def test_set_note_no_key(in_memory_project): - with pytest.raises(KeyError): - in_memory_project.set_note("key", "hello") + def test_set_note_no_key(self, project_fixture, request): + project = request.getfixturevalue(project_fixture) - in_memory_project.put("key", "hello") + with pytest.raises(KeyError): + project.set_note("key", "hello") - with pytest.raises(KeyError): - in_memory_project.set_note("key", "hello", version=10) + project.put("key", "hello") + with pytest.raises(KeyError): + project.set_note("key", "hello", version=10) -def test_set_note_not_strings(in_memory_project): - """If key or message is not a string, raise a TypeError.""" - with pytest.raises(TypeError): - in_memory_project.set_note(1, "hello") + def test_set_note_not_strings(self, project_fixture, request): + """If key or message is not a string, raise a TypeError.""" + project = request.getfixturevalue(project_fixture) - with pytest.raises(TypeError): - in_memory_project.set_note("key", 1) + with pytest.raises(TypeError): + project.set_note(1, "hello") + with pytest.raises(TypeError): + project.set_note("key", 1) -def test_delete_note(in_memory_project): - in_memory_project.put("key", "hello") - in_memory_project.set_note("key", "message") - in_memory_project.delete_note("key") - assert in_memory_project.get_note("key") is None + def test_delete_note(self, project_fixture, request): + project = request.getfixturevalue(project_fixture) + project.put("key", "hello") + project.set_note("key", "message") + project.delete_note("key") + assert project.get_note("key") is None -def test_delete_note_no_key(in_memory_project): - with pytest.raises(KeyError): - in_memory_project.delete_note("key") + def test_delete_note_no_key(self, project_fixture, request): + project = request.getfixturevalue(project_fixture) - in_memory_project.put("key", "hello") + with pytest.raises(KeyError): + project.delete_note("key") - with pytest.raises(KeyError): - in_memory_project.set_note("key", "hello", version=10) + project.put("key", "hello") + with pytest.raises(KeyError): + project.set_note("key", "hello", version=10) -def test_delete_note_no_note(in_memory_project): - in_memory_project.put("key", "hello") - assert in_memory_project.get_note("key") is None + def test_delete_note_no_note(self, project_fixture, request): + project = request.getfixturevalue(project_fixture) + project.put("key", "hello") + assert project.get_note("key") is None -def test_put_with_note(in_memory_project): - in_memory_project.put("key", "hello", note="note") - assert in_memory_project.get_note("key") == "note" + def test_put_with_note(self, project_fixture, request): + project = request.getfixturevalue(project_fixture) + project.put("key", "hello", note="note") + assert project.get_note("key") == "note" -def test_put_with_note_annotates_latest(in_memory_project): - """Adding the `note` argument annotates the latest version of the item.""" - in_memory_project.put("key", "hello") - in_memory_project.put("key", "goodbye", note="note") - assert in_memory_project.get_note("key", version=0) is None + def test_put_with_note_annotates_latest(self, project_fixture, request): + """Adding the `note` argument annotates the latest version of the item.""" + project = request.getfixturevalue(project_fixture) + project.put("key", "hello") + project.put("key", "goodbye", note="note") + assert project.get_note("key", version=0) is None -def test_put_with_note_wrong_type(in_memory_project): - """Adding the `note` argument annotates the latest version of the item.""" - with pytest.raises(TypeError): - in_memory_project.put("key", "hello", note=0) + def test_put_with_note_wrong_type(self, project_fixture, request): + """Adding the `note` argument annotates the latest version of the item.""" + project = request.getfixturevalue(project_fixture) + + with pytest.raises(TypeError): + project.put("key", "hello", note=0) From 3889f3282b37309c8b172eb12557b311893a9f2b Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Thu, 16 Jan 2025 10:51:27 +0100 Subject: [PATCH 14/27] refactor: Prepend with name of variable that we modify (#1128) closes #1118 As proposed in #1118. Add `_` at the end of the variables that we alter in the constructor to follow the scikit-learn convention. --- skore/src/skore/sklearn/_base.py | 4 ++-- skore/src/skore/sklearn/_base.pyi | 3 ++- .../sklearn/_estimator/metrics_accessor.py | 12 ++++++---- skore/src/skore/sklearn/_estimator/report.py | 14 +++++++---- skore/src/skore/sklearn/_estimator/utils.py | 2 +- skore/tests/unit/sklearn/test_base.py | 2 +- skore/tests/unit/sklearn/test_estimator.py | 24 +++++++++---------- 7 files changed, 35 insertions(+), 26 deletions(-) diff --git a/skore/src/skore/sklearn/_base.py b/skore/src/skore/sklearn/_base.py index a9a262a9e..e8089fe87 100644 --- a/skore/src/skore/sklearn/_base.py +++ b/skore/src/skore/sklearn/_base.py @@ -88,7 +88,7 @@ class _BaseReport(_HelpMixin): def _get_help_panel_title(self): return ( f"[bold cyan]Tools to diagnose estimator " - f"{self.estimator_name}[/bold cyan]" + f"{self.estimator_name_}[/bold cyan]" ) def _get_help_legend(self): @@ -230,7 +230,7 @@ def _get_X_y_and_data_source_hash(self, *, data_source, X=None, y=None): The hash of the data source. None when we are able to track the data, and thus relying on X_train, y_train, X_test, y_test. """ - is_cluster = is_clusterer(self._parent.estimator) + is_cluster = is_clusterer(self._parent.estimator_) if data_source == "test": if not (X is None or y is None): raise ValueError("X and y must be None when data_source is test.") diff --git a/skore/src/skore/sklearn/_base.pyi b/skore/src/skore/sklearn/_base.pyi index 155db3d05..5bc3ffd73 100644 --- a/skore/src/skore/sklearn/_base.pyi +++ b/skore/src/skore/sklearn/_base.pyi @@ -46,7 +46,8 @@ def _get_cached_response_values( class _BaseReport(_HelpMixin): _ACCESSOR_CONFIG: dict[str, dict[str, str]] - estimator_name: str + estimator_: BaseEstimator + estimator_name_: str _X_test: Optional[np.ndarray] _y_test: Optional[np.ndarray] _X_train: Optional[np.ndarray] diff --git a/skore/src/skore/sklearn/_estimator/metrics_accessor.py b/skore/src/skore/sklearn/_estimator/metrics_accessor.py index 934fc4fcd..1023ad83b 100644 --- a/skore/src/skore/sklearn/_estimator/metrics_accessor.py +++ b/skore/src/skore/sklearn/_estimator/metrics_accessor.py @@ -316,7 +316,7 @@ def _compute_metric_scores( y_pred = _get_cached_response_values( cache=self._parent._cache, estimator_hash=self._parent._hash, - estimator=self._parent.estimator, + estimator=self._parent.estimator_, X=X, response_method=response_method, pos_label=pos_label, @@ -357,7 +357,9 @@ def _compute_metric_scores( score = score.reshape(1, -1) else: # unknown task - try our best columns = [metric_name] if score.shape[0] == 1 else None - return pd.DataFrame(score, columns=columns, index=[self._parent.estimator_name]) + return pd.DataFrame( + score, columns=columns, index=[self._parent.estimator_name_] + ) @available_if( _check_supported_ml_task( @@ -1472,7 +1474,7 @@ def _get_display( y_pred = _get_cached_response_values( cache=self._parent._cache, estimator_hash=self._parent._hash, - estimator=self._parent.estimator, + estimator=self._parent.estimator_, X=X, response_method=response_method, data_source=data_source, @@ -1483,8 +1485,8 @@ def _get_display( display = display_class._from_predictions( y, y_pred, - estimator=self._parent.estimator, - estimator_name=self._parent.estimator_name, + estimator=self._parent.estimator_, + estimator_name=self._parent.estimator_name_, ml_task=self._parent._ml_task, data_source=data_source, **display_kwargs, diff --git a/skore/src/skore/sklearn/_estimator/report.py b/skore/src/skore/sklearn/_estimator/report.py index 7bf3f75cf..e34ce311b 100644 --- a/skore/src/skore/sklearn/_estimator/report.py +++ b/skore/src/skore/sklearn/_estimator/report.py @@ -48,6 +48,12 @@ class EstimatorReport(_BaseReport, DirNamesMixin): Attributes ---------- + estimator_ : estimator object + The cloned or copied estimator. + + estimator_name_ : str + The name of the estimator. + metrics : _MetricsAccessor Accessor for metrics-related operations. @@ -243,11 +249,11 @@ def cache_predictions(self, response_methods="auto", n_jobs=None): progress.update(task, advance=1, refresh=True) @property - def estimator(self): + def estimator_(self): return self._estimator - @estimator.setter - def estimator(self, value): + @estimator_.setter + def estimator_(self, value): raise AttributeError( "The estimator attribute is immutable. " f"Call the constructor of {self.__class__.__name__} to create a new report." @@ -294,7 +300,7 @@ def y_test(self, value): self._initialize_state() @property - def estimator_name(self): + def estimator_name_(self): if isinstance(self._estimator, Pipeline): name = self._estimator[-1].__class__.__name__ else: diff --git a/skore/src/skore/sklearn/_estimator/utils.py b/skore/src/skore/sklearn/_estimator/utils.py index 578fab610..aa89df041 100644 --- a/skore/src/skore/sklearn/_estimator/utils.py +++ b/skore/src/skore/sklearn/_estimator/utils.py @@ -3,7 +3,7 @@ def _check_supported_estimator(supported_estimators): def check(accessor): - estimator = accessor._parent.estimator + estimator = accessor._parent.estimator_ if isinstance(estimator, Pipeline): estimator = estimator.steps[-1][1] supported_estimator = isinstance(estimator, supported_estimators) diff --git a/skore/tests/unit/sklearn/test_base.py b/skore/tests/unit/sklearn/test_base.py index 0f6b12b91..53b4b84fe 100644 --- a/skore/tests/unit/sklearn/test_base.py +++ b/skore/tests/unit/sklearn/test_base.py @@ -186,7 +186,7 @@ def __init__(self, estimator, X_train=None, y_train=None, X_test=None, y_test=No self._y_test = y_test @property - def estimator(self): + def estimator_(self): return self._estimator @property diff --git a/skore/tests/unit/sklearn/test_estimator.py b/skore/tests/unit/sklearn/test_estimator.py index 55adf5341..ac7ba0249 100644 --- a/skore/tests/unit/sklearn/test_estimator.py +++ b/skore/tests/unit/sklearn/test_estimator.py @@ -113,7 +113,7 @@ def test_check_supported_estimator(): class MockParent: def __init__(self, estimator): - self.estimator = estimator + self.estimator_ = estimator class MockAccessor: def __init__(self, parent): @@ -170,8 +170,8 @@ def test_estimator_report_from_unfitted_estimator(fit): y_test=y_test, ) - check_is_fitted(report.estimator) - assert report.estimator is not estimator # the estimator should be cloned + check_is_fitted(report.estimator_) + assert report.estimator_ is not estimator # the estimator should be cloned assert report.X_train is X_train assert report.y_train is y_train @@ -180,7 +180,7 @@ def test_estimator_report_from_unfitted_estimator(fit): err_msg = "attribute is immutable" with pytest.raises(AttributeError, match=err_msg): - report.estimator = LinearRegression() + report.estimator_ = LinearRegression() with pytest.raises(AttributeError, match=err_msg): report.X_train = X_train with pytest.raises(AttributeError, match=err_msg): @@ -194,8 +194,8 @@ def test_estimator_report_from_fitted_estimator(binary_classification_data, fit) estimator, X, y = binary_classification_data report = EstimatorReport(estimator, fit=fit, X_test=X, y_test=y) - check_is_fitted(report.estimator) - assert isinstance(report.estimator, RandomForestClassifier) + check_is_fitted(report.estimator_) + assert isinstance(report.estimator_, RandomForestClassifier) assert report.X_train is None assert report.y_train is None assert report.X_test is X @@ -203,7 +203,7 @@ def test_estimator_report_from_fitted_estimator(binary_classification_data, fit) err_msg = "attribute is immutable" with pytest.raises(AttributeError, match=err_msg): - report.estimator = LinearRegression() + report.estimator_ = LinearRegression() with pytest.raises(AttributeError, match=err_msg): report.X_train = X with pytest.raises(AttributeError, match=err_msg): @@ -217,9 +217,9 @@ def test_estimator_report_from_fitted_pipeline(binary_classification_data_pipeli estimator, X, y = binary_classification_data_pipeline report = EstimatorReport(estimator, X_test=X, y_test=y) - check_is_fitted(report.estimator) - assert isinstance(report.estimator, Pipeline) - assert report.estimator_name == estimator[-1].__class__.__name__ + check_is_fitted(report.estimator_) + assert isinstance(report.estimator_, Pipeline) + assert report.estimator_name_ == estimator[-1].__class__.__name__ assert report.X_train is None assert report.y_train is None assert report.X_test is X @@ -1140,9 +1140,9 @@ def test_estimator_has_side_effects(prefit_estimator): y_test=y_test, ) - predictions_before = report.estimator.predict_proba(X_test) + predictions_before = report.estimator_.predict_proba(X_test) estimator.fit(X_test, y_test) - predictions_after = report.estimator.predict_proba(X_test) + predictions_after = report.estimator_.predict_proba(X_test) np.testing.assert_array_equal(predictions_before, predictions_after) From e6b5de827c7de2ef717c2824382c88b526bbd728 Mon Sep 17 00:00:00 2001 From: Auguste Baum <52001167+augustebaum@users.noreply.github.com> Date: Thu, 16 Jan 2025 13:17:40 +0100 Subject: [PATCH 15/27] ci: Avoid deadlock when computing coverage (#1134) Closes #1129 Co-authored-by: Guillaume Lemaitre --- .github/workflows/backend.yml | 2 +- skore/pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/backend.yml b/.github/workflows/backend.yml index 11360d694..44439b738 100644 --- a/.github/workflows/backend.yml +++ b/.github/workflows/backend.yml @@ -139,7 +139,7 @@ jobs: working-directory: skore/ run: | mkdir coverage - python -m pytest -n auto src/ tests/ --junitxml=coverage/coverage.xml --cov-config=pyproject.toml --cov | tee coverage/coverage.txt + python -m pytest src/ tests/ --junitxml=coverage/coverage.xml --cov-config=pyproject.toml --cov | tee coverage/coverage.txt - name: Upload coverage reports if: ${{ matrix.coverage && (github.event_name == 'pull_request') }} diff --git a/skore/pyproject.toml b/skore/pyproject.toml index 0af5534b3..37deb551e 100644 --- a/skore/pyproject.toml +++ b/skore/pyproject.toml @@ -115,7 +115,6 @@ doctest_optionflags = ["ELLIPSIS", "NORMALIZE_WHITESPACE"] [tool.coverage.run] branch = true -parallel = true source = ["skore"] [tool.coverage.report] From be29a2641839e6ae83831ca1210b278d7f515c2a Mon Sep 17 00:00:00 2001 From: Auguste Baum <52001167+augustebaum@users.noreply.github.com> Date: Thu, 16 Jan 2025 14:22:08 +0100 Subject: [PATCH 16/27] feat(_find_ml_task): Refine ML task detection logic (#1133) Closes #1080 Adds more logic in the case where `y` is detected as multiclass classification: we now additionally check whether `y` is a vector or sequential values that contains 0 (if so, we output "multiclass-classification", otherwise "regression"). This heuristic is suggested by @koaning. --------- Co-authored-by: Thomas S. --- skore/src/skore/sklearn/find_ml_task.py | 44 +++++++++++++++++++++++-- skore/tests/unit/sklearn/test_utils.py | 5 +++ 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/skore/src/skore/sklearn/find_ml_task.py b/skore/src/skore/sklearn/find_ml_task.py index 20d652acc..0af490d17 100644 --- a/skore/src/skore/sklearn/find_ml_task.py +++ b/skore/src/skore/sklearn/find_ml_task.py @@ -8,9 +8,19 @@ from skore.sklearn.types import MLTask +def _is_sequential(y) -> bool: + """Check whether ``y`` is vector of sequential integer values.""" + y_values = np.sort(np.unique(y)) + sequential = np.arange(y_values[0], y_values[-1] + 1) + return np.array_equal(y_values, sequential) + + def _find_ml_task(y, estimator=None) -> MLTask: """Guess the ML task being addressed based on a target array and an estimator. + This relies first on the estimator characteristics, and falls back on + analyzing ``y``. Check the examples for some of the heuristics relied on. + Parameters ---------- y : numpy.ndarray @@ -22,6 +32,26 @@ def _find_ml_task(y, estimator=None) -> MLTask: ------- MLTask The guess of the kind of ML task being performed. + + Examples + -------- + >>> import numpy + + # Discrete values, not sequential + >>> _find_ml_task(numpy.array([1, 5, 9])) + 'regression' + + # Discrete values, not sequential, containing 0 + >>> _find_ml_task(numpy.array([0, 1, 5, 9])) + 'regression' + + # Discrete sequential values, containing 0 + >>> _find_ml_task(numpy.array([0, 1, 2])) + 'multiclass-classification' + + # Discrete sequential values, not containing 0 + >>> _find_ml_task(numpy.array([1, 3, 2])) + 'regression' """ if estimator is not None: # checking the estimator is more robust and faster than checking the type of @@ -45,7 +75,12 @@ def _find_ml_task(y, estimator=None) -> MLTask: if target_type == "binary": return "binary-classification" if target_type == "multiclass": - return "multiclass-classification" + # If y is a vector of integers, type_of_target considers + # the task to be multiclass-classification. + # We refine this analysis a bit here. + if _is_sequential(y) and 0 in y: + return "multiclass-classification" + return "regression" return "unsupported" return "unsupported" else: @@ -60,5 +95,10 @@ def _find_ml_task(y, estimator=None) -> MLTask: if target_type == "binary": return "binary-classification" if target_type == "multiclass": - return "multiclass-classification" + # If y is a vector of integers, type_of_target considers + # the task to be multiclass-classification. + # We refine this analysis a bit here. + if _is_sequential(y) and 0 in y: + return "multiclass-classification" + return "regression" return "unsupported" diff --git a/skore/tests/unit/sklearn/test_utils.py b/skore/tests/unit/sklearn/test_utils.py index 9df684efc..631d64545 100644 --- a/skore/tests/unit/sklearn/test_utils.py +++ b/skore/tests/unit/sklearn/test_utils.py @@ -1,3 +1,4 @@ +import numpy import pytest from sklearn.cluster import KMeans from sklearn.datasets import ( @@ -53,6 +54,10 @@ def test_find_ml_task_with_estimator(X, y, estimator, expected_task, should_fit) (make_regression(n_samples=100, random_state=42)[1], "regression"), (None, "clustering"), (make_multilabel_classification(random_state=42)[1], "unsupported"), + (numpy.array([1, 5, 9]), "regression"), + (numpy.array([0, 1, 2]), "multiclass-classification"), + (numpy.array([1, 2, 3]), "regression"), + (numpy.array([0, 1, 5, 9]), "regression"), ], ) def test_find_ml_task_without_estimator(target, expected_task): From f473f143504f4ec478d4aced49dadec486ec0ef8 Mon Sep 17 00:00:00 2001 From: Auguste Baum <52001167+augustebaum@users.noreply.github.com> Date: Thu, 16 Jan 2025 15:23:33 +0100 Subject: [PATCH 17/27] fix(_find_ml_task): Deal with case where estimator is unfitted and `y` is None (#1136) Closes #1092 --- skore/src/skore/sklearn/find_ml_task.py | 3 +++ skore/tests/unit/sklearn/test_utils.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/skore/src/skore/sklearn/find_ml_task.py b/skore/src/skore/sklearn/find_ml_task.py index 0af490d17..7ba71f528 100644 --- a/skore/src/skore/sklearn/find_ml_task.py +++ b/skore/src/skore/sklearn/find_ml_task.py @@ -71,6 +71,9 @@ def _find_ml_task(y, estimator=None) -> MLTask: if estimator.classes_.size > 2: return "multiclass-classification" else: # fallback on the target + if y is None: + return "unsupported" + target_type = type_of_target(y) if target_type == "binary": return "binary-classification" diff --git a/skore/tests/unit/sklearn/test_utils.py b/skore/tests/unit/sklearn/test_utils.py index 631d64545..680b748d3 100644 --- a/skore/tests/unit/sklearn/test_utils.py +++ b/skore/tests/unit/sklearn/test_utils.py @@ -62,3 +62,10 @@ def test_find_ml_task_with_estimator(X, y, estimator, expected_task, should_fit) ) def test_find_ml_task_without_estimator(target, expected_task): assert _find_ml_task(target) == expected_task + + +def test_find_ml_task_unfitted_estimator(): + from sklearn.dummy import DummyClassifier + + estimator = DummyClassifier() + assert _find_ml_task(None, estimator) == "unsupported" From 9190e23c47bbf6d970ec03c254f36c8a6ac3bdc7 Mon Sep 17 00:00:00 2001 From: Guillaume Lemaitre Date: Thu, 16 Jan 2025 17:56:05 +0100 Subject: [PATCH 18/27] fix: Fix rendering in ipywidget by injecting CSS rule asap (#1139) closes #1132 The current patch was clean but not optimal: it was patching a public class. However, it involves calling `display()` once to create the ipywidget and then once for injection the CSS rule. I assume that the timing between the two displays call was actually the blinking observe. This PR patch the function creating the HTML code block for the widget and concatenate the CSS rule and thus involve a single call to display and remove the blinking. --- skore/src/skore/utils/_patch.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/skore/src/skore/utils/_patch.py b/skore/src/skore/utils/_patch.py index 765f8d884..819e485e5 100644 --- a/skore/src/skore/utils/_patch.py +++ b/skore/src/skore/utils/_patch.py @@ -2,11 +2,11 @@ from rich import jupyter -original_display = jupyter.display +_original_render_segments = jupyter._render_segments -def patched_display(segments, text): - """Patched version of rich.jupyter.display that includes VS Code styling. +def patched_render_segments(segments): + """Patched version of rich.jupyter._render_segments that includes VS Code styling. This is to make sure that the CSS style exposed by jupyter notebook and used by ipywidgets is applied to rich output. @@ -16,13 +16,9 @@ def patched_display(segments, text): https://github.com/microsoft/vscode-jupyter/issues/7161 """ - # Call the original display function first - original_display(segments, text) - + html = _original_render_segments(segments) # Apply VS Code styling if we're in VS Code if "VSCODE_PID" in os.environ: - from IPython.display import HTML, display - css = """ """ - display(HTML(css)) + html += css + + return html def setup_jupyter_display(): """Configure the jupyter display to work properly in VS Code.""" - jupyter.display = patched_display + jupyter._render_segments = patched_render_segments From bd4bd09f14204f66b4c5056f47b7f63c9e01f4d7 Mon Sep 17 00:00:00 2001 From: "Matt J." Date: Thu, 16 Jan 2025 18:14:09 +0100 Subject: [PATCH 19/27] feat(UI): Add markdown editor to annotate item (#1082) Part of #1041 - [x] upgrade dependencies (eslint 9 \o/) - [x] new icons - [x] minimal skinnable rich text editor - [x] expose python's `set_note` as a JSON API - [x] plug the API in the frontend ## UI preview https://github.com/user-attachments/assets/33cdf005-9ddd-42db-8059-c84c6a4a2433 ## implementation choices Finding a good and not too opinionated rich text editor is no easy job. After struggling with a few libs I decided that rolling a minimal editor may do the job (at least for now). The RichTextEditor component (probably badly named) tries to: - help user with basic markdown syntax - keep interactions button outside of it for best skinnability In a future work we may move to something more powerful. --- skore-ui/.eslintrc.cjs | 15 - skore-ui/eslint.config.js | 32 + skore-ui/index.html | 1 - skore-ui/package-lock.json | 3143 ++++++++++++----- skore-ui/package.json | 43 +- skore-ui/src/assets/fonts/icomoon.eot | Bin 10052 -> 10516 bytes skore-ui/src/assets/fonts/icomoon.svg | 89 +- skore-ui/src/assets/fonts/icomoon.ttf | Bin 9888 -> 10352 bytes skore-ui/src/assets/fonts/icomoon.woff | Bin 9964 -> 10428 bytes skore-ui/src/assets/styles/_icons.css | 98 +- skore-ui/src/assets/styles/_variables.css | 2 + .../CrossValidationReportResults.vue | 1 + skore-ui/src/components/FloatingTooltip.vue | 1 + skore-ui/src/components/ImageWidget.vue | 2 +- skore-ui/src/components/RichTextEditor.vue | 113 + skore-ui/src/components/SimpleButton.vue | 2 +- skore-ui/src/components/ToastNotification.vue | 2 +- skore-ui/src/components/TreeAccordionItem.vue | 4 +- skore-ui/src/dto.ts | 1 + skore-ui/src/models.ts | 2 + skore-ui/src/services/api.ts | 21 + skore-ui/src/services/utils.ts | 12 +- skore-ui/src/stores/project.ts | 17 +- skore-ui/src/views/ComponentsView.vue | 38 +- skore-ui/src/views/project/ItemNote.vue | 129 + skore-ui/src/views/project/ProjectView.vue | 9 +- skore-ui/tests/components/icons.spec.ts | 2 +- skore-ui/tests/services/api.spec.ts | 6 +- skore-ui/tests/stores/project.spec.ts | 3 +- skore-ui/tests/test.utils.ts | 1 + skore-ui/tsconfig.node.json | 2 +- skore-ui/vite.config.ts | 5 +- skore-ui/vitest.config.ts | 1 + skore-ui/vitest.setup.ts | 4 +- skore/src/skore/ui/project_routes.py | 19 + skore/tests/integration/ui/test_ui.py | 22 + 36 files changed, 2812 insertions(+), 1030 deletions(-) delete mode 100644 skore-ui/.eslintrc.cjs create mode 100644 skore-ui/eslint.config.js create mode 100644 skore-ui/src/components/RichTextEditor.vue create mode 100644 skore-ui/src/views/project/ItemNote.vue diff --git a/skore-ui/.eslintrc.cjs b/skore-ui/.eslintrc.cjs deleted file mode 100644 index bcdc223b4..000000000 --- a/skore-ui/.eslintrc.cjs +++ /dev/null @@ -1,15 +0,0 @@ -/* eslint-env node */ -require('@rushstack/eslint-patch/modern-module-resolution') - -module.exports = { - root: true, - extends: [ - 'eslint:recommended', - 'plugin:vue/vue3-essential', - '@vue/eslint-config-typescript', - '@vue/eslint-config-prettier/skip-formatting' - ], - parserOptions: { - ecmaVersion: 'latest' - } -} diff --git a/skore-ui/eslint.config.js b/skore-ui/eslint.config.js new file mode 100644 index 000000000..4ec6aa513 --- /dev/null +++ b/skore-ui/eslint.config.js @@ -0,0 +1,32 @@ +import pluginVitest from '@vitest/eslint-plugin' +import skipFormatting from '@vue/eslint-config-prettier/skip-formatting' +import vueTsEslintConfig from '@vue/eslint-config-typescript' +import pluginVue from 'eslint-plugin-vue' + +export default [ + { + name: 'app/files-to-lint', + files: ['**/*.{ts,mts,tsx,vue}'], + }, + + { + name: 'app/files-to-ignore', + ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**'], + }, + + ...pluginVue.configs['flat/essential'], + ...vueTsEslintConfig(), + + { + rules: { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-unsafe-function-type": "warn", + } + }, + + { + ...pluginVitest.configs.recommended, + files: ['src/**/__tests__/*'], + }, + skipFormatting, +] diff --git a/skore-ui/index.html b/skore-ui/index.html index 07f5bb907..9ff4361c5 100644 --- a/skore-ui/index.html +++ b/skore-ui/index.html @@ -6,7 +6,6 @@ - skore diff --git a/skore-ui/package-lock.json b/skore-ui/package-lock.json index 336deb407..b719e4d10 100644 --- a/skore-ui/package-lock.json +++ b/skore-ui/package-lock.json @@ -8,8 +8,8 @@ "name": "skore-ui", "version": "0.0.0", "dependencies": { - "@floating-ui/vue": "^1.1.5", - "@vscode/markdown-it-katex": "^1.1.0", + "@floating-ui/vue": "^1.1.6", + "@vscode/markdown-it-katex": "^1.1.1", "date-fns": "^4.1.0", "html-to-image": "^1.11.11", "interactjs": "^1.10.27", @@ -18,49 +18,50 @@ "markdown-it-highlightjs": "^4.2.0", "markdown-it-sub": "^2.0.0", "markdown-it-sup": "^2.0.0", - "pinia": "^2.2.7", - "plotly.js-dist-min": "^2.35.2", - "simplebar-vue": "^2.3.5", + "pinia": "^2.3.0", + "plotly.js-dist-min": "^2.35.3", + "simplebar-vue": "^2.4.0", "vega-embed": "^6.29.0", "vue": "^3.5.13", "vue-router": "^4.5.0" }, "devDependencies": { "@pinia/testing": "^0.1.7", - "@rushstack/eslint-patch": "^1.10.4", - "@tsconfig/node20": "^20.1.4", + "@tsconfig/node22": "^22.0.0", "@types/jsdom": "^21.1.7", "@types/markdown-it": "^14.1.2", "@types/markdown-it-emoji": "^3.0.1", "@types/markdown-it-highlightjs": "^3.3.4", - "@types/node": "^22.10.1", + "@types/node": "^22.10.2", "@types/plotly.js": "^2.35.1", "@types/plotly.js-dist-min": "^2.3.4", "@vitejs/plugin-vue": "^5.2.1", - "@vitest/coverage-v8": "^2.1.6", - "@vue/eslint-config-prettier": "^9.0.0", - "@vue/eslint-config-typescript": "^13.0.0", + "@vitest/coverage-v8": "^2.1.8", + "@vitest/eslint-plugin": "^1.1.24", + "@vue/eslint-config-prettier": "^10.1.0", + "@vue/eslint-config-typescript": "^14.1.3", "@vue/test-utils": "^2.4.6", "@vue/tsconfig": "^0.7.0", "autoprefixer": "^10.4.20", - "eslint": "^8.57.0", - "eslint-plugin-vue": "^9.31.0", + "eslint": "^9.14.0", + "eslint-plugin-vue": "^9.30.0", "jsdom": "^25.0.1", + "npm-run-all2": "^7.0.2", "postcss-html": "^1.7.0", "postcss-nesting": "^13.0.1", "postcss-sorting": "^9.1.0", - "prettier": "^3.4.1", - "stylelint": "^16.10.0", + "prettier": "^3.3.3", + "stylelint": "^16.12.0", "stylelint-config-idiomatic-order": "^10.0.0", "stylelint-config-recommended-vue": "^1.5.0", "stylelint-config-standard": "^36.0.1", "stylelint-order": "^6.0.4", - "typescript": "~5.6.2", - "vite": "^6.0.1", - "vite-plugin-vue-devtools": "^7.6.5", - "vitest": "^2.1.6", + "typescript": "~5.6.3", + "vite": "^6.0.5", + "vite-plugin-vue-devtools": "^7.6.8", + "vitest": "^2.1.8", "vitest-canvas-mock": "^0.3.3", - "vue-tsc": "^2.1.8" + "vue-tsc": "^2.1.10" } }, "node_modules/@ampproject/remapping": { @@ -110,9 +111,9 @@ "license": "MIT" }, "node_modules/@babel/compat-data": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz", - "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", "dev": true, "license": "MIT", "engines": { @@ -161,14 +162,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz", - "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.2", - "@babel/types": "^7.26.0", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" @@ -403,12 +404,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -513,9 +514,9 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz", - "integrity": "sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.3.tgz", + "integrity": "sha512-6+5hpdr6mETwSKjmJUdYw0EIkATiQhnELWlE3kJFBwSg/BGIVwVaVbX+gOXBCdc7Ln1RXZxyWGecIXhUfnl7oA==", "dev": true, "license": "MIT", "dependencies": { @@ -548,17 +549,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz", - "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==", + "version": "7.26.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.4.tgz", + "integrity": "sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.25.9", - "@babel/generator": "^7.25.9", - "@babel/parser": "^7.25.9", + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", "@babel/template": "^7.25.9", - "@babel/types": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -577,9 +578,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.25.9", @@ -597,9 +598,9 @@ "license": "MIT" }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.1.tgz", - "integrity": "sha512-lSquqZCHxDfuTg/Sk2hiS0mcSFCEBuj49JfzPHJogDBT0mGCyY5A1AQzBWngitrp7i1/HAZpIgzF/VjhOEIJIg==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.4.tgz", + "integrity": "sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==", "dev": true, "funding": [ { @@ -616,13 +617,13 @@ "node": ">=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.1" + "@csstools/css-tokenizer": "^3.0.3" } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.1.tgz", - "integrity": "sha512-UBqaiu7kU0lfvaP982/o3khfXccVlHPWp0/vwwiIgDF0GmqqqxoiXC/6FCjlS9u92f7CoEz6nXKQnrn1kIAkOw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.3.tgz", + "integrity": "sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==", "dev": true, "funding": [ { @@ -640,9 +641,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz", - "integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.2.tgz", + "integrity": "sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==", "dev": true, "funding": [ { @@ -659,31 +660,8 @@ "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.1", - "@csstools/css-tokenizer": "^3.0.1" - } - }, - "node_modules/@csstools/selector-specificity": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-4.0.0.tgz", - "integrity": "sha512-189nelqtPd8++phaHNwYovKZI0FOzH1vQEE3QhHHkNIGrg5fSs9CbYP3RvfEH5geztnIA9Jwq91wyOIwAW5JIQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "postcss-selector-parser": "^6.1.0" + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3" } }, "node_modules/@dual-bundle/import-meta-resolve": { @@ -697,9 +675,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", - "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", "cpu": [ "ppc64" ], @@ -714,9 +692,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", - "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", "cpu": [ "arm" ], @@ -731,9 +709,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", - "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", "cpu": [ "arm64" ], @@ -748,9 +726,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", - "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", "cpu": [ "x64" ], @@ -765,9 +743,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", - "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", "cpu": [ "arm64" ], @@ -782,9 +760,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", - "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", "cpu": [ "x64" ], @@ -799,9 +777,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", - "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", "cpu": [ "arm64" ], @@ -816,9 +794,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", - "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", "cpu": [ "x64" ], @@ -833,9 +811,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", - "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", "cpu": [ "arm" ], @@ -850,9 +828,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", - "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", "cpu": [ "arm64" ], @@ -867,9 +845,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", - "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", "cpu": [ "ia32" ], @@ -884,9 +862,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", - "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", "cpu": [ "loong64" ], @@ -901,9 +879,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", - "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", "cpu": [ "mips64el" ], @@ -918,9 +896,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", - "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", "cpu": [ "ppc64" ], @@ -935,9 +913,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", - "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", "cpu": [ "riscv64" ], @@ -952,9 +930,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", - "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", "cpu": [ "s390x" ], @@ -969,9 +947,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", - "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", "cpu": [ "x64" ], @@ -985,10 +963,27 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", - "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", "cpu": [ "x64" ], @@ -1003,9 +998,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", - "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", "cpu": [ "arm64" ], @@ -1020,9 +1015,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", - "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", "cpu": [ "x64" ], @@ -1037,9 +1032,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", - "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", "cpu": [ "x64" ], @@ -1054,9 +1049,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", - "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", "cpu": [ "arm64" ], @@ -1071,9 +1066,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", - "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", "cpu": [ "ia32" ], @@ -1088,9 +1083,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", - "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", "cpu": [ "x64" ], @@ -1120,24 +1115,78 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz", - "integrity": "sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, + "license": "MIT", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz", + "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.5", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/core": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz", + "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz", + "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -1145,7 +1194,7 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -1156,16 +1205,62 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, + "node_modules/@eslint/eslintrc/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -1174,12 +1269,36 @@ } }, "node_modules/@eslint/js": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz", - "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.17.0.tgz", + "integrity": "sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz", + "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz", + "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@floating-ui/core": { @@ -1202,19 +1321,19 @@ } }, "node_modules/@floating-ui/utils": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.8.tgz", - "integrity": "sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.9.tgz", + "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==", "license": "MIT" }, "node_modules/@floating-ui/vue": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.5.tgz", - "integrity": "sha512-ynL1p5Z+woPVSwgMGqeDrx6HrJfGIDzFyESFkyqJKilGW1+h/8yVY29Khn0LaU6wHBRwZ13ntG6reiHWK6jyzw==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/vue/-/vue-1.1.6.tgz", + "integrity": "sha512-XFlUzGHGv12zbgHNk5FN2mUB7ROul3oG2ENdTpWdE+qMFxyNxWSRmsoyhiEnpmabNm6WnUvR1OvJfUfN4ojC1A==", "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.0.0", - "@floating-ui/utils": "^0.2.8", + "@floating-ui/utils": "^0.2.9", "vue-demi": ">=0.13.0" } }, @@ -1244,41 +1363,42 @@ } } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.14", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz", - "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.2", - "debug": "^4.3.1", - "minimatch": "^3.0.5" - }, + "license": "Apache-2.0", "engines": { - "node": ">=10.10.0" + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "license": "Apache-2.0", "engines": { - "node": "*" + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, "node_modules/@humanwhocodes/module-importer": { @@ -1294,12 +1414,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz", + "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@interactjs/types": { "version": "1.10.27", @@ -1524,6 +1651,7 @@ "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" }, @@ -1539,9 +1667,9 @@ "license": "MIT" }, "node_modules/@rollup/pluginutils": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz", - "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz", + "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1825,18 +1953,32 @@ "win32" ] }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.10.4", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", - "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "dev": true, "license": "MIT" }, - "node_modules/@tsconfig/node20": { - "version": "20.1.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node20/-/node20-20.1.4.tgz", - "integrity": "sha512-sqgsT69YFeLWf5NtJ4Xq/xAF8p4ZQHlmGW74Nu2tD4+g5fAsposc4ZfaaPixVu4y01BEiDCWLRDCvDM5JOsRxg==", - "dev": true + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@tsconfig/node22": { + "version": "22.0.0", + "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.0.tgz", + "integrity": "sha512-twLQ77zevtxobBOD4ToAtVmuYrpeYUh3qh+TEp+08IWhpsrIflVHqQ1F1CiPxQGL7doCdBIOOCF+1Tm833faNg==", + "dev": true, + "license": "MIT" }, "node_modules/@types/estree": { "version": "1.0.6", @@ -1861,6 +2003,13 @@ "parse5": "^7.0.0" } }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -1868,21 +2017,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/lodash": { - "version": "4.17.7", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", - "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", - "license": "MIT", - "dependencies": { - "@types/lodash": "*" - } - }, "node_modules/@types/markdown-it": { "version": "14.1.2", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", @@ -1933,9 +2067,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.10.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", - "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1966,77 +2100,72 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.16.0.tgz", - "integrity": "sha512-py1miT6iQpJcs1BiJjm54AMzeuMPBSPuKPlnT8HlfudbcS5rYeX5jajpLf3mrdRh9dA/Ec2FVUY0ifeVNDIhZw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.1.tgz", + "integrity": "sha512-tJzcVyvvb9h/PB96g30MpxACd9IrunT7GF9wfA9/0TJ1LxGOJx1TdPzSbBBnNED7K9Ka8ybJsnEpiXPktolTLg==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "7.16.0", - "@typescript-eslint/type-utils": "7.16.0", - "@typescript-eslint/utils": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/type-utils": "8.19.1", + "@typescript-eslint/utils": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^7.0.0", - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.16.0.tgz", - "integrity": "sha512-ar9E+k7CU8rWi2e5ErzQiC93KKEFAXA2Kky0scAlPcxYblLt8+XZuHUZwlyfXILyQa95P6lQg+eZgh/dDs3+Vw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.1.tgz", + "integrity": "sha512-67gbfv8rAwawjYx3fYArwldTQKoYfezNUT4D5ioWetr/xCrxXxvleo3uuiFuKfejipvq+og7mjz3b0G2bVyUCw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "7.16.0", - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/typescript-estree": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0", + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz", - "integrity": "sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.1.tgz", + "integrity": "sha512-60L9KIuN/xgmsINzonOcMDSB8p82h95hoBfSBtXuO4jlR1R9L1xSkmVZKgCPVfavDlXihh4ARNjXhh1gGnLC7Q==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0" + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2044,39 +2173,37 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.16.0.tgz", - "integrity": "sha512-j0fuUswUjDHfqV/UdW6mLtOQQseORqfdmoBNDFOqs9rvNVR2e+cmu6zJu/Ku4SDuqiJko6YnhwcL8x45r8Oqxg==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.1.tgz", + "integrity": "sha512-Rp7k9lhDKBMRJB/nM9Ksp1zs4796wVNyihG9/TU9R6KCJDNkQbc2EOKjrBtLYh3396ZdpXLtr/MkaSEmNMtykw==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "7.16.0", - "@typescript-eslint/utils": "7.16.0", + "@typescript-eslint/typescript-estree": "8.19.1", + "@typescript-eslint/utils": "8.19.1", "debug": "^4.3.4", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz", - "integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.1.tgz", + "integrity": "sha512-JBVHMLj7B1K1v1051ZaMMgLW4Q/jre5qGK0Ew6UgXz1Rqh+/xPzV1aW581OM00X6iOfyr1be+QyW8LOUf19BbA==", "dev": true, + "license": "MIT", "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -2084,77 +2211,86 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz", - "integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.1.tgz", + "integrity": "sha512-jk/TZwSMJlxlNnqhy0Eod1PNEvCkpY6MXOXE/WLlblZ6ibb32i2We4uByoKPv1d0OD2xebDv4hbs3fm11SMw8Q==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/visitor-keys": "7.16.0", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/visitor-keys": "8.19.1", "debug": "^4.3.4", - "globby": "^11.1.0", + "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", - "ts-api-utils": "^1.3.0" + "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.0.tgz", - "integrity": "sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.1.tgz", + "integrity": "sha512-IxG5gLO0Ne+KaUc8iW1A+XuKLd63o4wlbI1Zp692n1xojCl/THvgIKXJXBZixTh5dd5+yTJ/VXH7GJaaw21qXA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "7.16.0", - "@typescript-eslint/types": "7.16.0", - "@typescript-eslint/typescript-estree": "7.16.0" + "@typescript-eslint/scope-manager": "8.19.1", + "@typescript-eslint/types": "8.19.1", + "@typescript-eslint/typescript-estree": "8.19.1" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.56.0" + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz", - "integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==", + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.1.tgz", + "integrity": "sha512-fzmjU8CHK853V/avYZAvuVut3ZTfwN5YtMaoi+X9Y9MA9keaWNHC3zEQ9zvyX/7Hj+5JkNyK1l7TOR2hevHB6Q==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "7.16.0", - "eslint-visitor-keys": "^3.4.3" + "@typescript-eslint/types": "8.19.1", + "eslint-visitor-keys": "^4.2.0" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, "node_modules/@vitejs/plugin-vue": { "version": "5.2.1", @@ -2171,9 +2307,9 @@ } }, "node_modules/@vitest/coverage-v8": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.6.tgz", - "integrity": "sha512-qItJVYDbG3MUFO68dOZUz+rWlqe9LMzotERXFXKg25s2A/kSVsyS9O0yNGrITfBd943GsnBeQZkBUu7Pc+zVeA==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.8.tgz", + "integrity": "sha512-2Y7BPlKH18mAZYAW1tYByudlCYrQyl5RGvnnDYJKW5tCiO5qg3KSAy3XAxcxKz900a0ZXxWtKrMuZLe3lKBpJw==", "dev": true, "license": "MIT", "dependencies": { @@ -2194,8 +2330,8 @@ "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "@vitest/browser": "2.1.6", - "vitest": "2.1.6" + "@vitest/browser": "2.1.8", + "vitest": "2.1.8" }, "peerDependenciesMeta": { "@vitest/browser": { @@ -2203,15 +2339,36 @@ } } }, + "node_modules/@vitest/eslint-plugin": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.24.tgz", + "integrity": "sha512-7IaENe4NNy33g0iuuy5bHY69JYYRjpv4lMx6H5Wp30W7ez2baLHwxsXF5TM4wa8JDYZt8ut99Ytoj7GiDO01hw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/utils": ">= 8.0", + "eslint": ">= 8.57.0", + "typescript": ">= 5.0.0", + "vitest": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.6.tgz", - "integrity": "sha512-9M1UR9CAmrhJOMoSwVnPh2rELPKhYo0m/CSgqw9PyStpxtkwhmdM6XYlXGKeYyERY1N6EIuzkQ7e3Lm1WKCoUg==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.8.tgz", + "integrity": "sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.6", - "@vitest/utils": "2.1.6", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", "chai": "^5.1.2", "tinyrainbow": "^1.2.0" }, @@ -2219,64 +2376,27 @@ "url": "https://opencollective.com/vitest" } }, - "node_modules/@vitest/mocker": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.6.tgz", - "integrity": "sha512-MHZp2Z+Q/A3am5oD4WSH04f9B0T7UvwEb+v5W0kCYMhtXGYbdyl2NUk1wdSMqGthmhpiThPDp/hEoVwu16+u1A==", + "node_modules/@vitest/pretty-format": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "2.1.6", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.12" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^5.0.0 || ^6.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/mocker/node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/@vitest/pretty-format": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.6.tgz", - "integrity": "sha512-exZyLcEnHgDMKc54TtHca4McV4sKT+NKAe9ix/yhd/qkYb/TP8HTyXRFDijV19qKqTZM0hPL4753zU/U8L/gAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^1.2.0" + "tinyrainbow": "^1.2.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, "node_modules/@vitest/runner": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.6.tgz", - "integrity": "sha512-SjkRGSFyrA82m5nz7To4CkRSEVWn/rwQISHoia/DB8c6IHIhaE/UNAo+7UfeaeJRE979XceGl00LNkIz09RFsA==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.8.tgz", + "integrity": "sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "2.1.6", + "@vitest/utils": "2.1.8", "pathe": "^1.1.2" }, "funding": { @@ -2284,13 +2404,13 @@ } }, "node_modules/@vitest/snapshot": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.6.tgz", - "integrity": "sha512-5JTWHw8iS9l3v4/VSuthCndw1lN/hpPB+mlgn1BUhFbobeIUj1J1V/Bj2t2ovGEmkXLTckFjQddsxS5T6LuVWw==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.6", + "@vitest/pretty-format": "2.1.8", "magic-string": "^0.30.12", "pathe": "^1.1.2" }, @@ -2299,9 +2419,9 @@ } }, "node_modules/@vitest/spy": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.6.tgz", - "integrity": "sha512-oTFObV8bd4SDdRka5O+mSh5w9irgx5IetrD5i+OsUUsk/shsBoHifwCzy45SAORzAhtNiprUVaK3hSCCzZh1jQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.8.tgz", + "integrity": "sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==", "dev": true, "license": "MIT", "dependencies": { @@ -2312,13 +2432,13 @@ } }, "node_modules/@vitest/utils": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.6.tgz", - "integrity": "sha512-ixNkFy3k4vokOUTU2blIUvOgKq/N2PW8vKIjZZYsGJCMX69MRa9J2sKqX5hY/k5O5Gty3YJChepkqZ3KM9LyIQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.8.tgz", + "integrity": "sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "2.1.6", + "@vitest/pretty-format": "2.1.8", "loupe": "^3.1.2", "tinyrainbow": "^1.2.0" }, @@ -2356,9 +2476,9 @@ } }, "node_modules/@vscode/markdown-it-katex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@vscode/markdown-it-katex/-/markdown-it-katex-1.1.0.tgz", - "integrity": "sha512-9cF2eJpsJOEs2V1cCAoJW/boKz9GQQLvZhNvI030K90z6ZE9lRGc9hDVvKut8zdFO2ObjwylPXXXVYvTdP2O2Q==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@vscode/markdown-it-katex/-/markdown-it-katex-1.1.1.tgz", + "integrity": "sha512-3KTlbsRBPJQLE2YmLL7K6nunTlU+W9T5+FjfNdWuIUKgxSS6HWLQHaO3L4MkJi7z7MpIPpY+g4N+cWNBPE/MSA==", "license": "MIT", "dependencies": { "katex": "^0.16.4" @@ -2483,31 +2603,50 @@ "license": "MIT" }, "node_modules/@vue/devtools-core": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.6.5.tgz", - "integrity": "sha512-PKTEZVzY4Ef6G8LnbACKkPDOcdr2snFn3Xk8YqyFgugmogDrA3cyYVQ58CS0XTO9AYUXU9E5FFt5JJf22kXF2w==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.0.tgz", + "integrity": "sha512-tSO3pghV5RZGSonZ87S2fOGru3X93epmar5IjZOWjHxH6XSwnK5UbR2aW5puZV+LgLoVYrcNou3krSo5k1F31g==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-kit": "^7.6.5", - "@vue/devtools-shared": "^7.6.5", + "@vue/devtools-kit": "^7.7.0", + "@vue/devtools-shared": "^7.7.0", "mitt": "^3.0.1", - "nanoid": "^3.3.4", + "nanoid": "^5.0.9", "pathe": "^1.1.2", - "vite-hot-client": "^0.2.3" + "vite-hot-client": "^0.2.4" }, "peerDependencies": { "vue": "^3.0.0" } }, + "node_modules/@vue/devtools-core/node_modules/nanoid": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz", + "integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, "node_modules/@vue/devtools-kit": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.6.5.tgz", - "integrity": "sha512-fLQhUwmUbtEDHW1SEiHUF5k2Ptw816As5ZUVb/SzrqkrJzXI8xjEIo8suNBe/N+ewdz/9m5ayeFH8fmcVIbr4Q==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.0.tgz", + "integrity": "sha512-5cvZ+6SA88zKC8XiuxUfqpdTwVjJbvYnQZY5NReh7qlSGPvVDjjzyEtW+gdzLXNSd8tStgOjAdMCpvDQamUXtA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/devtools-shared": "^7.6.5", + "@vue/devtools-shared": "^7.7.0", "birpc": "^0.2.19", "hookable": "^5.5.3", "mitt": "^3.0.1", @@ -2517,9 +2656,9 @@ } }, "node_modules/@vue/devtools-shared": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.6.5.tgz", - "integrity": "sha512-szsXQ0jlpjuFfmxb6F40qkSF4gtLC1W+dKRh/UiTulC+RekZsjqcN/qnVFkzqOO1YnzzShinZwfmv+MbfPJnpw==", + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.0.tgz", + "integrity": "sha512-jtlQY26R5thQxW9YQTpXbI0HoK0Wf9Rd4ekidOkRvSy7ChfK0kIU6vvcBtjj87/EcpeOSK49fZAicaFNJcoTcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2527,36 +2666,38 @@ } }, "node_modules/@vue/eslint-config-prettier": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-9.0.0.tgz", - "integrity": "sha512-z1ZIAAUS9pKzo/ANEfd2sO+v2IUalz7cM/cTLOZ7vRFOPk5/xuRKQteOu1DErFLAh/lYGXMVZ0IfYKlyInuDVg==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-10.1.0.tgz", + "integrity": "sha512-J6wV91y2pXc0Phha01k0WOHBTPsoSTf4xlmMjoKaeSxBpAdsgTppGF5RZRdOHM7OA74zAXD+VLANrtYXpiPKkQ==", "dev": true, + "license": "MIT", "dependencies": { - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.0" + "eslint-config-prettier": "^9.1.0", + "eslint-plugin-prettier": "^5.2.1" }, "peerDependencies": { - "eslint": ">= 8.0.0", + "eslint": ">= 8.21.0", "prettier": ">= 3.0.0" } }, "node_modules/@vue/eslint-config-typescript": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-13.0.0.tgz", - "integrity": "sha512-MHh9SncG/sfqjVqjcuFLOLD6Ed4dRAis4HNt0dXASeAuLqIAx4YMB1/m2o4pUKK1vCt8fUvYG8KKX2Ot3BVZTg==", + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-14.2.0.tgz", + "integrity": "sha512-JJ4wHuTJa2faQsBOUeWzuHOSFizVS7RWG2eH2noABk2LcT4wVcTOMZKM/lFobKBcgwADIPAKVRGFHVKooXImoA==", "dev": true, + "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "^7.1.1", - "@typescript-eslint/parser": "^7.1.1", - "vue-eslint-parser": "^9.3.1" + "fast-glob": "^3.3.2", + "typescript-eslint": "^8.18.1", + "vue-eslint-parser": "^9.4.3" }, "engines": { - "node": "^18.18.0 || >=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { - "eslint": "^8.56.0", - "eslint-plugin-vue": "^9.0.0", - "typescript": ">=4.7.4" + "eslint": "^9.10.0", + "eslint-plugin-vue": "^9.28.0", + "typescript": ">=4.8.4" }, "peerDependenciesMeta": { "typescript": { @@ -2678,10 +2819,11 @@ } }, "node_modules/acorn": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", - "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -2715,6 +2857,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -2784,6 +2927,7 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -3201,13 +3345,13 @@ } }, "node_modules/css-tree": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.0.0.tgz", - "integrity": "sha512-o88DVQ6GzsABn1+6+zo2ct801dBO5OASVyxbbvA2W20ue2puSh/VOuqUj90eUeMSX/xqGqBmOKiRQN7tJOuBXw==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", "dev": true, "license": "MIT", "dependencies": { - "mdn-data": "2.10.0", + "mdn-data": "2.12.2", "source-map-js": "^1.0.1" }, "engines": { @@ -3649,18 +3793,6 @@ "node": ">=8" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -3808,16 +3940,16 @@ } }, "node_modules/es-module-lexer": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz", - "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz", + "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==", "dev": true, "license": "MIT" }, "node_modules/esbuild": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", - "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", + "version": "0.24.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3828,30 +3960,31 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.24.0", - "@esbuild/android-arm": "0.24.0", - "@esbuild/android-arm64": "0.24.0", - "@esbuild/android-x64": "0.24.0", - "@esbuild/darwin-arm64": "0.24.0", - "@esbuild/darwin-x64": "0.24.0", - "@esbuild/freebsd-arm64": "0.24.0", - "@esbuild/freebsd-x64": "0.24.0", - "@esbuild/linux-arm": "0.24.0", - "@esbuild/linux-arm64": "0.24.0", - "@esbuild/linux-ia32": "0.24.0", - "@esbuild/linux-loong64": "0.24.0", - "@esbuild/linux-mips64el": "0.24.0", - "@esbuild/linux-ppc64": "0.24.0", - "@esbuild/linux-riscv64": "0.24.0", - "@esbuild/linux-s390x": "0.24.0", - "@esbuild/linux-x64": "0.24.0", - "@esbuild/netbsd-x64": "0.24.0", - "@esbuild/openbsd-arm64": "0.24.0", - "@esbuild/openbsd-x64": "0.24.0", - "@esbuild/sunos-x64": "0.24.0", - "@esbuild/win32-arm64": "0.24.0", - "@esbuild/win32-ia32": "0.24.0", - "@esbuild/win32-x64": "0.24.0" + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" } }, "node_modules/escalade": { @@ -3876,58 +4009,63 @@ } }, "node_modules/eslint": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz", - "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==", + "version": "9.17.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.17.0.tgz", + "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.0", - "@humanwhocodes/config-array": "^0.11.14", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.0", + "@eslint/core": "^0.9.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "9.17.0", + "@eslint/plugin-kit": "^0.2.3", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", + "@humanwhocodes/retry": "^0.4.1", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { @@ -3935,6 +4073,7 @@ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", "integrity": "sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==", "dev": true, + "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, @@ -3943,13 +4082,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", - "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", "dev": true, + "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" + "synckit": "^0.9.1" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -4033,6 +4173,54 @@ "concat-map": "0.0.1" } }, + "node_modules/eslint/node_modules/eslint-scope": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/espree": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.14.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4106,28 +4294,33 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "version": "9.5.2", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz", + "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==", "dev": true, + "license": "MIT", "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" }, "engines": { - "node": ">=16.17" + "node": "^18.19.0 || >=20.5.0" }, "funding": { "url": "https://github.com/sindresorhus/execa?sponsor=1" @@ -4153,7 +4346,8 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", - "dev": true + "dev": true, + "license": "Apache-2.0" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -4192,7 +4386,8 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -4201,10 +4396,21 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.1.tgz", - "integrity": "sha512-MWipKbbYiYI0UC7cl8m/i/IWTqfC8YXsqjzybjddLsFjStroQzsHXkc73JutMvBiXmOvapk+axIl79ig5t55Bw==", - "dev": true + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.5.tgz", + "integrity": "sha512-5JnBCWpFlMo0a3ciDy/JckMzzv1U9coZrIhedq+HXxxUfDTAiS0LA8OKVao4G9BxmCVck/jtA5r3KAtRWEyD8Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fastest-levenshtein": { "version": "1.0.16", @@ -4224,16 +4430,33 @@ "reusify": "^1.0.4" } }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -4265,17 +4488,17 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { @@ -4342,12 +4565,6 @@ "node": ">=14.14" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -4382,12 +4599,17 @@ } }, "node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4515,7 +4737,8 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/has-flag": { "version": "4.0.0", @@ -4635,12 +4858,13 @@ } }, "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", + "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=16.17.0" + "node": ">=18.18.0" } }, "node_modules/iconv-lite": { @@ -4689,23 +4913,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, "node_modules/ini": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", @@ -4809,13 +5016,17 @@ "node": ">=0.12.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/is-plain-object": { @@ -4834,12 +5045,26 @@ "dev": true }, "node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5059,9 +5284,9 @@ } }, "node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", "dev": true, "license": "MIT", "bin": { @@ -5077,11 +5302,22 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-parse-even-better-errors": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-4.0.0.tgz", + "integrity": "sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -5165,10 +5401,11 @@ } }, "node_modules/known-css-properties": { - "version": "0.34.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.34.0.tgz", - "integrity": "sha512-tBECoUqNFbyAY4RrbqsBQqDFpGXAEbdD5QKr8kACx3+rnArmuuR22nKQWKazvp07N9yjTyDZaw/20UIH8tL9DQ==", - "dev": true + "version": "0.35.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.35.0.tgz", + "integrity": "sha512-a/RAk2BfKk+WFGhhOCAYqSiFLc34k8Mt/6NWRI4joER0EYUzXIcFivjjnoD3+XU1DggLn/tZc3DOAgke7l8a4A==", + "dev": true, + "license": "MIT" }, "node_modules/kolorist": { "version": "1.8.0", @@ -5226,12 +5463,6 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, - "node_modules/lodash-es": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", - "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", - "license": "MIT" - }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -5242,7 +5473,8 @@ "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/loupe": { "version": "3.1.2", @@ -5349,9 +5581,9 @@ } }, "node_modules/mdn-data": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.10.0.tgz", - "integrity": "sha512-qq7C3EtK3yJXMwz1zAab65pjl+UhohqMOctTgcqjLOWABqmwj+me02LSsCuEUxnst9X1lCBpoE0WArGKgdGDzw==", + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", "dev": true, "license": "CC0-1.0" }, @@ -5361,6 +5593,15 @@ "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "license": "MIT" }, + "node_modules/memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==", + "dev": true, + "engines": { + "node": ">= 0.10.0" + } + }, "node_modules/meow": { "version": "13.2.0", "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", @@ -5373,12 +5614,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -5423,18 +5658,6 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -5605,68 +5828,105 @@ "node": ">=0.10.0" } }, - "node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "node_modules/npm-normalize-package-bin": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-4.0.0.tgz", + "integrity": "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/npm-run-all2": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-7.0.2.tgz", + "integrity": "sha512-7tXR+r9hzRNOPNTvXegM+QzCuMjzUIIq66VDunL6j60O4RrExx32XUhlrS7UK4VcdGw5/Wxzb3kfNcFix9JKDA==", "dev": true, + "license": "MIT", "dependencies": { - "path-key": "^4.0.0" + "ansi-styles": "^6.2.1", + "cross-spawn": "^7.0.6", + "memorystream": "^0.3.1", + "minimatch": "^9.0.0", + "pidtree": "^0.6.0", + "read-package-json-fast": "^4.0.0", + "shell-quote": "^1.7.3", + "which": "^5.0.0" }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "bin": { + "npm-run-all": "bin/npm-run-all/index.js", + "npm-run-all2": "bin/npm-run-all/index.js", + "run-p": "bin/run-p/index.js", + "run-s": "bin/run-s/index.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^18.17.0 || >=20.5.0", + "npm": ">= 9" } }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "node_modules/npm-run-all2/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "node_modules/npm-run-all2/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", "dev": true, - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" + "license": "ISC", + "engines": { + "node": ">=16" } }, - "node_modules/nwsapi": { - "version": "2.2.12", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", - "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==", - "dev": true - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "node_modules/npm-run-all2/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", "dev": true, + "license": "ISC", "dependencies": { - "wrappy": "1" + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" } }, - "node_modules/onetime": { + "node_modules/npm-run-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^4.0.0" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -5674,6 +5934,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/nwsapi": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.12.tgz", + "integrity": "sha512-qXDmcVlZV4XRtKFzddidpfVP4oMSGhga+xdMc25mv8kaLUHtgzCDhUxkrN8exkGdTlLNaXj7CV3GtON7zuGZ+w==", + "dev": true + }, "node_modules/open": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/open/-/open-10.1.0.tgz", @@ -5782,6 +6060,19 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", "dev": true }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parse5": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", @@ -5810,15 +6101,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5895,10 +6177,23 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/pinia": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.2.7.tgz", - "integrity": "sha512-M+X9Eh9V5De+8wyj0rD1cgB0zy1mPN/aBEpCI9y+DgVmzXV2dIwjYBluJ5cMQd/jAoHs0VW+EyUSHMZv/Wtcnw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.0.tgz", + "integrity": "sha512-ohZj3jla0LL0OH5PlLTDMzqKiVw2XARmC1XYLdLWIPBMdhDW/123ZWr4zVAhtJm+aoSkFa13pYXskAvAscIkhQ==", "license": "MIT", "dependencies": { "@vue/devtools-api": "^6.6.3", @@ -5908,14 +6203,10 @@ "url": "https://github.com/sponsors/posva" }, "peerDependencies": { - "@vue/composition-api": "^1.4.0", "typescript": ">=4.4.4", - "vue": "^2.6.14 || ^3.5.11" + "vue": "^2.7.0 || ^3.5.11" }, "peerDependenciesMeta": { - "@vue/composition-api": { - "optional": true - }, "typescript": { "optional": true } @@ -5948,9 +6239,9 @@ } }, "node_modules/plotly.js-dist-min": { - "version": "2.35.2", - "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.35.2.tgz", - "integrity": "sha512-oWDTf2kYOmTtEw3epeeSBdfH/H3OSktF0suST9oI6fIgKfbyd4MT7TPh8+CVzdHYllYon24Q0HI1hZjOnLqk6g==", + "version": "2.35.3", + "resolved": "https://registry.npmjs.org/plotly.js-dist-min/-/plotly.js-dist-min-2.35.3.tgz", + "integrity": "sha512-sz2HLP8gkysLx/BanM2PtJTtZ1PLPwdHwMWNri2YxLBy3IOeuDsVQtlmWa4hoK3j/fi4naaD3uZJqH5ozM3zGg==", "license": "MIT" }, "node_modules/postcss": { @@ -6167,6 +6458,7 @@ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", "dev": true, + "license": "MIT", "dependencies": { "fast-diff": "^1.1.2" }, @@ -6174,6 +6466,22 @@ "node": ">=6.0.0" } }, + "node_modules/pretty-ms": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz", + "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", @@ -6218,6 +6526,20 @@ } ] }, + "node_modules/read-package-json-fast": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-4.0.0.tgz", + "integrity": "sha512-qpt8EwugBWDw2cgE2W+/3oxC+KTez2uSVR8JU9Q36TXPAGCaozfQUs59v4j4GFpWTaw0i6hAZSvOmu1J0uOEUg==", + "dev": true, + "license": "ISC", + "dependencies": { + "json-parse-even-better-errors": "^4.0.0", + "npm-normalize-package-bin": "^4.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -6232,6 +6554,7 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -6262,65 +6585,6 @@ "dev": true, "license": "MIT" }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/robust-predicates": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", @@ -6463,6 +6727,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.2.tgz", + "integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/siginfo": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", @@ -6482,23 +6759,21 @@ } }, "node_modules/simplebar-core": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/simplebar-core/-/simplebar-core-1.2.6.tgz", - "integrity": "sha512-H5NYU+O+uvqOH5VXw3+lgoc1vTI6jL8LOZJsw4xgRpV7uIPjRpmLPdz0TrouxwKHBhpVLzVIlyKhaRLelIThMw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/simplebar-core/-/simplebar-core-1.3.0.tgz", + "integrity": "sha512-LpWl3w0caz0bl322E68qsrRPpIn+rWBGAaEJ0lUJA7Xpr2sw92AkIhg6VWj988IefLXYh50ILatfAnbNoCFrlA==", "license": "MIT", "dependencies": { - "@types/lodash-es": "^4.17.6", - "lodash": "^4.17.21", - "lodash-es": "^4.17.21" + "lodash": "^4.17.21" } }, "node_modules/simplebar-vue": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/simplebar-vue/-/simplebar-vue-2.3.5.tgz", - "integrity": "sha512-LCRpDfJ4nNNHoZK2h5HEhu3BXoLQkQOMzzcHngCb2GN7uVu22dwXW+Kel2f3MDhZgPR2abcTMMtRrRjK3ge0Yg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/simplebar-vue/-/simplebar-vue-2.4.0.tgz", + "integrity": "sha512-XUFGqoTCjzTKRWLHmS0/gy03GF7Id9FZhczrAqC3tbFO5OZ9vRCdzMZ7F2MuCI5+fp6Plpvug9GUgyBDJLTc5A==", "license": "MIT", "dependencies": { - "simplebar-core": "^1.2.6", + "simplebar-core": "^1.3.0", "vue-demi": "^0.13.11" }, "peerDependencies": { @@ -6560,6 +6835,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -6719,12 +6995,13 @@ } }, "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -6735,6 +7012,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -6743,9 +7021,9 @@ } }, "node_modules/stylelint": { - "version": "16.10.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.10.0.tgz", - "integrity": "sha512-z/8X2rZ52dt2c0stVwI9QL2AFJhLhbPkyfpDFcizs200V/g7v+UYY6SNcB9hKOLcDDX/yGLDsY/pX08sLkz9xQ==", + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.12.0.tgz", + "integrity": "sha512-F8zZ3L/rBpuoBZRvI4JVT20ZanPLXfQLzMOZg1tzPflRVh9mKpOZ8qcSIhh1my3FjAjZWG4T2POwGnmn6a6hbg==", "dev": true, "funding": [ { @@ -6759,16 +7037,16 @@ ], "license": "MIT", "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.1", - "@csstools/css-tokenizer": "^3.0.1", - "@csstools/media-query-list-parser": "^3.0.1", - "@csstools/selector-specificity": "^4.0.0", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "@csstools/media-query-list-parser": "^4.0.2", + "@csstools/selector-specificity": "^5.0.0", "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", "colord": "^2.9.3", "cosmiconfig": "^9.0.0", "css-functions-list": "^3.2.3", - "css-tree": "^3.0.0", + "css-tree": "^3.0.1", "debug": "^4.3.7", "fast-glob": "^3.3.2", "fastest-levenshtein": "^1.0.16", @@ -6780,22 +7058,22 @@ "ignore": "^6.0.2", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.34.0", + "known-css-properties": "^0.35.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", - "picocolors": "^1.0.1", - "postcss": "^8.4.47", + "picocolors": "^1.1.1", + "postcss": "^8.4.49", "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.1", - "postcss-selector-parser": "^6.1.2", + "postcss-selector-parser": "^7.0.0", "postcss-value-parser": "^4.2.0", "resolve-from": "^5.0.0", "string-width": "^4.2.3", "supports-hyperlinks": "^3.1.0", "svg-tags": "^1.0.0", - "table": "^6.8.2", + "table": "^6.9.0", "write-file-atomic": "^5.0.1" }, "bin": { @@ -6927,6 +7205,29 @@ "postcss": "^8.4.20" } }, + "node_modules/stylelint/node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, "node_modules/stylelint/node_modules/balanced-match": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", @@ -7003,6 +7304,20 @@ "postcss": "^8.4.31" } }, + "node_modules/stylelint/node_modules/postcss-selector-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.0.0.tgz", + "integrity": "sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/stylelint/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -7027,9 +7342,9 @@ } }, "node_modules/superjson": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz", - "integrity": "sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz", + "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==", "dev": true, "license": "MIT", "dependencies": { @@ -7081,10 +7396,11 @@ "dev": true }, "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.2.tgz", + "integrity": "sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==", "dev": true, + "license": "MIT", "dependencies": { "@pkgr/core": "^0.1.0", "tslib": "^2.6.2" @@ -7097,10 +7413,11 @@ } }, "node_modules/table": { - "version": "6.8.2", - "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", - "integrity": "sha512-w2sfv80nrAh2VCbqR5AK27wswXhqcck2AhfnNW76beQXskGZ1V12GwS//yYVa3d3fcvAip2OUnbDAjW2k3v9fA==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -7117,6 +7434,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -7132,19 +7450,22 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/table/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/table/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -7199,12 +7520,6 @@ "node": ">=18" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, "node_modules/tinybench": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", @@ -7337,15 +7652,16 @@ } }, "node_modules/ts-api-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", - "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz", + "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/tslib": { @@ -7391,6 +7707,29 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.19.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.19.1.tgz", + "integrity": "sha512-LKPUQpdEMVOeKluHi8md7rwLcoXHhwvWp3x+sJkMuq3gGm9yaYJtPo8sRZSblMFJ5pcOGCAak/scKf1mvZDlQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.19.1", + "@typescript-eslint/parser": "8.19.1", + "@typescript-eslint/utils": "8.19.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, "node_modules/uc.micro": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", @@ -7404,6 +7743,19 @@ "dev": true, "license": "MIT" }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -7450,6 +7802,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -7923,13 +8276,13 @@ } }, "node_modules/vite": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.1.tgz", - "integrity": "sha512-Ldn6gorLGr4mCdFnmeAOLweJxZ34HjKnDm4HGo6P66IEqTxQb36VEdFJQENKxWjupNfoIjvRUnswjn1hpYEpjQ==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.7.tgz", + "integrity": "sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.24.0", + "esbuild": "^0.24.2", "postcss": "^8.4.49", "rollup": "^4.23.0" }, @@ -8008,9 +8361,9 @@ } }, "node_modules/vite-node": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.6.tgz", - "integrity": "sha512-DBfJY0n9JUwnyLxPSSUmEePT21j8JZp/sR9n+/gBwQU6DcQOioPdb8/pibWfXForbirSagZCilseYIwaL3f95A==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.8.tgz", + "integrity": "sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==", "dev": true, "license": "MIT", "dependencies": { @@ -8018,172 +8371,1189 @@ "debug": "^4.3.7", "es-module-lexer": "^1.5.4", "pathe": "^1.1.2", - "vite": "^5.0.0 || ^6.0.0" + "vite": "^5.0.0" }, "bin": { "vite-node": "vite-node.mjs" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://opencollective.com/vitest" } }, - "node_modules/vite-plugin-vue-devtools": { - "version": "7.6.5", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.6.5.tgz", - "integrity": "sha512-5ISMSoLMrOl/77suAC3DigbuI4oSsWW7fgwdAoKbKvtY6+L3Jv51mjCnirzRog2uP0K59iIXwHHtORUg1aBQ2A==", + "node_modules/vite-node/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@vue/devtools-core": "^7.6.5", - "@vue/devtools-kit": "^7.6.5", - "@vue/devtools-shared": "^7.6.5", - "execa": "^8.0.1", - "sirv": "^3.0.0", - "vite-plugin-inspect": "^0.8.8", - "vite-plugin-vue-inspector": "^5.3.0" - }, - "engines": { + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vite-node/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vite-node/node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-inspect": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.9.tgz", + "integrity": "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "debug": "^4.3.7", + "error-stack-parser-es": "^0.1.5", + "fs-extra": "^11.2.0", + "open": "^10.1.0", + "perfect-debounce": "^1.0.0", + "picocolors": "^1.1.1", + "sirv": "^3.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite-plugin-vue-devtools": { + "version": "7.7.0", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.7.0.tgz", + "integrity": "sha512-1dWiREwIl4JELwXGHXih80hIgjcViMcZGr3j0edo6NQ9kNzAOxMIUgFqc/TO1ary4ZroJUxoB0YDI6jnDf13iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-core": "^7.7.0", + "@vue/devtools-kit": "^7.7.0", + "@vue/devtools-shared": "^7.7.0", + "execa": "^9.5.1", + "sirv": "^3.0.0", + "vite-plugin-inspect": "0.8.9", + "vite-plugin-vue-inspector": "^5.3.1" + }, + "engines": { "node": ">=v14.21.3" }, "peerDependencies": { "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" } }, - "node_modules/vite-plugin-vue-devtools/node_modules/vite-plugin-inspect": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.8.tgz", - "integrity": "sha512-aZlBuXsWUPJFmMK92GIv6lH7LrwG2POu4KJ+aEdcqnu92OAf+rhBnfMDQvxIJPEB7hE2t5EyY/PMgf5aDLT8EA==", + "node_modules/vite-plugin-vue-inspector": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.1.tgz", + "integrity": "sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.23.0", + "@babel/plugin-proposal-decorators": "^7.23.0", + "@babel/plugin-syntax-import-attributes": "^7.22.5", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-transform-typescript": "^7.22.15", + "@vue/babel-plugin-jsx": "^1.1.5", + "@vue/compiler-dom": "^3.3.4", + "kolorist": "^1.8.0", + "magic-string": "^0.30.4" + }, + "peerDependencies": { + "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0" + } + }, + "node_modules/vitest": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.8.tgz", + "integrity": "sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.8", + "@vitest/mocker": "2.1.8", + "@vitest/pretty-format": "^2.1.8", + "@vitest/runner": "2.1.8", + "@vitest/snapshot": "2.1.8", + "@vitest/spy": "2.1.8", + "@vitest/utils": "2.1.8", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.8", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.8", + "@vitest/ui": "2.1.8", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest-canvas-mock": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/vitest-canvas-mock/-/vitest-canvas-mock-0.3.3.tgz", + "integrity": "sha512-3P968tYBpqYyzzOaVtqnmYjqbe13576/fkjbDEJSfQAkHtC5/UjuRHOhFEN/ZV5HVZIkaROBUWgazDKJ+Ibw+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "jest-canvas-mock": "~2.5.2" + }, + "peerDependencies": { + "vitest": "*" + } + }, + "node_modules/vitest/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@antfu/utils": "^0.7.10", - "@rollup/pluginutils": "^5.1.3", - "debug": "^4.3.7", - "error-stack-parser-es": "^0.1.5", - "fs-extra": "^11.2.0", - "open": "^10.1.0", - "perfect-debounce": "^1.0.0", - "picocolors": "^1.1.1", - "sirv": "^3.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=14" + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/@vitest/mocker": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.8.tgz", + "integrity": "sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.8", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" }, "funding": { - "url": "https://github.com/sponsors/antfu" + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0" + "msw": "^2.4.9", + "vite": "^5.0.0" }, "peerDependenciesMeta": { - "@nuxt/kit": { + "msw": { + "optional": true + }, + "vite": { "optional": true } } }, - "node_modules/vite-plugin-vue-devtools/node_modules/vite-plugin-vue-inspector": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.0.tgz", - "integrity": "sha512-F6JNRUOrZl8FaUCTxPhsOLn2ka7N7Sz9ppxmmEwpybVBDYnhelbNnnlZpeFPc4ULnxbitSi8b0V2C0KT3CjReg==", + "node_modules/vitest/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "@babel/core": "^7.23.0", - "@babel/plugin-proposal-decorators": "^7.23.0", - "@babel/plugin-syntax-import-attributes": "^7.22.5", - "@babel/plugin-syntax-import-meta": "^7.10.4", - "@babel/plugin-transform-typescript": "^7.22.15", - "@vue/babel-plugin-jsx": "^1.1.5", - "@vue/compiler-dom": "^3.3.4", - "kolorist": "^1.8.0", - "magic-string": "^0.30.4" + "bin": { + "esbuild": "bin/esbuild" }, - "peerDependencies": { - "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0" + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitest/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" } }, - "node_modules/vitest": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.6.tgz", - "integrity": "sha512-isUCkvPL30J4c5O5hgONeFRsDmlw6kzFEdLQHLezmDdKQHy8Ke/B/dgdTMEgU0vm+iZ0TjW8GuK83DiahBoKWQ==", + "node_modules/vitest/node_modules/vite": { + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "2.1.6", - "@vitest/mocker": "2.1.6", - "@vitest/pretty-format": "^2.1.6", - "@vitest/runner": "2.1.6", - "@vitest/snapshot": "2.1.6", - "@vitest/spy": "2.1.6", - "@vitest/utils": "2.1.6", - "chai": "^5.1.2", - "debug": "^4.3.7", - "expect-type": "^1.1.0", - "magic-string": "^0.30.12", - "pathe": "^1.1.2", - "std-env": "^3.8.0", - "tinybench": "^2.9.0", - "tinyexec": "^0.3.1", - "tinypool": "^1.0.1", - "tinyrainbow": "^1.2.0", - "vite": "^5.0.0 || ^6.0.0", - "vite-node": "2.1.6", - "why-is-node-running": "^2.3.0" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { - "vitest": "vitest.mjs" + "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { - "url": "https://opencollective.com/vitest" + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" }, "peerDependencies": { - "@edge-runtime/vm": "*", - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "@vitest/browser": "2.1.6", - "@vitest/ui": "2.1.6", - "happy-dom": "*", - "jsdom": "*" + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" }, "peerDependenciesMeta": { - "@edge-runtime/vm": { + "@types/node": { "optional": true }, - "@types/node": { + "less": { "optional": true }, - "@vitest/browser": { + "lightningcss": { "optional": true }, - "@vitest/ui": { + "sass": { "optional": true }, - "happy-dom": { + "sass-embedded": { "optional": true }, - "jsdom": { + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { "optional": true } } }, - "node_modules/vitest-canvas-mock": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/vitest-canvas-mock/-/vitest-canvas-mock-0.3.3.tgz", - "integrity": "sha512-3P968tYBpqYyzzOaVtqnmYjqbe13576/fkjbDEJSfQAkHtC5/UjuRHOhFEN/ZV5HVZIkaROBUWgazDKJ+Ibw+Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "jest-canvas-mock": "~2.5.2" - }, - "peerDependencies": { - "vitest": "*" - } - }, "node_modules/vscode-uri": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", @@ -8473,12 +9843,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, "node_modules/write-file-atomic": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", @@ -8602,6 +9966,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/skore-ui/package.json b/skore-ui/package.json index 4a806eb8a..adca1fe3e 100644 --- a/skore-ui/package.json +++ b/skore-ui/package.json @@ -11,13 +11,13 @@ "test:unit:watch": "vitest", "test:unit:coverage": "vitest run --coverage", "type-check": "vue-tsc --build --force", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "lint": "eslint . --fix", "style-lint": "stylelint --fix src/**/*.{css,vue}", "format": "prettier --write src/" }, "dependencies": { - "@floating-ui/vue": "^1.1.5", - "@vscode/markdown-it-katex": "^1.1.0", + "@floating-ui/vue": "^1.1.6", + "@vscode/markdown-it-katex": "^1.1.1", "date-fns": "^4.1.0", "html-to-image": "^1.11.11", "interactjs": "^1.10.27", @@ -26,48 +26,49 @@ "markdown-it-highlightjs": "^4.2.0", "markdown-it-sub": "^2.0.0", "markdown-it-sup": "^2.0.0", - "pinia": "^2.2.7", - "plotly.js-dist-min": "^2.35.2", - "simplebar-vue": "^2.3.5", + "pinia": "^2.3.0", + "plotly.js-dist-min": "^2.35.3", + "simplebar-vue": "^2.4.0", "vega-embed": "^6.29.0", "vue": "^3.5.13", "vue-router": "^4.5.0" }, "devDependencies": { "@pinia/testing": "^0.1.7", - "@rushstack/eslint-patch": "^1.10.4", - "@tsconfig/node20": "^20.1.4", + "@tsconfig/node22": "^22.0.0", "@types/jsdom": "^21.1.7", "@types/markdown-it": "^14.1.2", "@types/markdown-it-emoji": "^3.0.1", "@types/markdown-it-highlightjs": "^3.3.4", - "@types/node": "^22.10.1", + "@types/node": "^22.10.2", "@types/plotly.js": "^2.35.1", "@types/plotly.js-dist-min": "^2.3.4", "@vitejs/plugin-vue": "^5.2.1", - "@vitest/coverage-v8": "^2.1.6", - "@vue/eslint-config-prettier": "^9.0.0", - "@vue/eslint-config-typescript": "^13.0.0", + "@vitest/coverage-v8": "^2.1.8", + "@vitest/eslint-plugin": "^1.1.24", + "@vue/eslint-config-prettier": "^10.1.0", + "@vue/eslint-config-typescript": "^14.1.3", "@vue/test-utils": "^2.4.6", "@vue/tsconfig": "^0.7.0", "autoprefixer": "^10.4.20", - "eslint": "^8.57.0", - "eslint-plugin-vue": "^9.31.0", + "eslint": "^9.14.0", + "eslint-plugin-vue": "^9.30.0", "jsdom": "^25.0.1", + "npm-run-all2": "^7.0.2", "postcss-html": "^1.7.0", "postcss-nesting": "^13.0.1", "postcss-sorting": "^9.1.0", - "prettier": "^3.4.1", - "stylelint": "^16.10.0", + "prettier": "^3.3.3", + "stylelint": "^16.12.0", "stylelint-config-idiomatic-order": "^10.0.0", "stylelint-config-recommended-vue": "^1.5.0", "stylelint-config-standard": "^36.0.1", "stylelint-order": "^6.0.4", - "typescript": "~5.6.2", - "vite": "^6.0.1", - "vite-plugin-vue-devtools": "^7.6.5", - "vitest": "^2.1.6", + "typescript": "~5.6.3", + "vite": "^6.0.5", + "vite-plugin-vue-devtools": "^7.6.8", + "vitest": "^2.1.8", "vitest-canvas-mock": "^0.3.3", - "vue-tsc": "^2.1.8" + "vue-tsc": "^2.1.10" } } diff --git a/skore-ui/src/assets/fonts/icomoon.eot b/skore-ui/src/assets/fonts/icomoon.eot index 5ad979c136213d5c048f3ee69e62a08217518893..9e4e68647af7f23ef171e46a2811bb8b4831caf6 100644 GIT binary patch delta 864 zcmaJ-Ur1A76hGg++nsLnZu4%_T~|5x@0RGUbGr>3HJlifT8t==s0}p?ygA53qFO`* zK?QmP2~uAy42uXWhy+D^a4#YDQsILXf)9HLB3}aQe4F(YJ$&bUzwBoX$oc)gYBw}B`IyCaqi8t_LKdcN4_GFfocVU1)0`Yk@Gm?c4SVcU8xb|X3 z?R$8%{3qfS)VQ4;yfg$XG9aCxJg_Wzq4ko(q(Pe|$1r{)7qr*RQ|&BsI){zF7Fl2y z!2$aK$00VmvxS6}Xe1CkL@?HqI?kc@Mw`Pn>PctXUR z&r7kGlrPX&VBYN5Jt4KDDdln{{2gk@lV6AhrC4`N3dRx zg3t^JIIi)0mEBtxh2+mIO(lK0^OnalrCp^@lQNb$f$}gn%ubneI0oAHqWxN&DWWuc zcHOeOEAyajI)p%7lY62-;`HllTDqvP{1-5v=v5GRBpT@&aIoY}{xHl#?XU|#UKft7;{qXhqW!s;uNxp;LaH(D-fx*Py2cR{2N_XvwuU%X9 znaBKQt>Ck3>%QtSe^AZBB&Id{|oWeaaR2yat zpUcu^)8*FkNuy{?8K0QqrY-Z3d8wkcB3to{-^<_NzgQwx(b{XpgOY*paA=H<_|#r3?N@6Be$etsTh#OpbRo+PfmVv z;xe=KNkA!}eujkH#EJq2PlgLX{tX~sAulmEb*2~RZy^5%(2ll({NfS@W}pm%S_4R) zfti_c<>VAbdq%CvOBlU@oMVs#Tg^0?QB!zxzWP!| E04q~#PXGV_ diff --git a/skore-ui/src/assets/fonts/icomoon.svg b/skore-ui/src/assets/fonts/icomoon.svg index 57ace39bb..81c801ddd 100644 --- a/skore-ui/src/assets/fonts/icomoon.svg +++ b/skore-ui/src/assets/fonts/icomoon.svg @@ -7,47 +7,50 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/skore-ui/src/assets/fonts/icomoon.ttf b/skore-ui/src/assets/fonts/icomoon.ttf index 839e72fb64e911cd63308ccf61b578882456ba81..d828dd428bc9b3ca8248bce226f8735c4b571b5b 100644 GIT binary patch delta 869 zcma)2T}V@57=FKV_UATtHqSh|qnY!}Z6&(X+}44HS(Ad=Qd9~gXa|1-j}A02sFaW( zsG!b;1f^X_7!?sl^dl(Zg}aM&QxPHkfEQi_krxHl`)$@$7ach7`#taTe&72%@14}w zws9Z;V1o%@L2m5`g}D-w2`dZur@J*ZQ+7P}1b|c`?(A1bGMJr+X}0tap6xq4wQvsc z7=Sc8(5v?BKGrY;P)qw`16VLmvELAPBK8evqhpT+JV7n?!|LF0_W^Y!_A(6MZ^lBO zrjBJG39k^(A+8=$wceXYihd$q#UYn6!y}_86u)2_<$+}%U0>Qy>Ti&vq+U;w)4Gef zr@tU4#qM^EYi)&LbxZ*GnoYN_b?qo;$EiX;0zRgiOnn zhWQ*bV^`59`jgOKHM&A4-fi*hwkI#Tpq_XM59F%IlQ@0iyq?YvSosT>xAe%1TR|G> zPu_?;%nxxt_{z20U+$mc;@`n$pxcE{56`&%WPMWFTM;hQ^CU2s*lPf^W&pj(C+MFg zMPHK)y6m&)Cw+Fz9o-q2hWD^Ry2t`qXPk_}q?t)(nO!#shKokQIB3k7Vy0W>a`T+| zoh5CVEpir3TSaTT^{y>$Tepwemx~V;XNn&aTL GnfwO9Rk(!! delta 399 zcmewmu)w#TfsuiMft#U$ftkU;KUm+0Ux@WDP-G7fCnV=47Kn#k+Q`7bCMc& zODdL%0a*;nAoKU+tueSW&>>$#4P4zX9Yc4GCQ~-YreH=C&?0Oqv+fOtL5tA}{`1;LU+X29^;K z1Qx`7u)wqj38SLI2&#c1KD0Npmx>;u2z=OEjn97W&`qe&wI$9f}s6X5^n?e$~yBPZ@OjGQ)lr}ns9M-~Xil=UXq2XQ?IzgU9 z0vp4?O6*lwQ^#cuLE_D31Bw#J=BUjv&o^CCP)qLa@_1V7l3Gy8E-L%x~uXPtk&zUtjm6)y1kBrxdM z8vv9ufZpO0^@uN`Kk*s#ai3YA_gT>&?M=fBe1sh7CK~gXsm67ivgM)Dfy@{hOSdSir!*m;n?I0b%j5OB>S@ zi$P+0fP4-p7D&&jOaqD?VPH^v0m3?|$9HF>CZ;ejD6avkF#};OOV;ZdKtZ6`6Chs& zgxRMvf5^x!sQ@xTW`WGyvs8?MAtygMk-?uq1!y2c0thcNTc4DhSOK(H6{JxCj6E4H zxZ_mbZcMOfSyg1^LA#K<}J^t78VTn3-2j=3%sF)SB$W=nW)iFlJ6($`mu1 zk!j`RCT8i)l`M03HdhLM7naF3a@!iuZ}XLbn+2$kf#LC@2Ww&U zVX~H*)Z{O!7LyIs(xZJC<}s{e6kxPrOkwO{+`@Q+NsQTsIg9xkOBTxkRyEcx)(>ns zY=79B*pG1-aU9^Z;Vj`i!4=2N#XXJt2~P^oB%X7;4!n!_Sop&DZTQ;+90b-0d;!Og K{^nn5hZq4IU}}&6 diff --git a/skore-ui/src/assets/styles/_icons.css b/skore-ui/src/assets/styles/_icons.css index e3a773bf1..8b97e7e4b 100644 --- a/skore-ui/src/assets/styles/_icons.css +++ b/skore-ui/src/assets/styles/_icons.css @@ -27,174 +27,186 @@ text-transform: none; } -.icon-ascending-arrow::before { +.icon-bar-chart::before { content: "\e900"; } -.icon-bar-chart::before { +.icon-ascending-arrow::before { content: "\e901"; } -.icon-branch::before { +.icon-bold::before { content: "\e902"; } -.icon-calendar::before { +.icon-branch::before { content: "\e903"; } -.icon-check::before { +.icon-bullets::before { content: "\e904"; } -.icon-chevron-down::before { +.icon-calendar::before { content: "\e905"; } -.icon-chevron-left::before { +.icon-check::before { content: "\e906"; } -.icon-chevron-right::before { +.icon-chevron-down::before { content: "\e907"; } -.icon-chevron-up::before { +.icon-chevron-left::before { content: "\e908"; } -.icon-copy::before { +.icon-chevron-right::before { content: "\e909"; } -.icon-dashboard::before { +.icon-chevron-up::before { content: "\e90a"; } -.icon-descending-arrow::before { +.icon-copy::before { content: "\e90b"; } -.icon-edit::before { +.icon-dashboard::before { content: "\e90c"; } -.icon-error-circle::before { +.icon-descending-arrow::before { content: "\e90d"; } -.icon-folder::before { +.icon-edit::before { content: "\e90e"; } -.icon-gift::before { +.icon-error-circle::before { content: "\e90f"; } -.icon-handle::before { +.icon-folder::before { content: "\e910"; } -.icon-hard-drive::before { +.icon-gift::before { content: "\e911"; } -.icon-history::before { +.icon-handle::before { content: "\e912"; } -.icon-info-circle::before { +.icon-hard-drive::before { content: "\e913"; } -.icon-large-bar-chart::before { +.icon-history::before { content: "\e914"; } -.icon-left-double-chevron::before { +.icon-info-circle::before { content: "\e915"; } -.icon-list-sparkle::before { +.icon-italic::before { content: "\e916"; } -.icon-maximize::before { +.icon-large-bar-chart::before { content: "\e917"; } -.icon-moon::before { +.icon-left-double-chevron::before { content: "\e918"; } -.icon-more::before { +.icon-list-sparkle::before { content: "\e919"; } -.icon-new-document::before { +.icon-maximize::before { content: "\e91a"; } -.icon-pie-chart::before { +.icon-moon::before { content: "\e91b"; } -.icon-pill::before { +.icon-more::before { content: "\e91c"; } -.icon-playground::before { +.icon-new-document::before { content: "\e91d"; } -.icon-plot::before { +.icon-pie-chart::before { content: "\e91e"; } -.icon-plus-circle::before { +.icon-pill::before { content: "\e91f"; } -.icon-plus::before { +.icon-playground::before { content: "\e920"; } -.icon-podium::before { +.icon-plot::before { content: "\e921"; } -.icon-recent-document::before { +.icon-plus-circle::before { content: "\e922"; } -.icon-search::before { +.icon-plus::before { content: "\e923"; } -.icon-square-cursor::before { +.icon-podium::before { content: "\e924"; } -.icon-success-circle::before { +.icon-recent-document::before { content: "\e925"; } -.icon-sun::before { +.icon-search::before { content: "\e926"; } -.icon-text::before { +.icon-square-cursor::before { content: "\e927"; } -.icon-trash::before { +.icon-success-circle::before { content: "\e928"; } -.icon-warning-circle::before { +.icon-sun::before { content: "\e929"; } -.icon-warning::before { +.icon-text::before { content: "\e92a"; } + +.icon-trash::before { + content: "\e92b"; +} + +.icon-warning-circle::before { + content: "\e92c"; +} + +.icon-warning::before { + content: "\e92d"; +} diff --git a/skore-ui/src/assets/styles/_variables.css b/skore-ui/src/assets/styles/_variables.css index 22fa25f06..b49e3e2ba 100644 --- a/skore-ui/src/assets/styles/_variables.css +++ b/skore-ui/src/assets/styles/_variables.css @@ -60,6 +60,7 @@ body[data-vscode-theme-kind="vscode-dark"], --color-text-branding: #f08b30; --color-text-button-primary: #fff; --color-text-logo: #fff; + --color-text-danger: #ff3b00; /* icons */ --color-icon-primary: #f08b30; @@ -98,6 +99,7 @@ body[data-vscode-theme-kind="vscode-light"], --color-text-branding: #3043f0; --color-text-button-primary: #fff; --color-text-logo: #000; + --color-text-danger: #ff3b00; /* icons */ --color-icon-primary: #f08b30; diff --git a/skore-ui/src/components/CrossValidationReportResults.vue b/skore-ui/src/components/CrossValidationReportResults.vue index ee48d77cc..811050928 100644 --- a/skore-ui/src/components/CrossValidationReportResults.vue +++ b/skore-ui/src/components/CrossValidationReportResults.vue @@ -3,6 +3,7 @@ import Simplebar from "simplebar-vue"; import { computed, ref, useTemplateRef } from "vue"; import DropdownButton from "@/components/DropdownButton.vue"; +import DropdownButtonItem from "@/components/DropdownButtonItem.vue"; import FloatingTooltip from "@/components/FloatingTooltip.vue"; import MetricFavorability from "@/components/MetricFavorability.vue"; import StaticRange from "@/components/StaticRange.vue"; diff --git a/skore-ui/src/components/FloatingTooltip.vue b/skore-ui/src/components/FloatingTooltip.vue index 651f5cf77..b380e62a1 100644 --- a/skore-ui/src/components/FloatingTooltip.vue +++ b/skore-ui/src/components/FloatingTooltip.vue @@ -62,6 +62,7 @@ const { floatingStyles } = useFloating(reference, floating, { border-radius: var(--radius-xs); background-color: var(--color-background-primary); box-shadow: 0 4px 18.2px -2px var(--color-shadow); + color: var(--color-text-secondary); } } diff --git a/skore-ui/src/components/ImageWidget.vue b/skore-ui/src/components/ImageWidget.vue index 9f8bd5db7..f7655c112 100644 --- a/skore-ui/src/components/ImageWidget.vue +++ b/skore-ui/src/components/ImageWidget.vue @@ -1,4 +1,4 @@ - + + + + diff --git a/skore-ui/src/components/SimpleButton.vue b/skore-ui/src/components/SimpleButton.vue index 8218c3068..a0f72797d 100644 --- a/skore-ui/src/components/SimpleButton.vue +++ b/skore-ui/src/components/SimpleButton.vue @@ -46,6 +46,7 @@ onBeforeMount(() => { diff --git a/skore-ui/src/views/project/ItemNote.vue b/skore-ui/src/views/project/ItemNote.vue new file mode 100644 index 000000000..8b02938be --- /dev/null +++ b/skore-ui/src/views/project/ItemNote.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/skore-ui/src/views/project/ProjectView.vue b/skore-ui/src/views/project/ProjectView.vue index 83ecbc8f4..2b3916ae5 100644 --- a/skore-ui/src/views/project/ProjectView.vue +++ b/skore-ui/src/views/project/ProjectView.vue @@ -9,6 +9,7 @@ import ProjectViewCard from "@/components/ProjectViewCard.vue"; import SimpleButton from "@/components/SimpleButton.vue"; import { useProjectStore } from "@/stores/project"; import { useToastsStore } from "@/stores/toasts"; +import ItemNote from "@/views/project/ItemNote.vue"; import ProjectItemList from "@/views/project/ProjectItemList.vue"; import ProjectViewNavigator from "@/views/project/ProjectViewNavigator.vue"; @@ -98,7 +99,7 @@ onBeforeUnmount(() => { @drop="onItemDrop($event)" @dragover.prevent > -