CVE-2024-34347 (TypeScript, published 2024-05-08T15:15Z)
Description: value: @hoppscotch/cli is a CLI to run Hoppscotch Test Scripts in CI environments. Prior to 0.8.0, the @hoppscotch/js-sandbox package provides a Javascript sandbox that uses the Node.js vm module. However, the vm module is not safe for sandboxing untrusted Javascript code. This is because code inside the vm context can break out if it can get a hold of any reference to an object created outside of the vm. In the case of @hoppscotch/js-sandbox, multiple references to external objects are passed into the vm context to allow pre-request scripts interactions with environment variables and more. But this also allows the pre-request script to escape the sandbox. This vulnerability is fixed in 0.8.0.
Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/hoppscotch/hoppscotch
Files changed:
.github/workflows/tests.yml
packages/hoppscotch-cli/bin/hopp.js
packages/hoppscotch-cli/package.json
packages/hoppscotch-cli/src/__tests__/commands/test.spec.ts
packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v1-req-v0.json
packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v1-req-v1.json
packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v2-req-v2.json
packages/hoppscotch-cli/src/__tests__/samples/collections/coll-v2-req-v3.json
packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-coll.json
packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-persistence-coll.json
packages/hoppscotch-cli/src/__tests__/samples/collections/secret-envs-persistence-scripting-coll.json
packages/hoppscotch-cli/src/__tests__/samples/environments/secret-envs.json
packages/hoppscotch-cli/src/__tests__/samples/environments/secret-supplied-values-envs.json
packages/hoppscotch-cli/src/__tests__/utils.ts
packages/hoppscotch-cli/src/index.ts
packages/hoppscotch-cli/src/utils/mutators.ts
packages/hoppscotch-js-sandbox/node.d.ts
packages/hoppscotch-js-sandbox/package.json
packages/hoppscotch-js-sandbox/setupFiles.ts
packages/hoppscotch-js-sandbox/src/__tests__/base64-helper-functions.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/env/get.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/env/getResolve.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/env/resolve.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/env/set.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/env/unset.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/expect/toBe.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/expect/toBeLevelxxx.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/expect/toBeType.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/expect/toHaveLength.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/expect/toInclude.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/pre-request.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/shared-utils.spec.ts
packages/hoppscotch-js-sandbox/src/__tests__/test-runner.spec.ts
packages/hoppscotch-js-sandbox/src/node/index.ts
packages/hoppscotch-js-sandbox/src/node/pre-request.ts
packages/hoppscotch-js-sandbox/src/node/test-runner.ts
packages/hoppscotch-js-sandbox/src/node/utils.ts
packages/hoppscotch-js-sandbox/src/shared-utils.ts
packages/hoppscotch-js-sandbox/src/web/index.ts
packages/hoppscotch-js-sandbox/src/web/pre-request/worker.ts
packages/hoppscotch-js-sandbox/src/web/test-runner/worker.ts
packages/hoppscotch-js-sandbox/vite.config.ts
packages/hoppscotch-js-sandbox/web.d.ts
pnpm-lock.yaml
Commit message: chore: migrate `Node.js` implementation for `js-sandbox` to `isolated-vm` (#3973)

Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>

Vulnerable code:
        uses: actions/checkout@v3
        uses: pnpm/action-setup@v2.2.4
      - name: Setup node
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node }}
          cache: pnpm

cli(process.argv);
  "version": "0.7.0",
    jest.setTimeout(10000);
    "v": 1,
    "name": "coll-v1",
    "folders": [],
    "requests": [
            "url": "https://httpbin.org",
            "path": "/get",
            "headers": [
              { "key": "Inactive-Header", "value": "Inactive Header", "active": false },
              { "key": "Authorization", "value": "Bearer token123", "active": true }
            ],
            "params": [
              { "key": "key", "value": "value", "active": true },
              { "key": "inactive-key", "value": "inactive-param", "active": false }
            ],
            "name": "req-v0",
            "method": "GET",
            "preRequestScript": "",
            "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
            "contentType": "application/json",
            "body": "",
            "auth": "Bearer Token",
            "bearerToken": "token123"
          }
    ]
}
    "folders": [],
            "endpoint": "https://httpbin.org/get",
            "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
    "folders": [],
            "endpoint": "https://httpbin.org/get",
            "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
    "folders": [],
            "endpoint": "https://httpbin.org/get",
            "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"Authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"Inactive-Header\"]).toBe(undefined)\n})",
      "auth": { "authType": "none", "authActive": true },
      "body": { "body": null, "contentType": null },
      "endpoint": "<<baseURL>>/headers",
      "testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n    const secretHeaderValue = pw.env.get(\"secretHeaderValue\")\n    pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n    \n    if (secretHeaderValue) {\n      pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n    }\n\n    pw.expect(pw.env.get(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
      "auth": { "authType": "none", "authActive": true },
      "endpoint": "<<baseURL>>/post",
      "testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n    const secretBodyValue = pw.env.get(\"secretBodyValue\")\n    pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n  \n    if (secretBodyValue) {\n      pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n    }\n\n    pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
      "auth": { "authType": "none", "authActive": true },
      "body": { "body": null, "contentType": null },
      "endpoint": "<<baseURL>>/get",
      "body": { "body": null, "contentType": null },
      "endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
      "testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n  \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n    pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n    pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n    if (secretBasicAuthUsername && secretBasicAuthPassword) {\n      const { authenticated, user } = pw.response.body\n      pw.expect(authenticated).toBe(true)\n      pw.expect(user).toBe(secretBasicAuthUsername)\n    }\n});",
      "body": { "body": null, "contentType": null },
      "endpoint": "<<baseURL>>/bearer",
      "testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n    const secretBearerToken = pw.env.get(\"secretBearerToken\")\n  const preReqSecretBearerToken = pw.env.get(\"preReqSecretBearerToken\")\n\n    pw.expect(secretBearerToken).toBe(\"test-token\")\n\n    if (secretBearerToken) { \n      pw.expect(pw.response.body.token).toBe(secretBearerToken)\n      pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n    }\n});",
      "auth": { "authType": "none", "authActive": true },
      "body": { "body": null, "contentType": null },
      "endpoint": "<<baseURL>>",
  "auth": { "authType": "inherit", "authActive": false },
}
  "name": "secret-envs-setters-coll",
      "endpoint": "<<baseURL>>/headers",
      "testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n    const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n    pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n    \n    if (secretHeaderValue) {\n      pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n    }\n\n    pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
      "endpoint": "<<baseURL>>/headers",
      "testScript": "pw.test(\"Value set at the pre-request script takes precedence\", () => {\n    const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n    pw.expect(secretHeaderValue).toBe(\"secret-header-value-overriden\")\n    \n    if (secretHeaderValue) {\n      pw.expect(pw.response.body.headers[\"Secret-Header-Key\"]).toBe(secretHeaderValue)\n    }\n\n    pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value-overriden\")\n})",
      "endpoint": "<<baseURL>>/post",
      "testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n    const secretBodyValue = pw.env.get(\"secretBodyValue\")\n    pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n  \n    if (secretBodyValue) {\n      pw.expect(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n    }\n\n    pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
      "endpoint": "<<baseURL>>/get",
      "endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
      "testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n  \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n    pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n    pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n    if (secretBasicAuthUsername && secretBasicAuthPassword) {\n      const { authenticated, user } = pw.response.body\n      pw.expect(authenticated).toBe(true)\n      pw.expect(user).toBe(secretBasicAuthUsername)\n    }\n});",
      "endpoint": "<<baseURL>>/bearer",
      "testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n    const secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n  const preReqSecretBearerToken = pw.env.resolve(\"<<preReqSecretBearerToken>>\")\n\n    pw.expect(secretBearerToken).toBe(\"test-token\")\n\n    if (secretBearerToken) { \n      pw.expect(pw.response.body.token).toBe(secretBearerToken)\n      pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n    }\n});",
}
      "endpoint": "https://httpbin.org/post",
      "testScript": "pw.test(\"Secret environment value set from the pre-request script takes precedence\", () => {\n  pw.expect(pw.env.get(\"preReqVarOne\")).toBe(\"pre-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the pre-request script\", () => {\n  pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n  pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request headers that are set in pre-request sccript\", () => {\n  pw.expect(pw.response.body.headers[\"Custom-Header\"]).toBe(\"custom-header-secret-value\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request body that are set in pre-request sccript\", () => {\n  pw.expect(pw.response.body.json.key).toBe(\"custom-body-value\")\n})\n\npw.test(\"Secret environment variable set from the post-request script takes precedence\", () => {\n  pw.env.set(\"postReqVarOne\", \"post-req-value-one\")\n  pw.expect(pw.env.get(\"postReqVarOne\")).toBe(\"post-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the post-request script\", () => {\n  pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n  pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully removes environment variables via the pw.env.unset method\", () => {\n  pw.env.unset(\"preReqVarOne\")\n  pw.env.unset(\"postReqVarTwo\")\n\n  pw.expect(pw.env.get(\"preReqVarOne\")).toBe(undefined)\n  pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(undefined)\n})",
      "key": "baseURL",
      "key": "baseURL",
export const runCLI = (args: string, options = {}): Promise<ExecResponse> =>
  {
    const CLI_PATH = resolve(__dirname, "../../bin/hopp");
    const command = `node ${CLI_PATH} ${args}`

    return new Promise((resolve) =>
      exec(command, options, (error, stdout, stderr) => resolve({ error, stdout, stderr }))
    );
  }
export const getTestJsonFilePath = (file: string, kind: "collection" | "environment") => {
  const filePath = resolve(__dirname, `../../src/__tests__/samples/${kindDir}/${file}`);
const program = new Command()
  return collectionSchemaParsedResult.data.map((collection) => {
    const requestSchemaParsedResult = z
      .array(entityReference(HoppRESTRequest))
      .safeParse(collection.requests);

    if (!requestSchemaParsedResult.success) {
      throw error({
        code: "MALFORMED_COLLECTION",
        path,
        data: "Please check the collection data.",
      });
    }

    return {
      ...collection,
      requests: requestSchemaParsedResult.data,
    };
  });
export default {
  preset: "ts-jest",
  testEnvironment: "jsdom",
  collectCoverage: true,
  setupFilesAfterEnv: ["./jest.setup.ts"],
  moduleNameMapper: {
    "~/(.*)": "<rootDir>/src/$1",
    "^lodash-es$": "lodash",
  },
}
require("@relmify/jest-fp-ts")
export { default } from "./dist/node.d.ts"
export * from "./dist/node.d.ts"
    "test": "pnpm exec jest",
    "jest": "27.5.1",
    "vite": "5.0.5"
import { runPreRequestScript } from "~/pre-request/node-vm"
import { runTestScript } from "~/test-runner/node-vm"
import "@relmify/jest-fp-ts"
import { runTestScript } from "~/test-runner/node-vm"
import "@relmify/jest-fp-ts"
import { runTestScript } from "~/test-runner/node-vm"
import { runTestScript } from "~/test-runner/node-vm"
import { runTestScript } from "~/test-runner/node-vm"
import { runTestScript } from "~/test-runner/node-vm"
import "@relmify/jest-fp-ts"
import { runTestScript } from "~/test-runner/node-vm"
              pw.expect(2).toBe(2)
import "@relmify/jest-fp-ts"
import { runTestScript } from "~/test-runner/node-vm"
import { runTestScript } from "~/test-runner/node-vm"
import { runTestScript } from "~/test-runner/node-vm"
import { runTestScript } from "~/test-runner/node-vm"
import "@relmify/jest-fp-ts"
import { runPreRequestScript } from "~/pre-request/node-vm"
import { preventCyclicObjects } from "~/utils"
import { runTestScript } from "~/test-runner/node-vm"
export * from "./pre-request/node-vm"
export * from "./test-runner/node-vm"
import { pipe } from "fp-ts/function"
import * as TE from "fp-ts/lib/TaskEither"
import { createContext, runInContext } from "vm"

import { TestResult } from "~/types"
import { getPreRequestScriptMethods } from "~/utils"

export const runPreRequestScript = (
  preRequestScript: string,
  envs: TestResult["envs"]
): TE.TaskEither<string, TestResult["envs"]> =>
  pipe(
    TE.tryCatch(
      async () => {
        return createContext()
      },
      (reason) => `Context initialization failed: ${reason}`
    ),
    TE.chain((context) =>
      TE.tryCatch(
        () =>
          new Promise((resolve) => {
            const { pw, updatedEnvs } = getPreRequestScriptMethods(envs)

            // Expose pw to the context
            context.pw = pw
            context.atob = atob
            context.btoa = btoa

            // Run the pre-request script in the provided context
            runInContext(preRequestScript, context)

            resolve(updatedEnvs)
          }),
        (reason) => `Script execution failed: ${reason}`
      )
    )
  )
    let assertion = expectVal === expectedVal
    const message = `Expected '${expectVal}' to${
    const parsedExpectVal = parseInt(expectVal)
      const message = `Expected ${level}-level status but could not parse value '${expectVal}'`
      let assertion = typeof expectVal === expectedType
      const message = `Expected '${expectVal}' to${
    if (!(Array.isArray(expectVal) || typeof expectVal === "string")) {
      let assertion = expectVal.length === expectedLength
    if (!(Array.isArray(expectVal) || typeof expectVal === "string")) {
    let assertion = expectVal.includes(needle)
    const expectValPretty = JSON.stringify(expectVal)
import * as E from "fp-ts/Either"
import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function"
import { createContext, runInContext } from "vm"

import { TestResponse, TestResult } from "~/types"
import { getTestRunnerScriptMethods, preventCyclicObjects } from "~/utils"

export const runTestScript = (
  testScript: string,
  envs: TestResult["envs"],
  response: TestResponse
): TE.TaskEither<string, TestResult> =>
  pipe(
    TE.tryCatch(
      async () => {
        return createContext()
      },
      (reason) => `Context initialization failed: ${reason}`
    ),
    TE.chain((context) =>
      TE.tryCatch(
        () => executeScriptInContext(testScript, envs, response, context),
        (reason) => `Script execution failed: ${reason}`
      )
    )
  )

const executeScriptInContext = (
  testScript: string,
  envs: TestResult["envs"],
  response: TestResponse,
  context: any
): Promise<TestResult> => {
  return new Promise((resolve, reject) => {
    // Parse response object
    const responseObjHandle = preventCyclicObjects(response)
    if (E.isLeft(responseObjHandle)) {
      return reject(`Response parsing failed: ${responseObjHandle.left}`)
    }

    const { pw, testRunStack, updatedEnvs } = getTestRunnerScriptMethods(envs)

    // Expose pw to the context
    context.pw = { ...pw, response: responseObjHandle.right }
    context.atob = atob
    context.btoa = btoa

    // Run the test script in the provided context
    runInContext(testScript, context)

    resolve({
      tests: testRunStack,
      envs: updatedEnvs,
    })
  })
}
export * from "./pre-request/web-worker"
export * from "./test-runner/web-worker"
import { getPreRequestScriptMethods } from "~/utils"
import { getTestRunnerScriptMethods, preventCyclicObjects } from "~/utils"
        web: "./src/web.ts",
        node: "./src/node.ts",
      external: ["vm"],
export { default } from "./dist/web.d.ts"
export * from "./dist/web.d.ts"
      jest:
        specifier: 27.5.1
        version: 27.5.1
      vue: 3.3.9(typescript@5.3.2)
      vue: 3.3.9(typescript@5.3.2)
      '@types/node': 18.18.8
      vite: 3.2.4(@types/node@18.18.8)(sass@1.58.0)(terser@5.27.0)
      vue: 3.3.9(typescript@5.3.2)
      vue: 3.3.9(typescript@5.3.2)
    dev: true
    dev: true
    dev: true
      semver: 7.5.4
      '@types/node': 18.18.8
    dev: true
    dev: true
      vite: 3.2.4(@types/node@18.18.8)(sass@1.58.0)(terser@5.27.0)
      vue: 3.3.9(typescript@5.3.2)
      vue: 3.3.9(typescript@5.3.2)

Fixed code:
        uses: actions/checkout@v4
      - name: Setup node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

        uses: pnpm/action-setup@v3
// @ts-check
import { spawnSync } from "child_process";
import { cloneDeep } from "lodash-es";

const nodeVersion = parseInt(process.versions.node.split(".")[0]);

// As per isolated-vm documentation, we need to supply `--no-node-snapshot` for node >= 20
// src: https://github.com/laverdet/isolated-vm?tab=readme-ov-file#requirements
if (nodeVersion >= 20 && !process.execArgv.includes("--no-node-snapshot")) {
  const argCopy = cloneDeep(process.argv);

  // Replace first argument with --no-node-snapshot
  // We can get argv[0] from process.argv0
  argCopy[0] = "--no-node-snapshot";

  const result = spawnSync(
    process.argv0,
    argCopy,
    { stdio: "inherit" }
  );

  // Exit with the same status code as the spawned process
  process.exit(result.status ?? 0);
} else {
  cli(process.argv);
}
  "version": "0.7.1",
    "isolated-vm": "4.7.2",
    jest.setTimeout(100000);
  "v": 1,
  "name": "coll-v1",
  "folders": [
    {
      "v": 1,
      "name": "coll-v1-child",
      "folders": [],
      "requests": [
          "url": "https://echo.hoppscotch.io",
          "path": "/get",
          "headers": [
            { "key": "Inactive-Header", "value": "Inactive Header", "active": false },
            { "key": "Authorization", "value": "Bearer token123", "active": true }
          ],
          "params": [
            { "key": "key", "value": "value", "active": true },
            { "key": "inactive-key", "value": "inactive-param", "active": false }
          ],
          "name": "req-v0-II",
          "method": "GET",
          "preRequestScript": "",
          "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
          "contentType": "application/json",
          "body": "",
          "auth": "Bearer Token",
          "bearerToken": "token123"
        }
      ]
    }
  ],
  "requests": [
    {
      "url": "https://echo.hoppscotch.io",
      "path": "/get",
      "headers": [
        { "key": "Inactive-Header", "value": "Inactive Header", "active": false },
        { "key": "Authorization", "value": "Bearer token123", "active": true }
      ],
      "params": [
        { "key": "key", "value": "value", "active": true },
        { "key": "inactive-key", "value": "inactive-param", "active": false }
      ],
      "name": "req-v0",
      "method": "GET",
      "preRequestScript": "",
      "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
      "contentType": "application/json",
      "body": "",
      "auth": "Bearer Token",
      "bearerToken": "token123"
    }
  ]
}
    "folders": [
        {
            "v": 1,
            "name": "coll-v1-child",
            "folders": [],
            "requests": [
                {
                    "v": "1",
                    "endpoint": "https://echo.hoppscotch.io",
                    "headers": [
                        {
                            "key": "Inactive-Header",
                            "value": "Inactive Header",
                            "active": false
                        },
                        {
                            "key": "Authorization",
                            "value": "Bearer token123",
                            "active": true
                        }
                    ],
                    "params": [
                        {
                            "key": "key",
                            "value": "value",
                            "active": true
                        },
                        {
                            "key": "inactive-key",
                            "value": "inactive-param",
                            "active": false
                        }
                    ],
                    "name": "req-v1-II",
                    "method": "GET",
                    "preRequestScript": "",
                    "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
                    "body": {
                        "contentType": null,
                        "body": null
                    },
                    "auth": {
                        "authType": "bearer",
                        "authActive": true,
                        "token": "token123"
                    }
                }
            ]
        }
    ],
            "endpoint": "https://echo.hoppscotch.io",
            "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
    "folders": [
        {
            "v": 2,
            "name": "coll-v2-child",
            "folders": [],
            "requests": [
                {
                    "v": "2",
                    "endpoint": "https://echo.hoppscotch.io",
                    "headers": [
                        {
                            "key": "Inactive-Header",
                            "value": "Inactive Header",
                            "active": false
                        },
                        {
                            "key": "Authorization",
                            "value": "Bearer token123",
                            "active": true
                        }
                    ],
                    "params": [
                        {
                            "key": "key",
                            "value": "value",
                            "active": true
                        },
                        {
                            "key": "inactive-key",
                            "value": "inactive-param",
                            "active": false
                        }
                    ],
                    "name": "req-v2-II",
                    "method": "GET",
                    "preRequestScript": "",
                    "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
                    "body": {
                        "contentType": null,
                        "body": null
                    },
                    "auth": {
                        "authType": "bearer",
                        "authActive": true,
                        "token": "token123"
                    },
                    "requestVariables": []
                }
            ],
            "auth": {
                "authType": "inherit",
                "authActive": true
            },
            "headers": []
        }
    ],
            "endpoint": "https://echo.hoppscotch.io",
            "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
    "folders": [
        {
            "v": 2,
            "name": "coll-v2-child",
            "folders": [],
            "requests": [
                {
                    "v": "3",
                    "endpoint": "https://echo.hoppscotch.io",
                    "headers": [
                        {
                            "key": "Inactive-Header",
                            "value": "Inactive Header",
                            "active": false
                        },
                        {
                            "key": "Authorization",
                            "value": "Bearer token123",
                            "active": true
                        }
                    ],
                    "params": [
                        {
                            "key": "key",
                            "value": "value",
                            "active": true
                        },
                        {
                            "key": "inactive-key",
                            "value": "inactive-param",
                            "active": false
                        }
                    ],
                    "name": "req-v3-II",
                    "method": "GET",
                    "preRequestScript": "",
                    "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
                    "body": {
                        "contentType": null,
                        "body": null
                    },
                    "auth": {
                        "authType": "bearer",
                        "authActive": true,
                        "token": "token123"
                    },
                    "requestVariables": []
                }
            ],
            "auth": {
                "authType": "inherit",
                "authActive": true
            },
            "headers": []
        }
    ],
            "endpoint": "https://echo.hoppscotch.io",
            "testScript": "pw.test(\"Asserts request params\", () => {\n  pw.expect(pw.response.body.args.key).toBe(\"value\")\n  pw.expect(pw.response.body.args[\"inactive-key\"]).toBe(undefined)\n})\n\npw.test(\"Asserts request headers\", () => {\n  pw.expect(pw.response.body.headers[\"authorization\"]).toBe(\"Bearer token123\")\n  pw.expect(pw.response.body.headers[\"inactive-header\"]).toBe(undefined)\n})",
      "auth": {
        "authType": "none",
        "authActive": true
      },
      "body": {
        "body": null,
        "contentType": null
      },
      "endpoint": "<<echoHoppBaseURL>>/headers",
      "testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n    const secretHeaderValue = pw.env.get(\"secretHeaderValue\")\n    pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n    \n    if (secretHeaderValue) {\n      pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n    }\n\n    pw.expect(pw.env.get(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
      "auth": {
        "authType": "none",
        "authActive": true
      },
      "endpoint": "<<echoHoppBaseURL>>/post",
      "testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n    const secretBodyValue = pw.env.get(\"secretBodyValue\")\n    pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n  \n    if (secretBodyValue) {\n      pw.expect(JSON.parse(pw.response.body.data).secretBodyKey).toBe(secretBodyValue)\n    }\n\n    pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
      "auth": {
        "authType": "none",
        "authActive": true
      },
      "body": {
        "body": null,
        "contentType": null
      },
      "endpoint": "<<echoHoppBaseURL>>",
      "body": {
        "body": null,
        "contentType": null
      },
      "endpoint": "<<httpbinBaseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
      "testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n  \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n    pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n    pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n    // The endpoint at times results in a `502` bad gateway\n    if (pw.response.status !== 200) {\n      return\n    }\n\n    if (secretBasicAuthUsername && secretBasicAuthPassword) {\n      const { authenticated, user } = pw.response.body\n      pw.expect(authenticated).toBe(true)\n      pw.expect(user).toBe(secretBasicAuthUsername)\n    }\n});",
      "body": {
        "body": null,
        "contentType": null
      },
      "endpoint": "<<httpbinBaseURL>>/bearer",
      "testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n    const secretBearerToken = pw.env.get(\"secretBearerToken\")\n  const preReqSecretBearerToken = pw.env.get(\"preReqSecretBearerToken\")\n\n    pw.expect(secretBearerToken).toBe(\"test-token\")\n\n    // Safeguard to prevent test failures due to the endpoint\n    if (pw.response.status !== 200) {\n      return\n    }\n\n    if (secretBearerToken) { \n      pw.expect(pw.response.body.token).toBe(secretBearerToken)\n      pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n    }\n});",
      "auth": {
        "authType": "none",
        "authActive": true
      },
      "body": {
        "body": null,
        "contentType": null
      },
      "endpoint": "<<echoHoppBaseURL>>",
  "auth": {
    "authType": "inherit",
    "authActive": false
  },
}
  "name": "secret-envs-persistence-coll",
      "endpoint": "<<echoHoppBaseURL>>",
      "testScript": "pw.test(\"Successfully parses secret variable holding the header value\", () => {\n    const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n    pw.expect(secretHeaderValue).toBe(\"secret-header-value\")\n    \n    if (secretHeaderValue) {\n      pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n    }\n\n    pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value\")\n})",
      "endpoint": "<<echoHoppBaseURL>>",
      "testScript": "pw.test(\"Value set at the pre-request script takes precedence\", () => {\n    const secretHeaderValue = pw.env.getResolve(\"secretHeaderValue\")\n    pw.expect(secretHeaderValue).toBe(\"secret-header-value-overriden\")\n    \n    if (secretHeaderValue) {\n      pw.expect(pw.response.body.headers[\"secret-header-key\"]).toBe(secretHeaderValue)\n    }\n\n    pw.expect(pw.env.getResolve(\"secretHeaderValueFromPreReqScript\")).toBe(\"secret-header-value-overriden\")\n})",
      "endpoint": "<<echoHoppBaseURL>>/post",
      "testScript": "pw.test(\"Successfully parses secret variable holding the request body value\", () => {\n    const secretBodyValue = pw.env.get(\"secretBodyValue\")\n    pw.expect(secretBodyValue).toBe(\"secret-body-value\")\n  \n    if (secretBodyValue) {\n      pw.expect(JSON.parse(pw.response.body.data).secretBodyKey).toBe(secretBodyValue)\n    }\n\n    pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})",
      "endpoint": "<<echoHoppBaseURL>>",
      "endpoint": "<<httpbinBaseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>",
      "testScript": "pw.test(\"Successfully parses secret variables holding basic auth credentials\", () => {\n\tconst secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n  \tconst secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\n    pw.expect(secretBasicAuthUsername).toBe(\"test-user\")\n    pw.expect(secretBasicAuthPassword).toBe(\"test-pass\")\n\n    // The endpoint at times results in a `502` bad gateway\n    if (pw.response.status !== 200) {\n      return\n    }\n\n    if (secretBasicAuthUsername && secretBasicAuthPassword) {\n      const { authenticated, user } = pw.response.body\n      pw.expect(authenticated).toBe(true)\n      pw.expect(user).toBe(secretBasicAuthUsername)\n    }\n});",
      "endpoint": "<<httpbinBaseURL>>/bearer",
      "testScript": "pw.test(\"Successfully parses secret variable holding the bearer token\", () => {\n    const secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n  const preReqSecretBearerToken = pw.env.resolve(\"<<preReqSecretBearerToken>>\")\n\n    pw.expect(secretBearerToken).toBe(\"test-token\")\n\n    // Safeguard to prevent test failures due to the endpoint\n    if (pw.response.status !== 200) {\n      return\n    }\n\n    if (secretBearerToken) { \n      pw.expect(pw.response.body.token).toBe(secretBearerToken)\n      pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n    }\n});",
}
      "endpoint": "https://echo.hoppscotch.io/post",
      "testScript": "pw.test(\"Secret environment value set from the pre-request script takes precedence\", () => {\n  pw.expect(pw.env.get(\"preReqVarOne\")).toBe(\"pre-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the pre-request script\", () => {\n  pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n  pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request headers that are set in pre-request script\", () => {\n  pw.expect(pw.response.body.headers[\"custom-header\"]).toBe(\"custom-header-secret-value\")\n})\n\npw.test(\"Successfully resolves secret variable values referred in request body that are set in pre-request script\", () => {\n  pw.expect(JSON.parse(pw.response.body.data).key).toBe(\"custom-body-value\")\n})\n\npw.test(\"Secret environment variable set from the post-request script takes precedence\", () => {\n  pw.env.set(\"postReqVarOne\", \"post-req-value-one\")\n  pw.expect(pw.env.get(\"postReqVarOne\")).toBe(\"post-req-value-one\")\n})\n\npw.test(\"Successfully sets initial value for the secret variable from the post-request script\", () => {\n  pw.env.set(\"postReqVarTwo\", \"post-req-value-two\")\n  pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(\"post-req-value-two\")\n})\n\npw.test(\"Successfully removes environment variables via the pw.env.unset method\", () => {\n  pw.env.unset(\"preReqVarOne\")\n  pw.env.unset(\"postReqVarTwo\")\n\n  pw.expect(pw.env.get(\"preReqVarOne\")).toBe(undefined)\n  pw.expect(pw.env.get(\"postReqVarTwo\")).toBe(undefined)\n})",
      "key": "echoHoppBaseURL",
      "value": "https://echo.hoppscotch.io",
      "secret": false
    },
    {
      "key": "httpbinBaseURL",
      "key": "echoHoppBaseURL",
      "value": "https://echo.hoppscotch.io",
      "secret": false
    },
    {
      "key": "httpbinBaseURL",
export const runCLI = (args: string, options = {}): Promise<ExecResponse> => {
  const CLI_PATH = resolve(__dirname, "../../bin/hopp.js");
  const command = `node ${CLI_PATH} ${args}`;

  return new Promise((resolve) =>
    exec(command, options, (error, stdout, stderr) =>
      resolve({ error, stdout, stderr })
    )
  );
};
export const getTestJsonFilePath = (
  file: string,
  kind: "collection" | "environment"
) => {
  const filePath = resolve(
    __dirname,
    `../../src/__tests__/samples/${kindDir}/${file}`
  );

const program = new Command();
const getValidRequests = (
  collections: HoppCollection[],
  collectionFilePath: string
) => {
  return collections.map((collection) => {
    // Validate requests using zod schema
    const requestSchemaParsedResult = z
      .array(entityReference(HoppRESTRequest))
      .safeParse(collection.requests);

    // Handle validation errors
    if (!requestSchemaParsedResult.success) {
      throw error({
        code: "MALFORMED_COLLECTION",
        path: collectionFilePath,
        data: "Please check the collection data.",
      });
    }

    // Recursively validate requests in nested folders
    if (collection.folders.length > 0) {
      collection.folders = getValidRequests(
        collection.folders,
        collectionFilePath
      );
    }

    // Return validated collection
    return {
      ...collection,
      requests: requestSchemaParsedResult.data,
    };
  });
};

  return getValidRequests(collectionSchemaParsedResult.data, path);
export { default } from "./dist/node/index.d.ts"
export * from "./dist/node/index.d.ts"
    "test": "vitest run",
    "vite": "5.0.5",
    "vitest": "0.34.6"
  },
  "peerDependencies": {
    "isolated-vm": "4.7.2"
  },
  "peerDependenciesMeta": {
    "isolated-vm": {
      "optional": true
    }
// Vitest doesn't work without globals
// Ref: https://github.com/relmify/jest-fp-ts/issues/11

import decodeMatchers from "@relmify/jest-fp-ts/dist/decodeMatchers"
import eitherMatchers from "@relmify/jest-fp-ts/dist/eitherMatchers"
import optionMatchers from "@relmify/jest-fp-ts/dist/optionMatchers"
import theseMatchers from "@relmify/jest-fp-ts/dist/theseMatchers"
import eitherOrTheseMatchers from "@relmify/jest-fp-ts/dist/eitherOrTheseMatchers"
import { expect } from "vitest"

expect.extend(decodeMatchers.matchers)
expect.extend(eitherMatchers.matchers)
expect.extend(optionMatchers.matchers)
expect.extend(theseMatchers.matchers)
expect.extend(eitherOrTheseMatchers.matchers)
import { describe, expect, test } from "vitest"

import { runPreRequestScript, runTestScript } from "~/node"
import { describe, expect, test } from "vitest"

import { runTestScript } from "~/node"
import { describe, expect, test } from "vitest"

import { runTestScript } from "~/node"
import { describe, expect, test } from "vitest"

import { runTestScript } from "~/node"
import { describe, expect, test } from "vitest"

import { runTestScript } from "~/node"
import { describe, expect, test } from "vitest"

import { runTestScript } from "~/node"
import { describe, expect, test } from "vitest"

import { runTestScript } from "~/node"
            pw.expect(2).toBe(2)
import { describe, expect, test } from "vitest"

import { runTestScript } from "~/node"
import { describe, expect, test } from "vitest"

import { runTestScript } from "~/node"
import { describe, expect, test } from "vitest"

import { runTestScript } from "~/node"
import { describe, expect, test } from "vitest"

import { runTestScript } from "~/node"
import { describe, expect, test } from "vitest"
import { runPreRequestScript } from "~/node"
import { preventCyclicObjects } from "~/shared-utils"

import { describe, expect, test } from "vitest"
import { describe, expect, test } from "vitest"

import { runTestScript } from "~/node"
export { runPreRequestScript } from "./pre-request"
export { runTestScript } from "./test-runner"
import { pipe } from "fp-ts/function"
import * as TE from "fp-ts/lib/TaskEither"
import { createRequire } from "module"

import type ivmT from "isolated-vm"

import { TestResult } from "~/types"
import { getPreRequestScriptMethods } from "~/shared-utils"
import { getSerializedAPIMethods } from "./utils"

const nodeRequire = createRequire(import.meta.url)
const ivm = nodeRequire("isolated-vm")

export const runPreRequestScript = (
  preRequestScript: string,
  envs: TestResult["envs"]
): TE.TaskEither<string, TestResult["envs"]> =>
  pipe(
    TE.tryCatch(
      async () => {
        const isolate: ivmT.Isolate = new ivm.Isolate()
        const context = await isolate.createContext()
        return { isolate, context }
      },
      (reason) => `Context initialization failed: ${reason}`
    ),
    TE.chain(({ isolate, context }) =>
      pipe(
        TE.tryCatch(
          async () => {
            const jail = context.global

            const { pw, updatedEnvs } = getPreRequestScriptMethods(envs)

            const serializedAPIMethods = getSerializedAPIMethods(pw)
            jail.setSync("serializedAPIMethods", serializedAPIMethods, {
              copy: true,
            })

            jail.setSync("atob", atob)
            jail.setSync("btoa", btoa)

            // Methods in the isolate context can't be invoked straightaway
            const finalScript = `
              const pw = new Proxy(serializedAPIMethods, {
                get: (pwObjTarget, pwObjProp) => {
                  const topLevelEntry = pwObjTarget[pwObjProp]

                  // "pw.env" set of API methods
                    if (topLevelEntry && typeof topLevelEntry === "object") {
                      return new Proxy(topLevelEntry, {
                        get: (subTarget, subProp) => {
                          const subLevelProperty = subTarget[subProp]
                          if (subLevelProperty && subLevelProperty.typeof === "function") {
                            return (...args) => subLevelProperty.applySync(null, args)
                          }
                        },
                      })
                    }
                }
              })

              ${preRequestScript}
            `

            // Create a script and compile it
            const script = await isolate.compileScript(finalScript)

            // Run the pre-request script in the provided context
            await script.run(context)

            return updatedEnvs
          },
          (reason) => reason
        ),
        TE.fold(
          (error) => TE.left(`Script execution failed: ${error}`),
          (result) =>
            pipe(
              TE.tryCatch(
                async () => {
                  await isolate.dispose()
                  return result
                },
                (disposeError) => `Isolate disposal failed: ${disposeError}`
              )
            )
        )
      )
    )
  )
import * as E from "fp-ts/Either"
import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function"
import { createRequire } from "module"

import type ivmT from "isolated-vm"

import { TestResponse, TestResult } from "~/types"
import {
  getTestRunnerScriptMethods,
  preventCyclicObjects,
} from "~/shared-utils"
import { getSerializedAPIMethods } from "./utils"

const nodeRequire = createRequire(import.meta.url)
const ivm = nodeRequire("isolated-vm")

export const runTestScript = (
  testScript: string,
  envs: TestResult["envs"],
  response: TestResponse
): TE.TaskEither<string, TestResult> =>
  pipe(
    TE.tryCatch(
      async () => {
        const isolate: ivmT.Isolate = new ivm.Isolate()
        const context = await isolate.createContext()
        return { isolate, context }
      },
      (reason) => `Context initialization failed: ${reason}`
    ),
    TE.chain(({ isolate, context }) =>
      pipe(
        TE.tryCatch(
          async () =>
            executeScriptInContext(
              testScript,
              envs,
              response,
              isolate,
              context
            ),
          (reason) => `Script execution failed: ${reason}`
        ),
        TE.chain((result) =>
          TE.tryCatch(
            async () => {
              await isolate.dispose()
              return result
            },
            (disposeReason) => `Isolate disposal failed: ${disposeReason}`
          )
        )
      )
    )
  )
const executeScriptInContext = (
  testScript: string,
  envs: TestResult["envs"],
  response: TestResponse,
  isolate: ivmT.Isolate,
  context: ivmT.Context
): Promise<TestResult> => {
  return new Promise((resolve, reject) => {
    // Parse response object
    const responseObjHandle = preventCyclicObjects(response)
    if (E.isLeft(responseObjHandle)) {
      return reject(`Response parsing failed: ${responseObjHandle.left}`)
    }

    const jail = context.global

    const { pw, testRunStack, updatedEnvs } = getTestRunnerScriptMethods(envs)

    const serializedAPIMethods = getSerializedAPIMethods({
      ...pw,
      response: responseObjHandle.right,
    })
    jail.setSync("serializedAPIMethods", serializedAPIMethods, { copy: true })

    jail.setSync("atob", atob)
    jail.setSync("btoa", btoa)

    jail.setSync("ivm", ivm)

    // Methods in the isolate context can't be invoked straightaway
    const finalScript = `
    const pw = new Proxy(serializedAPIMethods, {
      get: (pwObj, pwObjProp) => {
        // pw.expect(), pw.env, etc.
        const topLevelEntry = pwObj[pwObjProp]

        // If the entry exists and is a function
        // pw.expect(), pw.test(), etc.
        if (topLevelEntry && topLevelEntry.typeof === "function") {
          // pw.test() just involves invoking the function via "applySync()"
          if (pwObjProp === "test") {
            return (...args) => topLevelEntry.applySync(null, args)
          }

          // pw.expect() returns an object with matcher methods
          return (...args) => {
            // Invoke "pw.expect()" and get access to the object with matcher methods
            const expectFnResult = topLevelEntry.applySync(
              null,
              args.map((expectVal) => {
                if (typeof expectVal === "object") {
                  if (expectVal === null) {
                    return null
                  }

                  // Only arrays and objects stringified here should be parsed from the "pw.expect()" method definition
                  // The usecase is that any JSON string supplied should be preserved
                  // An extra "isStringifiedWithinIsolate" prop is added to indicate it has to be parsed

                  if (Array.isArray(expectVal)) {
                    return JSON.stringify({
                      arr: expectVal,
                      isStringifiedWithinIsolate: true,
                    })
                  }

                  return JSON.stringify({
                    ...expectVal,
                    isStringifiedWithinIsolate: true,
                  })
                }

                return expectVal
              })
            )

            // Matcher methods that can be chained with "pw.expect()"
            // pw.expect().toBe(), etc
            if (expectFnResult.typeof === "object") {
              // Access the getter that points to the negated matcher methods via "{ accessors: true }"
              const matcherMethods = {
                not: expectFnResult.getSync("not", { accessors: true }),
              }

              // Serialize matcher methods for use in the isolate context
              const matcherMethodNames = [
                "toBe",
                "toBeLevel2xx",
                "toBeLevel3xx",
                "toBeLevel4xx",
                "toBeLevel5xx",
                "toBeType",
                "toHaveLength",
                "toInclude",
              ]
              matcherMethodNames.forEach((methodName) => {
                matcherMethods[methodName] = expectFnResult.getSync(methodName)
              })

              return new Proxy(matcherMethods, {
                get: (matcherMethodTarget, matcherMethodProp) => {
                  // pw.expect().not.toBe(), etc
                  const matcherMethodEntry = matcherMethodTarget[matcherMethodProp]

                  if (matcherMethodProp === "not") {
                    return new Proxy(matcherMethodEntry, {
                      get: (negatedObjTarget, negatedObjprop) => {
                        // Return the negated matcher method defn that is invoked from the test script
                        const negatedMatcherMethodDefn = negatedObjTarget.getSync(negatedObjprop)
                        return negatedMatcherMethodDefn
                      },
                    })
                  }

                  // Return the matcher method defn that is invoked from the test script
                  return matcherMethodEntry
                },
              })
            }
          }
        }

        // "pw.env" set of API methods
        if (typeof topLevelEntry === "object" && pwObjProp !== "response") {
          return new Proxy(topLevelEntry, {
            get: (subTarget, subProp) => {
              const subLevelProperty = subTarget[subProp]
              if (
                subLevelProperty &&
                subLevelProperty.typeof === "function"
              ) {
                return (...args) => subLevelProperty.applySync(null, args)
              }
            },
          })
        }

        return topLevelEntry
      },
    })

      ${testScript}
    `

    // Create a script and compile it
    const script = isolate.compileScript(finalScript)

    // Run the test script in the provided context
    script
      .then((script) => script.run(context))
      .then(() => {
        resolve({
          tests: testRunStack,
          envs: updatedEnvs,
        })
      })
      .catch((error: Error) => {
        reject(error)
      })
  })
}
import { createRequire } from "module"

const nodeRequire = createRequire(import.meta.url)
const ivm = nodeRequire("isolated-vm")

// Helper function to recursively wrap methods in `ivm.Reference`
export const getSerializedAPIMethods = (
  namespaceObj: Record<string, unknown>
): Record<string, unknown> => {
  const result: Record<string, unknown> = {}

  for (const [key, value] of Object.entries(namespaceObj)) {
    if (typeof value === "object" && value !== null && !Array.isArray(value)) {
      result[key] = getSerializedAPIMethods(value as Record<string, unknown>)
    } else if (typeof value === "function") {
      result[key] = new ivm.Reference(value)
    } else {
      result[key] = value
    }
  }

  return result
}
const getResolvedExpectValue = (expectVal: any) => {
  if (typeof expectVal !== "string") {
    return expectVal
  }

  try {
    const parsedExpectVal = JSON.parse(expectVal)

    // Supplying non-primitive values is not permitted in the `isStringifiedWithinIsolate` property indicates that the object was stringified before executing the script from the isolate context
    // This is done to ensure a JSON string supplied as the "expectVal" is not parsed and preserved as is
    if (typeof parsedExpectVal === "object") {
      if (parsedExpectVal.isStringifiedWithinIsolate !== true) {
        return expectVal
      }

      // For an array, the contents are stored in the `arr` property
      if (Array.isArray(parsedExpectVal.arr)) {
        return parsedExpectVal.arr
      }

      delete parsedExpectVal.isStringifiedWithinIsolate
      return parsedExpectVal
    }

    return expectVal
  } catch (_) {
    return expectVal
  }
}

  // Non-primitive values supplied are stringified in the isolate context
  const resolvedExpectVal = getResolvedExpectValue(expectVal)

    let assertion = resolvedExpectVal === expectedVal
    const message = `Expected '${resolvedExpectVal}' to${
    const parsedExpectVal = parseInt(resolvedExpectVal)
      const message = `Expected ${level}-level status but could not parse value '${resolvedExpectVal}'`
      let assertion = typeof resolvedExpectVal === expectedType
      const message = `Expected '${resolvedExpectVal}' to${
    if (
      !(
        Array.isArray(resolvedExpectVal) ||
        typeof resolvedExpectVal === "string"
      )
    ) {
      let assertion = resolvedExpectVal.length === expectedLength
    if (
      !(
        Array.isArray(resolvedExpectVal) ||
        typeof resolvedExpectVal === "string"
      )
    ) {
    let assertion = resolvedExpectVal.includes(needle)
    const expectValPretty = JSON.stringify(resolvedExpectVal)
export { runPreRequestScript } from "./pre-request"
export { runTestScript } from "./test-runner"
import { getPreRequestScriptMethods } from "~/shared-utils"
import {
  getTestRunnerScriptMethods,
  preventCyclicObjects,
} from "~/shared-utils"
        web: "./src/web/index.ts",
        node: "./src/node/index.ts",
      external: ["module"],
  test: {
    environment: "node",
    setupFiles: ["./setupFiles.ts"],
  },
export { default } from "./dist/web/index.d.ts"
export * from "./dist/web/index.d.ts"
      isolated-vm:
        specifier: 4.7.2
        version: 4.7.2
      isolated-vm:
        specifier: 4.7.2
        version: 4.7.2
      vitest:
        specifier: 0.34.6
        version: 0.34.6(sass@1.69.5)(terser@5.27.0)
      vue: 3.3.9(typescript@4.9.5)
      vue: 3.3.9(typescript@4.9.5)
      '@types/node': 17.0.45
      vite: 3.2.4(@types/node@17.0.27)(terser@5.27.0)
      vue: 3.3.9(typescript@4.9.5)
      vue: 3.3.9(typescript@4.9.5)
  /chownr@1.1.4:
    resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
    dev: false

  /decompress-response@6.0.0:
    resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
    engines: {node: '>=10'}
    dependencies:
      mimic-response: 3.1.0
    dev: false

  /expand-template@2.0.3:
    resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
    engines: {node: '>=6'}
    dev: false

  /fs-constants@1.0.0:
    resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
    dev: false

  /github-from-package@0.0.0:
    resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
    dev: false

  /isolated-vm@4.7.2:
    resolution: {integrity: sha512-JVEs5gzWObzZK5+OlBplCdYSpokMcdhLSs/xWYYxmYWVfOOFF4oZJsYh7E/FmfX8e7gMioXMpMMeEyX1afuKrg==}
    engines: {node: '>=16.0.0'}
    requiresBuild: true
    dependencies:
      prebuild-install: 7.1.2
    dev: false

      semver: 7.6.0
      '@types/node': 17.0.45
  /mimic-response@3.1.0:
    resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
    engines: {node: '>=10'}
    dev: false

  /mkdirp-classic@0.5.3:
    resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
    dev: false

  /napi-build-utils@1.0.2:
    resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
    dev: false

  /node-abi@3.57.0:
    resolution: {integrity: sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==}
    engines: {node: '>=10'}
    dependencies:
      semver: 7.6.0
    dev: false

  /prebuild-install@7.1.2:
    resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==}
    engines: {node: '>=10'}
    hasBin: true
    dependencies:
      detect-libc: 2.0.1
      expand-template: 2.0.3
      github-from-package: 0.0.0
      minimist: 1.2.6
      mkdirp-classic: 0.5.3
      napi-build-utils: 1.0.2
      node-abi: 3.57.0
      pump: 3.0.0
      rc: 1.2.8
      simple-get: 4.0.1
      tar-fs: 2.1.1
      tunnel-agent: 0.6.0
    dev: false

  /simple-concat@1.0.1:
    resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==}
    dev: false

  /simple-get@4.0.1:
    resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==}
    dependencies:
      decompress-response: 6.0.0
      once: 1.4.0
      simple-concat: 1.0.1
    dev: false

  /tar-fs@2.1.1:
    resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
    dependencies:
      chownr: 1.1.4
      mkdirp-classic: 0.5.3
      pump: 3.0.0
      tar-stream: 2.2.0
    dev: false

  /tar-stream@2.2.0:
    resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==}
    engines: {node: '>=6'}
    dependencies:
      bl: 4.1.0
      end-of-stream: 1.4.4
      fs-constants: 1.0.0
      inherits: 2.0.4
      readable-stream: 3.6.0
    dev: false

  /tunnel-agent@0.6.0:
    resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
    dependencies:
      safe-buffer: 5.2.1
    dev: false

      vite: 3.2.4(@types/node@17.0.27)(terser@5.27.0)
  /vite@3.2.4(@types/node@17.0.27)(terser@5.27.0):
    resolution: {integrity: sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==}
    engines: {node: ^14.18.0 || >=16.0.0}
    hasBin: true
    peerDependencies:
      '@types/node': '>= 14'
      less: '*'
      sass: '*'
      stylus: '*'
      sugarss: '*'
      terser: ^5.4.0
    peerDependenciesMeta:
      '@types/node':
        optional: true
      less:
        optional: true
      sass:
        optional: true
      stylus:
        optional: true
      sugarss:
        optional: true
      terser:
        optional: true
    dependencies:
      '@types/node': 17.0.27
      esbuild: 0.15.18
      postcss: 8.4.32
      resolve: 1.22.8
      rollup: 2.79.1
      terser: 5.27.0
    optionalDependencies:
      fsevents: 2.3.3

      vue: 3.3.9(typescript@4.9.5)
      vue: 3.3.9(typescript@4.9.5)
    deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained
    deprecated: It is not compatible with newer versions of GA starting with v4, as long as you are using GAv3 it should be ok, but the package is not longer being maintained