chore: migrate Node.js implementation for js-sandbox to isolated-vm (#3973)

Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
James George
2024-04-19 08:38:46 -07:00
committed by GitHub
parent a079e0f04b
commit 22c6eabd13
52 changed files with 1028 additions and 285 deletions

View File

@@ -17,22 +17,21 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Setup environment - name: Setup environment
run: mv .env.example .env run: mv .env.example .env
- name: Setup node
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Setup pnpm - name: Setup pnpm
uses: pnpm/action-setup@v2.2.4 uses: pnpm/action-setup@v3
with: with:
version: 8 version: 8
run_install: true run_install: true
- name: Setup node
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
cache: pnpm
- name: Run tests - name: Run tests
run: pnpm test run: pnpm test

View File

@@ -1,6 +1,31 @@
#!/usr/bin/env node #!/usr/bin/env node
// * The entry point of the CLI // * The entry point of the CLI
// @ts-check
import { cli } from "../dist/index.js"; import { cli } from "../dist/index.js";
cli(process.argv); 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);
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "@hoppscotch/cli", "name": "@hoppscotch/cli",
"version": "0.7.0", "version": "0.7.1",
"description": "A CLI to run Hoppscotch test scripts in CI environments.", "description": "A CLI to run Hoppscotch test scripts in CI environments.",
"homepage": "https://hoppscotch.io", "homepage": "https://hoppscotch.io",
"type": "module", "type": "module",
@@ -44,6 +44,7 @@
"axios": "1.6.7", "axios": "1.6.7",
"chalk": "5.3.0", "chalk": "5.3.0",
"commander": "11.1.0", "commander": "11.1.0",
"isolated-vm": "4.7.2",
"lodash-es": "4.17.21", "lodash-es": "4.17.21",
"qs": "6.11.2", "qs": "6.11.2",
"verzod": "0.2.2", "verzod": "0.2.2",

View File

@@ -224,7 +224,7 @@ describe("Test `hopp test <file> --env <file>` command:", () => {
}); });
describe("Secret environment variables", () => { describe("Secret environment variables", () => {
jest.setTimeout(10000); jest.setTimeout(100000);
// Reads secret environment values from system environment // Reads secret environment values from system environment
test("Successfully picks the values for secret environment variables from `process.env` and persists the variables set from the pre-request script", async () => { test("Successfully picks the values for secret environment variables from `process.env` and persists the variables set from the pre-request script", async () => {

View File

@@ -1,27 +1,55 @@
{ {
"v": 1, "v": 1,
"name": "coll-v1", "name": "coll-v1",
"folders": [], "folders": [
"requests": [ {
"v": 1,
"name": "coll-v1-child",
"folders": [],
"requests": [
{ {
"url": "https://httpbin.org", "url": "https://echo.hoppscotch.io",
"path": "/get", "path": "/get",
"headers": [ "headers": [
{ "key": "Inactive-Header", "value": "Inactive Header", "active": false }, { "key": "Inactive-Header", "value": "Inactive Header", "active": false },
{ "key": "Authorization", "value": "Bearer token123", "active": true } { "key": "Authorization", "value": "Bearer token123", "active": true }
], ],
"params": [ "params": [
{ "key": "key", "value": "value", "active": true }, { "key": "key", "value": "value", "active": true },
{ "key": "inactive-key", "value": "inactive-param", "active": false } { "key": "inactive-key", "value": "inactive-param", "active": false }
], ],
"name": "req-v0", "name": "req-v0-II",
"method": "GET", "method": "GET",
"preRequestScript": "", "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})", "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", "contentType": "application/json",
"body": "", "body": "",
"auth": "Bearer Token", "auth": "Bearer Token",
"bearerToken": "token123" "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"
}
]
}

View File

@@ -1,11 +1,60 @@
{ {
"v": 1, "v": 1,
"name": "coll-v1", "name": "coll-v1",
"folders": [], "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"
}
}
]
}
],
"requests": [ "requests": [
{ {
"v": "1", "v": "1",
"endpoint": "https://httpbin.org/get", "endpoint": "https://echo.hoppscotch.io",
"headers": [ "headers": [
{ {
"key": "Inactive-Header", "key": "Inactive-Header",
@@ -33,7 +82,7 @@
"name": "req-v1", "name": "req-v1",
"method": "GET", "method": "GET",
"preRequestScript": "", "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})", "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": { "body": {
"contentType": null, "contentType": null,
"body": null "body": null

View File

@@ -1,11 +1,66 @@
{ {
"v": 2, "v": 2,
"name": "coll-v2", "name": "coll-v2",
"folders": [], "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": []
}
],
"requests": [ "requests": [
{ {
"v": "2", "v": "2",
"endpoint": "https://httpbin.org/get", "endpoint": "https://echo.hoppscotch.io",
"headers": [ "headers": [
{ {
"key": "Inactive-Header", "key": "Inactive-Header",
@@ -33,7 +88,7 @@
"name": "req-v2", "name": "req-v2",
"method": "GET", "method": "GET",
"preRequestScript": "", "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})", "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": { "body": {
"contentType": null, "contentType": null,
"body": null "body": null

View File

@@ -1,11 +1,66 @@
{ {
"v": 2, "v": 2,
"name": "coll-v2", "name": "coll-v2",
"folders": [], "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": []
}
],
"requests": [ "requests": [
{ {
"v": "3", "v": "3",
"endpoint": "https://httpbin.org/get", "endpoint": "https://echo.hoppscotch.io",
"headers": [ "headers": [
{ {
"key": "Inactive-Header", "key": "Inactive-Header",
@@ -33,7 +88,7 @@
"name": "req-v3", "name": "req-v3",
"method": "GET", "method": "GET",
"preRequestScript": "", "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})", "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": { "body": {
"contentType": null, "contentType": null,
"body": null "body": null

View File

@@ -5,8 +5,14 @@
"requests": [ "requests": [
{ {
"v": "3", "v": "3",
"auth": { "authType": "none", "authActive": true }, "auth": {
"body": { "body": null, "contentType": null }, "authType": "none",
"authActive": true
},
"body": {
"body": null,
"contentType": null
},
"name": "test-secret-headers", "name": "test-secret-headers",
"method": "GET", "method": "GET",
"params": [], "params": [],
@@ -18,13 +24,16 @@
} }
], ],
"requestVariables": [], "requestVariables": [],
"endpoint": "<<baseURL>>/headers", "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})", "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})",
"preRequestScript": "const secretHeaderValueFromPreReqScript = pw.env.get(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)" "preRequestScript": "const secretHeaderValueFromPreReqScript = pw.env.get(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
}, },
{ {
"v": "3", "v": "3",
"auth": { "authType": "none", "authActive": true }, "auth": {
"authType": "none",
"authActive": true
},
"body": { "body": {
"body": "{\n \"secretBodyKey\": \"<<secretBodyValue>>\"\n}", "body": "{\n \"secretBodyKey\": \"<<secretBodyValue>>\"\n}",
"contentType": "application/json" "contentType": "application/json"
@@ -34,14 +43,20 @@
"params": [], "params": [],
"headers": [], "headers": [],
"requestVariables": [], "requestVariables": [],
"endpoint": "<<baseURL>>/post", "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(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})", "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})",
"preRequestScript": "const secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)" "preRequestScript": "const secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
}, },
{ {
"v": "3", "v": "3",
"auth": { "authType": "none", "authActive": true }, "auth": {
"body": { "body": null, "contentType": null }, "authType": "none",
"authActive": true
},
"body": {
"body": null,
"contentType": null
},
"name": "test-secret-query-params", "name": "test-secret-query-params",
"method": "GET", "method": "GET",
"params": [ "params": [
@@ -53,7 +68,7 @@
], ],
"headers": [], "headers": [],
"requestVariables": [], "requestVariables": [],
"endpoint": "<<baseURL>>/get", "endpoint": "<<echoHoppBaseURL>>",
"testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})", "testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})",
"preRequestScript": "const secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)" "preRequestScript": "const secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
}, },
@@ -65,14 +80,17 @@
"username": "<<secretBasicAuthUsername>>", "username": "<<secretBasicAuthUsername>>",
"authActive": true "authActive": true
}, },
"body": { "body": null, "contentType": null }, "body": {
"body": null,
"contentType": null
},
"name": "test-secret-basic-auth", "name": "test-secret-basic-auth",
"method": "GET", "method": "GET",
"params": [], "params": [],
"headers": [], "headers": [],
"requestVariables": [], "requestVariables": [],
"endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>", "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 if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});", "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});",
"preRequestScript": "" "preRequestScript": ""
}, },
{ {
@@ -84,30 +102,42 @@
"username": "testuser", "username": "testuser",
"authActive": true "authActive": true
}, },
"body": { "body": null, "contentType": null }, "body": {
"body": null,
"contentType": null
},
"name": "test-secret-bearer-auth", "name": "test-secret-bearer-auth",
"method": "GET", "method": "GET",
"params": [], "params": [],
"headers": [], "headers": [],
"requestVariables": [], "requestVariables": [],
"endpoint": "<<baseURL>>/bearer", "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 if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});", "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});",
"preRequestScript": "const secretBearerToken = pw.env.get(\"secretBearerToken\")\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)" "preRequestScript": "const secretBearerToken = pw.env.get(\"secretBearerToken\")\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)"
}, },
{ {
"v": "3", "v": "3",
"auth": { "authType": "none", "authActive": true }, "auth": {
"body": { "body": null, "contentType": null }, "authType": "none",
"authActive": true
},
"body": {
"body": null,
"contentType": null
},
"name": "test-secret-fallback", "name": "test-secret-fallback",
"method": "GET", "method": "GET",
"params": [], "params": [],
"headers": [], "headers": [],
"requestVariables": [], "requestVariables": [],
"endpoint": "<<baseURL>>", "endpoint": "<<echoHoppBaseURL>>",
"testScript": "pw.test(\"Returns an empty string if the value for a secret environment variable is not found in the system environment\", () => {\n pw.expect(pw.env.get(\"nonExistentValueInSystemEnv\")).toBe(\"\")\n})", "testScript": "pw.test(\"Returns an empty string if the value for a secret environment variable is not found in the system environment\", () => {\n pw.expect(pw.env.get(\"nonExistentValueInSystemEnv\")).toBe(\"\")\n})",
"preRequestScript": "" "preRequestScript": ""
} }
], ],
"auth": { "authType": "inherit", "authActive": false }, "auth": {
"authType": "inherit",
"authActive": false
},
"headers": [] "headers": []
} }

View File

@@ -1,6 +1,6 @@
{ {
"v": 2, "v": 2,
"name": "secret-envs-setters-coll", "name": "secret-envs-persistence-coll",
"folders": [], "folders": [],
"requests": [ "requests": [
{ {
@@ -24,8 +24,8 @@
"active": true "active": true
} }
], ],
"endpoint": "<<baseURL>>/headers", "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})", "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})",
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)" "preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
}, },
{ {
@@ -49,8 +49,8 @@
"active": true "active": true
} }
], ],
"endpoint": "<<baseURL>>/headers", "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})", "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})",
"preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value-overriden\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)" "preRequestScript": "pw.env.set(\"secretHeaderValue\", \"secret-header-value-overriden\")\n\nconst secretHeaderValueFromPreReqScript = pw.env.getResolve(\"secretHeaderValue\")\npw.env.set(\"secretHeaderValueFromPreReqScript\", secretHeaderValueFromPreReqScript)"
}, },
{ {
@@ -68,8 +68,8 @@
"params": [], "params": [],
"requestVariables": [], "requestVariables": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/post", "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(pw.response.body.json.secretBodyKey).toBe(secretBodyValue)\n }\n\n pw.expect(pw.env.get(\"secretBodyValueFromPreReqScript\")).toBe(\"secret-body-value\")\n})", "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})",
"preRequestScript": "const secretBodyValue = pw.env.get(\"secretBodyValue\")\n\nif (!secretBodyValue) { \n pw.env.set(\"secretBodyValue\", \"secret-body-value\")\n}\n\nconst secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)" "preRequestScript": "const secretBodyValue = pw.env.get(\"secretBodyValue\")\n\nif (!secretBodyValue) { \n pw.env.set(\"secretBodyValue\", \"secret-body-value\")\n}\n\nconst secretBodyValueFromPreReqScript = pw.env.get(\"secretBodyValue\")\npw.env.set(\"secretBodyValueFromPreReqScript\", secretBodyValueFromPreReqScript)"
}, },
{ {
@@ -93,7 +93,7 @@
], ],
"requestVariables": [], "requestVariables": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/get", "endpoint": "<<echoHoppBaseURL>>",
"testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})", "testScript": "pw.test(\"Successfully parses secret variable holding the query param value\", () => {\n const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n pw.expect(secretQueryParamValue).toBe(\"secret-query-param-value\")\n \n if (secretQueryParamValue) {\n pw.expect(pw.response.body.args.secretQueryParamKey).toBe(secretQueryParamValue)\n }\n\n pw.expect(pw.env.get(\"secretQueryParamValueFromPreReqScript\")).toBe(\"secret-query-param-value\")\n})",
"preRequestScript": "const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n\nif (!secretQueryParamValue) {\n pw.env.set(\"secretQueryParamValue\", \"secret-query-param-value\")\n}\n\nconst secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)" "preRequestScript": "const secretQueryParamValue = pw.env.get(\"secretQueryParamValue\")\n\nif (!secretQueryParamValue) {\n pw.env.set(\"secretQueryParamValue\", \"secret-query-param-value\")\n}\n\nconst secretQueryParamValueFromPreReqScript = pw.env.get(\"secretQueryParamValue\")\npw.env.set(\"secretQueryParamValueFromPreReqScript\", secretQueryParamValueFromPreReqScript)"
}, },
@@ -114,8 +114,8 @@
"params": [], "params": [],
"requestVariables": [], "requestVariables": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/basic-auth/<<secretBasicAuthUsername>>/<<secretBasicAuthPassword>>", "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 if (secretBasicAuthUsername && secretBasicAuthPassword) {\n const { authenticated, user } = pw.response.body\n pw.expect(authenticated).toBe(true)\n pw.expect(user).toBe(secretBasicAuthUsername)\n }\n});", "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});",
"preRequestScript": "let secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n\nlet secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\nif (!secretBasicAuthUsername) {\n pw.env.set(\"secretBasicAuthUsername\", \"test-user\")\n}\n\nif (!secretBasicAuthPassword) {\n pw.env.set(\"secretBasicAuthPassword\", \"test-pass\")\n}" "preRequestScript": "let secretBasicAuthUsername = pw.env.get(\"secretBasicAuthUsername\")\n\nlet secretBasicAuthPassword = pw.env.get(\"secretBasicAuthPassword\")\n\nif (!secretBasicAuthUsername) {\n pw.env.set(\"secretBasicAuthUsername\", \"test-user\")\n}\n\nif (!secretBasicAuthPassword) {\n pw.env.set(\"secretBasicAuthPassword\", \"test-pass\")\n}"
}, },
{ {
@@ -136,8 +136,8 @@
"params": [], "params": [],
"requestVariables": [], "requestVariables": [],
"headers": [], "headers": [],
"endpoint": "<<baseURL>>/bearer", "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 if (secretBearerToken) { \n pw.expect(pw.response.body.token).toBe(secretBearerToken)\n pw.expect(preReqSecretBearerToken).toBe(\"test-token\")\n }\n});", "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});",
"preRequestScript": "let secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n\nif (!secretBearerToken) {\n pw.env.set(\"secretBearerToken\", \"test-token\")\n secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n}\n\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)" "preRequestScript": "let secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n\nif (!secretBearerToken) {\n pw.env.set(\"secretBearerToken\", \"test-token\")\n secretBearerToken = pw.env.resolve(\"<<secretBearerToken>>\")\n}\n\npw.env.set(\"preReqSecretBearerToken\", secretBearerToken)"
} }
], ],
@@ -146,4 +146,4 @@
"authActive": false "authActive": false
}, },
"headers": [] "headers": []
} }

View File

@@ -5,7 +5,7 @@
"requests": [ "requests": [
{ {
"v": "3", "v": "3",
"endpoint": "https://httpbin.org/post", "endpoint": "https://echo.hoppscotch.io/post",
"name": "req", "name": "req",
"params": [], "params": [],
"headers": [ "headers": [
@@ -18,7 +18,7 @@
"method": "POST", "method": "POST",
"auth": { "authType": "none", "authActive": true }, "auth": { "authType": "none", "authActive": true },
"preRequestScript": "pw.env.set(\"preReqVarOne\", \"pre-req-value-one\")\n\npw.env.set(\"preReqVarTwo\", \"pre-req-value-two\")\n\npw.env.set(\"customHeaderValueFromSecretVar\", \"custom-header-secret-value\")\n\npw.env.set(\"customBodyValue\", \"custom-body-value\")", "preRequestScript": "pw.env.set(\"preReqVarOne\", \"pre-req-value-one\")\n\npw.env.set(\"preReqVarTwo\", \"pre-req-value-two\")\n\npw.env.set(\"customHeaderValueFromSecretVar\", \"custom-header-secret-value\")\n\npw.env.set(\"customBodyValue\", \"custom-body-value\")",
"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})", "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})",
"body": { "body": {
"contentType": "application/json", "contentType": "application/json",
"body": "{\n \"key\": \"<<customBodyValue>>\"\n}" "body": "{\n \"key\": \"<<customBodyValue>>\"\n}"

View File

@@ -32,7 +32,12 @@
"secret": true "secret": true
}, },
{ {
"key": "baseURL", "key": "echoHoppBaseURL",
"value": "https://echo.hoppscotch.io",
"secret": false
},
{
"key": "httpbinBaseURL",
"value": "https://httpbin.org", "value": "https://httpbin.org",
"secret": false "secret": false
} }

View File

@@ -38,7 +38,12 @@
"secret": true "secret": true
}, },
{ {
"key": "baseURL", "key": "echoHoppBaseURL",
"value": "https://echo.hoppscotch.io",
"secret": false
},
{
"key": "httpbinBaseURL",
"value": "https://httpbin.org", "value": "https://httpbin.org",
"secret": false "secret": false
} }

View File

@@ -3,15 +3,16 @@ import { resolve } from "path";
import { ExecResponse } from "./types"; import { ExecResponse } from "./types";
export const runCLI = (args: string, options = {}): Promise<ExecResponse> => export const runCLI = (args: string, options = {}): Promise<ExecResponse> => {
{ const CLI_PATH = resolve(__dirname, "../../bin/hopp.js");
const CLI_PATH = resolve(__dirname, "../../bin/hopp"); const command = `node ${CLI_PATH} ${args}`;
const command = `node ${CLI_PATH} ${args}`
return new Promise((resolve) => return new Promise((resolve) =>
exec(command, options, (error, stdout, stderr) => resolve({ error, stdout, stderr })) exec(command, options, (error, stdout, stderr) =>
); resolve({ error, stdout, stderr })
} )
);
};
export const trimAnsi = (target: string) => { export const trimAnsi = (target: string) => {
const ansiRegex = const ansiRegex =
@@ -25,12 +26,18 @@ export const getErrorCode = (out: string) => {
return ansiTrimmedStr.split(" ")[0]; return ansiTrimmedStr.split(" ")[0];
}; };
export const getTestJsonFilePath = (file: string, kind: "collection" | "environment") => { export const getTestJsonFilePath = (
file: string,
kind: "collection" | "environment"
) => {
const kindDir = { const kindDir = {
collection: "collections", collection: "collections",
environment: "environments", environment: "environments",
}[kind]; }[kind];
const filePath = resolve(__dirname, `../../src/__tests__/samples/${kindDir}/${file}`); const filePath = resolve(
__dirname,
`../../src/__tests__/samples/${kindDir}/${file}`
);
return filePath; return filePath;
}; };

View File

@@ -1,6 +1,7 @@
import chalk from "chalk"; import chalk from "chalk";
import { Command } from "commander"; import { Command } from "commander";
import * as E from "fp-ts/Either"; import * as E from "fp-ts/Either";
import { version } from "../package.json"; import { version } from "../package.json";
import { test } from "./commands/test"; import { test } from "./commands/test";
import { handleError } from "./handlers/error"; import { handleError } from "./handlers/error";
@@ -20,7 +21,7 @@ const CLI_AFTER_ALL_TXT = `\nFor more help, head on to ${accent(
"https://docs.hoppscotch.io/documentation/clients/cli" "https://docs.hoppscotch.io/documentation/clients/cli"
)}`; )}`;
const program = new Command() const program = new Command();
program program
.name("hopp") .name("hopp")

View File

@@ -7,6 +7,41 @@ import { error } from "../types/errors";
import { FormDataEntry } from "../types/request"; import { FormDataEntry } from "../types/request";
import { isHoppErrnoException } from "./checks"; import { isHoppErrnoException } from "./checks";
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,
};
});
};
/** /**
* Parses array of FormDataEntry to FormData. * Parses array of FormDataEntry to FormData.
* @param values Array of FormDataEntry. * @param values Array of FormDataEntry.
@@ -82,22 +117,5 @@ export async function parseCollectionData(
}); });
} }
return collectionSchemaParsedResult.data.map((collection) => { return getValidRequests(collectionSchemaParsedResult.data, path);
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,
};
});
} }

View File

@@ -1,10 +0,0 @@
export default {
preset: "ts-jest",
testEnvironment: "jsdom",
collectCoverage: true,
setupFilesAfterEnv: ["./jest.setup.ts"],
moduleNameMapper: {
"~/(.*)": "<rootDir>/src/$1",
"^lodash-es$": "lodash",
},
}

View File

@@ -1 +0,0 @@
require("@relmify/jest-fp-ts")

View File

@@ -1,2 +1,2 @@
export { default } from "./dist/node.d.ts" export { default } from "./dist/node/index.d.ts"
export * from "./dist/node.d.ts" export * from "./dist/node/index.d.ts"

View File

@@ -30,7 +30,7 @@
"scripts": { "scripts": {
"lint": "eslint --ext .ts,.js --ignore-path .gitignore .", "lint": "eslint --ext .ts,.js --ignore-path .gitignore .",
"lintfix": "eslint --fix --ext .ts,.js --ignore-path .gitignore .", "lintfix": "eslint --fix --ext .ts,.js --ignore-path .gitignore .",
"test": "pnpm exec jest", "test": "vitest run",
"build": "vite build && tsc --emitDeclarationOnly", "build": "vite build && tsc --emitDeclarationOnly",
"clean": "pnpm tsc --build --clean", "clean": "pnpm tsc --build --clean",
"postinstall": "pnpm run build", "postinstall": "pnpm run build",
@@ -69,10 +69,18 @@
"eslint-config-prettier": "8.6.0", "eslint-config-prettier": "8.6.0",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "4.2.1",
"io-ts": "2.2.16", "io-ts": "2.2.16",
"jest": "27.5.1",
"prettier": "2.8.4", "prettier": "2.8.4",
"ts-jest": "27.1.5", "ts-jest": "27.1.5",
"typescript": "4.9.5", "typescript": "4.9.5",
"vite": "5.0.5" "vite": "5.0.5",
"vitest": "0.34.6"
},
"peerDependencies": {
"isolated-vm": "4.7.2"
},
"peerDependenciesMeta": {
"isolated-vm": {
"optional": true
}
} }
} }

View File

@@ -0,0 +1,15 @@
// 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)

View File

@@ -1,8 +1,9 @@
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runPreRequestScript } from "~/pre-request/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/test-runner/node-vm"
import { runPreRequestScript, runTestScript } from "~/node"
import { TestResponse, TestResult } from "~/types" import { TestResponse, TestResult } from "~/types"
describe("Base64 helper functions", () => { describe("Base64 helper functions", () => {

View File

@@ -1,8 +1,9 @@
import "@relmify/jest-fp-ts"
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runTestScript } from "~/test-runner/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/node"
import { TestResponse, TestResult } from "~/types" import { TestResponse, TestResult } from "~/types"
const fakeResponse: TestResponse = { const fakeResponse: TestResponse = {

View File

@@ -1,8 +1,9 @@
import "@relmify/jest-fp-ts"
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runTestScript } from "~/test-runner/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/node"
import { TestResponse, TestResult } from "~/types" import { TestResponse, TestResult } from "~/types"
const fakeResponse: TestResponse = { const fakeResponse: TestResponse = {

View File

@@ -1,7 +1,9 @@
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runTestScript } from "~/test-runner/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/node"
import { TestResponse, TestResult } from "~/types" import { TestResponse, TestResult } from "~/types"
const fakeResponse: TestResponse = { const fakeResponse: TestResponse = {

View File

@@ -1,7 +1,9 @@
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runTestScript } from "~/test-runner/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/node"
import { TestResponse, TestResult } from "~/types" import { TestResponse, TestResult } from "~/types"
const fakeResponse: TestResponse = { const fakeResponse: TestResponse = {

View File

@@ -1,7 +1,9 @@
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runTestScript } from "~/test-runner/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/node"
import { TestResponse, TestResult } from "~/types" import { TestResponse, TestResult } from "~/types"
const fakeResponse: TestResponse = { const fakeResponse: TestResponse = {

View File

@@ -1,8 +1,9 @@
import "@relmify/jest-fp-ts"
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runTestScript } from "~/test-runner/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/node"
import { TestResponse } from "~/types" import { TestResponse } from "~/types"
const fakeResponse: TestResponse = { const fakeResponse: TestResponse = {
@@ -23,7 +24,7 @@ describe("toBe", () => {
return expect( return expect(
func( func(
` `
pw.expect(2).toBe(2) pw.expect(2).toBe(2)
`, `,
fakeResponse fakeResponse
)() )()

View File

@@ -1,8 +1,9 @@
import "@relmify/jest-fp-ts"
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runTestScript } from "~/test-runner/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/node"
import { TestResponse } from "~/types" import { TestResponse } from "~/types"
const fakeResponse: TestResponse = { const fakeResponse: TestResponse = {

View File

@@ -1,7 +1,9 @@
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runTestScript } from "~/test-runner/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/node"
import { TestResponse } from "~/types" import { TestResponse } from "~/types"
const fakeResponse: TestResponse = { const fakeResponse: TestResponse = {

View File

@@ -1,7 +1,9 @@
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runTestScript } from "~/test-runner/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/node"
import { TestResponse } from "~/types" import { TestResponse } from "~/types"
const fakeResponse: TestResponse = { const fakeResponse: TestResponse = {

View File

@@ -1,7 +1,9 @@
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runTestScript } from "~/test-runner/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/node"
import { TestResponse } from "~/types" import { TestResponse } from "~/types"
const fakeResponse: TestResponse = { const fakeResponse: TestResponse = {

View File

@@ -1,6 +1,6 @@
import "@relmify/jest-fp-ts" import { describe, expect, test } from "vitest"
import { runPreRequestScript } from "~/pre-request/node-vm" import { runPreRequestScript } from "~/node"
describe("runPreRequestScript", () => { describe("runPreRequestScript", () => {
test("returns the updated environment properly", () => { test("returns the updated environment properly", () => {

View File

@@ -1,4 +1,6 @@
import { preventCyclicObjects } from "~/utils" import { preventCyclicObjects } from "~/shared-utils"
import { describe, expect, test } from "vitest"
describe("preventCyclicObjects", () => { describe("preventCyclicObjects", () => {
test("succeeds with a simple object", () => { test("succeeds with a simple object", () => {

View File

@@ -1,7 +1,9 @@
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { runTestScript } from "~/test-runner/node-vm" import { describe, expect, test } from "vitest"
import { runTestScript } from "~/node"
import { TestResponse } from "~/types" import { TestResponse } from "~/types"
const fakeResponse: TestResponse = { const fakeResponse: TestResponse = {

View File

@@ -1,2 +0,0 @@
export * from "./pre-request/node-vm"
export * from "./test-runner/node-vm"

View File

@@ -0,0 +1,2 @@
export { runPreRequestScript } from "./pre-request"
export { runTestScript } from "./test-runner"

View File

@@ -0,0 +1,91 @@
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}`
)
)
)
)
)
)

View File

@@ -0,0 +1,217 @@
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)
})
})
}

View File

@@ -0,0 +1,23 @@
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
}

View File

@@ -1,38 +0,0 @@
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}`
)
)
)

View File

@@ -182,6 +182,36 @@ const getSharedMethods = (envs: TestResult["envs"]) => {
} }
} }
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
}
}
export function preventCyclicObjects( export function preventCyclicObjects(
obj: Record<string, any> obj: Record<string, any>
): E.Left<string> | E.Right<Record<string, any>> { ): E.Left<string> | E.Right<Record<string, any>> {
@@ -215,15 +245,18 @@ export const createExpectation = (
) => { ) => {
const result: Record<string, unknown> = {} const result: Record<string, unknown> = {}
// Non-primitive values supplied are stringified in the isolate context
const resolvedExpectVal = getResolvedExpectValue(expectVal)
const toBeFn = (expectedVal: any) => { const toBeFn = (expectedVal: any) => {
let assertion = expectVal === expectedVal let assertion = resolvedExpectVal === expectedVal
if (negated) { if (negated) {
assertion = !assertion assertion = !assertion
} }
const status = assertion ? "pass" : "fail" const status = assertion ? "pass" : "fail"
const message = `Expected '${expectVal}' to${ const message = `Expected '${resolvedExpectVal}' to${
negated ? " not" : "" negated ? " not" : ""
} be '${expectedVal}'` } be '${expectedVal}'`
@@ -240,7 +273,7 @@ export const createExpectation = (
rangeStart: number, rangeStart: number,
rangeEnd: number rangeEnd: number
) => { ) => {
const parsedExpectVal = parseInt(expectVal) const parsedExpectVal = parseInt(resolvedExpectVal)
if (!Number.isNaN(parsedExpectVal)) { if (!Number.isNaN(parsedExpectVal)) {
let assertion = let assertion =
@@ -260,7 +293,7 @@ export const createExpectation = (
message, message,
}) })
} else { } else {
const message = `Expected ${level}-level status but could not parse value '${expectVal}'` const message = `Expected ${level}-level status but could not parse value '${resolvedExpectVal}'`
currTestStack[currTestStack.length - 1].expectResults.push({ currTestStack[currTestStack.length - 1].expectResults.push({
status: "error", status: "error",
message, message,
@@ -288,14 +321,14 @@ export const createExpectation = (
"function", "function",
].includes(expectedType) ].includes(expectedType)
) { ) {
let assertion = typeof expectVal === expectedType let assertion = typeof resolvedExpectVal === expectedType
if (negated) { if (negated) {
assertion = !assertion assertion = !assertion
} }
const status = assertion ? "pass" : "fail" const status = assertion ? "pass" : "fail"
const message = `Expected '${expectVal}' to${ const message = `Expected '${resolvedExpectVal}' to${
negated ? " not" : "" negated ? " not" : ""
} be type '${expectedType}'` } be type '${expectedType}'`
@@ -316,7 +349,12 @@ export const createExpectation = (
} }
const toHaveLengthFn = (expectedLength: any) => { const toHaveLengthFn = (expectedLength: any) => {
if (!(Array.isArray(expectVal) || typeof expectVal === "string")) { if (
!(
Array.isArray(resolvedExpectVal) ||
typeof resolvedExpectVal === "string"
)
) {
const message = const message =
"Expected toHaveLength to be called for an array or string" "Expected toHaveLength to be called for an array or string"
currTestStack[currTestStack.length - 1].expectResults.push({ currTestStack[currTestStack.length - 1].expectResults.push({
@@ -328,7 +366,7 @@ export const createExpectation = (
} }
if (typeof expectedLength === "number" && !Number.isNaN(expectedLength)) { if (typeof expectedLength === "number" && !Number.isNaN(expectedLength)) {
let assertion = expectVal.length === expectedLength let assertion = resolvedExpectVal.length === expectedLength
if (negated) { if (negated) {
assertion = !assertion assertion = !assertion
@@ -355,7 +393,12 @@ export const createExpectation = (
} }
const toIncludeFn = (needle: any) => { const toIncludeFn = (needle: any) => {
if (!(Array.isArray(expectVal) || typeof expectVal === "string")) { if (
!(
Array.isArray(resolvedExpectVal) ||
typeof resolvedExpectVal === "string"
)
) {
const message = "Expected toInclude to be called for an array or string" const message = "Expected toInclude to be called for an array or string"
currTestStack[currTestStack.length - 1].expectResults.push({ currTestStack[currTestStack.length - 1].expectResults.push({
status: "error", status: "error",
@@ -382,13 +425,13 @@ export const createExpectation = (
return undefined return undefined
} }
let assertion = expectVal.includes(needle) let assertion = resolvedExpectVal.includes(needle)
if (negated) { if (negated) {
assertion = !assertion assertion = !assertion
} }
const expectValPretty = JSON.stringify(expectVal) const expectValPretty = JSON.stringify(resolvedExpectVal)
const needlePretty = JSON.stringify(needle) const needlePretty = JSON.stringify(needle)
const status = assertion ? "pass" : "fail" const status = assertion ? "pass" : "fail"
const message = `Expected ${expectValPretty} to${ const message = `Expected ${expectValPretty} to${

View File

@@ -1,57 +0,0 @@
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,
})
})
}

View File

@@ -1,2 +0,0 @@
export * from "./pre-request/web-worker"
export * from "./test-runner/web-worker"

View File

@@ -0,0 +1,2 @@
export { runPreRequestScript } from "./pre-request"
export { runTestScript } from "./test-runner"

View File

@@ -1,7 +1,7 @@
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { TestResult } from "~/types" import { TestResult } from "~/types"
import { getPreRequestScriptMethods } from "~/utils" import { getPreRequestScriptMethods } from "~/shared-utils"
const executeScriptInContext = ( const executeScriptInContext = (
preRequestScript: string, preRequestScript: string,

View File

@@ -2,7 +2,10 @@ import * as E from "fp-ts/Either"
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { SandboxTestResult, TestResponse, TestResult } from "~/types" import { SandboxTestResult, TestResponse, TestResult } from "~/types"
import { getTestRunnerScriptMethods, preventCyclicObjects } from "~/utils" import {
getTestRunnerScriptMethods,
preventCyclicObjects,
} from "~/shared-utils"
const executeScriptInContext = ( const executeScriptInContext = (
testScript: string, testScript: string,

View File

@@ -7,16 +7,20 @@ export default defineConfig({
emptyOutDir: true, emptyOutDir: true,
lib: { lib: {
entry: { entry: {
web: "./src/web.ts", web: "./src/web/index.ts",
node: "./src/node.ts", node: "./src/node/index.ts",
}, },
name: "js-sandbox", name: "js-sandbox",
formats: ["es", "cjs"], formats: ["es", "cjs"],
}, },
rollupOptions: { rollupOptions: {
external: ["vm"], external: ["module"],
}, },
}, },
test: {
environment: "node",
setupFiles: ["./setupFiles.ts"],
},
resolve: { resolve: {
alias: { alias: {
"~": resolve(__dirname, "./src"), "~": resolve(__dirname, "./src"),

View File

@@ -1,2 +1,2 @@
export { default } from "./dist/web.d.ts" export { default } from "./dist/web/index.d.ts"
export * from "./dist/web.d.ts" export * from "./dist/web/index.d.ts"

184
pnpm-lock.yaml generated
View File

@@ -309,6 +309,9 @@ importers:
commander: commander:
specifier: 11.1.0 specifier: 11.1.0
version: 11.1.0 version: 11.1.0
isolated-vm:
specifier: 4.7.2
version: 4.7.2
lodash-es: lodash-es:
specifier: 4.17.21 specifier: 4.17.21
version: 4.17.21 version: 4.17.21
@@ -828,6 +831,9 @@ importers:
fp-ts: fp-ts:
specifier: 2.12.1 specifier: 2.12.1
version: 2.12.1 version: 2.12.1
isolated-vm:
specifier: 4.7.2
version: 4.7.2
lodash: lodash:
specifier: 4.17.21 specifier: 4.17.21
version: 4.17.21 version: 4.17.21
@@ -868,9 +874,6 @@ importers:
io-ts: io-ts:
specifier: 2.2.16 specifier: 2.2.16
version: 2.2.16(fp-ts@2.12.1) version: 2.2.16(fp-ts@2.12.1)
jest:
specifier: 27.5.1
version: 27.5.1
prettier: prettier:
specifier: 2.8.4 specifier: 2.8.4
version: 2.8.4 version: 2.8.4
@@ -883,6 +886,9 @@ importers:
vite: vite:
specifier: 5.0.5 specifier: 5.0.5
version: 5.0.5(@types/node@17.0.45)(terser@5.27.0) version: 5.0.5(@types/node@17.0.45)(terser@5.27.0)
vitest:
specifier: 0.34.6
version: 0.34.6(sass@1.69.5)(terser@5.27.0)
packages/hoppscotch-selfhost-desktop: packages/hoppscotch-selfhost-desktop:
dependencies: dependencies:
@@ -3061,7 +3067,7 @@ packages:
peerDependencies: peerDependencies:
vue: 3.3.9 vue: 3.3.9
dependencies: dependencies:
vue: 3.3.9(typescript@5.3.2) vue: 3.3.9(typescript@4.9.5)
/@codemirror/autocomplete@6.13.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.25.1)(@lezer/common@1.2.1): /@codemirror/autocomplete@6.13.0(@codemirror/language@6.10.1)(@codemirror/state@6.4.1)(@codemirror/view@6.25.1)(@lezer/common@1.2.1):
resolution: {integrity: sha512-SuDrho1klTINfbcMPnyro1ZxU9xJtwDMtb62R8TjL/tOl71IoOsvBo1a9x+hDvHhIzkTcJHy2VC+rmpGgYkRSw==} resolution: {integrity: sha512-SuDrho1klTINfbcMPnyro1ZxU9xJtwDMtb62R8TjL/tOl71IoOsvBo1a9x+hDvHhIzkTcJHy2VC+rmpGgYkRSw==}
@@ -6120,7 +6126,7 @@ packages:
peerDependencies: peerDependencies:
vue: 3.3.9 vue: 3.3.9
dependencies: dependencies:
vue: 3.3.9(typescript@5.3.2) vue: 3.3.9(typescript@4.9.5)
/@humanwhocodes/config-array@0.11.14: /@humanwhocodes/config-array@0.11.14:
resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==}
@@ -8396,7 +8402,7 @@ packages:
/@types/graceful-fs@4.1.5: /@types/graceful-fs@4.1.5:
resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==} resolution: {integrity: sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==}
dependencies: dependencies:
'@types/node': 18.18.8 '@types/node': 17.0.45
dev: true dev: true
/@types/har-format@1.2.15: /@types/har-format@1.2.15:
@@ -9621,7 +9627,7 @@ packages:
regenerator-runtime: 0.13.11 regenerator-runtime: 0.13.11
systemjs: 6.14.2 systemjs: 6.14.2
terser: 5.27.0 terser: 5.27.0
vite: 3.2.4(@types/node@18.18.8)(sass@1.58.0)(terser@5.27.0) vite: 3.2.4(@types/node@17.0.27)(terser@5.27.0)
/@vitejs/plugin-legacy@2.3.0(terser@5.27.0)(vite@4.5.0): /@vitejs/plugin-legacy@2.3.0(terser@5.27.0)(vite@4.5.0):
resolution: {integrity: sha512-Bh62i0gzQvvT8AeAAb78nOnqSYXypkRmQmOTImdPZ39meHR9e2une3AIFmVo4s1SDmcmJ6qj18Sa/lRc/14KaA==} resolution: {integrity: sha512-Bh62i0gzQvvT8AeAAb78nOnqSYXypkRmQmOTImdPZ39meHR9e2une3AIFmVo4s1SDmcmJ6qj18Sa/lRc/14KaA==}
@@ -10143,7 +10149,7 @@ packages:
'@types/web-bluetooth': 0.0.14 '@types/web-bluetooth': 0.0.14
'@vueuse/metadata': 8.7.5 '@vueuse/metadata': 8.7.5
'@vueuse/shared': 8.7.5(vue@3.3.9) '@vueuse/shared': 8.7.5(vue@3.3.9)
vue: 3.3.9(typescript@5.3.2) vue: 3.3.9(typescript@4.9.5)
vue-demi: 0.14.6(vue@3.3.9) vue-demi: 0.14.6(vue@3.3.9)
/@vueuse/core@9.12.0(vue@3.3.9): /@vueuse/core@9.12.0(vue@3.3.9):
@@ -10202,7 +10208,7 @@ packages:
vue: vue:
optional: true optional: true
dependencies: dependencies:
vue: 3.3.9(typescript@5.3.2) vue: 3.3.9(typescript@4.9.5)
vue-demi: 0.14.6(vue@3.3.9) vue-demi: 0.14.6(vue@3.3.9)
/@vueuse/shared@9.12.0(vue@3.3.9): /@vueuse/shared@9.12.0(vue@3.3.9):
@@ -11120,7 +11126,6 @@ packages:
buffer: 5.7.1 buffer: 5.7.1
inherits: 2.0.4 inherits: 2.0.4
readable-stream: 3.6.0 readable-stream: 3.6.0
dev: true
/blob@0.0.5: /blob@0.0.5:
resolution: {integrity: sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==} resolution: {integrity: sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==}
@@ -11257,7 +11262,6 @@ packages:
dependencies: dependencies:
base64-js: 1.5.1 base64-js: 1.5.1
ieee754: 1.2.1 ieee754: 1.2.1
dev: true
/buffer@6.0.3: /buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
@@ -11519,6 +11523,10 @@ packages:
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
/chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
dev: false
/chownr@2.0.0: /chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -12233,6 +12241,13 @@ packages:
resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==}
dev: true dev: true
/decompress-response@6.0.0:
resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==}
engines: {node: '>=10'}
dependencies:
mimic-response: 3.1.0
dev: false
/dedent@0.7.0: /dedent@0.7.0:
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
dev: true dev: true
@@ -12627,7 +12642,6 @@ packages:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
dependencies: dependencies:
once: 1.4.0 once: 1.4.0
dev: true
/engine.io-client@3.5.3: /engine.io-client@3.5.3:
resolution: {integrity: sha512-qsgyc/CEhJ6cgMUwxRRtOndGVhIu5hpL5tR4umSpmX/MvkFoIxUTM7oFMDQumHNzlNLwSVy6qhstFPoWTf7dOw==} resolution: {integrity: sha512-qsgyc/CEhJ6cgMUwxRRtOndGVhIu5hpL5tR4umSpmX/MvkFoIxUTM7oFMDQumHNzlNLwSVy6qhstFPoWTf7dOw==}
@@ -13991,6 +14005,11 @@ packages:
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
dev: true dev: true
/expand-template@2.0.3:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
dev: false
/expect@27.5.1: /expect@27.5.1:
resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==} resolution: {integrity: sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==}
engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0}
@@ -14409,6 +14428,10 @@ packages:
resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==}
dev: false dev: false
/fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
dev: false
/fs-extra@10.1.0: /fs-extra@10.1.0:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -14592,6 +14615,10 @@ packages:
through2: 4.0.2 through2: 4.0.2
dev: true dev: true
/github-from-package@0.0.0:
resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==}
dev: false
/glob-parent@5.1.2: /glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -15969,6 +15996,14 @@ packages:
/isexe@2.0.0: /isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
/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
/isomorphic-fetch@3.0.0: /isomorphic-fetch@3.0.0:
resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==} resolution: {integrity: sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==}
dependencies: dependencies:
@@ -16954,7 +16989,7 @@ packages:
jest-util: 27.5.1 jest-util: 27.5.1
natural-compare: 1.4.0 natural-compare: 1.4.0
pretty-format: 27.5.1 pretty-format: 27.5.1
semver: 7.5.4 semver: 7.6.0
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
@@ -17099,7 +17134,7 @@ packages:
resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==}
engines: {node: '>= 10.13.0'} engines: {node: '>= 10.13.0'}
dependencies: dependencies:
'@types/node': 18.18.8 '@types/node': 17.0.45
merge-stream: 2.0.0 merge-stream: 2.0.0
supports-color: 8.1.1 supports-color: 8.1.1
dev: true dev: true
@@ -18004,6 +18039,11 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: true dev: true
/mimic-response@3.1.0:
resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==}
engines: {node: '>=10'}
dev: false
/min-indent@1.0.1: /min-indent@1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'} engines: {node: '>=4'}
@@ -18446,6 +18486,10 @@ packages:
- encoding - encoding
dev: false dev: false
/mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
dev: false
/mkdirp@0.5.6: /mkdirp@0.5.6:
resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==}
hasBin: true hasBin: true
@@ -18548,6 +18592,10 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
/napi-build-utils@1.0.2:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
dev: false
/natural-compare-lite@1.4.0: /natural-compare-lite@1.4.0:
resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==}
dev: true dev: true
@@ -18582,6 +18630,13 @@ packages:
lower-case: 2.0.2 lower-case: 2.0.2
tslib: 2.6.2 tslib: 2.6.2
/node-abi@3.57.0:
resolution: {integrity: sha512-Dp+A9JWxRaKuHP35H77I4kCKesDy5HUDEmScia2FyncMTOXASMyg251F5PhFoDA5uqBrDDffiLpbqnrZmNXW+g==}
engines: {node: '>=10'}
dependencies:
semver: 7.6.0
dev: false
/node-abort-controller@3.1.1: /node-abort-controller@3.1.1:
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
@@ -19457,6 +19512,25 @@ packages:
punycode: 2.3.0 punycode: 2.3.0
dev: false 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
/prelude-ls@1.1.2: /prelude-ls@1.1.2:
resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -19819,7 +19893,6 @@ packages:
dependencies: dependencies:
end-of-stream: 1.4.4 end-of-stream: 1.4.4
once: 1.4.0 once: 1.4.0
dev: true
/punycode@1.4.1: /punycode@1.4.1:
resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==}
@@ -20577,7 +20650,6 @@ packages:
hasBin: true hasBin: true
dependencies: dependencies:
lru-cache: 6.0.0 lru-cache: 6.0.0
dev: true
/send@0.18.0: /send@0.18.0:
resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==}
@@ -20747,6 +20819,18 @@ packages:
resolution: {integrity: sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww==} resolution: {integrity: sha512-6+eerH9fEnNmi/hyM1DXcRK3pWdoMQtlkQ+ns0ntzunjKqp5i3sKCc80ym8Fib3iaYhdJUOPdhlJWj1tvge2Ww==}
dev: true dev: true
/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
/sirv@2.0.3: /sirv@2.0.3:
resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==} resolution: {integrity: sha512-O9jm9BsID1P+0HOi81VpXPoDxYP374pkOLzACAoyUQ/3OUVndNpsz6wMnY2z+yOxzbllCKZrM+9QrWsv4THnyA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
@@ -21467,6 +21551,26 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/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
/tar@6.1.13: /tar@6.1.13:
resolution: {integrity: sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==} resolution: {integrity: sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@@ -22137,6 +22241,12 @@ packages:
typescript: 4.9.5 typescript: 4.9.5
dev: true dev: true
/tunnel-agent@0.6.0:
resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
dependencies:
safe-buffer: 5.2.1
dev: false
/type-check@0.3.2: /type-check@0.3.2:
resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==} resolution: {integrity: sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@@ -23031,7 +23141,7 @@ packages:
'@types/eslint': 8.56.2 '@types/eslint': 8.56.2
eslint: 8.57.0 eslint: 8.57.0
rollup: 2.79.1 rollup: 2.79.1
vite: 3.2.4(@types/node@18.18.8)(sass@1.58.0)(terser@5.27.0) vite: 3.2.4(@types/node@17.0.27)(terser@5.27.0)
/vite-plugin-eslint@1.8.1(eslint@8.57.0)(vite@4.5.0): /vite-plugin-eslint@1.8.1(eslint@8.57.0)(vite@4.5.0):
resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==} resolution: {integrity: sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==}
@@ -23325,6 +23435,40 @@ packages:
- supports-color - supports-color
dev: true dev: true
/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
/vite@3.2.4(@types/node@18.18.8)(sass@1.58.0)(terser@5.27.0): /vite@3.2.4(@types/node@18.18.8)(sass@1.58.0)(terser@5.27.0):
resolution: {integrity: sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==} resolution: {integrity: sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
@@ -23746,7 +23890,7 @@ packages:
'@vue/composition-api': '@vue/composition-api':
optional: true optional: true
dependencies: dependencies:
vue: 3.3.9(typescript@5.3.2) vue: 3.3.9(typescript@4.9.5)
/vue-eslint-parser@9.3.1(eslint@8.47.0): /vue-eslint-parser@9.3.1(eslint@8.47.0):
resolution: {integrity: sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==} resolution: {integrity: sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==}
@@ -23978,7 +24122,7 @@ packages:
vue: 3.3.9 vue: 3.3.9
dependencies: dependencies:
sortablejs: 1.14.0 sortablejs: 1.14.0
vue: 3.3.9(typescript@5.3.2) vue: 3.3.9(typescript@4.9.5)
/w3c-hr-time@1.0.2: /w3c-hr-time@1.0.2:
resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==} resolution: {integrity: sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==}
@@ -24462,6 +24606,7 @@ packages:
/workbox-google-analytics@6.6.0: /workbox-google-analytics@6.6.0:
resolution: {integrity: sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==} resolution: {integrity: sha512-p4DJa6OldXWd6M9zRl0H6vB9lkrmqYFkRQ2xEiNdBFp9U0LhsGO7hsBscVEyH9H2/3eZZt8c97NB2FD9U2NJ+Q==}
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
dependencies: dependencies:
workbox-background-sync: 6.6.0 workbox-background-sync: 6.6.0
workbox-core: 6.6.0 workbox-core: 6.6.0
@@ -24471,6 +24616,7 @@ packages:
/workbox-google-analytics@7.0.0: /workbox-google-analytics@7.0.0:
resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==} resolution: {integrity: sha512-MEYM1JTn/qiC3DbpvP2BVhyIH+dV/5BjHk756u9VbwuAhu0QHyKscTnisQuz21lfRpOwiS9z4XdqeVAKol0bzg==}
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
dependencies: dependencies:
workbox-background-sync: 7.0.0 workbox-background-sync: 7.0.0
workbox-core: 7.0.0 workbox-core: 7.0.0