diff --git a/packages/hoppscotch-common/src/components/MonacoScriptEditor.vue b/packages/hoppscotch-common/src/components/MonacoScriptEditor.vue index c6b43d22..35949ddb 100644 --- a/packages/hoppscotch-common/src/components/MonacoScriptEditor.vue +++ b/packages/hoppscotch-common/src/components/MonacoScriptEditor.vue @@ -16,10 +16,11 @@ import { v4 as uuidv4 } from "uuid" import { computed, onMounted, onUnmounted, ref } from "vue" import { useColorMode } from "~/composables/theming" +import { MODULE_PREFIX } from "~/helpers/scripting" // Import type definitions as raw strings -import preRequestTypes from "~/types/pre-request.d.ts?raw" import postRequestTypes from "~/types/post-request.d.ts?raw" +import preRequestTypes from "~/types/pre-request.d.ts?raw" const props = withDefaults( defineProps<{ @@ -60,8 +61,6 @@ const extraLibRefs = new Map() // Track context-specific type definition for this editor instance const contextTypeDefRef = ref(null) -const MODULE_PREFIX = "export {};\n" as const - const ensureCompilerOptions = (() => { let applied = false diff --git a/packages/hoppscotch-common/src/components/collections/index.vue b/packages/hoppscotch-common/src/components/collections/index.vue index 4390a417..f8f0aa27 100644 --- a/packages/hoppscotch-common/src/components/collections/index.vue +++ b/packages/hoppscotch-common/src/components/collections/index.vue @@ -239,6 +239,7 @@ import { makeCollection, } from "@hoppscotch/data" import { useService } from "dioc/vue" +import { MODULE_PREFIX_REGEX_JSON_SERIALIZED } from "~/helpers/scripting" import * as TE from "fp-ts/TaskEither" import { pipe } from "fp-ts/function" @@ -2832,7 +2833,10 @@ const exportData = async (collection: HoppCollection | TeamCollection) => { const collectionJSON = JSON.stringify(collection, null, 2) // Strip `export {};\n` from `testScript` and `preRequestScript` fields - const cleanedCollectionJSON = collectionJSON.replace(/export \{\};\\n/g, "") + const cleanedCollectionJSON = collectionJSON.replace( + MODULE_PREFIX_REGEX_JSON_SERIALIZED, + "" + ) const name = (collection as HoppCollection).name @@ -2855,7 +2859,7 @@ const exportData = async (collection: HoppCollection | TeamCollection) => { // Strip `export {};\n` from `testScript` and `preRequestScript` fields const cleanedCollectionJSON = collectionJSONString.replace( - /export \{\};\\n/g, + MODULE_PREFIX_REGEX_JSON_SERIALIZED, "" ) diff --git a/packages/hoppscotch-common/src/composables/codemirror.ts b/packages/hoppscotch-common/src/composables/codemirror.ts index 283767ce..4911398a 100644 --- a/packages/hoppscotch-common/src/composables/codemirror.ts +++ b/packages/hoppscotch-common/src/composables/codemirror.ts @@ -44,6 +44,7 @@ import { isJSONContentType } from "@helpers/utils/contenttypes" import { useStreamSubscriber } from "@composables/stream" import { Completer } from "@helpers/editor/completion" import { LinterDefinition } from "@helpers/editor/linting/linter" +import { MODULE_PREFIX } from "@helpers/scripting" import { basicSetup, baseTheme, @@ -268,14 +269,12 @@ const getEditorLanguage = ( completer: Completer | undefined ): Extension => hoppLang(getLanguage(langMime) ?? undefined, linter, completer) -const MODULE_PREFIX = "export {};\n" as const - /** * Strips the `export {};\n` prefix from the value for display in the editor. - * The above is only used internally for Monaco editor's module scope, + * The prefix is used internally for Monaco editor's module scope, * and should not be visible in the CodeMirror editor. */ -const stripModulePrefix = (value?: string): string | undefined => { +const stripModulePrefixForDisplay = (value?: string): string | undefined => { return value?.startsWith(MODULE_PREFIX) ? value.slice(MODULE_PREFIX.length) : value @@ -488,7 +487,7 @@ export function useCodemirror( parent: el, state: EditorState.create({ doc: parseDoc( - stripModulePrefix(value.value), + stripModulePrefixForDisplay(value.value), options.extendedEditorConfig.mode ?? "" ), extensions, @@ -532,7 +531,7 @@ export function useCodemirror( } // Strip `export {};\n` before displaying in CodeMirror - const displayValue = stripModulePrefix(newVal) ?? "" + const displayValue = stripModulePrefixForDisplay(newVal) ?? "" if (cachedValue.value !== newVal) { view.value?.dispatch({ diff --git a/packages/hoppscotch-common/src/helpers/RequestRunner.ts b/packages/hoppscotch-common/src/helpers/RequestRunner.ts index 549f4a68..dc1a25ff 100644 --- a/packages/hoppscotch-common/src/helpers/RequestRunner.ts +++ b/packages/hoppscotch-common/src/helpers/RequestRunner.ts @@ -27,6 +27,7 @@ import { map } from "fp-ts/Either" import { runPreRequestScript, runTestScript } from "@hoppscotch/js-sandbox/web" import { useSetting } from "~/composables/settings" import { getService } from "~/modules/dioc" +import { stripModulePrefix } from "~/helpers/scripting" import { environmentsStore, getCurrentEnvironment, @@ -306,7 +307,10 @@ const delegatePreRequestScriptRunner = ( const { preRequestScript } = request if (!EXPERIMENTAL_SCRIPTING_SANDBOX.value) { - return runPreRequestScript(preRequestScript, { + // Strip `export {};\n` before executing in legacy sandbox to prevent syntax errors + const cleanScript = stripModulePrefix(preRequestScript) + + return runPreRequestScript(cleanScript, { envs, experimentalScriptingSandbox: false, }) @@ -352,7 +356,10 @@ const runPostRequestScript = ( const { testScript } = request if (!EXPERIMENTAL_SCRIPTING_SANDBOX.value) { - return runTestScript(testScript, { + // Strip `export {};\n` before executing in legacy sandbox to prevent syntax errors + const cleanScript = stripModulePrefix(testScript) + + return runTestScript(cleanScript, { envs, response, experimentalScriptingSandbox: false, diff --git a/packages/hoppscotch-common/src/helpers/scripting.ts b/packages/hoppscotch-common/src/helpers/scripting.ts new file mode 100644 index 00000000..b7cda9fa --- /dev/null +++ b/packages/hoppscotch-common/src/helpers/scripting.ts @@ -0,0 +1,22 @@ +/** + * Module prefix added by Monaco editor for TypeScript module mode. + * Enables IntelliSense and isolates variables across editor instances. + */ +export const MODULE_PREFIX = "export {};\n" as const + +/** + * Strips `export {};\n` prefix from scripts before legacy sandbox execution + * (non-module context) or when exporting collections. + */ +export const stripModulePrefix = (script: string): string => { + return script.startsWith(MODULE_PREFIX) + ? script.slice(MODULE_PREFIX.length) + : script +} + +/** + * Regex for stripping the JSON-serialized module prefix (`export {};\\n`) + * from scripts during collection exports. + * Note: This matches the literal backslash-n (`\\n`), not an actual newline character. + */ +export const MODULE_PREFIX_REGEX_JSON_SERIALIZED = /export \{\};\\n/g