Repository: skrub-data/skrub
Pull request #1531 (2025-07-21 14:38:05)

Improve choices example
This PR suggests a lot of rewording and adds links to references of the choices example. To be useful, this example should be significantly shorter, and most parts detailing the features of the data ops or choices should go to the user guides, in https://github.com/skrub-data/skrub/pull/1529.


Patch:
diff --git a/examples/data_ops/11_choices.py b/examples/data_ops/11_choices.py
index 918e99e..c256e17 100644
--- a/examples/data_ops/11_choices.py
+++ b/examples/data_ops/11_choices.py
@@ -2,39 +2,44 @@
 .. currentmodule:: skrub
 
 .. _example_tuning_pipelines:
-Tuning DataOps
-================
+
+Hyperparameter tuning with DataOps
+==============
 
 A machine-learning pipeline typically contains some values or choices which
 may influence its prediction performance, such as hyperparameters (e.g. the
-regularization parameter ``alpha`` of a ``RidgeClassifier``, the
-``learning_rate`` of a ``HistGradientBoostingClassifier``), which estimator to
-use (e.g. ``RidgeClassifier`` or ``HistGradientBoostingClassifier``), or which
-steps to include (e.g. should we join a table to bring additional information
+regularization parameter ``alpha`` of a :class:`~sklearn.linear_model.RidgeClassifier`,
+the ``learning_rate`` of a :class:`~sklearn.ensemble.HistGradientBoostingClassifier`),
+which estimator to use (e.g. ``RidgeClassifier`` or ``HistGradientBoostingClassifier``),
+or which steps to include (e.g. should we join a table to bring additional information
 or not).
 
 We want to tune those choices by trying several options and keeping those that
 give the best performance on a validation set.
 
-Skrub :ref:`DataOps <skrub_pipeline>` provide a convenient way to specify
+Skrub :ref:`DataOps<skrub_data_ops>` provide a convenient way to specify
 the range of possible values, by inserting it directly in place of the actual
 value. For example we can write:
+"""
+# %%
+from sklearn.linear_model import RidgeClassifier
 
-``RidgeClassifier(alpha=skrub.choose_from([0.1, 1.0, 10.0], name='α'))``
+import skrub
 
-instead of:
+RidgeClassifier(alpha=skrub.choose_from([0.1, 1.0, 10.0], name="α"))
 
-``RidgeClassifier(alpha=1.0)``.
+# %%
+# instead of:
 
-Skrub then inspects
-our DataOps plan to discover all the places where we used objects like
-``skrub.choose_from()`` and builds a grid of hyperparameters for us.
-"""
+RidgeClassifier(alpha=1.0)
 
 # %%
+# Skrub then inspects our DataOps plan to discover all the places where we used objects
+# like :func:`~skrub.choose_from()` and builds a grid of hyperparameters for us.
+#
 # We will illustrate hyperparameter tuning on the "toxicity" dataset. This
-#  dataset contains 1,000 texts and the task is to predict if they are
-#  flagged as being toxic or not.
+# dataset contains 1,000 texts and the task is to predict if they are
+# flagged as being toxic or not.
 #
 # We start from a very simple pipeline without any hyperparameters.
 
@@ -55,10 +60,12 @@ labels = data["is_toxic"]
 # %%
 # We mark the ``texts`` column as the input variable and the ``labels`` column as
 # the target variable.
+#
 # See `the previous example <10_expressions.html>`_ for a more detailed explanation
-# of ``skrub.X`` and ``skrub.y``.
-# We then encode the text with a ``MinHashEncoder`` and fit a
-# ``HistGradientBoostingClassifier`` on the resulting features.
+# of :func:`skrub.X` and :func:`skrub.y`.
+#
+# We then encode the text with a :class:`~skrub.MinHashEncoder` and fit a
+# :class:`~sklearn.ensemble.HistGradientBoostingClassifier` on the resulting features.
 
 # %%
 X = skrub.X(texts)
@@ -72,33 +79,23 @@ y
 pred = X.skb.apply(skrub.MinHashEncoder()).skb.apply(
     HistGradientBoostingClassifier(), y=y
 )
-
 pred.skb.cross_validate(n_jobs=4)["test_score"]
 
 # %%
-# For the sake of the example, we will focus on the number of ``MinHashEncoder``
-# components and the ``learning_rate`` of the ``HistGradientBoostingClassifier``
-# to illustrate the ``skrub.choose_from(...)`` objects.
-# When we use a scikit-learn hyperparameter-tuner like ``GridSearchCV`` or
-# ``RandomizedSearchCV``, we need to specify a grid of hyperparameters separately
-# from the estimator, with something similar to
+# In this example, we will focus on the ``n_components`` of the
+# ``MinHashEncoder`` and the ``learning_rate`` of the ``HistGradientBoostingClassifier``
+# to illustrate the choices objects.
+#
+# When we use a scikit-learn hyperparameter-tuner like
+# :class:`~sklearn.model_selection.GridSearchCV` or
+# :class:`~sklearn.model_selection.RandomizedSearchCV`, we need to specify a grid of
+# hyperparameters separately from the estimator, with something similar to
 # ``GridSearchCV(my_pipeline, param_grid={"encoder__n_components: [5, 10, 20]"})``.
+#
 # Instead, within a skrub DataOps plan we can use
 # ``skrub.choose_from(...)`` directly where the actual value
 # would normally go. Skrub then takes care of constructing the
-# ``GridSearchCV``'s parameter grid for us.
-#
-# Several utilities are available:
-#
-# - :func:`choose_from` to choose from a discrete set of values
-# - :func:`choose_float` and :func:`choose_int` to sample numbers in a given range
-# - :func:`choose_bool` to choose between ``True`` and ``False``
-# - :func:`optional` to choose between something and ``None``; typically to make a
-#   transformation step optional such as
-#   ``X.skb.apply(skrub.optional(StandardScaler()))``
-#
-# Choices can be given an optional name which is used to display hyperparameter
-# search results and plots, or to override their outcome (ADD A REFERENCE HERE).
+# :class:`~sklearn.model_selection.GridSearchCV`'s parameter grid for us.
 #
 # Note that :func:`skrub.choose_float()` and :func:`skrub.choose_int()` can be given a
 # ``log`` argument to sample in log scale, and that it is possible to specify the
@@ -130,20 +127,21 @@ search.results_
 
 # %%
 # If the plotly library is installed, we can visualize the results of the
-# hyperparameter search with ``.plot_results()``.
+# hyperparameter search with :func:`~skrub.ParamSearch.plot_results`.
 # In the plot below, each line represents a combination of hyperparameters (in
 # this case, only ``N components`` and ``learning rate``), and each column of
 # points represents either a hyperparameter, or the score of a given
 # combination of hyperparameters.
+#
 # The color of the line represents the score of the combination of hyperparameters.
 # The plot is interactive, and it is  possible to select only a subset of the
 # hyperparameters to visualize by dragging the mouse over each column to select
 # the desired range.
+#
 # This is particularly useful when there are many combinations of hyperparameters,
 # and we are interested in understanding which hyperparameters have the largest
 # impact on the score.
 
-# %%
 search.plot_results()
 
 # %%
diff --git a/examples/data_ops/13_use_case.py b/examples/data_ops/13_use_case.py
new file mode 100644
index 0000000..5838a19
--- /dev/null
+++ b/examples/data_ops/13_use_case.py
@@ -0,0 +1,171 @@
+"""
+Usecase: developing locally, and avoiding to repeat code in production
+======================================================================
+
+"""
+
+# %%
+# As a team of data scientists, we are tasked with a project to predict whether an email
+# is potentially malicious (i.e., spam or phishing). We develop and test our models
+# locally, either in a Jupyter notebook or within a Python script. Once we are satisfied
+# with the model's performance, we move on to deploying it.
+#
+# In this use case, every time the email provider receives a new email, they want to
+# verify whether it is spam before displaying it in the recipient’s inbox. To achieve
+# this, they plan to integrate a machine learning model within a microservice. This
+# microservice will accept an email’s data as a JSON payload and return a score between
+# 0 and 1, indicating the likelihood that the email is spam.
+#
+# To avoid rewriting the entire data pipeline when moving from model validation to
+# production deployment, which is both error-prone and inefficient, we prefer to load an
+# object that encapsulates the same processing pipeline used during model development.
+# This is where the :class:`~skrub.SkrubLearner` can help.
+#
+# Adopting this workflow also has the benefit of forcing us to clearly define the type
+# of data that will be available at the input of the microservice. It helps ensure we
+# build models that rely only on information accessible at this specific point in the
+# product pipeline. For example, since we want to detect spam before the email reaches
+# the recipient’s inbox, we cannot use features that are only available after the
+# recipient opens the email.
+#
+# Since this example is focused on the pipeline construction itself, we won't look at
+# our model performance.
+
+# %%
+# Generating the training data
+# ----------------------------
+# In this section, we define a few functions that help us with generating the
+# training data in dictionary form. We are going to generate a fully random data set.
+import random
+import string
+import uuid
+from datetime import datetime, timedelta
+
+import numpy as np
+
+
+def generate_id():
+    return str(uuid.uuid4())
+
+
+def generate_email():
+    length = random.randint(5, 10)
+    username = "".join(random.choice(string.ascii_lowercase) for _ in range(length))
+    domain = ["google", "yahoo", "whatever"]
+    tld = ["fr", "en", "com", "net"]
+    return f"{username}@{random.choice(domain)}.{random.choice(tld)}"
+
+
+def generate_datetime():
+    random_seconds = random.randint(0, int(timedelta(days=2).total_seconds()))
+    random_datetime = datetime.now() - timedelta(seconds=random_seconds)
+    return random_datetime
+
+
+def generate_text(min_str_length, max_str_length):
+    random_length = random.randint(min_str_length, max_str_length)
+    random_text = "".join(
+        random.choice(string.ascii_letters + string.digits + string.punctuation)
+        for _ in range(random_length)
+    )
+    return random_text
+
+
+# %%
+# We generate 1000 training samples and store them in a list of dictionaries:
+
+n_samples = 1000
+
+# %%
+# In this use case, the emails to be tested when the model is put in production
+# are not contained in a dataframe, but in a json. As a result, our training data
+# should also be contained in a list of dictionaries.
+
+X = [
+    {
+        "id": generate_id(),
+        "sender": generate_email(),
+        "title": generate_text(max_str_length=10, min_str_length=2),
+        "content": generate_text(max_str_length=100, min_str_length=10),
+        "date": generate_datetime(),
+        "cc_emails": [generate_email() for _ in range(random.randint(0, 5))],
+    }
+    for _ in range(n_samples)
+]
+
+
+# generate array of 1 and 0 to represent the target variable
+y = np.random.binomial(n=1, p=0.9, size=n_samples)
+
+# %%
+# Building the DataOps plan
+# -------------------------
+# Let's start our DataOps plan by indicating what are the features and the target
+# variable.
+import skrub
+
+X = skrub.X(X)
+y = skrub.y(y)
+
+# %%
+# The variable X for now is a list of dicts. It's not something that an estimator can
+# handle directly.
+# Let's convert it to a pandas DataFrame using :func:`~skrub.DataOp.skb.apply_func`.
+import pandas as pd
+
+df = X.skb.apply_func(pd.DataFrame)
+
+# %%
+# For this example, we will use a strong baseline, with skrub's
+# :func:`~skrub.tabular_pipeline()`.
+tab_pipeline = skrub.tabular_pipeline("classification")
+
+# We can now apply the predictive model to the data.
+# The DataOps plan is ready after applying the model to the data.
+predictions = df.skb.apply(tab_pipeline, y=y)
+
+# We can then explore the full plan:
+predictions.skb.draw_graph()
+
+# %%
+# To end the explorative work, we need to build the learner, fit it, and save it to a
+# file.
+# Passing ``fitted=True`` to the :func:`~skrub.DataOp.skb.make_learner`
+# function makes it so that the learner is fitted on the data that has been passed to
+# the variables of the DataOps plan.
+import joblib
+
+with open("learner.pkl", "wb") as f:
+    learner = predictions.skb.make_learner(fitted=True)
+    joblib.dump(learner, f)
+
+# %%
+# Production phase
+# ----------------
+#
+# In our microservice, we receive a payload in json format.
+X_input = {
+    "id": generate_id(),
+    "sender": generate_email(),
+    "title": generate_text(max_str_length=10, min_str_length=2),
+    "content": generate_text(max_str_length=100, min_str_length=10),
+    "date": generate_datetime(),
+    "cc_emails": [generate_email() for _ in range(random.randint(0, 5))],
+}
+
+# We just have to load the learner and use it to predict the score for this input.
+with open("learner.pkl", "rb") as f:
+    loaded_learner = joblib.load(f)
+# ``X_input`` must be passed as a list so that it can be parsed correctly as a dataframe
+# by Pandas.
+prediction = loaded_learner.predict({"X": [X_input]})
+prediction
+
+# %%
+# Conclusion
+# ----------
+#
+# Thanks to the skrub DataOps and learner, we are assured that all the transformations
+# and preprocessing done during model development are exactly the same that are done in
+# production.
+# It becomes easy and straightforward to deploy.
diff --git a/skrub/_data_ops/_data_ops.py b/skrub/_data_ops/_data_ops.py
index 05e0381..9977617 100644
--- a/skrub/_data_ops/_data_ops.py
+++ b/skrub/_data_ops/_data_ops.py
@@ -1187,6 +1187,38 @@ def _check_column_names(X):
     return cast_column_names_to_strings(X)
 
 
+def check_subsampled_X_y_shape(X_op, y_op, X_value, y_value, mode, environment, msg=""):
+    from ._subsampling import _should_subsample, uses_subsampling
+
+    if not isinstance(X_op, DataOp) or not isinstance(y_op, DataOp):
+        return
+    if not _should_subsample(mode, environment):
+        # no subsampling is taking place
+        return
+    try:
+        n_X, n_y = len(X_value), len(y_value)
+    except Exception:
+        # X or y is not an array (eg y is None), we cannot compare lengths
+        return
+    if n_X == n_y:
+        return
+    X_subsampled, y_subsampled = uses_subsampling(X_op), uses_subsampling(y_op)
+    if X_subsampled ^ y_subsampled:
+        sub, other = ("X", "y") if X_subsampled else ("y", "X")
+        raise ValueError(
+            f"`{sub}` was subsampled with `.skb.subsample()` but `{other}` was not, "
+            f"resulting in different sizes (X: {n_X}, y: {n_y}).{msg}"
+        )
+    # Either both used subsampling or neither did, but subsampling could still
+    # be at fault eg if they used a different number of samples. We
+    # could investigate further but for simplicity we raise the usual
+    # scikit-learn message.
+    raise ValueError(
+        "Found input variables with inconsistent numbers of samples: "
+        f"X: {n_X}, y: {n_y}"
+    )
+
+
 class Apply(DataOpImpl):
     """.skb.apply() nodes."""
 
@@ -1207,6 +1239,8 @@ class Apply(DataOpImpl):
         else:
             y = None
 
+        check_subsampled_X_y_shape(self.X, self.y, X, y, mode, environment)
+
         X = _check_column_names(X)
 
         if "fit" in method_name:
diff --git a/skrub/_data_ops/_estimator.py b/skrub/_data_ops/_estimator.py
index 106e16a..b698923 100644
--- a/skrub/_data_ops/_estimator.py
+++ b/skrub/_data_ops/_estimator.py
@@ -8,7 +8,7 @@ from sklearn.utils.validation import check_is_fitted
 
 from .. import _join_utils
 from ._choosing import BaseNumericChoice, get_default
-from ._data_ops import Apply
+from ._data_ops import Apply, check_subsampled_X_y_shape
 from ._evaluation import (
     choice_graph,
     chosen_or_default_outcomes,
@@ -518,6 +518,13 @@ def _compute_Xy(data_op, environment):
             environment=environment,
             clear=False,
         )
+        msg = (
+            "\nAre `.skb.subsample()` and `.skb.mark_as_*()` applied in the same order"
+            " for both X and y?"
+        )
+        check_subsampled_X_y_shape(
+            Xy["X"], Xy["y"], X, y, "fit_transform", environment, msg=msg
+        )
     else:
         y = None
     return X, y


Test patch:
diff --git a/skrub/_data_ops/tests/test_errors.py b/skrub/_data_ops/tests/test_errors.py
index c04d319..66bff70 100644
--- a/skrub/_data_ops/tests/test_errors.py
+++ b/skrub/_data_ops/tests/test_errors.py
@@ -1,4 +1,5 @@
 import pickle
+import re
 
 import numpy as np
 import pandas as pd
@@ -239,6 +240,94 @@ def test_warn_if_choice_before_X_or_y():
         )
 
 
+def test_inconsistent_subsampling():
+    X_a, y_a = make_classification(random_state=0)
+
+    # X subsampled but not y
+    X, y = skrub.var("X", X_a).skb.subsample(n=10), skrub.var("y", y_a)
+    with pytest.raises(
+        RuntimeError,
+        match=re.escape(
+            "`X` was subsampled with `.skb.subsample()` but `y` was not, resulting in"
+            " different sizes (X: 10, y: 100)"
+        ),
+    ):
+        X.skb.apply(LogisticRegression(), y=y)
+
+    # y subsampled but not X
+    X, y = skrub.var("X", X_a), skrub.var("y", y_a).skb.subsample(n=10)
+    with pytest.raises(
+        RuntimeError,
+        match=re.escape(
+            "`y` was subsampled with `.skb.subsample()` but `X` was not, resulting in"
+            " different sizes (X: 100, y: 10)"
+        ),
+    ):
+        X.skb.apply(LogisticRegression(), y=y)
+
+    # Same without previews
+    X, y = skrub.var("X"), skrub.var("y").skb.subsample(n=10)
+    pred = X.skb.apply(LogisticRegression(), y=y)
+    # evaluating without subsampling works
+    pred.skb.eval({"X": X_a, "y": y_a})
+    # evaluating with subsampling raises
+    with pytest.raises(ValueError, match="`y` was subsampled.*`X` was not"):
+        pred.skb.eval({"X": X_a, "y": y_a}, keep_subsampling=True)
+
+    # In the following case subsampling is consistent but the shapes don't
+    # match for some other reason
+    msg = re.escape(
+        "Found input variables with inconsistent numbers of samples: X: 99, y: 100"
+    )
+    X = skrub.var("X", X_a[:-1]).skb.subsample(n=1000)
+    y = skrub.var("y", y_a).skb.subsample(n=1000)
+    with pytest.raises(RuntimeError, match=msg):
+        X.skb.apply(LogisticRegression(), y=y)
+    X = skrub.var("X", X_a[:-1])
+    y = skrub.var("y", y_a)
+    with pytest.raises(RuntimeError, match=msg):
+        X.skb.apply(LogisticRegression(), y=y)
+
+
+def test_inconsistent_subsampling_and_split_order():
+    X_a, y_a = make_classification(random_state=0)
+
+    # Both are subsampled but X is subsampled after splitting and y is subsampled before
+    X = skrub.var("X", X_a).skb.mark_as_X().skb.subsample(n=10)
+    y = skrub.var("y", y_a).skb.subsample(n=10).skb.mark_as_y()
+    pred = X.skb.apply(LogisticRegression(), y=y)
+
+    # computing preview and eval works as there is no splitting
+    assert pred.shape.skb.preview() == (10,)
+    assert pred.shape.skb.eval() == (100,)
+    msg = re.escape(
+        "`y` was subsampled with `.skb.subsample()` but `X` was not, resulting in"
+        " different sizes (X: 100, y: 10)."
+        "\nAre `.skb.subsample()` and `.skb.mark_as_*()` applied in the same order"
+        " for both X and y?"
+    )
+    # splitting with subsampling disabled works
+    pred.skb.train_test_split()
+    # but splitting with subsampling enabled raises
+    with pytest.raises(ValueError, match=msg):
+        pred.skb.train_test_split(keep_subsampling=True)
+
+    # same for cross-validation
+    pred.skb.cross_validate()
+    with pytest.raises(ValueError, match=msg):
+        pred.skb.cross_validate(keep_subsampling=True)
+
+    # subsampling OK but mismatch for some other reason
+    msg = re.escape(
+        "Found input variables with inconsistent numbers of samples: X: 99, y: 100"
+    )
+    X = skrub.var("X").skb.subsample(n=1000).skb.mark_as_X()
+    y = skrub.var("y").skb.subsample(n=1000).skb.mark_as_y()
+    pred = X.skb.apply(LogisticRegression(), y=y)
+    with pytest.raises(ValueError, match=msg):
+        pred.skb.train_test_split({"X": X_a[:-1], "y": y_a}, keep_subsampling=True)
+
+
 #
 # Bad arguments passed to eval()
 #
diff --git a/skrub/_reporting/js_tests/package-lock.json b/skrub/_reporting/js_tests/package-lock.json
index 177ca5e..100787b 100644
--- a/skrub/_reporting/js_tests/package-lock.json
+++ b/skrub/_reporting/js_tests/package-lock.json
@@ -23,10 +23,11 @@
       }
     },
     "node_modules/@cypress/request": {
-      "version": "3.0.1",
-      "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz",
-      "integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==",
+      "version": "3.0.8",
+      "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.8.tgz",
+      "integrity": "sha512-h0NFgh1mJmm1nr4jCwkGHwKneVYKghUyWe6TMNrk0B9zsjAJxpg8C4/+BAcmLgCPa1vj1V8rNUaILl+zYRUWBQ==",
       "dev": true,
+      "license": "Apache-2.0",
       "dependencies": {
         "aws-sign2": "~0.7.0",
         "aws4": "^1.8.0",
@@ -34,16 +35,16 @@
         "combined-stream": "~1.0.6",
         "extend": "~3.0.2",
         "forever-agent": "~0.6.1",
-        "form-data": "~2.3.2",
-        "http-signature": "~1.3.6",
+        "form-data": "~4.0.0",
+        "http-signature": "~1.4.0",
         "is-typedarray": "~1.0.0",
         "isstream": "~0.1.2",
         "json-stringify-safe": "~5.0.1",
         "mime-types": "~2.1.19",
         "performance-now": "^2.1.0",
-        "qs": "6.10.4",
+        "qs": "6.14.0",
         "safe-buffer": "^5.1.2",
-        "tough-cookie": "^4.1.3",
+        "tough-cookie": "^5.0.0",
         "tunnel-agent": "^0.6.0",
         "uuid": "^8.3.2"
       },
@@ -188,6 +189,7 @@
       "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
       "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "safer-buffer": "~2.1.0"
       }
@@ -197,6 +199,7 @@
       "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
       "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
       "dev": true,
+      "license": "MIT",
       "engines": {
         "node": ">=0.8"
       }
@@ -220,7 +223,8 @@
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
       "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
-      "dev": true
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/at-least-node": {
       "version": "1.0.0",
@@ -271,6 +275,7 @@
       "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
       "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
       "dev": true,
+      "license": "BSD-3-Clause",
       "dependencies": {
         "tweetnacl": "^0.14.3"
       }
@@ -329,17 +334,29 @@
         "node": ">=6"
       }
     },
-    "node_modules/call-bind": {
-      "version": "1.0.7",
-      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
-      "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "es-define-property": "^1.0.0",
         "es-errors": "^1.3.0",
-        "function-bind": "^1.1.2",
-        "get-intrinsic": "^1.2.4",
-        "set-function-length": "^1.2.1"
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/call-bound": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+      "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "get-intrinsic": "^1.3.0"
       },
       "engines": {
         "node": ">= 0.4"
@@ -487,6 +504,7 @@
       "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
       "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "delayed-stream": "~1.0.0"
       },
@@ -516,7 +534,8 @@
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==",
-      "dev": true
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/cross-spawn": {
       "version": "7.0.3",
@@ -594,6 +613,7 @@
       "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
       "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "assert-plus": "^1.0.0"
       },
@@ -624,37 +644,37 @@
         }
       }
     },
-    "node_modules/define-data-property": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
-      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
-      "dev": true,
-      "dependencies": {
-        "es-define-property": "^1.0.0",
-        "es-errors": "^1.3.0",
-        "gopd": "^1.0.1"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
     "node_modules/delayed-stream": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
       "dev": true,
+      "license": "MIT",
       "engines": {
         "node": ">=0.4.0"
       }
     },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/ecc-jsbn": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
       "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "jsbn": "~0.1.0",
         "safer-buffer": "^2.1.0"
@@ -689,13 +709,11 @@
       }
     },
     "node_modules/es-define-property": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
-      "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
       "dev": true,
-      "dependencies": {
-        "get-intrinsic": "^1.2.4"
-      },
+      "license": "MIT",
       "engines": {
         "node": ">= 0.4"
       }
@@ -705,6 +723,36 @@
       "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
       "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
       "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
       "engines": {
         "node": ">= 0.4"
       }
@@ -792,7 +840,8 @@
       "dev": true,
       "engines": [
         "node >=0.6.0"
-      ]
+      ],
+      "license": "MIT"
     },
     "node_modules/fd-slicer": {
       "version": "1.1.0",
@@ -828,17 +877,20 @@
       }
     },
     "node_modules/form-data": {
-      "version": "2.3.3",
-      "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
-      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+      "version": "4.0.4",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
+      "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "asynckit": "^0.4.0",
-        "combined-stream": "^1.0.6",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
         "mime-types": "^2.1.12"
       },
       "engines": {
-        "node": ">= 0.12"
+        "node": ">= 6"
       }
     },
     "node_modules/fs-extra": {
@@ -861,21 +913,28 @@
       "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
       "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
       "dev": true,
+      "license": "MIT",
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
     },
     "node_modules/get-intrinsic": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
-      "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
         "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
         "function-bind": "^1.1.2",
-        "has-proto": "^1.0.1",
-        "has-symbols": "^1.0.3",
-        "hasown": "^2.0.0"
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
       },
       "engines": {
         "node": ">= 0.4"
@@ -884,6 +943,20 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/get-stream": {
       "version": "5.2.0",
       "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
@@ -913,6 +986,7 @@
       "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
       "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "assert-plus": "^1.0.0"
       }
@@ -933,12 +1007,13 @@
       }
     },
     "node_modules/gopd": {
-      "version": "1.0.1",
-      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
-      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
       "dev": true,
-      "dependencies": {
-        "get-intrinsic": "^1.1.3"
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
       },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
@@ -959,23 +1034,12 @@
         "node": ">=8"
       }
     },
-    "node_modules/has-property-descriptors": {
-      "version": "1.0.2",
-      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
-      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
-      "dev": true,
-      "dependencies": {
-        "es-define-property": "^1.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/ljharb"
-      }
-    },
-    "node_modules/has-proto": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
-      "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
       "dev": true,
+      "license": "MIT",
       "engines": {
         "node": ">= 0.4"
       },
@@ -983,11 +1047,15 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/has-symbols": {
-      "version": "1.0.3",
-      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
-      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
       "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
       "engines": {
         "node": ">= 0.4"
       },
@@ -1000,6 +1068,7 @@
       "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
       "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "function-bind": "^1.1.2"
       },
@@ -1008,14 +1077,15 @@
       }
     },
     "node_modules/http-signature": {
-      "version": "1.3.6",
-      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz",
-      "integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==",
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz",
+      "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "assert-plus": "^1.0.0",
         "jsprim": "^2.0.2",
-        "sshpk": "^1.14.1"
+        "sshpk": "^1.18.0"
       },
       "engines": {
         "node": ">=0.10"
@@ -1160,13 +1230,15 @@
       "version": "0.1.1",
       "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
       "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
-      "dev": true
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/json-schema": {
       "version": "0.4.0",
       "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
       "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
-      "dev": true
+      "dev": true,
+      "license": "(AFL-2.1 OR BSD-3-Clause)"
     },
     "node_modules/json-stringify-safe": {
       "version": "5.0.1",
@@ -1194,6 +1266,7 @@
       "engines": [
         "node >=0.6.0"
       ],
+      "license": "MIT",
       "dependencies": {
         "assert-plus": "1.0.0",
         "extsprintf": "1.3.0",
@@ -1314,6 +1387,16 @@
         "node": ">=8"
       }
     },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
     "node_modules/merge-stream": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
@@ -1325,6 +1408,7 @@
       "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
       "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
       "dev": true,
+      "license": "MIT",
       "engines": {
         "node": ">= 0.6"
       }
@@ -1334,6 +1418,7 @@
       "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
       "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "mime-db": "1.52.0"
       },
@@ -1378,10 +1463,11 @@
       }
     },
     "node_modules/object-inspect": {
-      "version": "1.13.2",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
-      "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+      "version": "1.13.4",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+      "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
       "dev": true,
+      "license": "MIT",
       "engines": {
         "node": ">= 0.4"
       },
@@ -1491,12 +1577,6 @@
       "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==",
       "dev": true
     },
-    "node_modules/psl": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
-      "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
-      "dev": true
-    },
     "node_modules/pump": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@@ -1507,22 +1587,14 @@
         "once": "^1.3.1"
       }
     },
-    "node_modules/punycode": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
-      "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
-      "dev": true,
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/qs": {
-      "version": "6.10.4",
-      "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz",
-      "integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==",
+      "version": "6.14.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz",
+      "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==",
       "dev": true,
+      "license": "BSD-3-Clause",
       "dependencies": {
-        "side-channel": "^1.0.4"
+        "side-channel": "^1.1.0"
       },
       "engines": {
         "node": ">=0.6"
@@ -1531,12 +1603,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/querystringify": {
-      "version": "2.2.0",
-      "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
-      "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
-      "dev": true
-    },
     "node_modules/request-progress": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
@@ -1546,12 +1612,6 @@
         "throttleit": "^1.0.0"
       }
     },
-    "node_modules/requires-port": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
-      "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
-      "dev": true
-    },
     "node_modules/restore-cursor": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz",
@@ -1604,7 +1664,8 @@
       "version": "2.1.2",
       "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
       "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
-      "dev": true
+      "dev": true,
+      "license": "MIT"
     },
     "node_modules/semver": {
       "version": "7.6.2",
@@ -1618,23 +1679,6 @@
         "node": ">=10"
       }
     },
-    "node_modules/set-function-length": {
-      "version": "1.2.2",
-      "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
-      "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
-      "dev": true,
-      "dependencies": {
-        "define-data-property": "^1.1.4",
-        "es-errors": "^1.3.0",
-        "function-bind": "^1.1.2",
-        "get-intrinsic": "^1.2.4",
-        "gopd": "^1.0.1",
-        "has-property-descriptors": "^1.0.2"
-      },
-      "engines": {
-        "node": ">= 0.4"
-      }
-    },
     "node_modules/shebang-command": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -1657,15 +1701,73 @@
       }
     },
     "node_modules/side-channel": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
-      "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+      "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "object-inspect": "^1.13.3",
+        "side-channel-list": "^1.0.0",
+        "side-channel-map": "^1.0.1",
+        "side-channel-weakmap": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-list": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+      "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
-        "call-bind": "^1.0.7",
         "es-errors": "^1.3.0",
-        "get-intrinsic": "^1.2.4",
-        "object-inspect": "^1.13.1"
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-map": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+      "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/side-channel-weakmap": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+      "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "call-bound": "^1.0.2",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.5",
+        "object-inspect": "^1.13.3",
+        "side-channel-map": "^1.0.1"
       },
       "engines": {
         "node": ">= 0.4"
@@ -1699,6 +1801,7 @@
       "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
       "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "asn1": "~0.2.3",
         "assert-plus": "^1.0.0",
@@ -1784,6 +1887,26 @@
       "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==",
       "dev": true
     },
+    "node_modules/tldts": {
+      "version": "6.1.86",
+      "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz",
+      "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "tldts-core": "^6.1.86"
+      },
+      "bin": {
+        "tldts": "bin/cli.js"
+      }
+    },
+    "node_modules/tldts-core": {
+      "version": "6.1.86",
+      "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz",
+      "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/tmp": {
       "version": "0.2.3",
       "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz",
@@ -1794,27 +1917,16 @@
       }
     },
     "node_modules/tough-cookie": {
-      "version": "4.1.4",
-      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
-      "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz",
+      "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==",
       "dev": true,
+      "license": "BSD-3-Clause",
       "dependencies": {
-        "psl": "^1.1.33",
-        "punycode": "^2.1.1",
-        "universalify": "^0.2.0",
-        "url-parse": "^1.5.3"
+        "tldts": "^6.1.32"
       },
       "engines": {
-        "node": ">=6"
-      }
-    },
-    "node_modules/tough-cookie/node_modules/universalify": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
-      "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
-      "dev": true,
-      "engines": {
-        "node": ">= 4.0.0"
+        "node": ">=16"
       }
     },
     "node_modules/tslib": {
@@ -1839,7 +1951,8 @@
       "version": "0.14.5",
       "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
       "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
-      "dev": true
+      "dev": true,
+      "license": "Unlicense"
     },
     "node_modules/type-fest": {
       "version": "0.21.3",
@@ -1878,16 +1991,6 @@
         "node": ">=8"
       }
     },
-    "node_modules/url-parse": {
-      "version": "1.5.10",
-      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
-      "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
-      "dev": true,
-      "dependencies": {
-        "querystringify": "^2.1.1",
-        "requires-port": "^1.0.0"
-      }
-    },
     "node_modules/uuid": {
       "version": "8.3.2",
       "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
@@ -1905,6 +2008,7 @@
       "engines": [
         "node >=0.6.0"
       ],
+      "license": "MIT",
       "dependencies": {
         "assert-plus": "^1.0.0",
         "core-util-is": "1.0.2",