feat: move scripting code editors to use Monaco (#5191)

Replaces CodeMirror with Monaco for scripting editors under the experimental scripting sandbox.
The legacy CodeMirror-based editors are preserved for backwards compatibility and will
continue to power the legacy scripting sandbox.

This introduces improved type support, IntelliSense, and JSDoc hinting via Monaco, with
the backing for pre-request and post-request scripts in tabbed views. Type definitions are
isolated per editor to avoid variable leakage.
This commit is contained in:
James George 2025-07-25 13:35:37 +05:30 committed by GitHub
parent cfa2caa1db
commit 40aca4d35b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 416 additions and 58 deletions

View file

@ -34,6 +34,7 @@
"@codemirror/search": "6.5.6",
"@codemirror/state": "6.4.1",
"@codemirror/view": "6.25.1",
"@guolao/vue-monaco-editor": "1.5.5",
"@hoppscotch/codemirror-lang-graphql": "workspace:^",
"@hoppscotch/data": "workspace:^",
"@hoppscotch/httpsnippet": "3.0.9",
@ -79,6 +80,7 @@
"lossless-json": "4.0.2",
"markdown-it": "14.1.0",
"minisearch": "7.1.0",
"monaco-editor": "0.52.2",
"nprogress": "0.2.0",
"paho-mqtt": "1.1.0",
"path": "0.12.7",

View file

@ -0,0 +1,196 @@
<template>
<vue-monaco-editor
v-model:value="value"
:theme="monacoEditorTheme"
language="typescript"
:options="MONACO_EDITOR_OPTIONS"
:model="editorModel"
/>
</template>
<script setup lang="ts">
import { VueMonacoEditor } from "@guolao/vue-monaco-editor"
import { watchDebounced } from "@vueuse/core"
import * as monaco from "monaco-editor"
import { v4 as uuidv4 } from "uuid"
import { computed, onMounted, ref } from "vue"
import { useColorMode } from "~/composables/theming"
const props = withDefaults(
defineProps<{
modelValue: string
type: "pre-request" | "post-request"
}>(),
{
modelValue: "",
}
)
const theme = useColorMode()
const emit = defineEmits<{
(e: "update:modelValue", value: string): void
}>()
const editorModel = ref<monaco.editor.ITextModel | null>(null)
const MONACO_EDITOR_OPTIONS: Readonly<monaco.editor.IStandaloneEditorConstructionOptions> =
{
automaticLayout: true,
formatOnType: true,
formatOnPaste: true,
}
// Static imports: import X from "URL"
const staticImportRegex =
/import\s+(?:[\w*\s{},]+?\s+from\s+)?["']([^"']+)["']/g
// Dynamic imports: import("URL")
const dynamicImportRegex = /import\(\s*["']([^"']+)["']\s*\)/g
const typeDefCache = new Map<string, string>()
const extraLibRefs = new Map<string, monaco.IDisposable>()
const MODULE_PREFIX = "export {};\n" as const
const ensureCompilerOptions = (() => {
let applied = false
return () => {
if (applied) {
return
}
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
allowJs: true,
checkJs: true,
allowSyntheticDefaultImports: true,
esModuleInterop: true,
moduleResolution: monaco.languages.typescript.ModuleResolutionKind.NodeJs,
module: monaco.languages.typescript.ModuleKind.ESNext,
noEmit: true,
target: monaco.languages.typescript.ScriptTarget.ES2020,
allowNonTsExtensions: true,
})
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false,
noSyntaxValidation: false,
})
// Disable Cmd/Ctrl+Enter key binding
monaco.editor.addKeybindingRule({
keybinding: monaco.KeyMod.CtrlCmd | monaco.KeyCode.Enter,
command: null,
})
applied = true
}
})()
onMounted(() => {
ensureCompilerOptions()
const uuid = uuidv4()
const scriptFileURI = monaco.Uri.parse(
`inmemory://model/${uuid}.${props.type}.ts`
)
// Add `export {}` to make it a module and isolate scope
const initialValue = value.value
? `${MODULE_PREFIX}${value.value}`
: MODULE_PREFIX
editorModel.value = monaco.editor.createModel(
initialValue,
"typescript",
scriptFileURI
)
})
const value = computed({
get: () => {
const modelValue = props.modelValue
return modelValue.startsWith(MODULE_PREFIX)
? modelValue.slice(MODULE_PREFIX.length) // Remove the prefix for display
: modelValue
},
set: (newValue) => {
// Always ensure the export prefix exists
const finalValue = newValue.startsWith(MODULE_PREFIX.trim())
? newValue
: `${MODULE_PREFIX}${newValue}`
emit("update:modelValue", finalValue)
},
})
const resolveAndAddDTS = async (url: string) => {
if (
typeDefCache.has(url) ||
url.includes("?no-dts") ||
!url.includes("esm.sh")
) {
return
}
const headResp = await fetch(url, { method: "HEAD" }).catch(() => null)
const typesHeader = headResp?.headers.get("X-TypeScript-Types")
const dtsURL = typesHeader ? new URL(typesHeader, url).href : `${url}.d.ts`
const dtsResp = await fetch(dtsURL).catch(() => null)
if (!dtsResp?.ok) {
return
}
let dtsText = await dtsResp.text()
if (!/declare\s+module\s+["']/.test(dtsText)) {
dtsText = `declare module "${url}" {\n${dtsText}\n}`
}
typeDefCache.set(url, dtsURL)
const libUri = `inmemory://lib/${encodeURIComponent(url)}.d.ts`
const disposable = monaco.languages.typescript.typescriptDefaults.addExtraLib(
dtsText,
libUri
)
// Clean up old one if already tracked
extraLibRefs.get(url)?.dispose()
extraLibRefs.set(url, disposable)
}
// Dispose libs no longer used
const updateExtraLibs = (newValue: string) => {
const found = new Set<string>()
const staticMatches = newValue.matchAll(staticImportRegex)
const dynamicMatches = newValue.matchAll(dynamicImportRegex)
for (const match of staticMatches) found.add(match[1])
for (const match of dynamicMatches) found.add(match[1])
// Remove stale imports
for (const oldUrl of extraLibRefs.keys()) {
if (!found.has(oldUrl)) {
extraLibRefs.get(oldUrl)?.dispose()
extraLibRefs.delete(oldUrl)
}
}
// Resolve new ones
found.forEach(resolveAndAddDTS)
}
watchDebounced(() => props.modelValue, updateExtraLibs, {
debounce: 500,
immediate: true,
})
const monacoEditorTheme = computed(() =>
["dark", "black"].includes(theme.value) ? "vs-dark" : "vs"
)
</script>

View file

@ -38,7 +38,18 @@
</div>
<div class="flex flex-1 border-b border-dividerLight">
<div class="w-2/3 border-r border-dividerLight h-full relative">
<div ref="preRequestEditor" class="h-full absolute inset-0"></div>
<MonacoScriptEditor
v-if="EXPERIMENTAL_SCRIPTING_SANDBOX && props.isActive"
v-model="preRequestScript"
:is-active="props.isActive"
type="pre-request"
/>
<div
v-else
ref="preRequestEditor"
class="h-full absolute inset-0"
></div>
</div>
<div
class="z-[9] sticky top-upperTertiaryStickyFold h-full min-w-[12rem] max-w-1/3 flex-shrink-0 overflow-auto overflow-x-auto bg-primary p-4"
@ -76,31 +87,33 @@
</template>
<script setup lang="ts">
import IconHelpCircle from "~icons/lucide/help-circle"
import IconWrapText from "~icons/lucide/wrap-text"
import IconTrash2 from "~icons/lucide/trash-2"
import IconSparkles from "~icons/lucide/sparkles"
import { reactive, ref, computed } from "vue"
import snippets from "@helpers/preRequestScriptSnippets"
import { useCodemirror } from "@composables/codemirror"
import linter from "~/helpers/editor/linting/preRequest"
import completer from "~/helpers/editor/completion/preRequest"
import { useI18n } from "@composables/i18n"
import { useVModel } from "@vueuse/core"
import { useNestedSetting } from "~/composables/settings"
import { toggleNestedSetting } from "~/newstore/settings"
import { useAIExperiments } from "~/composables/ai-experiments"
import { useService } from "dioc/vue"
import { RESTTabService } from "~/services/tab/rest"
import { platform } from "~/platform"
import { useReadonlyStream } from "~/composables/stream"
import AiexperimentsModifyPreRequestModal from "@components/aiexperiments/ModifyPreRequestModal.vue"
import { useCodemirror } from "@composables/codemirror"
import { useI18n } from "@composables/i18n"
import snippets from "@helpers/preRequestScriptSnippets"
import { useVModel } from "@vueuse/core"
import { useService } from "dioc/vue"
import { computed, reactive, ref } from "vue"
import { useAIExperiments } from "~/composables/ai-experiments"
import { useNestedSetting, useSetting } from "~/composables/settings"
import { useReadonlyStream } from "~/composables/stream"
import { invokeAction } from "~/helpers/actions"
import completer from "~/helpers/editor/completion/preRequest"
import linter from "~/helpers/editor/linting/preRequest"
import { toggleNestedSetting } from "~/newstore/settings"
import { platform } from "~/platform"
import { RESTTabService } from "~/services/tab/rest"
import IconHelpCircle from "~icons/lucide/help-circle"
import IconSparkles from "~icons/lucide/sparkles"
import IconTrash2 from "~icons/lucide/trash-2"
import IconWrapText from "~icons/lucide/wrap-text"
const t = useI18n()
const props = defineProps<{
modelValue: string
isActive?: boolean
}>()
const emit = defineEmits<{
(e: "update:modelValue", value: string): void
@ -127,6 +140,10 @@ useCodemirror(
})
)
const EXPERIMENTAL_SCRIPTING_SANDBOX = useSetting(
"EXPERIMENTAL_SCRIPTING_SANDBOX"
)
const useSnippet = (script: string) => {
preRequestScript.value += script
}

View file

@ -64,6 +64,7 @@
<HttpPreRequestScript
v-if="'preRequestScript' in request"
v-model="request.preRequestScript"
:is-active="selectedOptionTab === 'preRequestScript'"
/>
</HoppSmartTab>
<HoppSmartTab
@ -78,7 +79,11 @@
: false
"
>
<HttpTests v-if="'testScript' in request" v-model="request.testScript" />
<HttpTests
v-if="'testScript' in request"
v-model="request.testScript"
:is-active="selectedOptionTab === 'tests'"
/>
</HoppSmartTab>
<HoppSmartTab
v-if="properties?.includes('requestVariables') ?? true"
@ -99,11 +104,16 @@ import {
HoppRESTResponseOriginalRequest,
} from "@hoppscotch/data"
import { useVModel } from "@vueuse/core"
import { computed } from "vue"
import * as monaco from "monaco-editor"
import { computed, onUnmounted, watch } from "vue"
import { defineActionHandler } from "~/helpers/actions"
import { HoppInheritedProperty } from "~/helpers/types/HoppInheritedProperties"
import { AggregateEnvironment } from "~/newstore/environments"
import postRequestPWModDefn from "~/types/post-request.d.ts?raw"
import preRequestPWModDefn from "~/types/pre-request.d.ts?raw"
const VALID_OPTION_TABS = [
"params",
"bodyParams",
@ -140,6 +150,36 @@ const emit = defineEmits<{
const request = useVModel(props, "modelValue", emit)
const selectedOptionTab = useVModel(props, "optionTab", emit)
let extraLibRef: monaco.IDisposable | null = null
const libDefs = {
"pre-request": preRequestPWModDefn,
"post-request": postRequestPWModDefn,
}
const scriptEditorTabs = ["preRequestScript", "tests"]
onUnmounted(() => extraLibRef?.dispose())
watch(
() => selectedOptionTab.value,
(newTab) => {
if (!scriptEditorTabs.includes(newTab)) {
return
}
extraLibRef?.dispose()
monaco.languages.typescript.typescriptDefaults.setExtraLibs([])
extraLibRef = monaco.languages.typescript.typescriptDefaults.addExtraLib(
libDefs[newTab === "preRequestScript" ? "pre-request" : "post-request"],
`inmemory://lib/pw-${newTab}.d.ts`
)
},
{ immediate: true }
)
const showPreRequestScriptTab = computed(() => {
return (
props.properties?.includes("preRequestScript") ??

View file

@ -38,7 +38,18 @@
</div>
<div class="flex flex-1 border-b border-dividerLight">
<div class="w-2/3 border-r border-dividerLight h-full relative">
<div ref="testScriptEditor" class="h-full absolute inset-0"></div>
<MonacoScriptEditor
v-if="EXPERIMENTAL_SCRIPTING_SANDBOX && props.isActive"
v-model="testScript"
:is-active="props.isActive"
type="post-request"
/>
<div
v-else
ref="testScriptEditor"
class="h-full absolute inset-0"
></div>
</div>
<div
class="z-[9] sticky top-upperTertiaryStickyFold h-full min-w-[12rem] max-w-1/3 flex-shrink-0 overflow-auto overflow-x-auto bg-primary p-4"
@ -76,31 +87,32 @@
</template>
<script setup lang="ts">
import IconHelpCircle from "~icons/lucide/help-circle"
import IconWrapText from "~icons/lucide/wrap-text"
import IconTrash2 from "~icons/lucide/trash-2"
import IconSparkles from "~icons/lucide/sparkles"
import { reactive, ref, computed } from "vue"
import testSnippets from "~/helpers/testSnippets"
import AiexperimentsModifyTestScriptModal from "@components/aiexperiments/ModifyTestScriptModal.vue"
import { useCodemirror } from "@composables/codemirror"
import linter from "~/helpers/editor/linting/testScript"
import completer from "~/helpers/editor/completion/testScript"
import { useI18n } from "@composables/i18n"
import { useVModel } from "@vueuse/core"
import { useNestedSetting } from "~/composables/settings"
import { toggleNestedSetting } from "~/newstore/settings"
import { useAIExperiments } from "~/composables/ai-experiments"
import { useService } from "dioc/vue"
import { RESTTabService } from "~/services/tab/rest"
import { platform } from "~/platform"
import { computed, reactive, ref } from "vue"
import { useAIExperiments } from "~/composables/ai-experiments"
import { useNestedSetting, useSetting } from "~/composables/settings"
import { useReadonlyStream } from "~/composables/stream"
import AiexperimentsModifyTestScriptModal from "@components/aiexperiments/ModifyTestScriptModal.vue"
import { invokeAction } from "~/helpers/actions"
import completer from "~/helpers/editor/completion/testScript"
import linter from "~/helpers/editor/linting/testScript"
import testSnippets from "~/helpers/testSnippets"
import { toggleNestedSetting } from "~/newstore/settings"
import { platform } from "~/platform"
import { RESTTabService } from "~/services/tab/rest"
import IconHelpCircle from "~icons/lucide/help-circle"
import IconSparkles from "~icons/lucide/sparkles"
import IconTrash2 from "~icons/lucide/trash-2"
import IconWrapText from "~icons/lucide/wrap-text"
const t = useI18n()
const props = defineProps<{
modelValue: string
isActive?: boolean
}>()
const emit = defineEmits(["update:modelValue"])
const testScript = useVModel(props, "modelValue", emit)
@ -123,6 +135,10 @@ useCodemirror(
})
)
const EXPERIMENTAL_SCRIPTING_SANDBOX = useSetting(
"EXPERIMENTAL_SCRIPTING_SANDBOX"
)
const useSnippet = (script: string) => {
testScript.value += script
}

View file

@ -1,12 +1,18 @@
import { getKernelMode, initKernel } from "@hoppscotch/kernel"
import { HOPP_MODULES } from "@modules/."
import { createApp } from "vue"
import { PlatformDef, setPlatformDef } from "./platform"
import { initKernel, getKernelMode } from "@hoppscotch/kernel"
import { loader } from "@guolao/vue-monaco-editor"
import * as monaco from "monaco-editor"
import editorWorker from "monaco-editor/esm/vs/editor/editor.worker?worker"
import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"
import { PlatformDef, setPlatformDef } from "./platform"
import "nprogress/nprogress.css"
import "../assets/scss/styles.scss"
import "../assets/scss/tailwind.scss"
import "../assets/themes/themes.scss"
import "../assets/scss/styles.scss"
import "nprogress/nprogress.css"
import "unfonts.css"
@ -36,6 +42,18 @@ export async function createHoppApp(
)
}
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === "typescript") {
return new tsWorker()
}
return new editorWorker()
},
}
loader.config({ monaco })
HOPP_MODULES.forEach((mod) => mod.onVueAppInit?.(app))
platformDef.addedHoppModules?.forEach((mod) => mod.onVueAppInit?.(app))

View file

@ -0,0 +1,35 @@
declare namespace pw {
function test(name: string, func: () => void): void
function expect(value: any): Expectation
const response: {
status: number
headers: any
body: any
}
namespace env {
function set(key: string, value: string): void
function unset(key: string): void
function get(key: string): string
function getResolve(key: string): string
function resolve(value: string): string
}
}
interface Expectation extends ExpectationMethods {
not: BaseExpectation
}
interface BaseExpectation extends ExpectationMethods {}
interface ExpectationMethods {
toBe(value: any): void
toBeLevel2xx(): void
toBeLevel3xx(): void
toBeLevel4xx(): void
toBeLevel5xx(): void
toBeType(type: string): void
toHaveLength(length: number): void
toInclude(value: any): void
}

View file

@ -0,0 +1,9 @@
declare const pw: {
env: {
get(key: string): string
set(key: string, value: string): void
unset(key: string): void
getResolve(key: string): string
resolve(value: string): string
}
}

View file

@ -7,7 +7,7 @@
"dev:vite": "vite",
"dev:gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml dotenv_config_path=\"../../.env\" --watch",
"dev": "pnpm exec npm-run-all -p -l dev:*",
"build": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build",
"build": "node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build",
"preview": "vite preview",
"lint": "eslint src --ext .ts,.js,.vue --ignore-path .gitignore .",
"lint:ts": "vue-tsc --noEmit",

View file

@ -214,7 +214,7 @@ export default defineConfig({
registerType: "prompt",
workbox: {
cleanupOutdatedCaches: true,
maximumFileSizeToCacheInBytes: 10485760,
maximumFileSizeToCacheInBytes: 15728640, // 15 MB,
navigateFallbackDenylist: [
/robots.txt/,
/sitemap.xml/,

View file

@ -509,6 +509,9 @@ importers:
'@codemirror/view':
specifier: 6.25.1
version: 6.25.1
'@guolao/vue-monaco-editor':
specifier: 1.5.5
version: 1.5.5(monaco-editor@0.52.2)(vue@3.5.12(typescript@5.8.3))
'@hoppscotch/codemirror-lang-graphql':
specifier: workspace:^
version: link:../codemirror-lang-graphql
@ -644,6 +647,9 @@ importers:
minisearch:
specifier: 7.1.0
version: 7.1.0
monaco-editor:
specifier: 0.52.2
version: 0.52.2
nprogress:
specifier: 0.2.0
version: 0.2.0
@ -4575,6 +4581,16 @@ packages:
peerDependencies:
graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0
'@guolao/vue-monaco-editor@1.5.5':
resolution: {integrity: sha512-NFGImQ8dBYj6ehIxy1DngPRkctB9b6GbxvCm6aXZztNsgm/TtM4u+YM9ZwZHQPlXt7a4IODXoKCcTYEVycBSyA==}
peerDependencies:
'@vue/composition-api': ^1.7.1
monaco-editor: '>=0.43.0'
vue: 3.5.12
peerDependenciesMeta:
'@vue/composition-api':
optional: true
'@hapi/b64@5.0.0':
resolution: {integrity: sha512-ngu0tSEmrezoiIaNGG6rRvKOUkUuDdf4XTPnONHGYfSGRmDqPZX5oJL6HAdKTo1UQHECbdB4OzhWrfgVppjHUw==}
@ -5071,6 +5087,9 @@ packages:
'@microsoft/tsdoc@0.15.1':
resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==}
'@monaco-editor/loader@1.5.0':
resolution: {integrity: sha512-hKoGSM+7aAc7eRTRjpqAZucPmoNOC4UUbknb/VNoTkEIkCPhqV8LfbsgM1webRM7S/z21eHEx9Fkwx8Z/C/+Xw==}
'@nestjs-modules/mailer@2.0.2':
resolution: {integrity: sha512-+z4mADQasg0H1ZaGu4zZTuKv2pu+XdErqx99PLFPzCDNTN/q9U59WPgkxVaHnsvKHNopLj5Xap7G4ZpptduoYw==}
peerDependencies:
@ -10959,6 +10978,9 @@ packages:
engines: {node: '>= 12.0.0'}
hasBin: true
monaco-editor@0.52.2:
resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==}
mrmime@2.0.0:
resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==}
engines: {node: '>=10'}
@ -12655,6 +12677,9 @@ packages:
standard-as-callback@2.1.0:
resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
state-local@1.0.7:
resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==}
statuses@2.0.1:
resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==}
engines: {node: '>= 0.8'}
@ -13998,17 +14023,6 @@ packages:
'@vue/composition-api':
optional: true
vue-demi@0.14.7:
resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
engines: {node: '>=12'}
hasBin: true
peerDependencies:
'@vue/composition-api': ^1.0.0-rc.1
vue: 3.5.12
peerDependenciesMeta:
'@vue/composition-api':
optional: true
vue-eslint-parser@9.4.3:
resolution: {integrity: sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==}
engines: {node: ^14.17.0 || >=16.0.0}
@ -18500,6 +18514,13 @@ snapshots:
dependencies:
graphql: 16.9.0
'@guolao/vue-monaco-editor@1.5.5(monaco-editor@0.52.2)(vue@3.5.12(typescript@5.8.3))':
dependencies:
'@monaco-editor/loader': 1.5.0
monaco-editor: 0.52.2
vue: 3.5.12(typescript@5.8.3)
vue-demi: 0.14.10(vue@3.5.12(typescript@5.8.3))
'@hapi/b64@5.0.0':
dependencies:
'@hapi/hoek': 9.3.0
@ -19324,6 +19345,10 @@ snapshots:
'@microsoft/tsdoc@0.15.1': {}
'@monaco-editor/loader@1.5.0':
dependencies:
state-local: 1.0.7
'@nestjs-modules/mailer@2.0.2(@nestjs/common@11.1.1(class-transformer@0.5.1)(class-validator@0.14.2)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.1)(nodemailer@7.0.3)(relateurl@0.2.7)(svgo@3.3.2)(terser@5.39.2)(typescript@5.8.3)':
dependencies:
'@css-inline/css-inline': 0.14.1
@ -21420,7 +21445,7 @@ snapshots:
'@vueuse/shared@8.9.4(vue@3.5.12(typescript@5.8.3))':
dependencies:
vue-demi: 0.14.7(vue@3.5.12(typescript@5.8.3))
vue-demi: 0.14.10(vue@3.5.12(typescript@5.8.3))
optionalDependencies:
vue: 3.5.12(typescript@5.8.3)
@ -26942,6 +26967,8 @@ snapshots:
yargs-parser: 20.2.4
yargs-unparser: 2.0.0
monaco-editor@0.52.2: {}
mrmime@2.0.0: {}
ms@2.0.0: {}
@ -28844,6 +28871,8 @@ snapshots:
standard-as-callback@2.1.0:
optional: true
state-local@1.0.7: {}
statuses@2.0.1: {}
std-env@3.7.0: {}
@ -30630,10 +30659,6 @@ snapshots:
dependencies:
vue: 3.5.12(typescript@5.8.3)
vue-demi@0.14.7(vue@3.5.12(typescript@5.8.3)):
dependencies:
vue: 3.5.12(typescript@5.8.3)
vue-eslint-parser@9.4.3(eslint@8.47.0):
dependencies:
debug: 4.4.1