chore: merge hoppscotch/main into hoppscotch/next

This commit is contained in:
jamesgeorge007 2025-10-08 11:59:09 +05:30
commit bb8b9cec8f
26 changed files with 226 additions and 84 deletions

View file

@ -1,6 +1,6 @@
{
"name": "hoppscotch-backend",
"version": "2025.9.1",
"version": "2025.9.2",
"description": "",
"author": "",
"private": true,

View file

@ -1,7 +1,7 @@
{
"name": "@hoppscotch/common",
"private": true,
"version": "2025.9.1",
"version": "2025.9.2",
"scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*",
"test": "vitest --run",

View file

@ -2085,7 +2085,9 @@ const selectRequest = (selectedRequest: {
originLocation: "user-collection",
requestIndex: parseInt(requestIndex),
folderPath: folderPath!,
requestRefID: request._ref_id ?? request.id,
})
if (possibleTab) {
tabs.setActiveTab(possibleTab.value.id)
} else {

View file

@ -436,7 +436,7 @@ const workingEnvID = computed(() => {
const getCurrentValue = (id: string | "Global", varIndex: number) => {
const env = workingEnv.value?.variables[varIndex]
if (env && env.secret) {
if (env?.secret) {
return secretEnvironmentService.getSecretEnvironmentVariable(id, varIndex)
?.value
}
@ -444,6 +444,15 @@ const getCurrentValue = (id: string | "Global", varIndex: number) => {
?.currentValue
}
const getInitialValue = (id: string | "Global", varIndex: number) => {
const env = workingEnv.value?.variables[varIndex]
if (env?.secret) {
return secretEnvironmentService.getSecretEnvironmentVariable(id, varIndex)
?.initialValue
}
return env?.initialValue
}
watch(
() => props.show,
(show) => {
@ -469,7 +478,13 @@ watch(
: workingEnvID.value,
index
) ?? e.currentValue,
initialValue: e.initialValue,
initialValue:
getInitialValue(
props.editingEnvironmentIndex === "Global"
? "Global"
: workingEnvID.value,
index
) ?? e.initialValue,
secret: e.secret,
},
}))
@ -535,6 +550,7 @@ const saveEnvironment = () => {
key: e.key,
value: e.currentValue,
varIndex: i,
initialValue: e.initialValue,
})
: O.none
)
@ -583,7 +599,7 @@ const saveEnvironment = () => {
A.map((e) => ({
key: e.key,
secret: e.secret,
initialValue: e.initialValue || "",
initialValue: e.secret ? "" : e.initialValue,
currentValue: "",
}))
)

View file

@ -419,6 +419,13 @@ const getCurrentValue = (
)?.currentValue
}
const getInitialValue = (editingID: string, varIndex: number) => {
return secretEnvironmentService.getSecretEnvironmentVariable(
editingID,
varIndex
)?.initialValue
}
watch(
() => props.show,
(show) => {
@ -449,7 +456,11 @@ watch(
index,
e.secret
) ?? e.currentValue,
initialValue: e.initialValue,
initialValue: e.secret
? (getInitialValue(props.editingEnvironment?.id ?? "", index) ??
e.initialValue ??
"")
: e.initialValue,
secret: e.secret,
},
}))
@ -516,7 +527,12 @@ const saveEnvironment = async () => {
filteredVariables,
A.filterMapWithIndex((i, e) =>
e.secret
? O.some({ key: e.key, value: e.currentValue, varIndex: i })
? O.some({
key: e.key,
value: e.currentValue,
varIndex: i,
initialValue: e.initialValue,
})
: O.none
)
)
@ -540,7 +556,7 @@ const saveEnvironment = async () => {
A.map((e) => ({
key: e.key,
secret: e.secret,
initialValue: e.initialValue || "",
initialValue: e.secret ? "" : e.initialValue,
currentValue: "",
}))
)

View file

@ -167,15 +167,15 @@ const updateEnvironments = (
key: e.key,
value: e.currentValue ?? "",
varIndex: index,
initialValue: e.initialValue ?? "",
})
// delete the value from the environment
// so that it doesn't get saved in the environment
// create a new object with cleared values for secret variables
// so that these values don't get saved in the environment
return {
key: e.key,
secret: e.secret,
initialValue: e.initialValue ?? "",
initialValue: e.secret ? "" : (e.initialValue ?? ""),
currentValue: "",
}
}
@ -210,24 +210,36 @@ const updateEnvironments = (
return updatedEnv
}
/**
* Get the environment variable value from the secret environment service
* @param envID The environment ID
* @param index The index of the environment variable
* @returns Current value and initial value of the environment variable
*/
const getSecretEnvironmentVariableValue = (
envID: string,
index: number
): {
value: string
initialValue?: string
} | null => {
return secretEnvironmentService.getSecretEnvironmentVariableValue(
envID,
index
)
}
/**
* Get the environment variable value from the current environment
* @param envID The environment ID
* @param index The index of the environment variable
* @param isSecret Whether the environment variable is a secret
* @returns The environment variable value
* @returns Current value of the environment variable
*/
const getEnvironmentVariableValue = (
envID: string,
index: number,
isSecret: boolean
index: number
): string | undefined => {
if (isSecret) {
return secretEnvironmentService.getSecretEnvironmentVariableValue(
envID,
index
)
}
return currentEnvironmentValueService.getEnvironmentVariableValue(
envID,
index
@ -823,6 +835,27 @@ const getUpdatedEnvVariables = (
)
)
// Helper to resolve currentValue & initialValue for (secret/non-secret) env vars
const resolveEnvVars = (
envID: string,
vars: Environment["variables"]
): Environment["variables"] =>
vars.map((v, index) => {
const secretMeta = v.secret
? getSecretEnvironmentVariableValue(envID, index)
: null
return {
...v,
currentValue:
(v.secret
? secretMeta?.value
: getEnvironmentVariableValue(envID, index)) ?? "",
// fallback to var initialValue if secretMeta is not found
initialValue:
(v.secret ? secretMeta?.initialValue : "") ?? v.initialValue,
}
})
function translateToSandboxTestResults(
testDesc: SandboxTestResult
): HoppTestResult {
@ -834,20 +867,10 @@ function translateToSandboxTestResults(
}
}
const globals = cloneDeep(getGlobalVariables()).map((g, index) => ({
...g,
currentValue: getEnvironmentVariableValue("Global", index, g.secret) ?? "",
}))
const envVars = getCurrentEnvironment().variables.map((e, index) => ({
...e,
currentValue:
getEnvironmentVariableValue(
getCurrentEnvironment().id,
index,
e.secret
) ?? "",
}))
const globals = resolveEnvVars("Global", cloneDeep(getGlobalVariables()))
const { id: currentEnvID, variables: currentEnvVariables } =
getCurrentEnvironment()
const envVars = resolveEnvVars(currentEnvID, currentEnvVariables)
return {
description: "",

View file

@ -145,10 +145,15 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
? tooltipEnv.sourceEnvID!
: currentSelectedEnvironment.id
const hasSecretStored = secretEnvironmentService.hasSecretValue(
const hasSecretValueStored = secretEnvironmentService.hasSecretValue(
tooltipSourceEnvID,
tooltipEnv?.key ?? ""
)
const hasSecretInitialValueStored =
secretEnvironmentService.hasSecretInitialValue(
tooltipSourceEnvID,
tooltipEnv?.key ?? ""
)
// We need to check if the environment is a secret and if it has a secret value stored in the secret environment service
// If it is a secret and has a secret value, we need to show "******" in the tooltip
@ -158,12 +163,12 @@ const cursorTooltipField = (aggregateEnvs: AggregateEnvironment[]) =>
// If the source environment is not found, we need to show "Not Found" in the tooltip, ie the the environment
// is not defined in the selected environment or the global environment
if (isSecret) {
if (!hasSecretStored && envInitialValue) {
if (hasSecretValueStored && hasSecretInitialValueStored) {
envInitialValue = "******"
} else if (hasSecretStored && !envInitialValue) {
envCurrentValue = "******"
} else if (hasSecretStored && envInitialValue) {
} else if (!hasSecretValueStored && hasSecretInitialValueStored) {
envInitialValue = "******"
} else if (hasSecretValueStored && !hasSecretInitialValueStored) {
envCurrentValue = "******"
} else {
envInitialValue = "Empty"

View file

@ -36,6 +36,7 @@ const unWrapEnvironments = (
return {
...globalVar,
currentValue: secretVar.value,
initialValue: secretVar.initialValue ?? "",
}
}
return {
@ -58,6 +59,7 @@ const unWrapEnvironments = (
return {
...selectedVar,
currentValue: secretVar.value,
initialValue: secretVar.initialValue ?? "",
}
}
return {

View file

@ -556,12 +556,19 @@ export function getAggregateEnvsWithCurrentValue() {
...currentEnv.variables.map((x, index) => {
let currentValue = x.currentValue
let initialValue = x.initialValue
if (x.secret) {
currentValue =
secretEnvironmentService.getSecretEnvironmentVariableValue(
currentEnv.id,
index
) ?? ""
)?.value ?? ""
initialValue =
secretEnvironmentService.getSecretEnvironmentVariableValue(
currentEnv.id,
index
)?.initialValue ?? ""
}
return <AggregateEnvironment>{
@ -571,19 +578,26 @@ export function getAggregateEnvsWithCurrentValue() {
currentEnv.id,
index
) ?? currentValue,
initialValue: x.initialValue,
initialValue: x.initialValue ?? initialValue,
secret: x.secret,
sourceEnv: currentEnv.name,
}
}),
...getGlobalVariables().map((x, index) => {
let currentValue = x.currentValue
let initialValue = x.initialValue
if (x.secret) {
currentValue =
secretEnvironmentService.getSecretEnvironmentVariableValue(
"Global",
index
) ?? ""
)?.value ?? ""
initialValue =
secretEnvironmentService.getSecretEnvironmentVariableValue(
"Global",
index
)?.initialValue ?? ""
}
return <AggregateEnvironment>{
key: x.key,
@ -592,7 +606,7 @@ export function getAggregateEnvsWithCurrentValue() {
"Global",
index
) ?? currentValue,
initialValue: x.initialValue,
initialValue: x.initialValue ?? initialValue,
secret: x.secret,
sourceEnv: "Global",
}
@ -623,12 +637,19 @@ export const aggregateEnvsWithCurrentValue$: Observable<
selectedEnv?.variables.map((x, index) => {
let currentValue = x.currentValue
let initialValue = x.initialValue
if (x.secret) {
currentValue =
secretEnvironmentService.getSecretEnvironmentVariableValue(
selectedEnv.id,
index
) ?? ""
)?.value ?? ""
initialValue =
secretEnvironmentService.getSecretEnvironmentVariableValue(
selectedEnv.id,
index
)?.initialValue ?? ""
}
results.push({
key: x.key,
@ -637,7 +658,7 @@ export const aggregateEnvsWithCurrentValue$: Observable<
selectedEnv.id,
index
) ?? currentValue,
initialValue: x.initialValue,
initialValue: x.initialValue ?? initialValue,
secret: x.secret,
sourceEnv: selectedEnv.name,
})
@ -645,12 +666,19 @@ export const aggregateEnvsWithCurrentValue$: Observable<
globalEnv.variables.map((x, index) => {
let currentValue = x.currentValue
let initialValue = x.initialValue
if (x.secret) {
currentValue =
secretEnvironmentService.getSecretEnvironmentVariableValue(
"Global",
index
) ?? ""
)?.value ?? ""
initialValue =
secretEnvironmentService.getSecretEnvironmentVariableValue(
"Global",
index
)?.initialValue ?? ""
}
results.push({
key: x.key,
@ -659,7 +687,7 @@ export const aggregateEnvsWithCurrentValue$: Observable<
"Global",
index
) ?? currentValue,
initialValue: x.initialValue,
initialValue: x.initialValue ?? initialValue,
secret: x.secret,
sourceEnv: "Global",
})

View file

@ -72,18 +72,37 @@ describe("SecretEnvironmentService", () => {
})
describe("getSecretEnvironmentVariableValue", () => {
it("should return the value of the specified secret environment variable", () => {
it("should return the value and initialValue of the specified secret environment variable", () => {
const id = "testEnvironment"
const secretVars = [{ key: "key1", value: "value1", varIndex: 1 }]
const secretVars = [
{ key: "key1", value: "value1", initialValue: "init1", varIndex: 1 },
]
service.secretEnvironments.set(id, secretVars)
const result = service.getSecretEnvironmentVariableValue(id, 1)
expect(result).toEqual(secretVars[0].value)
expect(result).toEqual({
value: "value1",
initialValue: "init1",
})
})
it("should return undefined if the specified variable does not exist", () => {
it("should return null if the variable has no value/initialValue", () => {
const id = "testEnvironment"
const secretVars = [{ key: "key1", varIndex: 1 }]
service.secretEnvironments.set(id, secretVars as any)
const result = service.getSecretEnvironmentVariableValue(id, 1)
expect(result).toEqual({
value: "",
initialValue: "",
})
})
it("should return null if the specified variable does not exist", () => {
const id = "testEnvironment"
const secretVars = [{ key: "key1", value: "value1", varIndex: 1 }]
@ -91,15 +110,15 @@ describe("SecretEnvironmentService", () => {
const result = service.getSecretEnvironmentVariableValue(id, 2)
expect(result).toBeUndefined()
expect(result).toBeNull()
})
it("should return undefined if the specified environment does not exist", () => {
it("should return null if the specified environment does not exist", () => {
const id = "nonExistentEnvironment"
const result = service.getSecretEnvironmentVariableValue(id, 1)
expect(result).toBeUndefined()
expect(result).toBeNull()
})
})

View file

@ -379,6 +379,7 @@ export const SECRET_ENVIRONMENT_VARIABLE_SCHEMA = z.union([
.object({
key: z.string(),
value: z.string(),
initialValue: z.string().optional().catch(""),
varIndex: z.number(),
})
.strict()

View file

@ -4,11 +4,15 @@ import { reactive, computed, watch } from "vue"
/**
* Defines a secret environment variable.
* Value is the current value of the variable.
* InitialValue is the value of the variable when it was created.
* VarIndex is the index of the variable in the environment.
*/
export type SecretVariable = {
key: string
value: string
varIndex: number
initialValue?: string
}
/**
@ -61,13 +65,17 @@ export class SecretEnvironmentService extends Service {
}
/**
* Used to get the value of a secret environment variable.
* Used to get the initial and current value of a secret environment variable.
* @param id ID of the environment
* @param varIndex Index of the variable in the environment
*/
public getSecretEnvironmentVariableValue(id: string, varIndex: number) {
const secretVar = this.getSecretEnvironmentVariable(id, varIndex)
return secretVar?.value
if (!secretVar) return null
return {
value: secretVar.value || "",
initialValue: secretVar.initialValue || "",
}
}
/**
@ -134,6 +142,21 @@ export class SecretEnvironmentService extends Service {
)
}
/**
* Checks if a secret variable has an initial value set.
* @param id ID of the environment
* @param key Key of the variable to check the initial value exists
* @returns true if the key has an initial value
*/
public hasSecretInitialValue(id: string, key: string) {
return (
this.secretEnvironments.has(id) &&
this.secretEnvironments
.get(id)!
.some((secretVar) => secretVar.key === key && secretVar.initialValue)
)
}
/**
* Used to update the value of a secret environment variable.
*/

View file

@ -256,9 +256,11 @@ declare namespace pw {
headers: HoppRESTResponseHeader[]
}>
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(key: string): string
function resolve(value: string): string
}
}

View file

@ -1,7 +1,7 @@
{
"name": "hoppscotch-desktop",
"private": true,
"version": "25.9.1",
"version": "25.9.2",
"type": "module",
"scripts": {
"dev": "vite",

View file

@ -2307,7 +2307,7 @@ dependencies = [
[[package]]
name = "hoppscotch-desktop"
version = "25.9.1"
version = "25.9.2"
dependencies = [
"axum",
"dirs 6.0.0",

View file

@ -1,6 +1,6 @@
[package]
name = "hoppscotch-desktop"
version = "25.9.1"
version = "25.9.2"
description = "Desktop App for hoppscotch.io"
authors = ["CuriousCorrelation"]
edition = "2021"

View file

@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Hoppscotch",
"version": "25.9.1",
"version": "25.9.2",
"identifier": "io.hoppscotch.desktop",
"build": {
"beforeDevCommand": "pnpm dev",

View file

@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Hoppscotch",
"version": "25.9.1",
"version": "25.9.2",
"identifier": "io.hoppscotch.desktop",
"build": {
"beforeDevCommand": "pnpm dev",

View file

@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "Hoppscotch",
"version": "25.9.1",
"version": "25.9.2",
"identifier": "io.hoppscotch.desktop",
"build": {
"beforeDevCommand": "pnpm dev",

View file

@ -299,7 +299,7 @@ const loadVendored = async () => {
const vendoredInstance: VendoredInstance = {
type: "vendored",
displayName: "Hoppscotch",
version: "25.9.1",
version: "25.9.2",
}
const connectionState: ConnectionState = {

View file

@ -158,21 +158,24 @@ export function getSharedEnvMethods(
}
/**
* Legacy sandbox version - Returns flat methods for `pw` namespace only
* Legacy sandbox version - Methods pre-wrapped in `env` for direct `pw` namespace assignment
* (Experimental sandbox powered by `faraday-cage` handles this wrapping via bootstrap code)
*/
export function getSharedEnvMethods(
envs: TestResult["envs"],
isHoppNamespace?: false
): {
methods: {
get: (key: string, options?: EnvAPIOptions) => string | null | undefined
getResolve: (
key: string,
options?: EnvAPIOptions
) => string | null | undefined
set: (key: string, value: string, options?: EnvAPIOptions) => void
unset: (key: string, options?: EnvAPIOptions) => void
resolve: (key: string) => string
env: {
get: (key: string, options?: EnvAPIOptions) => string | null | undefined
getResolve: (
key: string,
options?: EnvAPIOptions
) => string | null | undefined
set: (key: string, value: string, options?: EnvAPIOptions) => void
unset: (key: string, options?: EnvAPIOptions) => void
resolve: (key: string) => string
}
}
updatedEnvs: TestResult["envs"]
}
@ -380,11 +383,13 @@ export function getSharedEnvMethods(
// Legacy scripting sandbox (Only `pw` namespace)
return {
methods: {
get: envGetFn,
getResolve: envGetResolveFn,
set: envSetFn,
unset: envUnsetFn,
resolve: envResolveFn,
env: {
get: envGetFn,
getResolve: envGetResolveFn,
set: envSetFn,
unset: envUnsetFn,
resolve: envResolveFn,
},
},
updatedEnvs,
}

View file

@ -1,7 +1,7 @@
{
"name": "@hoppscotch/selfhost-web",
"private": true,
"version": "2025.9.1",
"version": "2025.9.2",
"type": "module",
"scripts": {
"dev:vite": "vite",

View file

@ -84,7 +84,7 @@ async function initApp() {
displayConfig: {
displayName: "Hoppscotch",
description: "On-Prem",
version: "25.9.1",
version: "25.9.2",
connectingMessage: "Connecting to On-prem",
connectedMessage: "Connected to On-prem",
},

View file

@ -47,7 +47,7 @@ pub struct Bundle {
impl Bundle {
pub fn new(bundle_version: Option<String>, content: Vec<u8>, signature: Signature, files: Vec<FileEntry>) -> Self {
let metadata = BundleMetadata {
version: "2025.9.1".to_string(),
version: "2025.9.2".to_string(),
created_at: Utc::now(),
signature,
manifest: Manifest { files },

View file

@ -54,7 +54,7 @@ impl Default for ServerConfig {
Self {
port: default_port(),
max_bundle_size: default_max_bundle_size(),
bundle_version: Some("2025.9.1".to_string()),
bundle_version: Some("2025.9.2".to_string()),
csp_directives: None,
signing_key: None,
verifying_key: None,
@ -75,7 +75,7 @@ impl ServerConfig {
Self {
signing_key: Some(key_pair.signing_key),
verifying_key: Some(key_pair.verifying_key),
bundle_version: Some("2025.9.1".to_string()),
bundle_version: Some("2025.9.2".to_string()),
frontend_path,
is_dev: cfg!(debug_assertions),
..Default::default()

View file

@ -1,7 +1,7 @@
{
"name": "hoppscotch-sh-admin",
"private": true,
"version": "2025.9.1",
"version": "2025.9.2",
"type": "module",
"scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*",