Repository: glasskube/glasskube
Pull request #448 (2024-03-28 13:16:45)

feat(cli, ui): show removed dependencies before uninstall
<!-- Thanks for creating this pull request 🤗 Please make sure you followed the conventional commit -->

<!-- If this pull request closes an issue, please mention the issue number below -->


## 📑 Description
This PR partially implements #399 (only for package uninstallation).

## ✅ Checks
- [ ] My code requires changes to the documentation
- [ ] I have updated the documentation as required

## ℹ Additional Information
<!-- Any additional information like breaking changes, dependencies added, screenshots, comparisons between new and old behavior, etc. -->

kosmoz: > Works great, well done. As far as I see this implementation closes #398 too.

True, although this implements the "alternatives you've considered" as described in that issue (which TBH I think I prefer…)

Patch:
diff --git a/cmd/glasskube/cmd/install.go b/cmd/glasskube/cmd/install.go
index 0d88b6d..e70f996 100644
--- a/cmd/glasskube/cmd/install.go
+++ b/cmd/glasskube/cmd/install.go
@@ -5,9 +5,6 @@ import (
 	"os"
 	"strings"
 
-	"github.com/glasskube/glasskube/internal/controller/owners"
-	"k8s.io/client-go/kubernetes/scheme"
-
 	"github.com/fatih/color"
 	"github.com/glasskube/glasskube/api/v1alpha1"
 	"github.com/glasskube/glasskube/internal/cliutils"
@@ -38,8 +35,7 @@ var installCmd = &cobra.Command{
 		ctx := cmd.Context()
 		config := client.RawConfigFromContext(ctx)
 		pkgClient := client.FromContext(ctx)
-		dm := dependency.NewDependencyManager(clientadapter.NewGoClientAdapter(pkgClient),
-			owners.NewOwnerManager(scheme.Scheme))
+		dm := dependency.NewDependencyManager(clientadapter.NewGoClientAdapter(pkgClient))
 		installer := install.NewInstaller(pkgClient).WithStatusWriter(statuswriter.Spinner())
 		bold := color.New(color.Bold).SprintFunc()
 		packageName := args[0]
diff --git a/cmd/glasskube/cmd/uninstall.go b/cmd/glasskube/cmd/uninstall.go
index 6a9d065..02e35f6 100644
--- a/cmd/glasskube/cmd/uninstall.go
+++ b/cmd/glasskube/cmd/uninstall.go
@@ -4,8 +4,10 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/glasskube/glasskube/api/v1alpha1"
+	"github.com/fatih/color"
 	"github.com/glasskube/glasskube/internal/cliutils"
+	"github.com/glasskube/glasskube/internal/dependency"
+	clientadapter "github.com/glasskube/glasskube/internal/dependency/adapter/goclient"
 	pkgClient "github.com/glasskube/glasskube/pkg/client"
 	"github.com/glasskube/glasskube/pkg/statuswriter"
 	"github.com/glasskube/glasskube/pkg/uninstall"
@@ -24,45 +26,60 @@ var uninstallCmd = &cobra.Command{
 	Args:   cobra.ExactArgs(1),
 	PreRun: cliutils.SetupClientContext(true, &rootCmdOptions.SkipUpdateCheck),
 	Run: func(cmd *cobra.Command, args []string) {
-		client := pkgClient.FromContext(cmd.Context())
 		pkgName := args[0]
-		var pkg v1alpha1.Package
-		if err := client.Packages().Get(cmd.Context(), pkgName, &pkg); err != nil {
-			fmt.Fprintf(os.Stderr, "Could not get installed package %v:\n%v\n", pkgName, err)
+		ctx := cmd.Context()
+		currentContext := pkgClient.RawConfigFromContext(cmd.Context()).CurrentContext
+		client := pkgClient.FromContext(cmd.Context())
+		dm := dependency.NewDependencyManager(clientadapter.NewGoClientAdapter(client))
+
+		if g, err := dm.NewGraph(ctx); err != nil {
+			fmt.Fprintf(os.Stderr, "❌ Error validating uninstall: %v\n", err)
 			os.Exit(1)
-			return
+		} else {
+			g.Delete(pkgName)
+			pruned := g.Prune()
+			if err := g.Validate(); err != nil {
+				fmt.Fprintf(os.Stderr, "❌ %v can not be uninstalled for the following reason: %v\n", pkgName, err)
+				os.Exit(1)
+			} else {
+				showUninstallDetails(currentContext, pkgName, pruned)
+				if !uninstallCmdOptions.ForceUninstall && !cliutils.YesNoPrompt("Do you want to continue?", false) {
+					fmt.Println("❌ Uninstallation cancelled.")
+					os.Exit(0)
+				}
+			}
 		}
-		proceed := uninstallCmdOptions.ForceUninstall || cliutils.YesNoPrompt(
-			fmt.Sprintf(
-				"%v will be removed from your cluster (%v). Are you sure?",
-				pkgName,
-				pkgClient.RawConfigFromContext(cmd.Context()).CurrentContext,
-			),
-			false,
-		)
 
 		uninstaller := uninstall.NewUninstaller(client).WithStatusWriter(statuswriter.Spinner())
-		if proceed {
-			if uninstallCmdOptions.NoWait {
-				if err := uninstaller.Uninstall(cmd.Context(), &pkg); err != nil {
-					fmt.Fprintf(os.Stderr, "An error occurred during uninstallation:\n\n%v\n", err)
-					os.Exit(1)
-				}
-				fmt.Fprintln(os.Stderr, "Uninstallation started in background")
-			} else {
-				if err := uninstaller.UninstallBlocking(cmd.Context(), &pkg); err != nil {
-					fmt.Fprintf(os.Stderr, "An error occurred during uninstallation:\n\n%v\n", err)
-					os.Exit(1)
-					return
-				}
-				fmt.Fprintf(os.Stderr, "🗑️ %v uninstalled successfully.\n", pkgName)
+		pkg := pkgClient.NewPackage(pkgName, "")
+		if uninstallCmdOptions.NoWait {
+			if err := uninstaller.Uninstall(ctx, pkg); err != nil {
+				fmt.Fprintf(os.Stderr, "\n❌ An error occurred during uninstallation:\n\n%v\n", err)
+				os.Exit(1)
 			}
+			fmt.Fprintln(os.Stderr, "Uninstallation started in background")
 		} else {
-			fmt.Println("❌ Uninstallation cancelled.")
+			if err := uninstaller.UninstallBlocking(ctx, pkg); err != nil {
+				fmt.Fprintf(os.Stderr, "\n❌ An error occurred during uninstallation:\n\n%v\n", err)
+				os.Exit(1)
+				return
+			}
+			fmt.Fprintf(os.Stderr, "🗑️  %v uninstalled successfully.\n", pkgName)
 		}
 	},
 }
 
+func showUninstallDetails(context, name string, pruned []string) {
+	fmt.Fprintf(os.Stderr,
+		"The following packages will be %v from your cluster (%v):\n",
+		color.New(color.Bold).Sprint("removed"),
+		context)
+	fmt.Fprintf(os.Stderr, " * %v (requested by user)\n", name)
+	for _, dep := range pruned {
+		fmt.Fprintf(os.Stderr, " * %v (dependency)\n", dep)
+	}
+}
+
 func init() {
 	uninstallCmd.PersistentFlags().BoolVar(&uninstallCmdOptions.ForceUninstall, "force", false,
 		"skip the confirmation question and uninstall right away")
diff --git a/internal/controller/package_controller.go b/internal/controller/package_controller.go
index 42c2a2e..903076c 100644
--- a/internal/controller/package_controller.go
+++ b/internal/controller/package_controller.go
@@ -105,7 +105,7 @@ func (r *PackageReconciler) SetupWithManager(mgr ctrl.Manager) error {
 		r.OwnerManager = owners.NewOwnerManager(r.Scheme)
 	}
 	if r.dependencyMgr == nil {
-		r.dependencyMgr = dependency.NewDependencyManager(controllerruntime.NewControllerRuntimeAdapter(r.Client), r.OwnerManager)
+		r.dependencyMgr = dependency.NewDependencyManager(controllerruntime.NewControllerRuntimeAdapter(r.Client))
 	}
 	controllerBuilder := ctrl.NewControllerManagedBy(mgr).For(&packagesv1alpha1.Package{})
 	for _, adapter := range []manifest.ManifestAdapter{r.HelmAdapter, r.KustomizeAdapter, r.ManifestAdapter} {
diff --git a/internal/dependency/manager.go b/internal/dependency/manager.go
index f5d975d..f5b6119 100644
--- a/internal/dependency/manager.go
+++ b/internal/dependency/manager.go
@@ -6,7 +6,6 @@ import (
 	"slices"
 	"strings"
 
-	"github.com/glasskube/glasskube/internal/controller/owners"
 	"github.com/glasskube/glasskube/internal/names"
 	"go.uber.org/multierr"
 
@@ -22,14 +21,12 @@ import (
 type DependendcyManager struct {
 	clientAdapter adapter.ClientAdapter
 	repoAdapter   adapter.RepoAdapter
-	*owners.OwnerManager
 }
 
-func NewDependencyManager(adapter adapter.ClientAdapter, ownerMgr *owners.OwnerManager) *DependendcyManager {
+func NewDependencyManager(adapter adapter.ClientAdapter) *DependendcyManager {
 	return &DependendcyManager{
 		clientAdapter: adapter,
 		repoAdapter:   &defaultRepoAdapter{repo: repo.DefaultClient},
-		OwnerManager:  ownerMgr,
 	}
 }
 
diff --git a/internal/web/server.go b/internal/web/server.go
index 893b175..cda2b02 100644
--- a/internal/web/server.go
+++ b/internal/web/server.go
@@ -18,10 +18,8 @@ import (
 	"syscall"
 
 	"github.com/glasskube/glasskube/internal/config"
-	"github.com/glasskube/glasskube/internal/controller/owners"
 	"github.com/glasskube/glasskube/internal/dependency"
 	"github.com/glasskube/glasskube/internal/dependency/adapter/goclient"
-	"k8s.io/client-go/kubernetes/scheme"
 
 	"github.com/glasskube/glasskube/pkg/update"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -183,6 +181,7 @@ func (s *server) Start(ctx context.Context) error {
 	router.Handle("/packages/update", s.requireReady(s.update))
 	router.Handle("/packages/update/modal", s.requireReady(s.updateModal))
 	router.Handle("/packages/uninstall", s.requireReady(s.uninstall))
+	router.Handle("/packages/uninstall/modal", s.requireReady(s.uninstallModal))
 	router.Handle("/packages/open", s.requireReady(s.open))
 	router.Handle("/packages/{pkgName}", s.requireReady(s.packageDetail))
 	router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, "/packages", http.StatusFound) })
@@ -345,6 +344,27 @@ func (s *server) update(w http.ResponseWriter, r *http.Request) {
 	}
 }
 
+func (s *server) uninstallModal(w http.ResponseWriter, r *http.Request) {
+	pkgName := r.FormValue("packageName")
+	var pruned []string
+	var err error
+	if g, err1 := s.dependencyMgr.NewGraph(r.Context()); err1 != nil {
+		err = fmt.Errorf("error validating uninstall: %w", err1)
+	} else {
+		g.Delete(pkgName)
+		pruned = g.Prune()
+		if err1 := g.Validate(); err1 != nil {
+			err = fmt.Errorf("%v cannot be uninstalled: %w", pkgName, err1)
+		}
+	}
+	err = pkgUninstallModalTmpl.Execute(w, map[string]any{
+		"PackageName": pkgName,
+		"Pruned":      pruned,
+		"Err":         err,
+	})
+	checkTmplError(err, "pkgUninstallModalTmpl")
+}
+
 func (s *server) uninstall(w http.ResponseWriter, r *http.Request) {
 	ctx := r.Context()
 	pkgName := r.FormValue("packageName")
@@ -567,7 +587,7 @@ func (server *server) initKubeConfig() ServerConfigError {
 	server.restConfig = restConfig
 	server.rawConfig = rawConfig
 	server.pkgClient = client
-	server.dependencyMgr = dependency.NewDependencyManager(goclient.NewGoClientAdapter(server.pkgClient), owners.NewOwnerManager(scheme.Scheme))
+	server.dependencyMgr = dependency.NewDependencyManager(goclient.NewGoClientAdapter(server.pkgClient))
 	return nil
 }
 
@@ -693,7 +713,7 @@ func (s *server) initPackageInfoStoreAndController(ctx context.Context) (cache.S
 
 func (s *server) isUpdateAvailable(ctx context.Context, packages ...string) bool {
 	if tx, err := update.NewUpdater(s.pkgClient).Prepare(ctx, packages); err != nil {
-		fmt.Fprintf(os.Stderr, "Error checking for updates: %v", err)
+		fmt.Fprintf(os.Stderr, "Error checking for updates: %v\n", err)
 		return false
 	} else {
 		return !tx.IsEmpty()
diff --git a/internal/web/templates.go b/internal/web/templates.go
index b180894..caf7a4d 100644
--- a/internal/web/templates.go
+++ b/internal/web/templates.go
@@ -17,22 +17,23 @@ import (
 )
 
 var (
-	baseTemplate        *template.Template
-	pkgsPageTmpl        *template.Template
-	pkgPageTmpl         *template.Template
-	supportPageTmpl     *template.Template
-	bootstrapPageTmpl   *template.Template
-	kubeconfigPageTmpl  *template.Template
-	pkgOverviewBtnTmpl  *template.Template
-	pkgDetailBtnsTmpl   *template.Template
-	pkgInstallModalTmpl *template.Template
-	pkgUpdateModalTmpl  *template.Template
-	pkgUpdateAlertTmpl  *template.Template
-	alertTmpl           *template.Template
-	templatesBaseDir    = "internal/web"
-	templatesDir        = "templates"
-	componentsDir       = path.Join(templatesDir, "components")
-	pagesDir            = path.Join(templatesDir, "pages")
+	baseTemplate          *template.Template
+	pkgsPageTmpl          *template.Template
+	pkgPageTmpl           *template.Template
+	supportPageTmpl       *template.Template
+	bootstrapPageTmpl     *template.Template
+	kubeconfigPageTmpl    *template.Template
+	pkgOverviewBtnTmpl    *template.Template
+	pkgDetailBtnsTmpl     *template.Template
+	pkgInstallModalTmpl   *template.Template
+	pkgUpdateModalTmpl    *template.Template
+	pkgUpdateAlertTmpl    *template.Template
+	pkgUninstallModalTmpl *template.Template
+	alertTmpl             *template.Template
+	templatesBaseDir      = "internal/web"
+	templatesDir          = "templates"
+	componentsDir         = path.Join(templatesDir, "components")
+	pagesDir              = path.Join(templatesDir, "pages")
 )
 
 func watchTemplates() error {
@@ -86,6 +87,7 @@ func parseTemplates() {
 	pkgUpdateAlertTmpl = componentTmpl(pkg_update_alert.TemplateId, "pkg-update-alert.html")
 	pkgInstallModalTmpl = componentTmpl("pkg-install-modal", "pkg-install-modal.html")
 	pkgUpdateModalTmpl = componentTmpl("pkg-update-modal", "pkg-update-modal.html")
+	pkgUninstallModalTmpl = componentTmpl("pkg-uninstall-modal", "pkg-uninstall-modal.html")
 	alertTmpl = componentTmpl("alert", "alert.html")
 }
 
diff --git a/internal/web/templates/components/pkg-detail-btns.html b/internal/web/templates/components/pkg-detail-btns.html
index e9d880d..06a6f6a 100644
--- a/internal/web/templates/components/pkg-detail-btns.html
+++ b/internal/web/templates/components/pkg-detail-btns.html
@@ -1,10 +1,14 @@
 {{ define "pkg-detail-uninstall" }}
   <button
-    hx-post="/packages/uninstall"
-    class="btn btn-danger btn-sm"
+    hx-post="/packages/uninstall/modal"
+    hx-target="#modal-container"
+    hx-swap="innerHTML"
+    hx-select="#pkg-uninstall-modal"
     name="packageName"
     value="{{ .PackageName }}"
-    hx-confirm="You are going to uninstall {{ .PackageName }}. Are you sure?">
+    class="btn btn-danger btn-sm"
+    data-bs-toggle="modal"
+    data-bs-target="#modal-container">
     <i class="bi bi-trash"></i>
   </button>
 {{ end }}
diff --git a/internal/web/templates/components/pkg-uninstall-modal.html b/internal/web/templates/components/pkg-uninstall-modal.html
new file mode 100644
index 0000000..36104e6
--- /dev/null
+++ b/internal/web/templates/components/pkg-uninstall-modal.html
@@ -0,0 +1,45 @@
+{{ define "pkg-uninstall-modal" }}
+  <div class="modal-dialog modal-dialog-centered" id="pkg-uninstall-modal">
+    <div class="modal-content">
+      <form hx-post="/packages/uninstall">
+        <div class="modal-header">
+          <h1 class="modal-title fs-5">Uninstall {{ .PackageName }}</h1>
+          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
+        </div>
+        {{ if eq .Err nil }}
+          <div class="modal-body" id="pkg-update-modal-body">
+            <div class="alert alert-warning m-0" role="alert">
+              <div>The following packages will be <strong>removed</strong> from your cluster:</div>
+              <ul class="m-0 mt-1">
+                <li>{{ $.PackageName }} (requested by user)</li>
+                {{ range .Pruned }}
+                  <li>{{ . }} (dependency)</li>
+                {{ end }}
+              </ul>
+            </div>
+          </div>
+          <div class="modal-footer">
+            <button type="button" class="btn btn-outline-primary btn-sm" data-bs-dismiss="modal">Cancel</button>
+            <button
+              type="submit"
+              data-bs-dismiss="modal"
+              name="packageName"
+              class="btn btn-danger btn-sm"
+              value="{{ .PackageName }}">
+              Confirm
+            </button>
+          </div>
+        {{ else }}
+          <div class="modal-body" id="pkg-update-modal-body">
+            <div class="alert alert-danger m-0" role="alert">
+              {{ .Err }}
+            </div>
+          </div>
+          <div class="modal-footer">
+            <button type="button" class="btn btn-primary btn-sm" data-bs-dismiss="modal">OK</button>
+          </div>
+        {{ end }}
+      </form>
+    </div>
+  </div>
+{{ end }}
diff --git a/internal/webhook/package_validator.go b/internal/webhook/package_validator.go
index 25e2273..fd8f2ef 100644
--- a/internal/webhook/package_validator.go
+++ b/internal/webhook/package_validator.go
@@ -32,7 +32,7 @@ var _ webhook.CustomValidator = &PackageValidatingWebhook{}
 
 func (p *PackageValidatingWebhook) SetupWithManager(mgr ctrl.Manager) error {
 	p.OwnerManager = owners.NewOwnerManager(p.Scheme())
-	p.DependendcyManager = dependency.NewDependencyManager(ctrladapter.NewControllerRuntimeAdapter(p.Client), p.OwnerManager)
+	p.DependendcyManager = dependency.NewDependencyManager(ctrladapter.NewControllerRuntimeAdapter(p.Client))
 	p.repo = repo.DefaultClient
 	return ctrl.NewWebhookManagedBy(mgr).
 		For(&v1alpha1.Package{}).
diff --git a/pkg/uninstall/uninstall.go b/pkg/uninstall/uninstall.go
index b55c26a..ade4bb5 100644
--- a/pkg/uninstall/uninstall.go
+++ b/pkg/uninstall/uninstall.go
@@ -9,7 +9,6 @@ import (
 	"github.com/glasskube/glasskube/pkg/client"
 	"github.com/glasskube/glasskube/pkg/statuswriter"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/watch"
 )
 
@@ -34,38 +33,33 @@ func (obj *uninstaller) WithStatusWriter(sw statuswriter.StatusWriter) *uninstal
 func (obj *uninstaller) UninstallBlocking(ctx context.Context, pkg *v1alpha1.Package) error {
 	obj.status.Start()
 	defer obj.status.Stop()
-	pkgUID, err := obj.delete(ctx, pkg)
+	err := obj.delete(ctx, pkg)
 	if err != nil {
 		return err
 	}
-	return obj.awaitDeletion(ctx, pkgUID)
+	return obj.awaitDeletion(ctx, pkg.Name)
 }
 
 // Uninstall deletes the v1alpha1.Package custom resource from the cluster.
 func (obj *uninstaller) Uninstall(ctx context.Context, pkg *v1alpha1.Package) error {
 	obj.status.Start()
 	defer obj.status.Stop()
-	_, err := obj.delete(ctx, pkg)
-	return err
+	return obj.delete(ctx, pkg)
 }
 
-func (obj *uninstaller) delete(ctx context.Context, pkg *v1alpha1.Package) (types.UID, error) {
+func (obj *uninstaller) delete(ctx context.Context, pkg *v1alpha1.Package) error {
 	obj.status.SetStatus(fmt.Sprintf("Uninstalling %v...", pkg.Name))
-	err := obj.client.Packages().Delete(ctx, pkg, metav1.DeleteOptions{PropagationPolicy: &deletePropagationForeground})
-	if err != nil {
-		return "", err
-	}
-	return pkg.GetUID(), nil
+	return obj.client.Packages().Delete(ctx, pkg, metav1.DeleteOptions{PropagationPolicy: &deletePropagationForeground})
 }
 
-func (obj *uninstaller) awaitDeletion(ctx context.Context, pkgUID types.UID) error {
+func (obj *uninstaller) awaitDeletion(ctx context.Context, name string) error {
 	watcher, err := obj.client.Packages().Watch(ctx)
 	if err != nil {
 		return err
 	}
 	defer watcher.Stop()
 	for event := range watcher.ResultChan() {
-		if obj, ok := event.Object.(*v1alpha1.Package); ok && obj.GetUID() == pkgUID {
+		if pkg, ok := event.Object.(*v1alpha1.Package); ok && pkg.Name == name {
 			if event.Type == watch.Deleted {
 				return nil // Package deletion confirmed
 			}
diff --git a/pkg/update/update.go b/pkg/update/update.go
index 2cff81a..ff2d5f7 100644
--- a/pkg/update/update.go
+++ b/pkg/update/update.go
@@ -6,7 +6,6 @@ import (
 	"fmt"
 
 	"github.com/glasskube/glasskube/api/v1alpha1"
-	"github.com/glasskube/glasskube/internal/controller/owners"
 	"github.com/glasskube/glasskube/internal/dependency"
 	clientadapter "github.com/glasskube/glasskube/internal/dependency/adapter/goclient"
 	"github.com/glasskube/glasskube/internal/repo"
@@ -16,7 +15,6 @@ import (
 	"github.com/glasskube/glasskube/pkg/statuswriter"
 	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/client-go/kubernetes/scheme"
 )
 
 type UpdateTransaction struct {
@@ -58,10 +56,7 @@ func NewUpdater(client client.PackageV1Alpha1Client) *updater {
 	return &updater{
 		client: client,
 		status: statuswriter.Noop(),
-		dm: dependency.NewDependencyManager(
-			clientadapter.NewGoClientAdapter(client),
-			owners.NewOwnerManager(scheme.Scheme),
-		),
+		dm:     dependency.NewDependencyManager(clientadapter.NewGoClientAdapter(client)),
 	}
 }
 
diff --git a/website/package-lock.json b/website/package-lock.json
index 27d3678..ae67f66 100644
--- a/website/package-lock.json
+++ b/website/package-lock.json
@@ -8,9 +8,9 @@
       "name": "glasskube-docs",
       "version": "0.0.0",
       "dependencies": {
-        "@docusaurus/core": "3.1.1",
+        "@docusaurus/core": "3.2.0",
         "@docusaurus/plugin-ideal-image": "^3.1.0",
-        "@docusaurus/preset-classic": "3.1.1",
+        "@docusaurus/preset-classic": "3.2.0",
         "@docusaurus/theme-mermaid": "^3.1.0",
         "@easyops-cn/docusaurus-search-local": "^0.40.1",
         "@fortawesome/fontawesome-svg-core": "^6.5.1",
@@ -29,9 +29,9 @@
         "typewriter-effect": "^2.21.0"
       },
       "devDependencies": {
-        "@docusaurus/module-type-aliases": "3.1.1",
-        "@docusaurus/tsconfig": "3.1.1",
-        "@docusaurus/types": "3.1.1",
+        "@docusaurus/module-type-aliases": "3.2.0",
+        "@docusaurus/tsconfig": "3.2.0",
+        "@docusaurus/types": "3.2.0",
         "eslint-plugin-import": "^2.29.1",
         "typescript": "~5.4.0"
       },
@@ -2192,9 +2192,9 @@
       }
     },
     "node_modules/@docusaurus/core": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.1.1.tgz",
-      "integrity": "sha512-2nQfKFcf+MLEM7JXsXwQxPOmQAR6ytKMZVSx7tVi9HEm9WtfwBH1fp6bn8Gj4zLUhjWKCLoysQ9/Wm+EZCQ4yQ==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/core/-/core-3.2.0.tgz",
+      "integrity": "sha512-WTO6vW4404nhTmK9NL+95nd13I1JveFwZ8iOBYxb4xt+N2S3KzY+mm+1YtWw2vV37FbYfH+w+KrlrRaWuy5Hzw==",
       "dependencies": {
         "@babel/core": "^7.23.3",
         "@babel/generator": "^7.23.3",
@@ -2206,14 +2206,13 @@
         "@babel/runtime": "^7.22.6",
         "@babel/runtime-corejs3": "^7.22.6",
         "@babel/traverse": "^7.22.8",
-        "@docusaurus/cssnano-preset": "3.1.1",
-        "@docusaurus/logger": "3.1.1",
-        "@docusaurus/mdx-loader": "3.1.1",
+        "@docusaurus/cssnano-preset": "3.2.0",
+        "@docusaurus/logger": "3.2.0",
+        "@docusaurus/mdx-loader": "3.2.0",
         "@docusaurus/react-loadable": "5.5.2",
-        "@docusaurus/utils": "3.1.1",
-        "@docusaurus/utils-common": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
-        "@slorber/static-site-generator-webpack-plugin": "^4.0.7",
+        "@docusaurus/utils": "3.2.0",
+        "@docusaurus/utils-common": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "@svgr/webpack": "^6.5.1",
         "autoprefixer": "^10.4.14",
         "babel-loader": "^9.1.3",
@@ -2234,6 +2233,7 @@
         "detect-port": "^1.5.1",
         "escape-html": "^1.0.3",
         "eta": "^2.2.0",
+        "eval": "^0.1.8",
         "file-loader": "^6.2.0",
         "fs-extra": "^11.1.1",
         "html-minifier-terser": "^7.2.0",
@@ -2242,6 +2242,7 @@
         "leven": "^3.1.0",
         "lodash": "^4.17.21",
         "mini-css-extract-plugin": "^2.7.6",
+        "p-map": "^4.0.0",
         "postcss": "^8.4.26",
         "postcss-loader": "^7.3.3",
         "prompts": "^2.4.2",
@@ -2278,9 +2279,9 @@
       }
     },
     "node_modules/@docusaurus/cssnano-preset": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.1.1.tgz",
-      "integrity": "sha512-LnoIDjJWbirdbVZDMq+4hwmrTl2yHDnBf9MLG9qyExeAE3ac35s4yUhJI8yyTCdixzNfKit4cbXblzzqMu4+8g==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/cssnano-preset/-/cssnano-preset-3.2.0.tgz",
+      "integrity": "sha512-H88RXGUia7r/VF3XfyoA4kbwgpUZcKsObF6VvwBOP91EdArTf6lnHbJ/x8Ca79KS/zf98qaWyBGzW+5ez58Iyw==",
       "dependencies": {
         "cssnano-preset-advanced": "^5.3.10",
         "postcss": "^8.4.26",
@@ -2292,9 +2293,9 @@
       }
     },
     "node_modules/@docusaurus/logger": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.1.1.tgz",
-      "integrity": "sha512-BjkNDpQzewcTnST8trx4idSoAla6zZ3w22NqM/UMcFtvYJgmoE4layuTzlfql3VFPNuivvj7BOExa/+21y4X2Q==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/logger/-/logger-3.2.0.tgz",
+      "integrity": "sha512-Z1R1NcOGXZ8CkIJSvjvyxnuDDSlx/+1xlh20iVTw1DZRjonFmI3T3tTgk40YpXyWUYQpIgAoqqPMpuseMMdgRQ==",
       "dependencies": {
         "chalk": "^4.1.2",
         "tslib": "^2.6.0"
@@ -2304,11 +2305,11 @@
       }
     },
     "node_modules/@docusaurus/lqip-loader": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/lqip-loader/-/lqip-loader-3.1.1.tgz",
-      "integrity": "sha512-s06lySAX5ghCiQe0+/GaMWcVvgkBQ6U8p182fW+JbdjxABS8ecx2in2AQJbvrwKNgiMjOhsXiaE6BmbQAmT6nw==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/lqip-loader/-/lqip-loader-3.2.0.tgz",
+      "integrity": "sha512-lmYT3fslGH3ibdXySSUgtd4E3B8sQ7xizlNhmTj5eiqSQdiRiD15rVH73RohK8h+yrbu2QUDHTDAH6j7O5e2Gg==",
       "dependencies": {
-        "@docusaurus/logger": "3.1.1",
+        "@docusaurus/logger": "3.2.0",
         "file-loader": "^6.2.0",
         "lodash": "^4.17.21",
         "sharp": "^0.32.3",
@@ -2319,15 +2320,13 @@
       }
     },
     "node_modules/@docusaurus/mdx-loader": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.1.1.tgz",
-      "integrity": "sha512-xN2IccH9+sv7TmxwsDJNS97BHdmlqWwho+kIVY4tcCXkp+k4QuzvWBeunIMzeayY4Fu13A6sAjHGv5qm72KyGA==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/mdx-loader/-/mdx-loader-3.2.0.tgz",
+      "integrity": "sha512-JtkI5o6R/rJSr1Y23cHKz085aBJCvJw3AYHihJ7r+mBX+O8EuQIynG0e6/XpbSCpr7Ino0U50UtxaXcEbFwg9Q==",
       "dependencies": {
-        "@babel/parser": "^7.22.7",
-        "@babel/traverse": "^7.22.8",
-        "@docusaurus/logger": "3.1.1",
-        "@docusaurus/utils": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+        "@docusaurus/logger": "3.2.0",
+        "@docusaurus/utils": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "@mdx-js/mdx": "^3.0.0",
         "@slorber/remark-comment": "^1.0.0",
         "escape-html": "^1.0.3",
@@ -2359,12 +2358,12 @@
       }
     },
     "node_modules/@docusaurus/module-type-aliases": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.1.1.tgz",
-      "integrity": "sha512-xBJyx0TMfAfVZ9ZeIOb1awdXgR4YJMocIEzTps91rq+hJDFJgJaylDtmoRhUxkwuYmNK1GJpW95b7DLztSBJ3A==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/module-type-aliases/-/module-type-aliases-3.2.0.tgz",
+      "integrity": "sha512-jRSp9YkvBwwNz6Xgy0RJPsnie+Ebb//gy7GdbkJ2pW2gvvlYKGib2+jSF0pfIzvyZLulfCynS1KQdvDKdSl8zQ==",
       "dependencies": {
         "@docusaurus/react-loadable": "5.5.2",
-        "@docusaurus/types": "3.1.1",
+        "@docusaurus/types": "3.2.0",
         "@types/history": "^4.7.11",
         "@types/react": "*",
         "@types/react-router-config": "*",
@@ -2378,17 +2377,17 @@
       }
     },
     "node_modules/@docusaurus/plugin-content-blog": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.1.1.tgz",
-      "integrity": "sha512-ew/3VtVoG3emoAKmoZl7oKe1zdFOsI0NbcHS26kIxt2Z8vcXKCUgK9jJJrz0TbOipyETPhqwq4nbitrY3baibg==",
-      "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/logger": "3.1.1",
-        "@docusaurus/mdx-loader": "3.1.1",
-        "@docusaurus/types": "3.1.1",
-        "@docusaurus/utils": "3.1.1",
-        "@docusaurus/utils-common": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-blog/-/plugin-content-blog-3.2.0.tgz",
+      "integrity": "sha512-MABqwjSicyHmYEfQueMthPCz18JkVxhK3EGhXTSRWwReAZ0UTuw9pG6+Wo+uXAugDaIcJH28rVZSwTDINPm2bw==",
+      "dependencies": {
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/logger": "3.2.0",
+        "@docusaurus/mdx-loader": "3.2.0",
+        "@docusaurus/types": "3.2.0",
+        "@docusaurus/utils": "3.2.0",
+        "@docusaurus/utils-common": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "cheerio": "^1.0.0-rc.12",
         "feed": "^4.2.2",
         "fs-extra": "^11.1.1",
@@ -2409,17 +2408,18 @@
       }
     },
     "node_modules/@docusaurus/plugin-content-docs": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.1.1.tgz",
-      "integrity": "sha512-lhFq4E874zw0UOH7ujzxnCayOyAt0f9YPVYSb9ohxrdCM8B4szxitUw9rIX4V9JLLHVoqIJb6k+lJJ1jrcGJ0A==",
-      "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/logger": "3.1.1",
-        "@docusaurus/mdx-loader": "3.1.1",
-        "@docusaurus/module-type-aliases": "3.1.1",
-        "@docusaurus/types": "3.1.1",
-        "@docusaurus/utils": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-docs/-/plugin-content-docs-3.2.0.tgz",
+      "integrity": "sha512-uuqhahmsBnirxOz+SXksnWt7+wc+iN4ntxNRH48BUgo7QRNLATWjHCgI8t6zrMJxK4o+QL9DhLaPDlFHs91B3Q==",
+      "dependencies": {
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/logger": "3.2.0",
+        "@docusaurus/mdx-loader": "3.2.0",
+        "@docusaurus/module-type-aliases": "3.2.0",
+        "@docusaurus/types": "3.2.0",
+        "@docusaurus/utils": "3.2.0",
+        "@docusaurus/utils-common": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "@types/react-router-config": "^5.0.7",
         "combine-promises": "^1.1.0",
         "fs-extra": "^11.1.1",
@@ -2438,15 +2438,15 @@
       }
     },
     "node_modules/@docusaurus/plugin-content-pages": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.1.1.tgz",
-      "integrity": "sha512-NQHncNRAJbyLtgTim9GlEnNYsFhuCxaCNkMwikuxLTiGIPH7r/jpb7O3f3jUMYMebZZZrDq5S7om9a6rvB/YCA==",
-      "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/mdx-loader": "3.1.1",
-        "@docusaurus/types": "3.1.1",
-        "@docusaurus/utils": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-content-pages/-/plugin-content-pages-3.2.0.tgz",
+      "integrity": "sha512-4ofAN7JDsdb4tODO9OIrizWY5DmEJXr0eu+UDIkLqGP+gXXTahJZv8h2mlxO+lPXGXRCVBOfA14OG1hOYJVPwA==",
+      "dependencies": {
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/mdx-loader": "3.2.0",
+        "@docusaurus/types": "3.2.0",
+        "@docusaurus/utils": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "fs-extra": "^11.1.1",
         "tslib": "^2.6.0",
         "webpack": "^5.88.1"
@@ -2460,13 +2460,13 @@
       }
     },
     "node_modules/@docusaurus/plugin-debug": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.1.1.tgz",
-      "integrity": "sha512-xWeMkueM9wE/8LVvl4+Qf1WqwXmreMjI5Kgr7GYCDoJ8zu4kD+KaMhrh7py7MNM38IFvU1RfrGKacCEe2DRRfQ==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-debug/-/plugin-debug-3.2.0.tgz",
+      "integrity": "sha512-p6WxtO5XZGz66y6QNQtCJwBefq4S6/w75XaXVvH1/2P9uaijvF7R+Cm2EWQZ5WsvA5wl//DFWblyDHRyVC207Q==",
       "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/types": "3.1.1",
-        "@docusaurus/utils": "3.1.1",
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/types": "3.2.0",
+        "@docusaurus/utils": "3.2.0",
         "fs-extra": "^11.1.1",
         "react-json-view-lite": "^1.2.0",
         "tslib": "^2.6.0"
@@ -2480,13 +2480,13 @@
       }
     },
     "node_modules/@docusaurus/plugin-google-analytics": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.1.1.tgz",
-      "integrity": "sha512-+q2UpWTqVi8GdlLoSlD5bS/YpxW+QMoBwrPrUH/NpvpuOi0Of7MTotsQf9JWd3hymZxl2uu1o3PIrbpxfeDFDQ==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-analytics/-/plugin-google-analytics-3.2.0.tgz",
+      "integrity": "sha512-//TepJTEyAZSvBwHKEbXHu9xT/VkK3wUil2ZakKvQZYfUC01uWn6A1E3toa8R7WhCy1xPUeIukqmJy1Clg8njQ==",
       "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/types": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/types": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "tslib": "^2.6.0"
       },
       "engines": {
@@ -2498,13 +2498,13 @@
       }
     },
     "node_modules/@docusaurus/plugin-google-gtag": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.1.1.tgz",
-      "integrity": "sha512-0mMPiBBlQ5LFHTtjxuvt/6yzh8v7OxLi3CbeEsxXZpUzcKO/GC7UA1VOWUoBeQzQL508J12HTAlR3IBU9OofSw==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-gtag/-/plugin-google-gtag-3.2.0.tgz",
+      "integrity": "sha512-3s6zxlaMMb87MW2Rxy6EnSRDs0WDEQPuHilZZH402C8kOrUnIwlhlfjWZ4ZyLDziGl/Eec/DvD0PVqj0qHRomA==",
       "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/types": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/types": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "@types/gtag.js": "^0.0.12",
         "tslib": "^2.6.0"
       },
@@ -2517,13 +2517,13 @@
       }
     },
     "node_modules/@docusaurus/plugin-google-tag-manager": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.1.1.tgz",
-      "integrity": "sha512-d07bsrMLdDIryDtY17DgqYUbjkswZQr8cLWl4tzXrt5OR/T/zxC1SYKajzB3fd87zTu5W5klV5GmUwcNSMXQXA==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-google-tag-manager/-/plugin-google-tag-manager-3.2.0.tgz",
+      "integrity": "sha512-rAKtsJ11vPHA7dTAqWCgyIy7AyFRF/lpI77Zd/4HKgqcIvIayVBvL3QtelhUazfYTLTH6ls6kQ9wjMcIFxRiGg==",
       "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/types": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/types": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "tslib": "^2.6.0"
       },
       "engines": {
@@ -2535,16 +2535,16 @@
       }
     },
     "node_modules/@docusaurus/plugin-ideal-image": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-ideal-image/-/plugin-ideal-image-3.1.1.tgz",
-      "integrity": "sha512-FYce5eV5/fWO4qIG8sKYdK3MTwusdxQML/M62IiltUNM/cqFkDrty1d+H+/I2PYX1s7AOoL3YomdJNP4vra/Tg==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-ideal-image/-/plugin-ideal-image-3.2.0.tgz",
+      "integrity": "sha512-FPvNyOmSRBnSUQkiti7098N9W950EC4z7hXRn5ZwaG7Q+JGLdXC7sYWyTsjR5KZbEGolJWG1uyi+bG2vd1zDsw==",
       "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/lqip-loader": "3.1.1",
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/lqip-loader": "3.2.0",
         "@docusaurus/responsive-loader": "^1.7.0",
-        "@docusaurus/theme-translations": "3.1.1",
-        "@docusaurus/types": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+        "@docusaurus/theme-translations": "3.2.0",
+        "@docusaurus/types": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "@slorber/react-ideal-image": "^0.0.12",
         "react-waypoint": "^10.3.0",
         "sharp": "^0.32.3",
@@ -2566,16 +2566,16 @@
       }
     },
     "node_modules/@docusaurus/plugin-sitemap": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.1.1.tgz",
-      "integrity": "sha512-iJ4hCaMmDaUqRv131XJdt/C/jJQx8UreDWTRqZKtNydvZVh/o4yXGRRFOplea1D9b/zpwL1Y+ZDwX7xMhIOTmg==",
-      "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/logger": "3.1.1",
-        "@docusaurus/types": "3.1.1",
-        "@docusaurus/utils": "3.1.1",
-        "@docusaurus/utils-common": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/plugin-sitemap/-/plugin-sitemap-3.2.0.tgz",
+      "integrity": "sha512-gnWDFt6MStjLkdtt63Lzc+14EPSd8B6mzJGJp9GQMvWDUoMAUijUqpVIHYQq+DPMcI4PJZ5I2nsl5XFf1vOldA==",
+      "dependencies": {
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/logger": "3.2.0",
+        "@docusaurus/types": "3.2.0",
+        "@docusaurus/utils": "3.2.0",
+        "@docusaurus/utils-common": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "fs-extra": "^11.1.1",
         "sitemap": "^7.1.1",
         "tslib": "^2.6.0"
@@ -2589,23 +2589,23 @@
       }
     },
     "node_modules/@docusaurus/preset-classic": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.1.1.tgz",
-      "integrity": "sha512-jG4ys/hWYf69iaN/xOmF+3kjs4Nnz1Ay3CjFLDtYa8KdxbmUhArA9HmP26ru5N0wbVWhY+6kmpYhTJpez5wTyg==",
-      "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/plugin-content-blog": "3.1.1",
-        "@docusaurus/plugin-content-docs": "3.1.1",
-        "@docusaurus/plugin-content-pages": "3.1.1",
-        "@docusaurus/plugin-debug": "3.1.1",
-        "@docusaurus/plugin-google-analytics": "3.1.1",
-        "@docusaurus/plugin-google-gtag": "3.1.1",
-        "@docusaurus/plugin-google-tag-manager": "3.1.1",
-        "@docusaurus/plugin-sitemap": "3.1.1",
-        "@docusaurus/theme-classic": "3.1.1",
-        "@docusaurus/theme-common": "3.1.1",
-        "@docusaurus/theme-search-algolia": "3.1.1",
-        "@docusaurus/types": "3.1.1"
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/preset-classic/-/preset-classic-3.2.0.tgz",
+      "integrity": "sha512-t7tXyk8kUgT7hUqEOgSJnPs+Foem9ucuan/a9QVYaVFCDjp92Sb2FpCY8bVasAokYCjodYe2LfpAoSCj5YDYWg==",
+      "dependencies": {
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/plugin-content-blog": "3.2.0",
+        "@docusaurus/plugin-content-docs": "3.2.0",
+        "@docusaurus/plugin-content-pages": "3.2.0",
+        "@docusaurus/plugin-debug": "3.2.0",
+        "@docusaurus/plugin-google-analytics": "3.2.0",
+        "@docusaurus/plugin-google-gtag": "3.2.0",
+        "@docusaurus/plugin-google-tag-manager": "3.2.0",
+        "@docusaurus/plugin-sitemap": "3.2.0",
+        "@docusaurus/theme-classic": "3.2.0",
+        "@docusaurus/theme-common": "3.2.0",
+        "@docusaurus/theme-search-algolia": "3.2.0",
+        "@docusaurus/types": "3.2.0"
       },
       "engines": {
         "node": ">=18.0"
@@ -2651,22 +2651,22 @@
       }
     },
     "node_modules/@docusaurus/theme-classic": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.1.1.tgz",
-      "integrity": "sha512-GiPE/jbWM8Qv1A14lk6s9fhc0LhPEQ00eIczRO4QL2nAQJZXkjPG6zaVx+1cZxPFWbAsqSjKe2lqkwF3fGkQ7Q==",
-      "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/mdx-loader": "3.1.1",
-        "@docusaurus/module-type-aliases": "3.1.1",
-        "@docusaurus/plugin-content-blog": "3.1.1",
-        "@docusaurus/plugin-content-docs": "3.1.1",
-        "@docusaurus/plugin-content-pages": "3.1.1",
-        "@docusaurus/theme-common": "3.1.1",
-        "@docusaurus/theme-translations": "3.1.1",
-        "@docusaurus/types": "3.1.1",
-        "@docusaurus/utils": "3.1.1",
-        "@docusaurus/utils-common": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/theme-classic/-/theme-classic-3.2.0.tgz",
+      "integrity": "sha512-4oSO5BQOJ5ja7WYdL6jK1n4J96tp+VJHamdwao6Ea252sA3W3vvR0otTflG4p4XVjNZH6hlPQoi5lKW0HeRgfQ==",
+      "dependencies": {
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/mdx-loader": "3.2.0",
+        "@docusaurus/module-type-aliases": "3.2.0",
+        "@docusaurus/plugin-content-blog": "3.2.0",
+        "@docusaurus/plugin-content-docs": "3.2.0",
+        "@docusaurus/plugin-content-pages": "3.2.0",
+        "@docusaurus/theme-common": "3.2.0",
+        "@docusaurus/theme-translations": "3.2.0",
+        "@docusaurus/types": "3.2.0",
+        "@docusaurus/utils": "3.2.0",
+        "@docusaurus/utils-common": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "@mdx-js/react": "^3.0.0",
         "clsx": "^2.0.0",
         "copy-text-to-clipboard": "^3.2.0",
@@ -2690,17 +2690,17 @@
       }
     },
     "node_modules/@docusaurus/theme-common": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.1.1.tgz",
-      "integrity": "sha512-38urZfeMhN70YaXkwIGXmcUcv2CEYK/2l4b05GkJPrbEbgpsIZM3Xc+Js2ehBGGZmfZq8GjjQ5RNQYG+MYzCYg==",
-      "dependencies": {
-        "@docusaurus/mdx-loader": "3.1.1",
-        "@docusaurus/module-type-aliases": "3.1.1",
-        "@docusaurus/plugin-content-blog": "3.1.1",
-        "@docusaurus/plugin-content-docs": "3.1.1",
-        "@docusaurus/plugin-content-pages": "3.1.1",
-        "@docusaurus/utils": "3.1.1",
-        "@docusaurus/utils-common": "3.1.1",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/theme-common/-/theme-common-3.2.0.tgz",
+      "integrity": "sha512-sFbw9XviNJJ+760kAcZCQMQ3jkNIznGqa6MQ70E5BnbP+ja36kGgPOfjcsvAcNey1H1Rkhh3p2Mhf4HVLdKVVw==",
+      "dependencies": {
+        "@docusaurus/mdx-loader": "3.2.0",
+        "@docusaurus/module-type-aliases": "3.2.0",
+        "@docusaurus/plugin-content-blog": "3.2.0",
+        "@docusaurus/plugin-content-docs": "3.2.0",
+        "@docusaurus/plugin-content-pages": "3.2.0",
+        "@docusaurus/utils": "3.2.0",
+        "@docusaurus/utils-common": "3.2.0",
         "@types/history": "^4.7.11",
         "@types/react": "*",
         "@types/react-router-config": "*",
@@ -2719,15 +2719,15 @@
       }
     },
     "node_modules/@docusaurus/theme-mermaid": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.1.1.tgz",
-      "integrity": "sha512-O6u9/7QX/ZapV4HJJSzNs0Jir1KA/LRLORWYeDvbGswqZNusj6q4iLELrKIClysJ3PB3zWUzyKtI/wjIKiV1vA==",
-      "dependencies": {
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/module-type-aliases": "3.1.1",
-        "@docusaurus/theme-common": "3.1.1",
-        "@docusaurus/types": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/theme-mermaid/-/theme-mermaid-3.2.0.tgz",
+      "integrity": "sha512-PvN6K6m3JaM9cr9oSPyba6OlwAiSfBzqQtNqdgPFDjakKuT4kj6JODfExi+HKtWuxayOVRQlRl7zTnWxM4sTVw==",
+      "dependencies": {
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/module-type-aliases": "3.2.0",
+        "@docusaurus/theme-common": "3.2.0",
+        "@docusaurus/types": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "mermaid": "^10.4.0",
         "tslib": "^2.6.0"
       },
@@ -2740,18 +2740,18 @@
       }
     },
     "node_modules/@docusaurus/theme-search-algolia": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.1.1.tgz",
-      "integrity": "sha512-tBH9VY5EpRctVdaAhT+b1BY8y5dyHVZGFXyCHgTrvcXQy5CV4q7serEX7U3SveNT9zksmchPyct6i1sFDC4Z5g==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/theme-search-algolia/-/theme-search-algolia-3.2.0.tgz",
+      "integrity": "sha512-PgvF4qHoqJp8+GfqClUbTF/zYNOsz4De251IuzXon7+7FAXwvb2qmYtA2nEwyMbB7faKOz33Pxzv+y+153KS/g==",
       "dependencies": {
         "@docsearch/react": "^3.5.2",
-        "@docusaurus/core": "3.1.1",
-        "@docusaurus/logger": "3.1.1",
-        "@docusaurus/plugin-content-docs": "3.1.1",
-        "@docusaurus/theme-common": "3.1.1",
-        "@docusaurus/theme-translations": "3.1.1",
-        "@docusaurus/utils": "3.1.1",
-        "@docusaurus/utils-validation": "3.1.1",
+        "@docusaurus/core": "3.2.0",
+        "@docusaurus/logger": "3.2.0",
+        "@docusaurus/plugin-content-docs": "3.2.0",
+        "@docusaurus/theme-common": "3.2.0",
+        "@docusaurus/theme-translations": "3.2.0",
+        "@docusaurus/utils": "3.2.0",
+        "@docusaurus/utils-validation": "3.2.0",
         "algoliasearch": "^4.18.0",
         "algoliasearch-helper": "^3.13.3",
         "clsx": "^2.0.0",
@@ -2770,9 +2770,9 @@
       }
     },
     "node_modules/@docusaurus/theme-translations": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.1.1.tgz",
-      "integrity": "sha512-xvWQFwjxHphpJq5fgk37FXCDdAa2o+r7FX8IpMg+bGZBNXyWBu3MjZ+G4+eUVNpDhVinTc+j6ucL0Ain5KCGrg==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/theme-translations/-/theme-translations-3.2.0.tgz",
+      "integrity": "sha512-VXzZJBuyVEmwUYyud+7IgJQEBRM6R2u/s10Rp3DOP19CBQxeKgHYTKkKhFtDeKMHDassb665kjgOi0YlJfUT6w==",
       "dependencies": {
         "fs-extra": "^11.1.1",
         "tslib": "^2.6.0"
@@ -2782,15 +2782,15 @@
       }
     },
     "node_modules/@docusaurus/tsconfig": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.1.1.tgz",
-      "integrity": "sha512-FTBuY3KvaHfMVBgvlPmDQ+KS9Q/bYtVftq2ugou3PgBDJoQmw2aUZ4Sg15HKqLGbfIkxoy9t6cqE4Yw1Ta8Q1A==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/tsconfig/-/tsconfig-3.2.0.tgz",
+      "integrity": "sha512-gWGa/72TYRxSRRxGFU2G6Au0yif6zmbkAgzW3+SeXAxoq1a7OLytMLGEwaHjMoIRXl7WbThnpx4gZVwo0xRRAg==",
       "dev": true
     },
     "node_modules/@docusaurus/types": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.1.1.tgz",
-      "integrity": "sha512-grBqOLnubUecgKFXN9q3uit2HFbCxTWX4Fam3ZFbMN0sWX9wOcDoA7lwdX/8AmeL20Oc4kQvWVgNrsT8bKRvzg==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/types/-/types-3.2.0.tgz",
+      "integrity": "sha512-uG3FfTkkkbZIPPNYx6xRfZHKeGyRd/inIT1cqvYt1FobFLd+7WhRXrSBqwJ9JajJjEAjNioRMVFgGofGf/Wdww==",
       "dependencies": {
         "@mdx-js/mdx": "^3.0.0",
         "@types/history": "^4.7.11",
@@ -2808,11 +2808,12 @@
       }
     },
     "node_modules/@docusaurus/utils": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.1.1.tgz",
-      "integrity": "sha512-ZJfJa5cJQtRYtqijsPEnAZoduW6sjAQ7ZCWSZavLcV10Fw0Z3gSaPKA/B4micvj2afRZ4gZxT7KfYqe5H8Cetg==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/utils/-/utils-3.2.0.tgz",
+      "integrity": "sha512-3rgrE7iL60yV2JQivlcoxUNNTK2APmn+OHLUmTvX2pueIM8DEOCEFHpJO4MiWjFO7V/Wq3iA/W1M03JnjdugVw==",
       "dependencies": {
-        "@docusaurus/logger": "3.1.1",
+        "@docusaurus/logger": "3.2.0",
+        "@docusaurus/utils-common": "3.2.0",
         "@svgr/webpack": "^6.5.1",
         "escape-string-regexp": "^4.0.0",
         "file-loader": "^6.2.0",
@@ -2824,6 +2825,7 @@
         "js-yaml": "^4.1.0",
         "lodash": "^4.17.21",
         "micromatch": "^4.0.5",
+        "prompts": "^2.4.2",
         "resolve-pathname": "^3.0.0",
         "shelljs": "^0.8.5",
         "tslib": "^2.6.0",
@@ -2843,9 +2845,9 @@
       }
     },
     "node_modules/@docusaurus/utils-common": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.1.1.tgz",
-      "integrity": "sha512-eGne3olsIoNfPug5ixjepZAIxeYFzHHnor55Wb2P57jNbtVaFvij/T+MS8U0dtZRFi50QU+UPmRrXdVUM8uyMg==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/utils-common/-/utils-common-3.2.0.tgz",
+      "integrity": "sha512-WEQT5L2lT/tBQgDRgeZQAIi9YJBrwEILb1BuObQn1St3T/4K1gx5fWwOT8qdLOov296XLd1FQg9Ywu27aE9svw==",
       "dependencies": {
         "tslib": "^2.6.0"
       },
@@ -2862,12 +2864,13 @@
       }
     },
     "node_modules/@docusaurus/utils-validation": {
-      "version": "3.1.1",
-      "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.1.1.tgz",
-      "integrity": "sha512-KlY4P9YVDnwL+nExvlIpu79abfEv6ZCHuOX4ZQ+gtip+Wxj0daccdReIWWtqxM/Fb5Cz1nQvUCc7VEtT8IBUAA==",
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/@docusaurus/utils-validation/-/utils-validation-3.2.0.tgz",
+      "integrity": "sha512-rCzMTqwNrBrEOyU8EaD1fYWdig4TDhfj+YLqB8DY68VUAqSIgbY+yshpqFKB0bznFYNBJbn0bGpvVuImQOa/vA==",
       "dependencies": {
-        "@docusaurus/logger": "3.1.1",
-        "@docusaurus/utils": "3.1.1",
+        "@docusaurus/logger": "3.2.0",
+        "@docusaurus/utils": "3.2.0",
+        "@docusaurus/utils-common": "3.2.0",
         "joi": "^17.9.2",
         "js-yaml": "^4.1.0",
         "tslib": "^2.6.0"
@@ -3704,19 +3707,6 @@
         "micromark-util-symbol": "^1.0.1"
       }
     },
-    "node_modules/@slorber/static-site-generator-webpack-plugin": {
-      "version": "4.0.7",
-      "resolved": "https://registry.npmjs.org/@slorber/static-site-generator-webpack-plugin/-/static-site-generator-webpack-plugin-4.0.7.tgz",
-      "integrity": "sha512-Ug7x6z5lwrz0WqdnNFOMYrDQNTPAprvHLSh6+/fmml3qUiz6l5eq+2MzLKWtn/q5K5NpSiFsZTP/fck/3vjSxA==",
-      "dependencies": {
-        "eval": "^0.1.8",
-        "p-map": "^4.0.0",
-        "webpack-sources": "^3.2.2"
-      },
-      "engines": {
-        "node": ">=14"
-      }
-    },
     "node_modules/@svgr/babel-plugin-add-jsx-attribute": {
       "version": "6.5.1",
       "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-6.5.1.tgz",
diff --git a/website/package.json b/website/package.json
index 0f8a0ad..6b8b714 100644
--- a/website/package.json
+++ b/website/package.json
@@ -15,9 +15,9 @@
     "typecheck": "tsc"
   },
   "dependencies": {
-    "@docusaurus/core": "3.1.1",
+    "@docusaurus/core": "3.2.0",
     "@docusaurus/plugin-ideal-image": "^3.1.0",
-    "@docusaurus/preset-classic": "3.1.1",
+    "@docusaurus/preset-classic": "3.2.0",
     "@docusaurus/theme-mermaid": "^3.1.0",
     "@easyops-cn/docusaurus-search-local": "^0.40.1",
     "@fortawesome/fontawesome-svg-core": "^6.5.1",
@@ -36,9 +36,9 @@
     "typewriter-effect": "^2.21.0"
   },
   "devDependencies": {
-    "@docusaurus/module-type-aliases": "3.1.1",
-    "@docusaurus/tsconfig": "3.1.1",
-    "@docusaurus/types": "3.1.1",
+    "@docusaurus/module-type-aliases": "3.2.0",
+    "@docusaurus/tsconfig": "3.2.0",
+    "@docusaurus/types": "3.2.0",
     "eslint-plugin-import": "^2.29.1",
     "typescript": "~5.4.0"
   },


Test patch:
diff --git a/internal/dependency/manager_test.go b/internal/dependency/manager_test.go
index 7021ba6..f989cb2 100644
--- a/internal/dependency/manager_test.go
+++ b/internal/dependency/manager_test.go
@@ -45,7 +45,7 @@ func (a *testClientAdapter) ListPackages(ctx context.Context) (*v1alpha1.Package
 }
 
 func createDependencyManager() *DependendcyManager {
-	return NewDependencyManager(&testClientAdapter{}, nil).WithRepo(fakeRepo)
+	return NewDependencyManager(&testClientAdapter{}).WithRepo(fakeRepo)
 }
 
 var dm *DependendcyManager
diff --git a/internal/webhook/package_validator_test.go b/internal/webhook/package_validator_test.go
index 455b1bf..e5e8190 100644
--- a/internal/webhook/package_validator_test.go
+++ b/internal/webhook/package_validator_test.go
@@ -29,10 +29,8 @@ func newPackageValidatingWebhook(objects ...client.Object) *PackageValidatingWeb
 	return &PackageValidatingWebhook{
 		Client:       fakeClient,
 		OwnerManager: ownerManager,
-		DependendcyManager: dependency.NewDependencyManager(
-			ctrladapter.NewControllerRuntimeAdapter(fakeClient),
-			ownerManager,
-		).WithRepo(&fakeRepo),
+		DependendcyManager: dependency.NewDependencyManager(ctrladapter.NewControllerRuntimeAdapter(fakeClient)).
+			WithRepo(&fakeRepo),
 		repo: &fakeRepo,
 	}
 }