diff --git a/package.json b/package.json index 8ea8f0cb02a..ce10b9f5e2c 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "glob@<11.1.0": "11.1.0", "hono@4.10.6": "4.11.4", "jws@<3.2.3": "3.2.3", - "nodemailer@<7.0.12": "7.0.12", + "nodemailer@<7.0.12": "8.0.0", "qs@6.14.0": "6.14.1", "subscriptions-transport-ws>ws": "7.5.10", "vue": "3.5.27", diff --git a/packages/hoppscotch-backend/package.json b/packages/hoppscotch-backend/package.json index fed569b57b4..30447f6079b 100644 --- a/packages/hoppscotch-backend/package.json +++ b/packages/hoppscotch-backend/package.json @@ -1,6 +1,6 @@ { "name": "hoppscotch-backend", - "version": "2026.1.0", + "version": "2026.1.1", "description": "", "author": "", "private": true, @@ -65,7 +65,7 @@ "handlebars": "4.7.8", "io-ts": "2.2.22", "morgan": "1.10.1", - "nodemailer": "7.0.12", + "nodemailer": "8.0.0", "passport": "0.7.0", "passport-github2": "0.1.12", "passport-google-oauth20": "2.0.0", diff --git a/packages/hoppscotch-backend/src/auth/helper.ts b/packages/hoppscotch-backend/src/auth/helper.ts index c417fb26730..0f58b8be603 100644 --- a/packages/hoppscotch-backend/src/auth/helper.ts +++ b/packages/hoppscotch-backend/src/auth/helper.ts @@ -58,13 +58,13 @@ export const authCookieHandler = ( httpOnly: true, secure: configService.get('INFRA.ALLOW_SECURE_COOKIES') === 'true', sameSite: 'lax', - maxAge: Date.now() + accessTokenValidityInMs, + maxAge: accessTokenValidityInMs, }); res.cookie(AuthTokenType.REFRESH_TOKEN, authTokens.refresh_token, { httpOnly: true, secure: configService.get('INFRA.ALLOW_SECURE_COOKIES') === 'true', sameSite: 'lax', - maxAge: Date.now() + refreshTokenValidityInMs, + maxAge: refreshTokenValidityInMs, }); if (!redirect) { diff --git a/packages/hoppscotch-backend/src/infra-config/helper.ts b/packages/hoppscotch-backend/src/infra-config/helper.ts index 76b2b1da2ca..084a78fd79c 100644 --- a/packages/hoppscotch-backend/src/infra-config/helper.ts +++ b/packages/hoppscotch-backend/src/infra-config/helper.ts @@ -16,6 +16,29 @@ type DefaultInfraConfig = { isEncrypted: boolean; }; +// Singleton PrismaService instance for infra config operations +let sharedPrismaInstance: PrismaService | null = null; + +/** + * Get or create a shared PrismaService instance for infra config operations + */ +function getSharedPrismaInstance(): PrismaService { + if (!sharedPrismaInstance) { + sharedPrismaInstance = new PrismaService(); + } + return sharedPrismaInstance; +} + +/** + * Disconnect the shared Prisma instance during application shutdown + */ +export async function disconnectSharedPrismaInstance(): Promise { + if (sharedPrismaInstance) { + await sharedPrismaInstance.onModuleDestroy(); + sharedPrismaInstance = null; + } +} + /** * Returns a mapping of authentication providers to their required configuration keys based on the current environment configuration. */ @@ -67,8 +90,8 @@ export function getAuthProviderRequiredKeys( * (ConfigModule will set the environment variables in the process) */ export async function loadInfraConfiguration() { + const prisma = getSharedPrismaInstance(); try { - const prisma = new PrismaService(); const infraConfigs = await prisma.infraConfig.findMany(); const environmentObject: Record = {}; @@ -97,7 +120,7 @@ export async function loadInfraConfiguration() { * @returns Array of default infra configs */ export async function getDefaultInfraConfigs(): Promise { - const prisma = new PrismaService(); + const prisma = getSharedPrismaInstance(); // Prepare rows for 'infra_config' table with default values (from .env) for each 'name' const onboardingCompleteStatus = await isOnboardingCompleted(); @@ -324,7 +347,7 @@ export async function getDefaultInfraConfigs(): Promise { export async function getMissingInfraConfigEntries( infraConfigDefaultObjs: DefaultInfraConfig[], ) { - const prisma = new PrismaService(); + const prisma = getSharedPrismaInstance(); const dbInfraConfigs = await prisma.infraConfig.findMany(); const missingEntries = infraConfigDefaultObjs.filter( @@ -342,7 +365,7 @@ export async function getMissingInfraConfigEntries( export async function getEncryptionRequiredInfraConfigEntries( infraConfigDefaultObjs: DefaultInfraConfig[], ) { - const prisma = new PrismaService(); + const prisma = getSharedPrismaInstance(); const dbInfraConfigs = await prisma.infraConfig.findMany(); const requiredEncryption = dbInfraConfigs.filter((dbConfig) => { @@ -400,7 +423,7 @@ export function stopApp() { * @returns Array of configured SSO providers */ export async function getConfiguredSSOProvidersFromInfraConfig() { - const prisma = new PrismaService(); + const prisma = getSharedPrismaInstance(); const env = await loadInfraConfiguration(); const providerConfigKeys = getAuthProviderRequiredKeys(env); @@ -437,7 +460,7 @@ export async function getConfiguredSSOProvidersFromInfraConfig() { * @returns boolean */ export async function isOnboardingCompleted(): Promise { - const prisma = new PrismaService(); + const prisma = getSharedPrismaInstance(); const allowedProviders = await prisma.infraConfig.findUnique({ where: { name: InfraConfigEnum.VITE_ALLOWED_AUTH_PROVIDERS }, select: { value: true }, diff --git a/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts b/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts index fa763d77cbd..e0042682c6f 100644 --- a/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts +++ b/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts @@ -1,4 +1,4 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; +import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common'; import { InfraConfig } from './infra-config.model'; import { PrismaService } from 'src/prisma/prisma.service'; import { InfraConfig as DBInfraConfig } from 'src/generated/prisma/client'; @@ -26,6 +26,7 @@ import { ConfigService } from '@nestjs/config'; import { ServiceStatus, buildDerivedEnv, + disconnectSharedPrismaInstance, getDefaultInfraConfigs, getEncryptionRequiredInfraConfigEntries, getMissingInfraConfigEntries, @@ -45,7 +46,7 @@ import * as crypto from 'crypto'; import { PrismaError } from 'src/prisma/prisma-error-codes'; @Injectable() -export class InfraConfigService implements OnModuleInit { +export class InfraConfigService implements OnModuleInit, OnModuleDestroy { constructor( private readonly prisma: PrismaService, private readonly configService: ConfigService, @@ -72,6 +73,9 @@ export class InfraConfigService implements OnModuleInit { async onModuleInit() { await this.initializeInfraConfigTable(); } + async onModuleDestroy() { + await disconnectSharedPrismaInstance(); + } /** * Initialize the 'infra_config' table with values from .env diff --git a/packages/hoppscotch-cli/package.json b/packages/hoppscotch-cli/package.json index b2345bb0e2e..849e73fa068 100644 --- a/packages/hoppscotch-cli/package.json +++ b/packages/hoppscotch-cli/package.json @@ -1,6 +1,6 @@ { "name": "@hoppscotch/cli", - "version": "0.30.1", + "version": "0.30.2", "description": "A CLI to run Hoppscotch test scripts in CI environments.", "homepage": "https://hoppscotch.io", "type": "module", diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/scripting-revamp-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/scripting-revamp-coll.json index a7d1b45ccd6..f236cd7a262 100644 --- a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/scripting-revamp-coll.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/scripting-revamp-coll.json @@ -19,7 +19,7 @@ } ], "preRequestScript": "", - "testScript": "hopp.test(\"`hopp.response.body.asJSON()` parses response body as JSON\", () => {\n const parsedData = JSON.parse(hopp.response.body.asJSON().data)\n\n hopp.expect(parsedData.name).toBe('John Doe')\n hopp.expect(parsedData.age).toBeType(\"number\")\n})\n\npm.test(\"`pm.response.json()` parses response body as JSON\", () => {\n const parsedData = JSON.parse(pm.response.json().data)\n\n pm.expect(parsedData.name).toBe('John Doe')\n pm.expect(parsedData.age).toBeType(\"number\")\n})\n\nhopp.test(\"`hopp.response.body.asText()` parses response body as plain text\", () => {\n const textResponse = hopp.response.body.asText()\n hopp.expect(textResponse).toInclude('\\\"test-header\\\":\\\"test\\\"')\n})\n\npm.test(\"`pm.response.text()` parses response body as plain text\", () => {\n const textResponse = pm.response.text()\n pm.expect(textResponse).toInclude('\\\"test-header\\\":\\\"test\\\"')\n})\n\nhopp.test(\"hopp.response.bytes()` parses response body as raw bytes\", () => {\n const rawResponse = hopp.response.body.bytes()\n\n hopp.expect(rawResponse[0]).toBe(123)\n})\n\npm.test(\"pm.response.stream` parses response body as raw bytes\", () => {\n const rawResponse = pm.response.stream\n\n pm.expect(rawResponse[0]).toBe(123)\n})\n", + "testScript": "export {};\nhopp.test(\"`hopp.response.body.asJSON()` parses response body as JSON\", () => {\n const parsedData = JSON.parse(hopp.response.body.asJSON().data)\n\n hopp.expect(parsedData.name).toBe('John Doe')\n hopp.expect(parsedData.age).toBeType(\"number\")\n})\n\npm.test(\"`pm.response.json()` parses response body as JSON\", () => {\n const parsedData = JSON.parse(pm.response.json().data)\n\n pm.expect(parsedData.name).toBe('John Doe')\n pm.expect(parsedData.age).toBeType(\"number\")\n})\n\nhopp.test(\"`hopp.response.body.asText()` parses response body as plain text\", () => {\n const textResponse = hopp.response.body.asText()\n hopp.expect(textResponse).toInclude('\\\"test-header\\\":\\\"test\\\"')\n})\n\npm.test(\"`pm.response.text()` parses response body as plain text\", () => {\n const textResponse = pm.response.text()\n pm.expect(textResponse).toInclude('\\\"test-header\\\":\\\"test\\\"')\n})\n\nhopp.test(\"hopp.response.bytes()` parses response body as raw bytes\", () => {\n const rawResponse = hopp.response.body.bytes()\n\n hopp.expect(rawResponse[0]).toBe(123)\n})\n\npm.test(\"pm.response.stream` parses response body as raw bytes\", () => {\n const rawResponse = pm.response.stream\n\n pm.expect(rawResponse[0]).toBe(123)\n})\n", "auth": { "authType": "inherit", "authActive": true @@ -69,7 +69,7 @@ "endpoint": "https://echo.hoppscotch.io", "params": [], "headers": [], - "preRequestScript": "hopp.env.set('test_key', 'test_value')\nhopp.env.set('recursive_key', '<>')\nhopp.env.global.set('global_key', 'global_value')\nhopp.env.active.set('active_key', 'active_value')\n\n// `pm` namespace equivalents\npm.variables.set('pm_test_key', 'pm_test_value')\npm.environment.set('pm_active_key', 'pm_active_value')\npm.globals.set('pm_global_key', 'pm_global_value')\n", + "preRequestScript": "export {};\nhopp.env.set('test_key', 'test_value')\nhopp.env.set('recursive_key', '<>')\nhopp.env.global.set('global_key', 'global_value')\nhopp.env.active.set('active_key', 'active_value')\n\n// `pm` namespace equivalents\npm.variables.set('pm_test_key', 'pm_test_value')\npm.environment.set('pm_active_key', 'pm_active_value')\npm.globals.set('pm_global_key', 'pm_global_value')\n", "testScript": "\nhopp.test('`hopp.env.get()` retrieves environment variables', () => {\n const value = hopp.env.get('test_key')\n hopp.expect(value).toBe('test_value')\n})\n\npm.test('`pm.variables.get()` retrieves environment variables', () => {\n const value = pm.variables.get('test_key')\n pm.expect(value).toBe('test_value')\n})\n\nhopp.test('`hopp.env.getRaw()` retrieves raw environment variables without resolution', () => {\n const rawValue = hopp.env.getRaw('recursive_key')\n hopp.expect(rawValue).toBe('<>')\n})\n\nhopp.test('`hopp.env.get()` resolves recursive environment variables', () => {\n const resolvedValue = hopp.env.get('recursive_key')\n hopp.expect(resolvedValue).toBe('test_value')\n})\n\npm.test('`pm.variables.replaceIn()` resolves template variables', () => {\n const resolved = pm.variables.replaceIn('Value is {{test_key}}')\n pm.expect(resolved).toBe('Value is test_value')\n})\n\nhopp.test('`hopp.env.global.get()` retrieves global environment variables', () => {\n const globalValue = hopp.env.global.get('global_key')\n\n // `hopp.env.global` would be empty for the CLI\n if (globalValue) {\n hopp.expect(globalValue).toBe('global_value')\n }\n})\n\npm.test('`pm.globals.get()` retrieves global environment variables', () => {\n const globalValue = pm.globals.get('global_key')\n\n // `pm.globals` would be empty for the CLI\n if (globalValue) {\n pm.expect(globalValue).toBe('global_value')\n }\n})\n\nhopp.test('`hopp.env.active.get()` retrieves active environment variables', () => {\n const activeValue = hopp.env.active.get('active_key')\n hopp.expect(activeValue).toBe('active_value')\n})\n\npm.test('`pm.environment.get()` retrieves active environment variables', () => {\n const activeValue = pm.environment.get('active_key')\n pm.expect(activeValue).toBe('active_value')\n})\n\nhopp.test('Environment methods return null for non-existent keys', () => {\n hopp.expect(hopp.env.get('non_existent')).toBe(null)\n hopp.expect(hopp.env.getRaw('non_existent')).toBe(null)\n hopp.expect(hopp.env.global.get('non_existent')).toBe(null)\n hopp.expect(hopp.env.active.get('non_existent')).toBe(null)\n})\n\npm.test('`pm` environment methods handle non-existent keys correctly', () => {\n pm.expect(pm.variables.get('non_existent')).toBe(undefined)\n pm.expect(pm.environment.get('non_existent')).toBe(undefined)\n pm.expect(pm.globals.get('non_existent')).toBe(undefined)\n pm.expect(pm.variables.has('non_existent')).toBe(false)\n pm.expect(pm.environment.has('non_existent')).toBe(false)\n pm.expect(pm.globals.has('non_existent')).toBe(false)\n})\n\npm.test('`pm` variables set in pre-request script are accessible', () => {\n pm.expect(pm.variables.get('pm_test_key')).toBe('pm_test_value')\n pm.expect(pm.environment.get('pm_active_key')).toBe('pm_active_value')\n\n const pmGlobalValue = hopp.env.global.get('pm_global_key')\n\n // `hopp.env.global` would be empty for the CLI\n if (pmGlobalValue) {\n hopp.expect(pmGlobalValue).toBe('pm_global_value')\n }\n})\n", "auth": { "authType": "inherit", @@ -760,7 +760,7 @@ "endpoint": "https://echo.hoppscotch.io", "params": [], "headers": [], - "preRequestScript": "export {};", + "preRequestScript": "export {};\n", "testScript": "\n// Map & Set Assertions\npm.test('Map assertions - size property', () => {\n const map = new Map([['key1', 'value1'], ['key2', 'value2']])\n pm.expect(map).to.have.property('size', 2)\n pm.expect(map.size).to.equal(2)\n})\n\npm.test('Set assertions - size property', () => {\n const set = new Set([1, 2, 3, 4])\n pm.expect(set).to.have.property('size', 4)\n pm.expect(set.size).to.equal(4)\n})\n\npm.test('Map instanceOf assertion', () => {\n const map = new Map()\n pm.expect(map).to.be.instanceOf(Map)\n pm.expect(map).to.be.an.instanceOf(Map)\n})\n\npm.test('Set instanceOf assertion', () => {\n const set = new Set()\n pm.expect(set).to.be.instanceOf(Set)\n pm.expect(set).to.be.an.instanceOf(Set)\n})\n\n// Advanced Chai - closeTo\npm.test('closeTo - validates numbers within delta', () => {\n pm.expect(3.14159).to.be.closeTo(3.14, 0.01)\n pm.expect(10.5).to.be.closeTo(11, 1)\n})\n\npm.test('closeTo - negation works', () => {\n pm.expect(100).to.not.be.closeTo(50, 10)\n pm.expect(3.14).to.not.be.closeTo(10, 0.1)\n})\n\npm.test('approximately - alias for closeTo', () => {\n pm.expect(2.5).to.approximately(2.4, 0.2)\n pm.expect(99.99).to.approximately(100, 0.1)\n})\n\n// Advanced Chai - finite\npm.test('finite - validates finite numbers', () => {\n pm.expect(123).to.be.finite\n pm.expect(0).to.be.finite\n pm.expect(-456).to.be.finite\n})\n\npm.test('finite - negation for Infinity', () => {\n pm.expect(Infinity).to.not.be.finite\n pm.expect(-Infinity).to.not.be.finite\n pm.expect(NaN).to.not.be.finite\n})\n\n// Advanced Chai - satisfy\npm.test('satisfy - custom predicate function', () => {\n pm.expect(10).to.satisfy((num) => num > 5)\n pm.expect('hello').to.satisfy((str) => str.length === 5)\n})\n\npm.test('satisfy - complex validation', () => {\n const obj = { name: 'test', value: 100 }\n pm.expect(obj).to.satisfy((o) => o.value > 50 && o.name.length > 0)\n})\n\npm.test('satisfy - negation works', () => {\n pm.expect(5).to.not.satisfy((num) => num > 10)\n pm.expect('abc').to.not.satisfy((str) => str.length > 5)\n})\n\n// Advanced Chai - respondTo\npm.test('respondTo - validates method existence', () => {\n class TestClass {\n testMethod() { return 'test' }\n anotherMethod() { return 'another' }\n }\n pm.expect(TestClass).to.respondTo('testMethod')\n pm.expect(TestClass).to.respondTo('anotherMethod')\n})\n\npm.test('respondTo - with itself for static methods', () => {\n class MyClass {\n static staticMethod() { return 'static' }\n instanceMethod() { return 'instance' }\n }\n pm.expect(MyClass).itself.to.respondTo('staticMethod')\n pm.expect(MyClass).to.not.itself.respondTo('instanceMethod')\n pm.expect(MyClass).to.respondTo('instanceMethod')\n})\n\n// Property Ownership - own.property\npm.test('own.property - distinguishes own vs inherited', () => {\n const parent = { inherited: true }\n const obj = Object.create(parent)\n obj.own = true\n pm.expect(obj).to.have.own.property('own')\n pm.expect(obj).to.not.have.own.property('inherited')\n pm.expect(obj).to.have.property('inherited')\n})\n\npm.test('deep.own.property - deep check with ownership', () => {\n const proto = { shared: 'inherited' }\n const obj = Object.create(proto)\n obj.data = { nested: 'value' }\n pm.expect(obj).to.have.deep.own.property('data', { nested: 'value' })\n pm.expect(obj).to.not.have.deep.own.property('shared')\n})\n\npm.test('ownProperty - alias for own.property', () => {\n const obj = { prop: 'value' }\n pm.expect(obj).to.have.ownProperty('prop')\n pm.expect(obj).to.have.ownProperty('prop', 'value')\n})\n\n// Hopp namespace parity tests\npm.test('hopp.expect Map/Set support', () => {\n const map = new Map([['x', 1]])\n const set = new Set([1, 2])\n hopp.expect(map.size).toBe(1)\n hopp.expect(set.size).toBe(2)\n})\n\npm.test('hopp.expect closeTo support', () => {\n hopp.expect(3.14).to.be.closeTo(3.1, 0.1)\n hopp.expect(10).to.be.closeTo(10.5, 1)\n})\n\npm.test('hopp.expect finite support', () => {\n hopp.expect(42).to.be.finite\n hopp.expect(Infinity).to.not.be.finite\n})\n\npm.test('hopp.expect satisfy support', () => {\n hopp.expect(100).to.satisfy((n) => n > 50)\n hopp.expect('test').to.satisfy((s) => s.length === 4)\n})\n\npm.test('hopp.expect respondTo support', () => {\n class TestClass { method() {} }\n hopp.expect(TestClass).to.respondTo('method')\n})\n\npm.test('hopp.expect own.property support', () => {\n const obj = Object.create({ inherited: 1 })\n obj.own = 2\n hopp.expect(obj).to.have.own.property('own')\n hopp.expect(obj).to.not.have.own.property('inherited')\n})\n\npm.test('hopp.expect ordered.members support', () => {\n const arr = ['a', 'b', 'c']\n hopp.expect(arr).to.have.ordered.members(['a', 'b', 'c'])\n})\n", "auth": { "authType": "inherit", @@ -1683,4 +1683,4 @@ "headers": [], "variables": [], "description": null -} \ No newline at end of file +} diff --git a/packages/hoppscotch-cli/src/utils/mutators.ts b/packages/hoppscotch-cli/src/utils/mutators.ts index 153e66b24a9..5348ba17d41 100644 --- a/packages/hoppscotch-cli/src/utils/mutators.ts +++ b/packages/hoppscotch-cli/src/utils/mutators.ts @@ -158,3 +158,19 @@ export async function parseCollectionData( return getValidRequests(collectionSchemaParsedResult.data, pathOrId); } + +/** + * Module prefix added by Monaco editor for TypeScript module mode. + */ +const MODULE_PREFIX = "export {};\n" as const; + +/** + * Strips `export {};\n` prefix from scripts before sandbox execution. + * The prefix is added by the web app's Monaco editor for IntelliSense + * and must be removed before execution. + */ +export const stripModulePrefix = (script: string): string => { + return script.startsWith(MODULE_PREFIX) + ? script.slice(MODULE_PREFIX.length) + : script; +}; diff --git a/packages/hoppscotch-cli/src/utils/pre-request.ts b/packages/hoppscotch-cli/src/utils/pre-request.ts index 1b9d01518fa..dea46d10383 100644 --- a/packages/hoppscotch-cli/src/utils/pre-request.ts +++ b/packages/hoppscotch-cli/src/utils/pre-request.ts @@ -1,17 +1,17 @@ import { Environment, EnvironmentVariable, + HoppCollectionVariable, HoppRESTRequest, + calculateHawkHeader, + generateJWTToken, parseBodyEnvVariablesE, parseRawKeyValueEntriesE, parseTemplateString, parseTemplateStringE, - generateJWTToken, - HoppCollectionVariable, - calculateHawkHeader, } from "@hoppscotch/data"; import { runPreRequestScript } from "@hoppscotch/js-sandbox/node"; -import { createHoppFetchHook } from "./hopp-fetch"; +import { AwsV4Signer } from "aws4fetch"; import * as A from "fp-ts/Array"; import * as E from "fp-ts/Either"; import * as O from "fp-ts/Option"; @@ -20,22 +20,22 @@ import * as TE from "fp-ts/TaskEither"; import { flow, pipe } from "fp-ts/function"; import * as S from "fp-ts/string"; import qs from "qs"; -import { AwsV4Signer } from "aws4fetch"; +import { createHoppFetchHook } from "./hopp-fetch"; import { EffectiveHoppRESTRequest } from "../interfaces/request"; import { HoppCLIError, error } from "../types/errors"; import { HoppEnvs } from "../types/request"; import { PreRequestMetrics } from "../types/response"; -import { isHoppCLIError } from "./checks"; -import { arrayFlatMap, arraySort, tupleToRecord } from "./functions/array"; -import { getEffectiveFinalMetaData, getResolvedVariables } from "./getters"; -import { toFormData } from "./mutators"; import { DigestAuthParams, fetchInitialDigestAuthInfo, generateDigestAuthHeader, } from "./auth/digest"; +import { isHoppCLIError } from "./checks"; +import { arrayFlatMap, arraySort, tupleToRecord } from "./functions/array"; +import { getEffectiveFinalMetaData, getResolvedVariables } from "./getters"; import { stripComments } from "./jsonc"; +import { stripModulePrefix, toFormData } from "./mutators"; /** * Runs pre-request-script runner over given request which extracts set ENVs and @@ -60,7 +60,7 @@ export const preRequestScriptRunner = ( return pipe( TE.of(request), TE.chain(({ preRequestScript }) => - runPreRequestScript(preRequestScript, { + runPreRequestScript(stripModulePrefix(preRequestScript), { envs, experimentalScriptingSandbox, request, @@ -586,7 +586,10 @@ function getFinalBodyFromRequest( // and vendor-specific JSON media types (for example those with a +json suffix // or subtypes whose names end with "json" or "-json"). if (request.body.contentType) { - const mimeType = request.body.contentType.split(";")[0].trim().toLowerCase(); + const mimeType = request.body.contentType + .split(";")[0] + .trim() + .toLowerCase(); if ( mimeType === "application/json" || @@ -594,43 +597,46 @@ function getFinalBodyFromRequest( mimeType.endsWith("/json") || mimeType.endsWith("-json") ) { - const envResult = parseBodyEnvVariablesE(request.body.body, resolvedVariables); - - if (E.isLeft(envResult)) { - return E.left( - error({ - code: "PARSING_ERROR", - data: `${request.body.body} (${envResult.left})`, - }) + const envResult = parseBodyEnvVariablesE( + request.body.body, + resolvedVariables ); - } - const bodyString = envResult.right; + if (E.isLeft(envResult)) { + return E.left( + error({ + code: "PARSING_ERROR", + data: `${request.body.body} (${envResult.left})`, + }) + ); + } - // If the body string is empty or null, return null - if (!bodyString || S.isEmpty(bodyString.trim())) { - return E.right(null); - } + const bodyString = envResult.right; - // Strip comments and trailing commas from JSONC - // This ensures collections with comments work the same in CLI as in desktop app - const cleanedBody = stripComments(bodyString); - - // Try to parse the JSON body - try { - const parsedBody = JSON.parse(cleanedBody); - return E.right(JSON.stringify(parsedBody)); - } catch (err) { - // If parsing fails after stripping comments, return error to provide - // immediate feedback instead of sending invalid JSON to the API. - // Use original template string to avoid leaking secrets from env vars. - return E.left( - error({ - code: "PARSING_ERROR", - data: `${request.body.body} (Invalid JSON in request body: ${err instanceof Error ? err.message : String(err)})`, - }) - ); - } + // If the body string is empty or null, return null + if (!bodyString || S.isEmpty(bodyString.trim())) { + return E.right(null); + } + + // Strip comments and trailing commas from JSONC + // This ensures collections with comments work the same in CLI as in desktop app + const cleanedBody = stripComments(bodyString); + + // Try to parse the JSON body + try { + const parsedBody = JSON.parse(cleanedBody); + return E.right(JSON.stringify(parsedBody)); + } catch (err) { + // If parsing fails after stripping comments, return error to provide + // immediate feedback instead of sending invalid JSON to the API. + // Use original template string to avoid leaking secrets from env vars. + return E.left( + error({ + code: "PARSING_ERROR", + data: `${request.body.body} (Invalid JSON in request body: ${err instanceof Error ? err.message : String(err)})`, + }) + ); + } } } return pipe( diff --git a/packages/hoppscotch-cli/src/utils/test.ts b/packages/hoppscotch-cli/src/utils/test.ts index 0ada256d09a..f358bcf6112 100644 --- a/packages/hoppscotch-cli/src/utils/test.ts +++ b/packages/hoppscotch-cli/src/utils/test.ts @@ -18,6 +18,7 @@ import { HoppEnvs } from "../types/request"; import { ExpectResult, TestMetrics, TestRunnerRes } from "../types/response"; import { getDurationInSeconds } from "./getters"; import { createHoppFetchHook } from "./hopp-fetch"; +import { stripModulePrefix } from "./mutators"; /** * Executes test script and runs testDescriptorParser to generate test-report using @@ -52,7 +53,7 @@ export const testRunner = ( const experimentalScriptingSandbox = !legacySandbox; const hoppFetchHook = createHoppFetchHook(); - return runTestScript(request.testScript, { + return runTestScript(stripModulePrefix(request.testScript), { envs, request, response: effectiveResponse, diff --git a/packages/hoppscotch-common/assets/scss/styles.scss b/packages/hoppscotch-common/assets/scss/styles.scss index 25052adb213..37594450c7e 100644 --- a/packages/hoppscotch-common/assets/scss/styles.scss +++ b/packages/hoppscotch-common/assets/scss/styles.scss @@ -22,13 +22,13 @@ @apply selection:bg-accentDark; @apply selection:text-accentContrast; - @apply overscroll-none; } :root { - @apply antialiased; accent-color: var(--accent-color); font-variant-ligatures: common-ligatures; + @apply antialiased; + @apply overscroll-none; } ::-webkit-scrollbar-track { diff --git a/packages/hoppscotch-common/package.json b/packages/hoppscotch-common/package.json index 6fb230c9ebe..463dc3d7976 100644 --- a/packages/hoppscotch-common/package.json +++ b/packages/hoppscotch-common/package.json @@ -1,7 +1,7 @@ { "name": "@hoppscotch/common", "private": true, - "version": "2026.1.0", + "version": "2026.1.1", "scripts": { "dev": "pnpm exec npm-run-all -p -l dev:*", "test": "vitest --run", diff --git a/packages/hoppscotch-common/src/components/mockServer/MockServerDashboard.vue b/packages/hoppscotch-common/src/components/mockServer/MockServerDashboard.vue index 39ecdef69d8..5d131612ec6 100644 --- a/packages/hoppscotch-common/src/components/mockServer/MockServerDashboard.vue +++ b/packages/hoppscotch-common/src/components/mockServer/MockServerDashboard.vue @@ -32,7 +32,7 @@
@@ -209,12 +209,14 @@ import { computed, ref } from "vue" import { TippyComponent } from "vue-tippy" import { useMockServerStatus } from "~/composables/mockServer" import { useToast } from "~/composables/toast" +import { useReadonlyStream } from "~/composables/stream" import { copyToClipboard } from "~/helpers/utils/clipboard" import type { MockServer } from "~/newstore/mockServers" import { platform } from "~/platform" import { deleteMockServer as deleteMockServerInStore, + loading$, showCreateMockServerModal$, updateMockServer as updateMockServerInStore, } from "~/newstore/mockServers" @@ -242,6 +244,7 @@ const t = useI18n() const toast = useToast() const colorMode = useColorMode() const { mockServers } = useMockServerStatus() +const isFetchingServers = useReadonlyStream(loading$, false) const loading = ref(false) const showEditModal = ref(false) const showLogsModal = ref(false) diff --git a/packages/hoppscotch-common/src/composables/mockServerWorkspace.ts b/packages/hoppscotch-common/src/composables/mockServerWorkspace.ts index 83a67149bd6..f975ca99707 100644 --- a/packages/hoppscotch-common/src/composables/mockServerWorkspace.ts +++ b/packages/hoppscotch-common/src/composables/mockServerWorkspace.ts @@ -1,9 +1,10 @@ -import { onMounted, watch } from "vue" import { useService } from "dioc/vue" -import { WorkspaceService } from "~/services/workspace.service" -import { setMockServers, loadMockServers } from "~/newstore/mockServers" +import { watch } from "vue" +import { loadMockServers, setMockServers } from "~/newstore/mockServers" import { platform } from "~/platform" +import { WorkspaceService } from "~/services/workspace.service" import { useMockServerVisibility } from "./mockServerVisibility" +import { useReadonlyStream } from "./stream" /** * Composable to handle mock server state when workspace changes @@ -13,19 +14,27 @@ import { useMockServerVisibility } from "./mockServerVisibility" export function useMockServerWorkspaceSync() { const workspaceService = useService(WorkspaceService) const { isMockServerVisible } = useMockServerVisibility() - const isAuthenticated = !!platform.auth.getCurrentUser() - // Initial load of mock servers for the current workspace - onMounted(() => { - if (!isAuthenticated || !isMockServerVisible.value) return + const currentUser = useReadonlyStream( + platform.auth.getCurrentUserStream(), + platform.auth.getCurrentUser() + ) + + const loadServers = () => { + if (!currentUser.value || !isMockServerVisible.value) return loadMockServers().catch(() => setMockServers([])) + } + + // Load mock servers when authentication or visibility changes + watch([currentUser, isMockServerVisible], loadServers, { + immediate: true, }) // Watch for workspace changes and clear mock servers immediately watch( () => workspaceService.currentWorkspace.value, (newWorkspace, oldWorkspace) => { - if (!isAuthenticated || !isMockServerVisible.value) return + if (!currentUser.value || !isMockServerVisible.value) return // Clear mock servers when workspace changes to prevent stale data if ( @@ -34,16 +43,10 @@ export function useMockServerWorkspaceSync() { oldWorkspace?.type === "team" && newWorkspace.teamID !== oldWorkspace.teamID) ) { - // Clear mock servers immediately to prevent showing stale data setMockServers([]) - - // If user is authenticated, reload mock servers for the new workspace - if (platform.auth.getCurrentUser()) { - // fire-and-forget; loadMockServers handles errors internally - loadMockServers().catch(() => setMockServers([])) - } + loadServers() } }, - { deep: true, immediate: false } + { deep: true } ) } diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/openapi/example-generators/v2.ts b/packages/hoppscotch-common/src/helpers/import-export/import/openapi/example-generators/v2.ts index cd00ef57319..39b95106c12 100644 --- a/packages/hoppscotch-common/src/helpers/import-export/import/openapi/example-generators/v2.ts +++ b/packages/hoppscotch-common/src/helpers/import-export/import/openapi/example-generators/v2.ts @@ -104,10 +104,12 @@ const generateExampleArrayFromOpenAPIV2ItemsObject = ( ) } -const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( +export const generateRequestBodyExampleFromOpenAPIV2BodySchema = ( schema: OpenAPIV2.SchemaObject ): RequestBodyExample => { - if (schema.example) return schema.example as RequestBodyExample + if (!schema) return "" + + if (schema.example !== undefined) return schema.example as RequestBodyExample const primitiveTypeExample = pipe( schema, diff --git a/packages/hoppscotch-common/src/helpers/import-export/import/openapi/index.ts b/packages/hoppscotch-common/src/helpers/import-export/import/openapi/index.ts index d8d578246c5..47dd06ff948 100644 --- a/packages/hoppscotch-common/src/helpers/import-export/import/openapi/index.ts +++ b/packages/hoppscotch-common/src/helpers/import-export/import/openapi/index.ts @@ -33,7 +33,10 @@ import { IMPORTER_INVALID_FILE_FORMAT } from ".." import { cloneDeep } from "lodash-es" import { getStatusCodeReasonPhrase } from "~/helpers/utils/statusCodes" import { isNumeric } from "~/helpers/utils/number" -import { generateRequestBodyExampleFromOpenAPIV2Body } from "./example-generators/v2" +import { + generateRequestBodyExampleFromOpenAPIV2Body, + generateRequestBodyExampleFromOpenAPIV2BodySchema as generateV2ExampleFromSchemaObject, +} from "./example-generators/v2" import { generateRequestBodyExampleFromMediaObject as generateV3Example } from "./example-generators/v3" import { generateRequestBodyExampleFromMediaObject as generateV31Example } from "./example-generators/v31" @@ -165,6 +168,7 @@ const parseOpenAPIVariables = ( ) const parseOpenAPIV3Responses = ( + doc: OpenAPI.Document, op: OpenAPIV3.OperationObject | OpenAPIV31.OperationObject, originalRequest: HoppRESTResponseOriginalRequest ): HoppRESTRequestResponses => { @@ -178,9 +182,11 @@ const parseOpenAPIV3Responses = ( | OpenAPIV3.ResponseObject | OpenAPIV31.ResponseObject - // add support for schema key as well const contentType = Object.keys(response.content ?? {})[0] - const body = response.content?.[contentType] + const mediaObject = response.content?.[contentType] as + | OpenAPIV3.MediaTypeObject + | OpenAPIV31.MediaTypeObject + | undefined const name = response.description ?? key @@ -198,16 +204,80 @@ const parseOpenAPIV3Responses = ( ] let stringifiedBody = "" + // Track whether an explicit example was found (even if empty string) + // to avoid overwriting valid empty examples with schema-generated content + let hasExplicitExample = false + + if (mediaObject) { + // Priority: example > examples > generate from schema + if (mediaObject.example !== undefined) { + // Direct example on media object + hasExplicitExample = true + try { + stringifiedBody = + typeof mediaObject.example === "string" + ? mediaObject.example + : JSON.stringify(mediaObject.example, null, 2) + } catch (_e) { + stringifiedBody = "" + } + } else if ( + mediaObject.examples && + Object.keys(mediaObject.examples).length > 0 + ) { + // Examples object (OpenAPI v3 format) + const firstExampleKey = Object.keys(mediaObject.examples)[0] + const firstExample = mediaObject.examples[firstExampleKey] + + // Skip if this is an unresolved reference + if (firstExample && "$ref" in firstExample) { + // Reference wasn't dereferenced, fall through to schema generation + } else { + // Handle Example Object (with value property) or direct value + const exampleValue = + firstExample && "value" in firstExample + ? firstExample.value + : firstExample + + hasExplicitExample = true + try { + stringifiedBody = + typeof exampleValue === "string" + ? exampleValue + : JSON.stringify(exampleValue, null, 2) + } catch (_e) { + stringifiedBody = "" + } + } + } - // I think it'll be better to just drop the response body with circular refs - // because it's not possible to stringify them, using stringify from a library like flatted, will change the structure, - // and it converts the object into an array format, which can only be parsed back by the parse method from the same library - // also we're displaying it as a string, so doesnt make much sense - try { - stringifiedBody = JSON.stringify(body ?? "") - // the parsing will fail for a circular response schema - } catch (_e) { - // eat five star, do nothing + // Only stringify if an example was generated (undefined indicates failure, null and other values are valid) + if (!hasExplicitExample && mediaObject.schema) { + // Generate example from schema as fallback + try { + let generatedExample: string | number | boolean | null | object + if (isOpenAPIV31Document(doc)) { + generatedExample = generateV31Example( + mediaObject as OpenAPIV31.MediaTypeObject + ) + } else { + generatedExample = generateV3Example( + mediaObject as OpenAPIV3.MediaTypeObject + ) + } + + // Only stringify if we got a valid example (null is valid in OpenAPI v3.1) + if (generatedExample !== undefined) { + stringifiedBody = + typeof generatedExample === "string" + ? generatedExample + : JSON.stringify(generatedExample, null, 2) + } + } catch (_e) { + // If generation fails, leave body empty + stringifiedBody = "" + } + } } res[name] = { @@ -236,9 +306,8 @@ const parseOpenAPIV2Responses = ( for (const [key, value] of Object.entries(responses)) { const response = value as OpenAPIV2.ResponseObject - // add support for schema key as well + // Get content type from examples or default to application/json const contentType = Object.keys(response.examples ?? {})[0] - const body = response.examples?.[contentType] const name = response.description ?? key @@ -254,12 +323,44 @@ const parseOpenAPIV2Responses = ( }, ] + let stringifiedBody = "" + + // Priority: examples > generate from schema + if (response.examples && contentType) { + // Use the example for the content type + const exampleBody = response.examples[contentType] + try { + stringifiedBody = + typeof exampleBody === "string" + ? exampleBody + : JSON.stringify(exampleBody, null, 2) + } catch (_e) { + stringifiedBody = "" + } + } else if (response.schema) { + // Generate example from schema as fallback + try { + const generatedExample = generateV2ExampleFromSchemaObject( + response.schema + ) + if (generatedExample !== undefined) { + stringifiedBody = + typeof generatedExample === "string" + ? generatedExample + : JSON.stringify(generatedExample, null, 2) + } + } catch (_e) { + // If generation fails, leave body empty + stringifiedBody = "" + } + } + res[name] = { name, status, code, headers, - body: body ?? "", + body: stringifiedBody, originalRequest, } } @@ -273,7 +374,7 @@ const parseOpenAPIResponses = ( originalRequest: HoppRESTResponseOriginalRequest ): HoppRESTRequestResponses => isOpenAPIV3Operation(doc, op) - ? parseOpenAPIV3Responses(op, originalRequest) + ? parseOpenAPIV3Responses(doc, op, originalRequest) : parseOpenAPIV2Responses(op, originalRequest) const parseOpenAPIHeaders = (params: OpenAPIParamsType[]): HoppRESTHeader[] => @@ -418,48 +519,45 @@ const parseOpenAPIV3Body = ( // For other content types (JSON, XML, etc.), try to generate sample from schema if (media.schema) { try { - const docAny = doc as any - const isV31 = docAny.openapi && docAny.openapi.startsWith("3.1") - - let sampleBody: any - if (isV31) { - sampleBody = generateV31Example(media as any) + let sampleBody: string | number | boolean | null | object + if (isOpenAPIV31Document(doc)) { + sampleBody = generateV31Example(media as OpenAPIV31.MediaTypeObject) } else { - sampleBody = generateV3Example(media as any) + sampleBody = generateV3Example(media as OpenAPIV3.MediaTypeObject) } return { - contentType: contentType as any, + contentType, body: typeof sampleBody === "string" ? sampleBody : JSON.stringify(sampleBody, null, 2), - } + } as HoppRESTReqBody } catch (_e) { // If we can't generate a sample, check for examples if (media.example !== undefined) { return { - contentType: contentType as any, + contentType, body: typeof media.example === "string" ? media.example : JSON.stringify(media.example, null, 2), - } + } as HoppRESTReqBody } // Fallback to empty body - return { contentType: contentType as any, body: "" } + return { contentType, body: "" } as HoppRESTReqBody } } // Check for examples if no schema if (media.example !== undefined) { return { - contentType: contentType as any, + contentType, body: typeof media.example === "string" ? media.example : JSON.stringify(media.example, null, 2), - } + } as HoppRESTReqBody } // Check for examples array (OpenAPI v3 supports multiple examples) @@ -467,21 +565,27 @@ const parseOpenAPIV3Body = ( const firstExampleKey = Object.keys(media.examples)[0] const firstExample = media.examples[firstExampleKey] - // Handle both Example Object and Reference Object + // Skip if this is an unresolved reference + if (firstExample && "$ref" in firstExample) { + // Reference wasn't dereferenced, return empty body + return { contentType, body: "" } as HoppRESTReqBody + } + + // Handle Example Object (with value property) or direct value const exampleValue = "value" in firstExample ? firstExample.value : firstExample return { - contentType: contentType as any, + contentType, body: typeof exampleValue === "string" ? exampleValue : JSON.stringify(exampleValue, null, 2), - } + } as HoppRESTReqBody } // Fallback to empty body for textual content types - return { contentType: contentType as any, body: "" } + return { contentType, body: "" } as HoppRESTReqBody } const isOpenAPIV3Operation = ( @@ -492,6 +596,13 @@ const isOpenAPIV3Operation = ( typeof doc.openapi === "string" && doc.openapi.startsWith("3.") +const isOpenAPIV31Document = ( + doc: OpenAPI.Document +): doc is OpenAPIV31.Document => + objectHasProperty(doc, "openapi") && + typeof doc.openapi === "string" && + doc.openapi.startsWith("3.1") + const parseOpenAPIBody = ( doc: OpenAPI.Document, op: OpenAPIOperationType diff --git a/packages/hoppscotch-common/src/helpers/keybindings.ts b/packages/hoppscotch-common/src/helpers/keybindings.ts index af49df6c732..a1a048f54d4 100644 --- a/packages/hoppscotch-common/src/helpers/keybindings.ts +++ b/packages/hoppscotch-common/src/helpers/keybindings.ts @@ -230,6 +230,22 @@ function handleKeyDown(ev: KeyboardEvent) { return } + // Special handling for shift-/ (support menu) - don't trigger in editors or inputs + if (binding === "shift-/") { + const target = ev.target + + if (!isDOMElement(target)) return + + // Let editors and inputs handle it normally (user is just typing "/") + if ( + isCodeMirrorEditor(target) || + isMonacoEditor(target) || + isTypableElement(target) + ) { + return + } + } + // If no action is bound, do nothing if (!boundAction) return diff --git a/packages/hoppscotch-common/src/newstore/mockServers.ts b/packages/hoppscotch-common/src/newstore/mockServers.ts index 09441e36b6a..f27e993d24f 100644 --- a/packages/hoppscotch-common/src/newstore/mockServers.ts +++ b/packages/hoppscotch-common/src/newstore/mockServers.ts @@ -62,6 +62,7 @@ export type CreateMockServerModalData = { const defaultMockServerState = { mockServers: [] as MockServer[], + loading: false, } type MockServerStoreType = typeof defaultMockServerState @@ -73,6 +74,7 @@ const mockServerDispatchers = defineDispatchers({ ) { return { mockServers, + loading: false, } }, @@ -104,6 +106,12 @@ const mockServerDispatchers = defineDispatchers({ mockServers: mockServers.filter((server) => server.id !== id), } }, + + setLoading(_: MockServerStoreType, { loading }: { loading: boolean }) { + return { + loading, + } + }, }) export const mockServerStore = new DispatchingStore( @@ -112,6 +120,7 @@ export const mockServerStore = new DispatchingStore( ) export const mockServers$ = mockServerStore.subject$.pipe(pluck("mockServers")) +export const loading$ = mockServerStore.subject$.pipe(pluck("loading")) export function setMockServers(mockServers: MockServer[]) { mockServerStore.dispatch({ @@ -141,6 +150,13 @@ export function deleteMockServer(id: string) { }) } +export function setLoading(loading: boolean) { + mockServerStore.dispatch({ + dispatcher: "setLoading", + payload: { loading }, + }) +} + // Modal state management const defaultCreateMockServerModalState: CreateMockServerModalData = { show: false, @@ -161,6 +177,7 @@ export function loadMockServers(skip?: number, take?: number) { if (currentWorkspace.type === "team" && currentWorkspace.teamID) { return loadTeamMockServers(currentWorkspace.teamID, skip, take) } + setLoading(true) return pipe( getMyMockServers(skip, take), TE.match( @@ -176,6 +193,7 @@ export function loadMockServers(skip?: number, take?: number) { )() } catch (_error) { // Fallback to user mock servers if workspace service is not available + setLoading(true) return pipe( getMyMockServers(skip, take), TE.match( @@ -198,6 +216,7 @@ export function loadTeamMockServers( skip?: number, take?: number ) { + setLoading(true) return pipe( getTeamMockServers(teamID, skip, take), TE.match( diff --git a/packages/hoppscotch-common/src/platform/instance.ts b/packages/hoppscotch-common/src/platform/instance.ts index be64cad9747..92b625ce3a3 100644 --- a/packages/hoppscotch-common/src/platform/instance.ts +++ b/packages/hoppscotch-common/src/platform/instance.ts @@ -18,7 +18,7 @@ export const VENDORED_INSTANCE_CONFIG: Instance = { kind: "vendored" as const, serverUrl: "app://hoppscotch", displayName: "Hoppscotch Desktop", - version: "26.1.0", + version: "26.1.1", lastUsed: new Date().toISOString(), bundleName: "Hoppscotch", } diff --git a/packages/hoppscotch-desktop/package.json b/packages/hoppscotch-desktop/package.json index b6819d9e326..5423bbd6a0c 100644 --- a/packages/hoppscotch-desktop/package.json +++ b/packages/hoppscotch-desktop/package.json @@ -1,7 +1,7 @@ { "name": "hoppscotch-desktop", "private": true, - "version": "26.1.0", + "version": "26.1.1", "type": "module", "scripts": { "dev": "vite", diff --git a/packages/hoppscotch-desktop/src-tauri/Cargo.lock b/packages/hoppscotch-desktop/src-tauri/Cargo.lock index cd28c2d88fc..138b350143d 100644 --- a/packages/hoppscotch-desktop/src-tauri/Cargo.lock +++ b/packages/hoppscotch-desktop/src-tauri/Cargo.lock @@ -2307,7 +2307,7 @@ dependencies = [ [[package]] name = "hoppscotch-desktop" -version = "26.1.0" +version = "26.1.1" dependencies = [ "axum", "dirs 6.0.0", diff --git a/packages/hoppscotch-desktop/src-tauri/Cargo.toml b/packages/hoppscotch-desktop/src-tauri/Cargo.toml index ba00c47d423..5be00aabe81 100644 --- a/packages/hoppscotch-desktop/src-tauri/Cargo.toml +++ b/packages/hoppscotch-desktop/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hoppscotch-desktop" -version = "26.1.0" +version = "26.1.1" description = "Desktop App for hoppscotch.io" authors = ["CuriousCorrelation"] edition = "2021" diff --git a/packages/hoppscotch-desktop/src-tauri/tauri.conf.json b/packages/hoppscotch-desktop/src-tauri/tauri.conf.json index e0fba9d1ca9..e09f113508e 100644 --- a/packages/hoppscotch-desktop/src-tauri/tauri.conf.json +++ b/packages/hoppscotch-desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Hoppscotch", - "version": "26.1.0", + "version": "26.1.1", "identifier": "io.hoppscotch.desktop", "build": { "beforeDevCommand": "pnpm dev", diff --git a/packages/hoppscotch-desktop/src-tauri/tauri.portable.macos.conf.json b/packages/hoppscotch-desktop/src-tauri/tauri.portable.macos.conf.json index 8445b3c046e..d8f7e0f2471 100644 --- a/packages/hoppscotch-desktop/src-tauri/tauri.portable.macos.conf.json +++ b/packages/hoppscotch-desktop/src-tauri/tauri.portable.macos.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Hoppscotch", - "version": "26.1.0", + "version": "26.1.1", "identifier": "io.hoppscotch.desktop", "build": { "beforeDevCommand": "pnpm dev", diff --git a/packages/hoppscotch-desktop/src-tauri/tauri.portable.windows.conf.json b/packages/hoppscotch-desktop/src-tauri/tauri.portable.windows.conf.json index d10aefdc6bb..ff4689bec70 100644 --- a/packages/hoppscotch-desktop/src-tauri/tauri.portable.windows.conf.json +++ b/packages/hoppscotch-desktop/src-tauri/tauri.portable.windows.conf.json @@ -1,7 +1,7 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Hoppscotch", - "version": "26.1.0", + "version": "26.1.1", "identifier": "io.hoppscotch.desktop", "build": { "beforeDevCommand": "pnpm dev", diff --git a/packages/hoppscotch-desktop/src/assets/scss/styles.scss b/packages/hoppscotch-desktop/src/assets/scss/styles.scss index 7718900ee29..24f5dbbdf74 100644 --- a/packages/hoppscotch-desktop/src/assets/scss/styles.scss +++ b/packages/hoppscotch-desktop/src/assets/scss/styles.scss @@ -69,13 +69,13 @@ @apply selection:bg-accentDark; @apply selection:text-accentContrast; - @apply overscroll-none; } :root { - @apply antialiased; accent-color: var(--accent-color); font-variant-ligatures: common-ligatures; + @apply antialiased; + @apply overscroll-none; } input::placeholder, diff --git a/packages/hoppscotch-desktop/src/views/Home.vue b/packages/hoppscotch-desktop/src/views/Home.vue index b57a0912f0c..eaf35a9b276 100644 --- a/packages/hoppscotch-desktop/src/views/Home.vue +++ b/packages/hoppscotch-desktop/src/views/Home.vue @@ -299,7 +299,7 @@ const loadVendored = async () => { const vendoredInstance: VendoredInstance = { type: "vendored", displayName: "Hoppscotch", - version: "26.1.0", + version: "26.1.1", } const connectionState: ConnectionState = { diff --git a/packages/hoppscotch-selfhost-web/package.json b/packages/hoppscotch-selfhost-web/package.json index c9eeab39813..0d3976c3545 100644 --- a/packages/hoppscotch-selfhost-web/package.json +++ b/packages/hoppscotch-selfhost-web/package.json @@ -1,7 +1,7 @@ { "name": "@hoppscotch/selfhost-web", "private": true, - "version": "2026.1.0", + "version": "2026.1.1", "type": "module", "scripts": { "dev:vite": "vite", diff --git a/packages/hoppscotch-selfhost-web/webapp-server/internal/bundle/types.go b/packages/hoppscotch-selfhost-web/webapp-server/internal/bundle/types.go index 74a22f29138..1c931046188 100644 --- a/packages/hoppscotch-selfhost-web/webapp-server/internal/bundle/types.go +++ b/packages/hoppscotch-selfhost-web/webapp-server/internal/bundle/types.go @@ -3,7 +3,7 @@ package bundle import "time" const ( - Version = "2026.1.0" + Version = "2026.1.1" DefaultMaxSize = 50 * 1024 * 1024 diff --git a/packages/hoppscotch-sh-admin/assets/scss/styles.scss b/packages/hoppscotch-sh-admin/assets/scss/styles.scss index ba40d2d8369..87d32c8892f 100644 --- a/packages/hoppscotch-sh-admin/assets/scss/styles.scss +++ b/packages/hoppscotch-sh-admin/assets/scss/styles.scss @@ -87,13 +87,13 @@ @apply selection:bg-accentDark; @apply selection:text-accentContrast; - @apply overscroll-none; } :root { - @apply antialiased; accent-color: var(--accent-color); font-variant-ligatures: common-ligatures; + @apply antialiased; + @apply overscroll-none; } ::-webkit-scrollbar-track { diff --git a/packages/hoppscotch-sh-admin/package.json b/packages/hoppscotch-sh-admin/package.json index d74cd3f7896..dc6563f6bca 100644 --- a/packages/hoppscotch-sh-admin/package.json +++ b/packages/hoppscotch-sh-admin/package.json @@ -1,7 +1,7 @@ { "name": "hoppscotch-sh-admin", "private": true, - "version": "2026.1.0", + "version": "2026.1.1", "type": "module", "scripts": { "dev": "pnpm exec npm-run-all -p -l dev:*", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 99ff598cfdf..dcc57146ad3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,7 +14,7 @@ overrides: glob@<11.1.0: 11.1.0 hono@4.10.6: 4.11.4 jws@<3.2.3: 3.2.3 - nodemailer@<7.0.12: 7.0.12 + nodemailer@<7.0.12: 8.0.0 qs@6.14.0: 6.14.1 subscriptions-transport-ws>ws: 7.5.10 vue: 3.5.27 @@ -178,7 +178,7 @@ importers: version: 1.1.2(@apollo/server@5.2.0(graphql@16.12.0))(express@5.2.1) '@nestjs-modules/mailer': specifier: 2.0.2 - version: 2.0.2(@nestjs/common@11.1.12(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.12)(nodemailer@7.0.12)(terser@5.44.1)(typescript@5.9.3) + version: 2.0.2(@nestjs/common@11.1.12(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.12)(nodemailer@8.0.0)(terser@5.44.1)(typescript@5.9.3) '@nestjs/apollo': specifier: 13.2.3 version: 13.2.3(@apollo/server@5.2.0(graphql@16.12.0))(@as-integrations/express5@1.1.2(@apollo/server@5.2.0(graphql@16.12.0))(express@5.2.1))(@nestjs/common@11.1.12(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.12)(@nestjs/graphql@13.2.3(@nestjs/common@11.1.12(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.12)(class-transformer@0.5.1)(class-validator@0.14.3)(graphql@16.12.0)(reflect-metadata@0.2.2))(graphql@16.12.0) @@ -273,8 +273,8 @@ importers: specifier: 1.10.1 version: 1.10.1 nodemailer: - specifier: 7.0.12 - version: 7.0.12 + specifier: 8.0.0 + version: 8.0.0 passport: specifier: 0.7.0 version: 0.7.0 @@ -4981,7 +4981,7 @@ packages: peerDependencies: '@nestjs/common': '>=7.0.9' '@nestjs/core': '>=7.0.9' - nodemailer: 7.0.12 + nodemailer: 8.0.0 '@nestjs/apollo@13.2.3': resolution: {integrity: sha512-dzayHaGSS6XUFHPj6YMb+ixsphZiwjdez3TWmL6wiRxBGTSc4uGkHNrz3qv7Qb5tq/4VwrqP7/qoti+XZsc0mA==} @@ -10582,8 +10582,8 @@ packages: node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - nodemailer@7.0.12: - resolution: {integrity: sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==} + nodemailer@8.0.0: + resolution: {integrity: sha512-xvVJf/f0bzmNpnRIbhCp/IKxaHgJ6QynvUbLXzzMRPG3LDQr5oXkYuw4uDFyFYs8cge8agwwrJAXZsd4hhMquw==} engines: {node: '>=6.0.0'} normalize-package-data@2.5.0: @@ -18295,13 +18295,13 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@nestjs-modules/mailer@2.0.2(@nestjs/common@11.1.12(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.12)(nodemailer@7.0.12)(terser@5.44.1)(typescript@5.9.3)': + '@nestjs-modules/mailer@2.0.2(@nestjs/common@11.1.12(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/core@11.1.12)(nodemailer@8.0.0)(terser@5.44.1)(typescript@5.9.3)': dependencies: '@css-inline/css-inline': 0.14.1 '@nestjs/common': 11.1.12(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2) '@nestjs/core': 11.1.12(@nestjs/common@11.1.12(class-transformer@0.5.1)(class-validator@0.14.3)(reflect-metadata@0.2.2)(rxjs@7.8.2))(@nestjs/platform-express@11.1.12)(reflect-metadata@0.2.2)(rxjs@7.8.2) glob: 11.1.0 - nodemailer: 7.0.12 + nodemailer: 8.0.0 optionalDependencies: '@types/ejs': 3.1.5 '@types/mjml': 4.7.4 @@ -24364,7 +24364,7 @@ snapshots: iconv-lite: 0.7.0 libmime: 5.3.7 linkify-it: 5.0.0 - nodemailer: 7.0.12 + nodemailer: 8.0.0 punycode.js: 2.3.1 tlds: 1.261.0 optional: true @@ -25177,7 +25177,7 @@ snapshots: node-releases@2.0.27: {} - nodemailer@7.0.12: {} + nodemailer@8.0.0: {} normalize-package-data@2.5.0: dependencies: @@ -25999,7 +25999,7 @@ snapshots: fixpack: 4.0.0 get-port: 5.1.1 mailparser: 3.9.0 - nodemailer: 7.0.12 + nodemailer: 8.0.0 open: 7.4.2 p-event: 4.2.0 p-wait-for: 3.2.0