@usebruno/js 0.43.0 → 0.44.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,23 +1,20 @@
1
1
  {
2
2
  "name": "@usebruno/js",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "license": "MIT",
5
5
  "main": "src/index.js",
6
6
  "files": [
7
7
  "src",
8
8
  "package.json"
9
9
  ],
10
- "peerDependencies": {
11
- "@usebruno/vm2": "^3.9.13"
12
- },
13
10
  "scripts": {
14
11
  "test": "node --experimental-vm-modules $(npx which jest) --testPathIgnorePatterns test.js",
15
12
  "sandbox:bundle-libraries": "node ./src/sandbox/bundle-libraries.js",
16
13
  "prepack": "npm run test"
17
14
  },
18
15
  "dependencies": {
19
- "@usebruno/common": "0.16.0",
20
- "@usebruno/query": "0.1.0",
16
+ "@usebruno/common": "0.17.0",
17
+ "@usebruno/query": "0.2.0",
21
18
  "ajv": "^8.12.0",
22
19
  "ajv-formats": "^2.1.1",
23
20
  "atob": "^2.1.2",
@@ -28,7 +25,7 @@
28
25
  "cheerio": "^1.0.0",
29
26
  "crypto-js": "^4.2.0",
30
27
  "json-query": "^2.2.2",
31
- "jsonwebtoken": "^9.0.2",
28
+ "jsonwebtoken": "^9.0.3",
32
29
  "lodash": "^4.17.21",
33
30
  "moment": "^2.29.4",
34
31
  "nanoid": "3.3.8",
package/src/bru.js CHANGED
@@ -27,7 +27,7 @@ class Bru {
27
27
  this.cookies = {
28
28
  jar: () => {
29
29
  const cookieJar = createCookieJar();
30
-
30
+
31
31
  return {
32
32
  getCookie: (url, cookieName, callback) => {
33
33
  const interpolatedUrl = this.interpolate(url);
@@ -82,7 +82,7 @@ class Bru {
82
82
  iterationData: new IterationDataManager(iterationDetails?.iterationData),
83
83
  iterationIndex: iterationDetails?.iterationIndex,
84
84
  totalIterations: iterationDetails?.totalIterations
85
- };
85
+ };
86
86
 
87
87
  this.utils = {
88
88
  minifyJson: (json) => {
@@ -128,7 +128,6 @@ class Bru {
128
128
  }
129
129
  };
130
130
  }
131
-
132
131
 
133
132
  interpolate = (strOrObj) => {
134
133
  if (!strOrObj) return strOrObj;
@@ -179,7 +178,7 @@ class Bru {
179
178
  if (!key) {
180
179
  throw new Error('Creating a env variable without specifying a name is not allowed.');
181
180
  }
182
-
181
+
183
182
  if (variableNameRegex.test(key) === false) {
184
183
  throw new Error(
185
184
  `Variable name: "${key}" contains invalid characters! Names must only contain alpha-numeric characters, "-", "_", "."`
@@ -242,8 +241,8 @@ class Bru {
242
241
 
243
242
  if (variableNameRegex.test(key) === false) {
244
243
  throw new Error(
245
- `Variable name: "${key}" contains invalid characters!` +
246
- ' Names must only contain alpha-numeric characters, "-", "_", "."'
244
+ `Variable name: "${key}" contains invalid characters!`
245
+ + ' Names must only contain alpha-numeric characters, "-", "_", "."'
247
246
  );
248
247
  }
249
248
 
@@ -262,8 +261,8 @@ class Bru {
262
261
  getVar(key) {
263
262
  if (variableNameRegex.test(key) === false) {
264
263
  throw new Error(
265
- `Variable name: "${key}" contains invalid characters!` +
266
- ' Names must only contain alpha-numeric characters, "-", "_", "."'
264
+ `Variable name: "${key}" contains invalid characters!`
265
+ + ' Names must only contain alpha-numeric characters, "-", "_", "."'
267
266
  );
268
267
  }
269
268
 
@@ -298,12 +297,10 @@ class Bru {
298
297
  this.nextRequest = nextRequest;
299
298
  }
300
299
 
301
-
302
300
  getSecretVar(key) {
303
301
  return this.secretVariables?.[`$secrets.${key}`];
304
302
  }
305
303
 
306
-
307
304
  visualize(type, data) {
308
305
  if (type == 'table') {
309
306
  if (data?.provider == 'ag-grid') {
@@ -8,7 +8,7 @@ class BrunoRequest {
8
8
  * - req.headers
9
9
  * - req.timeout
10
10
  * - req.body
11
- *
11
+ *
12
12
  * Above shorthands are useful for accessing the request properties directly in the scripts
13
13
  * It must be noted that the user cannot set these properties directly.
14
14
  * They should use the respective setter methods to set these properties.
@@ -26,9 +26,9 @@ class BrunoRequest {
26
26
  /**
27
27
  * We automatically parse the JSON body if the content type is JSON
28
28
  * This is to make it easier for the user to access the body directly
29
- *
29
+ *
30
30
  * It must be noted that the request data is always a string and is what gets sent over the network
31
- * If the user wants to access the raw data, they can use getBody({raw: true}) method
31
+ * If the user wants to access the raw data, they can use getBody({raw: true}) method
32
32
  */
33
33
  const isJson = this.hasJSONContentType(this.req.headers);
34
34
  if (isJson) {
@@ -48,6 +48,7 @@ class BrunoRequest {
48
48
  getMethod() {
49
49
  return this.req.method;
50
50
  }
51
+
51
52
  getAuthMode() {
52
53
  if (this.req?.oauth2) {
53
54
  return 'oauth2';
@@ -104,7 +105,7 @@ class BrunoRequest {
104
105
 
105
106
  /**
106
107
  * Get the body of the request
107
- *
108
+ *
108
109
  * We automatically parse and return the JSON body if the content type is JSON
109
110
  * If the user wants the raw body, they can pass the raw option as true
110
111
  */
@@ -128,7 +129,7 @@ class BrunoRequest {
128
129
  * Otherwise
129
130
  * - We set the request data as the data itself
130
131
  * - We set the body property as the data itself
131
- *
132
+ *
132
133
  * If the user wants to override this behavior, they can pass the raw option as true
133
134
  */
134
135
  setBody(data, options = {}) {
@@ -140,7 +141,7 @@ class BrunoRequest {
140
141
  createdAt: new Date().toISOString()
141
142
  });
142
143
  }
143
-
144
+
144
145
  if (options.raw) {
145
146
  this.req.data = data;
146
147
  this.body = data;
@@ -170,7 +171,7 @@ class BrunoRequest {
170
171
  this.timeout = timeout;
171
172
  this.req.timeout = timeout;
172
173
  }
173
-
174
+
174
175
  onFail(callback) {
175
176
  if (typeof callback === 'function') {
176
177
  this.req.onFailHandler = callback;
@@ -198,7 +199,6 @@ class BrunoRequest {
198
199
  __isObject(obj) {
199
200
  return obj !== null && typeof obj === 'object';
200
201
  }
201
-
202
202
 
203
203
  disableParsingResponseJson() {
204
204
  this.req.__brunoDisableParsingResponseJson = true;
@@ -65,7 +65,7 @@ class BrunoResponse {
65
65
 
66
66
  const { data, dataBuffer, headers } = this.res;
67
67
  let bodySize = 0;
68
-
68
+
69
69
  // Use raw received bytes
70
70
  if (Buffer.isBuffer(dataBuffer)) {
71
71
  bodySize = dataBuffer.length;
@@ -94,7 +94,6 @@ class BrunoResponse {
94
94
  const headerSize = Buffer.byteLength(headerLines.join('\r\n'));
95
95
 
96
96
  return { header: headerSize, body: bodySize, total: headerSize + bodySize };
97
-
98
97
  }
99
98
 
100
99
  getDataBuffer() {
@@ -241,7 +241,7 @@ const evaluateRhsOperand = (rhsOperand, operator, context, runtime) => {
241
241
 
242
242
  class AssertRuntime {
243
243
  constructor(props) {
244
- this.runtime = props?.runtime || 'vm2';
244
+ this.runtime = props?.runtime || 'quickjs';
245
245
  }
246
246
 
247
247
  runAssertions(assertions, request, response, envVariables, runtimeVariables, processEnvVars, historyLogger, secretVariables) {
@@ -1,45 +1,15 @@
1
- const { NodeVM } = require('@usebruno/vm2');
2
- const path = require('path');
3
- const http = require('http');
4
- const https = require('https');
5
- const stream = require('stream');
6
- const util = require('util');
7
- const zlib = require('zlib');
8
- const url = require('url');
9
- const punycode = require('punycode');
10
- const fs = require('fs');
11
- const { get } = require('lodash');
1
+ const chai = require('chai');
12
2
  const Bru = require('../bru');
13
3
  const BrunoRequest = require('../bruno-request');
14
4
  const BrunoResponse = require('../bruno-response');
15
5
  const { cleanJson } = require('../utils');
16
6
  const { createBruTestResultMethods } = require('../utils/results');
17
7
  const { runScriptInNodeVm } = require('../sandbox/node-vm');
18
-
19
- // Inbuilt Library Support
20
- const ajv = require('ajv');
21
- const addFormats = require('ajv-formats');
22
- const atob = require('atob');
23
- const btoa = require('btoa');
24
- const lodash = require('lodash');
25
- const moment = require('moment');
26
- const uuid = require('uuid');
27
- const nanoid = require('nanoid');
28
- const axios = require('axios');
29
- const fetch = require('node-fetch');
30
- const chai = require('chai');
31
- const CryptoJS = require('crypto-js');
32
- const NodeVault = require('node-vault');
33
- const xml2js = require('xml2js');
34
- const cheerio = require('cheerio');
35
- const tv4 = require('tv4');
36
- const jsonwebtoken = require('jsonwebtoken');
37
8
  const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
38
- const { mixinTypedArrays } = require('../sandbox/mixins/typed-arrays');
39
9
 
40
10
  class ScriptRuntime {
41
11
  constructor(props) {
42
- this.runtime = props?.runtime || 'vm2';
12
+ this.runtime = props?.runtime || 'quickjs';
43
13
  }
44
14
 
45
15
  // This approach is getting out of hand
@@ -61,7 +31,7 @@ class ScriptRuntime {
61
31
  let visualizations = [];
62
32
  let setVisualizations = (data) => {
63
33
  visualizations.push(data);
64
- }
34
+ };
65
35
  const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
66
36
  const oauth2CredentialVariables = request?.oauth2CredentialVariables || {};
67
37
  const collectionVariables = request?.collectionVariables || {};
@@ -73,24 +43,6 @@ class ScriptRuntime {
73
43
  // TODO please clean this up
74
44
  const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, setVisualizations, secretVariables, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, collectionName, promptVariables);
75
45
  const req = new BrunoRequest(request);
76
- const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
77
- const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
78
- const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []);
79
- const additionalContextRootsAbsolute = lodash
80
- .chain(additionalContextRoots)
81
- .map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr)))
82
- .value();
83
-
84
- const whitelistedModules = {};
85
-
86
- for (let module of moduleWhitelist) {
87
- try {
88
- whitelistedModules[module] = require(module);
89
- } catch (e) {
90
- // Ignore
91
- console.warn(e);
92
- }
93
- }
94
46
 
95
47
  // extend bru with result getter methods
96
48
  const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
@@ -104,10 +56,6 @@ class ScriptRuntime {
104
56
  __brunoTestResults: __brunoTestResults
105
57
  };
106
58
 
107
- if (this.runtime === 'vm2') {
108
- mixinTypedArrays(context);
109
- }
110
-
111
59
  if (onConsoleLog && typeof onConsoleLog === 'function') {
112
60
  const customLogger = (type) => {
113
61
  return (...args) => {
@@ -149,70 +97,12 @@ class ScriptRuntime {
149
97
  };
150
98
  }
151
99
 
152
- if (this.runtime === 'quickjs') {
153
- await executeQuickJsVmAsync({
154
- script: script,
155
- context: context,
156
- collectionPath
157
- });
158
-
159
- return {
160
- request,
161
- envVariables: cleanJson(envVariables),
162
- runtimeVariables: cleanJson(runtimeVariables),
163
- visualizations,
164
- persistentEnvVariables: bru.persistentEnvVariables,
165
- globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
166
- results: cleanJson(__brunoTestResults.getResults()),
167
- nextRequestName: bru.nextRequest,
168
- skipRequest: bru.skipRequest,
169
- stopExecution: bru.stopExecution
170
- };
171
- }
172
-
173
- // default runtime is vm2
174
- const vm = new NodeVM({
175
- sandbox: context,
176
- require: {
177
- context: 'sandbox',
178
- builtin: [ "*" ],
179
- external: true,
180
- root: [collectionPath, ...additionalContextRootsAbsolute],
181
- mock: {
182
- // node libs
183
- path,
184
- stream,
185
- util,
186
- url,
187
- http,
188
- https,
189
- punycode,
190
- zlib,
191
- // 3rd party libs
192
- ajv,
193
- 'ajv-formats': addFormats,
194
- atob,
195
- btoa,
196
- lodash,
197
- moment,
198
- uuid,
199
- nanoid,
200
- axios,
201
- chai,
202
- 'node-fetch': fetch,
203
- 'crypto-js': CryptoJS,
204
- xml2js: xml2js,
205
- jsonwebtoken,
206
- cheerio,
207
- tv4,
208
- ...whitelistedModules,
209
- fs: allowScriptFilesystemAccess ? fs : undefined,
210
- 'node-vault': NodeVault
211
- }
212
- }
100
+ // default runtime is `quickjs`
101
+ await executeQuickJsVmAsync({
102
+ script: script,
103
+ context: context,
104
+ collectionPath
213
105
  });
214
- const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js'));
215
- await asyncVM();
216
106
 
217
107
  return {
218
108
  request,
@@ -246,7 +136,7 @@ class ScriptRuntime {
246
136
  let visualizations = [];
247
137
  let setVisualizations = (data) => {
248
138
  visualizations.push(data);
249
- }
139
+ };
250
140
  const globalEnvironmentVariables = request?.globalEnvironmentVariables || {};
251
141
  const oauth2CredentialVariables = request?.oauth2CredentialVariables || {};
252
142
  const collectionVariables = request?.collectionVariables || {};
@@ -258,24 +148,6 @@ class ScriptRuntime {
258
148
  const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, setVisualizations, secretVariables, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, collectionName, promptVariables);
259
149
  const req = new BrunoRequest(request);
260
150
  const res = new BrunoResponse(response);
261
- const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
262
- const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
263
- const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []);
264
- const additionalContextRootsAbsolute = lodash
265
- .chain(additionalContextRoots)
266
- .map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr)))
267
- .value();
268
-
269
- const whitelistedModules = {};
270
-
271
- for (let module of moduleWhitelist) {
272
- try {
273
- whitelistedModules[module] = require(module);
274
- } catch (e) {
275
- // Ignore
276
- console.warn(e);
277
- }
278
- }
279
151
 
280
152
  // extend bru with result getter methods
281
153
  const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
@@ -290,10 +162,6 @@ class ScriptRuntime {
290
162
  __brunoTestResults: __brunoTestResults
291
163
  };
292
164
 
293
- if (this.runtime === 'vm2') {
294
- mixinTypedArrays(context);
295
- }
296
-
297
165
  if (onConsoleLog && typeof onConsoleLog === 'function') {
298
166
  const customLogger = (type) => {
299
167
  return (...args) => {
@@ -335,71 +203,13 @@ class ScriptRuntime {
335
203
  };
336
204
  }
337
205
 
338
- if (this.runtime === 'quickjs') {
339
- await executeQuickJsVmAsync({
340
- script: script,
341
- context: context,
342
- collectionPath
343
- });
344
-
345
- return {
346
- response,
347
- envVariables: cleanJson(envVariables),
348
- persistentEnvVariables: cleanJson(bru.persistentEnvVariables),
349
- runtimeVariables: cleanJson(runtimeVariables),
350
- visualizations,
351
- globalEnvironmentVariables: cleanJson(globalEnvironmentVariables),
352
- results: cleanJson(__brunoTestResults.getResults()),
353
- nextRequestName: bru.nextRequest,
354
- skipRequest: bru.skipRequest,
355
- stopExecution: bru.stopExecution
356
- };
357
- }
358
-
359
- // default runtime is vm2
360
- const vm = new NodeVM({
361
- sandbox: context,
362
- require: {
363
- context: 'sandbox',
364
- builtin: [ "*" ],
365
- external: true,
366
- root: [collectionPath, ...additionalContextRootsAbsolute],
367
- mock: {
368
- // node libs
369
- path,
370
- stream,
371
- util,
372
- url,
373
- http,
374
- https,
375
- punycode,
376
- zlib,
377
- // 3rd party libs
378
- ajv,
379
- 'ajv-formats': addFormats,
380
- atob,
381
- btoa,
382
- lodash,
383
- moment,
384
- uuid,
385
- nanoid,
386
- axios,
387
- 'node-fetch': fetch,
388
- 'crypto-js': CryptoJS,
389
- 'xml2js': xml2js,
390
- jsonwebtoken,
391
- cheerio,
392
- tv4,
393
- ...whitelistedModules,
394
- fs: allowScriptFilesystemAccess ? fs : undefined,
395
- 'node-vault': NodeVault
396
- }
397
- }
206
+ // default runtime is `quickjs`
207
+ await executeQuickJsVmAsync({
208
+ script: script,
209
+ context: context,
210
+ collectionPath
398
211
  });
399
212
 
400
- const asyncVM = vm.run(`module.exports = async () => { ${script} }`, path.join(collectionPath, 'vm.js'));
401
- await asyncVM();
402
-
403
213
  return {
404
214
  response,
405
215
  envVariables: cleanJson(envVariables),
@@ -1,47 +1,17 @@
1
- const { NodeVM } = require('@usebruno/vm2');
2
- const { runScriptInNodeVm } = require('../sandbox/node-vm');
3
1
  const chai = require('chai');
4
- const path = require('path');
5
- const http = require('http');
6
- const https = require('https');
7
- const stream = require('stream');
8
- const util = require('util');
9
- const zlib = require('zlib');
10
- const url = require('url');
11
- const punycode = require('punycode');
12
- const fs = require('fs');
13
- const { get } = require('lodash');
2
+ const uuid = require('uuid');
14
3
  const Bru = require('../bru');
15
4
  const BrunoRequest = require('../bruno-request');
16
5
  const BrunoResponse = require('../bruno-response');
17
- const Test = require('../test');
18
- const TestResults = require('../test-results');
19
6
  const { cleanJson } = require('../utils');
20
7
  const { createBruTestResultMethods } = require('../utils/results');
21
-
22
- // Inbuilt Library Support
23
- const ajv = require('ajv');
24
- const addFormats = require('ajv-formats');
25
- const atob = require('atob');
26
- const btoa = require('btoa');
27
- const lodash = require('lodash');
28
- const moment = require('moment');
29
- const uuid = require('uuid');
30
- const nanoid = require('nanoid');
31
- const axios = require('axios');
32
- const fetch = require('node-fetch');
33
- const CryptoJS = require('crypto-js');
34
- const NodeVault = require('node-vault');
35
- const xml2js = require('xml2js');
36
- const cheerio = require('cheerio');
37
- const tv4 = require('tv4');
8
+ const { runScriptInNodeVm } = require('../sandbox/node-vm');
38
9
  const jsonwebtoken = require('jsonwebtoken');
39
10
  const { executeQuickJsVmAsync } = require('../sandbox/quickjs');
40
- const { mixinTypedArrays } = require('../sandbox/mixins/typed-arrays');
41
11
 
42
12
  class TestRuntime {
43
13
  constructor(props) {
44
- this.runtime = props?.runtime || 'vm2';
14
+ this.runtime = props?.runtime || 'quickjs';
45
15
  }
46
16
 
47
17
  async runTests(
@@ -70,24 +40,6 @@ class TestRuntime {
70
40
  const bru = new Bru(envVariables, runtimeVariables, processEnvVars, collectionPath, historyLogger, undefined, secretVariables, collectionVariables, folderVariables, requestVariables, globalEnvironmentVariables, oauth2CredentialVariables, iterationDetails, collectionName, promptVariables);
71
41
  const req = new BrunoRequest(request, historyLogger);
72
42
  const res = new BrunoResponse(response);
73
- const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
74
- const moduleWhitelist = get(scriptingConfig, 'moduleWhitelist', []);
75
- const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []);
76
- const additionalContextRootsAbsolute = lodash
77
- .chain(additionalContextRoots)
78
- .map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr)))
79
- .value();
80
-
81
- const whitelistedModules = {};
82
-
83
- for (let module of moduleWhitelist) {
84
- try {
85
- whitelistedModules[module] = require(module);
86
- } catch (e) {
87
- // Ignore
88
- console.warn(e);
89
- }
90
- }
91
43
 
92
44
  // extend bru with result getter methods
93
45
  const { __brunoTestResults, test } = createBruTestResultMethods(bru, assertionResults, chai);
@@ -114,10 +66,6 @@ class TestRuntime {
114
66
  jwt: jsonwebtoken
115
67
  };
116
68
 
117
- if (this.runtime === 'vm2') {
118
- mixinTypedArrays(context);
119
- }
120
-
121
69
  if (onConsoleLog && typeof onConsoleLog === 'function') {
122
70
  const customLogger = (type) => {
123
71
  return (...args) => {
@@ -133,20 +81,14 @@ class TestRuntime {
133
81
  };
134
82
  }
135
83
 
136
- if(runRequestByItemPathname) {
84
+ if (runRequestByItemPathname) {
137
85
  context.bru.runRequest = runRequestByItemPathname;
138
86
  }
139
87
 
140
88
  let scriptError = null;
141
89
 
142
90
  try {
143
- if (this.runtime === 'quickjs') {
144
- await executeQuickJsVmAsync({
145
- script: testsFile,
146
- context: context,
147
- collectionPath
148
- });
149
- } else if (this.runtime === 'nodevm') {
91
+ if (this.runtime === 'nodevm') {
150
92
  await runScriptInNodeVm({
151
93
  script: testsFile,
152
94
  context,
@@ -154,49 +96,12 @@ class TestRuntime {
154
96
  scriptingConfig
155
97
  });
156
98
  } else {
157
- // default runtime is vm2
158
- const vm = new NodeVM({
159
- sandbox: context,
160
- require: {
161
- context: 'sandbox',
162
- external: true,
163
- builtin: ['*'],
164
- root: [collectionPath, ...additionalContextRootsAbsolute],
165
- mock: {
166
- // node libs
167
- path,
168
- stream,
169
- util,
170
- url,
171
- http,
172
- https,
173
- punycode,
174
- zlib,
175
- // 3rd party libs
176
- ajv,
177
- 'ajv-formats': addFormats,
178
- btoa,
179
- atob,
180
- lodash,
181
- moment,
182
- uuid,
183
- nanoid,
184
- axios,
185
- chai,
186
- 'node-fetch': fetch,
187
- 'crypto-js': CryptoJS,
188
- 'xml2js': xml2js,
189
- cheerio,
190
- tv4,
191
- 'jsonwebtoken': jsonwebtoken,
192
- ...whitelistedModules,
193
- fs: allowScriptFilesystemAccess ? fs : undefined,
194
- 'node-vault': NodeVault
195
- }
196
- }
99
+ // default runtime is `quickjs`
100
+ await executeQuickJsVmAsync({
101
+ script: testsFile,
102
+ context: context,
103
+ collectionPath
197
104
  });
198
- const asyncVM = vm.run(`module.exports = async () => { ${testsFile}}`, path.join(collectionPath, 'vm.js'));
199
- await asyncVM();
200
105
  }
201
106
  } catch (error) {
202
107
  scriptError = error;
@@ -20,7 +20,7 @@ const evaluateJsExpressionBasedOnRuntime = (expr, context, runtime, mode) => {
20
20
 
21
21
  class VarsRuntime {
22
22
  constructor(props) {
23
- this.runtime = props?.runtime || 'vm2';
23
+ this.runtime = props?.runtime || 'quickjs';
24
24
  this.mode = props?.mode || 'developer';
25
25
  }
26
26
 
@@ -3,7 +3,6 @@ const fs = require('node:fs');
3
3
  const path = require('node:path');
4
4
  const { get } = require('lodash');
5
5
  const lodash = require('lodash');
6
- const { cleanJson } = require('../../utils');
7
6
  const { mixinTypedArrays } = require('../mixins/typed-arrays');
8
7
 
9
8
  class ScriptError extends Error {
@@ -37,6 +36,17 @@ async function runScriptInNodeVm({
37
36
  }
38
37
 
39
38
  try {
39
+ const allowScriptFilesystemAccess = get(scriptingConfig, 'filesystemAccess.allow', false);
40
+
41
+ // Compute additional context roots
42
+ const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []);
43
+ const additionalContextRootsAbsolute = lodash
44
+ .chain(additionalContextRoots)
45
+ .map((acr) => (path.isAbsolute(acr) ? acr : path.join(collectionPath, acr)))
46
+ .map((acr) => path.normalize(acr))
47
+ .value();
48
+ additionalContextRootsAbsolute.push(path.normalize(collectionPath));
49
+
40
50
  // Create script context with all necessary variables
41
51
  const scriptContext = {
42
52
  // Bruno context
@@ -77,7 +87,9 @@ async function runScriptInNodeVm({
77
87
  collectionPath,
78
88
  scriptContext,
79
89
  currentModuleDir: collectionPath,
80
- localModuleCache
90
+ localModuleCache,
91
+ allowScriptFilesystemAccess,
92
+ additionalContextRootsAbsolute
81
93
  });
82
94
 
83
95
  // Execute the script in an isolated VM context
@@ -96,7 +108,6 @@ async function runScriptInNodeVm({
96
108
  return;
97
109
  }
98
110
 
99
-
100
111
  /**
101
112
  * Creates a custom require function with enhanced security and local module support
102
113
  * @param {Object} options - Configuration options
@@ -105,6 +116,8 @@ async function runScriptInNodeVm({
105
116
  * @param {Object} options.scriptContext - Script execution context
106
117
  * @param {string} options.currentModuleDir - Current module directory for relative imports
107
118
  * @param {Map} options.localModuleCache - Cache for loaded local modules
119
+ * @param {boolean} options.allowScriptFilesystemAccess - Whether to allow fs module access
120
+ * @param {Array<string>} options.additionalContextRootsAbsolute - Pre-computed absolute context roots
108
121
  * @returns {Function} Custom require function
109
122
  */
110
123
  function createCustomRequire({
@@ -112,32 +125,54 @@ function createCustomRequire({
112
125
  collectionPath,
113
126
  scriptContext,
114
127
  currentModuleDir = collectionPath,
115
- localModuleCache = new Map()
128
+ localModuleCache = new Map(),
129
+ allowScriptFilesystemAccess = false,
130
+ additionalContextRootsAbsolute = []
116
131
  }) {
117
- const additionalContextRoots = get(scriptingConfig, 'additionalContextRoots', []);
118
- const additionalContextRootsAbsolute = lodash
119
- .chain(additionalContextRoots)
120
- .map((acr) => (acr.startsWith('/') ? acr : path.join(collectionPath, acr)))
121
- .value();
122
- additionalContextRootsAbsolute.push(collectionPath);
123
-
124
132
  return (moduleName) => {
125
- // Check if it's a local module (starts with ./ or ../)
126
- if (moduleName.startsWith('./') || moduleName.startsWith('../')) {
127
- return loadLocalModule({ moduleName, collectionPath, scriptContext, localModuleCache, currentModuleDir });
133
+ // Check if it's a local module (starts with ./ or ../ or .\ or ..\)
134
+ // Normalize backslashes to forward slashes for cross-platform compatibility
135
+ const normalizedModuleName = moduleName.replace(/\\/g, '/');
136
+ if (normalizedModuleName.startsWith('./') || normalizedModuleName.startsWith('../')) {
137
+ return loadLocalModule({ moduleName: normalizedModuleName, collectionPath, scriptContext, localModuleCache, currentModuleDir, additionalContextRootsAbsolute });
128
138
  }
129
139
 
140
+ // Helper function to check if a module is the fs module or a submodule
141
+ const isFsModule = (module) => {
142
+ if (!module) return false;
143
+ const fsModule = require('fs');
144
+ // Check if it's the fs module itself
145
+ if (module === fsModule) return true;
146
+ // Check if it's fs/promises submodule
147
+ if (module === fsModule.promises) return true;
148
+ // Check if it's fs/promises by comparing with require('fs/promises')
149
+ try {
150
+ if (module === require('fs/promises')) return true;
151
+ } catch {
152
+ // fs/promises might not be available in all Node versions
153
+ }
154
+ return false;
155
+ };
156
+
130
157
  // First try to require as a native/npm module
131
158
  try {
132
- return require(moduleName);
133
- } catch {
134
- // If that fails, try to resolve from additionalContextRoots
135
- try {
136
- const modulePath = require.resolve(moduleName, { paths: additionalContextRootsAbsolute });
137
- return require(modulePath);
138
- } catch (error) {
139
- throw new Error(`Could not resolve module "${moduleName}": ${error.message}\n\nThis most likely means you did not install the module under "additionalContextRoots" using a package manager like npm.\n\nThese are your current "additionalContextRoots":\n${additionalContextRootsAbsolute.map(root => ` - ${root}`).join('\n') || ' - No "additionalContextRoots" defined'}`);
159
+ const requiredModulePath = require.resolve(moduleName, { paths: [...additionalContextRootsAbsolute, ...module.paths] });
160
+ const requiredModule = require(requiredModulePath);
161
+
162
+ // Block filesystem module access if filesystem access is not allowed
163
+ if (!allowScriptFilesystemAccess && isFsModule(requiredModule)) {
164
+ throw new Error('Filesystem access is not allowed. Enable "filesystemAccess.allow" in scripting config to use the fs module.');
140
165
  }
166
+
167
+ return requiredModule;
168
+ } catch (requireError) {
169
+ // Re-throw if it's our filesystem access error
170
+ if (requireError.message && requireError.message.includes('Enable "filesystemAccess.allow"')) {
171
+ throw requireError;
172
+ }
173
+
174
+ // If that fails, try to resolve from additionalContextRoots
175
+ throw new Error(`Could not resolve module "${moduleName}": ${requireError.message}\n\nThis most likely means you did not install the module under the collection or the "additionalContextRoots" using a package manager like npm.\n\nThese are your current "additionalContextRoots":\n${additionalContextRootsAbsolute.map((root) => ` - ${root}`).join('\n') || ' - No "additionalContextRoots" defined'}`);
141
176
  }
142
177
  };
143
178
  }
@@ -150,6 +185,7 @@ function createCustomRequire({
150
185
  * @param {Object} options.scriptContext - Script execution context to inherit
151
186
  * @param {Map} options.localModuleCache - Cache for loaded modules
152
187
  * @param {string} options.currentModuleDir - Directory of the current module for relative resolution
188
+ * @param {Array<string>} options.additionalContextRootsAbsolute - Additional allowed context root paths
153
189
  * @returns {*} The exported content of the loaded module
154
190
  * @throws {Error} When module is outside collection path or cannot be loaded
155
191
  */
@@ -158,7 +194,8 @@ function loadLocalModule({
158
194
  collectionPath,
159
195
  scriptContext,
160
196
  localModuleCache,
161
- currentModuleDir
197
+ currentModuleDir,
198
+ additionalContextRootsAbsolute = []
162
199
  }) {
163
200
  // Check if the filename has an extension
164
201
  const hasExtension = path.extname(moduleName) !== '';
@@ -167,12 +204,19 @@ function loadLocalModule({
167
204
  // Resolve the file path relative to the current module's directory
168
205
  const filePath = path.resolve(currentModuleDir, resolvedFilename);
169
206
  const normalizedFilePath = path.normalize(filePath);
170
- const normalizedCollectionPath = path.normalize(collectionPath);
171
207
 
172
- // Cross-platform security check: ensure the resolved file is within collectionPath
173
- const relativePath = path.relative(normalizedCollectionPath, normalizedFilePath);
174
- if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) {
175
- throw new Error(`Access to files outside of the collectionPath is not allowed: ${moduleName}`);
208
+ const isWithinAllowedRoot = additionalContextRootsAbsolute.some((allowedRoot) => {
209
+ const normalizedAllowedRoot = path.normalize(allowedRoot);
210
+ const relativePath = path.relative(normalizedAllowedRoot, normalizedFilePath);
211
+ return !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
212
+ });
213
+
214
+ if (!isWithinAllowedRoot) {
215
+ const allowedRootsDisplay = additionalContextRootsAbsolute.map((root) => ` - ${root}`).join('\n');
216
+ throw new Error(
217
+ `Access to files outside of the allowed context roots is not allowed: ${moduleName}\n\n`
218
+ + `Allowed context roots:\n${allowedRootsDisplay}`
219
+ );
176
220
  }
177
221
 
178
222
  // Check cache first (use normalized path as key)
@@ -202,11 +246,13 @@ function loadLocalModule({
202
246
  __dirname: moduleDir,
203
247
  // Create a custom require function for this module that resolves relative to its directory
204
248
  require: createCustomRequire({
205
- scriptingConfig: scriptContext.scriptingConfig || {},
206
- collectionPath,
207
- scriptContext,
208
- currentModuleDir: moduleDir,
209
- localModuleCache
249
+ scriptingConfig: scriptContext.scriptingConfig || {},
250
+ collectionPath,
251
+ scriptContext,
252
+ currentModuleDir: moduleDir,
253
+ localModuleCache,
254
+ allowScriptFilesystemAccess: get(scriptContext.scriptingConfig, 'filesystemAccess.allow', false),
255
+ additionalContextRootsAbsolute
210
256
  })
211
257
  };
212
258
 
@@ -0,0 +1,252 @@
1
+ const { describe, it, expect, beforeEach, afterEach } = require('@jest/globals');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const { runScriptInNodeVm } = require('./index');
6
+
7
+ describe('node-vm sandbox', () => {
8
+ let testDir;
9
+ let collectionPath;
10
+
11
+ beforeEach(() => {
12
+ // Create a temporary test directory
13
+ testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'bruno-test-'));
14
+ collectionPath = path.join(testDir, 'collection');
15
+ fs.mkdirSync(collectionPath);
16
+ });
17
+
18
+ afterEach(() => {
19
+ // Clean up test directory
20
+ fs.rmSync(testDir, { recursive: true, force: true });
21
+ });
22
+
23
+ describe('createCustomRequire - local modules', () => {
24
+ it('should load local module with ./ path', async () => {
25
+ // Create a local module
26
+ fs.writeFileSync(
27
+ path.join(collectionPath, 'helper.js'),
28
+ 'module.exports = { value: 42 };'
29
+ );
30
+
31
+ const script = `
32
+ const helper = require('./helper');
33
+ bru.setVar('result', helper.value);
34
+ `;
35
+
36
+ const context = {
37
+ bru: { setVar: jest.fn() },
38
+ console: console
39
+ };
40
+
41
+ await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
42
+
43
+ expect(context.bru.setVar).toHaveBeenCalledWith('result', 42);
44
+ });
45
+
46
+ it('should load local module with ../ path', async () => {
47
+ // Create a subdirectory and modules
48
+ const subDir = path.join(collectionPath, 'subdir');
49
+ fs.mkdirSync(subDir);
50
+ fs.writeFileSync(
51
+ path.join(collectionPath, 'parent.js'),
52
+ 'module.exports = { name: "parent" };'
53
+ );
54
+ fs.writeFileSync(
55
+ path.join(subDir, 'child.js'),
56
+ 'const parent = require("../parent"); module.exports = parent;'
57
+ );
58
+
59
+ const script = `
60
+ const child = require('./subdir/child');
61
+ bru.setVar('result', child.name);
62
+ `;
63
+
64
+ const context = {
65
+ bru: { setVar: jest.fn() },
66
+ console: console
67
+ };
68
+
69
+ await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
70
+
71
+ expect(context.bru.setVar).toHaveBeenCalledWith('result', 'parent');
72
+ });
73
+
74
+ it('should handle backslashes on Windows', async () => {
75
+ const subDir = path.join(collectionPath, 'utils');
76
+ fs.mkdirSync(subDir);
77
+ fs.writeFileSync(
78
+ path.join(subDir, 'module.js'),
79
+ 'module.exports = { platform: "cross-platform" };'
80
+ );
81
+
82
+ // Simulate Windows-style path with backslashes
83
+ const script = `
84
+ const mod = require('.\\\\utils\\\\module');
85
+ bru.setVar('result', mod.platform);
86
+ `;
87
+
88
+ const context = {
89
+ bru: { setVar: jest.fn() },
90
+ console: console
91
+ };
92
+
93
+ await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
94
+
95
+ expect(context.bru.setVar).toHaveBeenCalledWith('result', 'cross-platform');
96
+ });
97
+
98
+ it('should block access outside collection path', async () => {
99
+ const script = `
100
+ const outside = require('../../outside');
101
+ `;
102
+
103
+ const context = { console: console };
104
+
105
+ await expect(
106
+ runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} })
107
+ ).rejects.toThrow('Access to files outside of the allowed context roots is not allowed');
108
+ });
109
+ });
110
+
111
+ describe('createCustomRequire - additionalContextRoots', () => {
112
+ it('should allow module access from additionalContextRoots', async () => {
113
+ // Create an additional context root at same level as collection
114
+ const additionalRoot = path.join(testDir, 'shared');
115
+ fs.mkdirSync(additionalRoot);
116
+ fs.writeFileSync(
117
+ path.join(additionalRoot, 'shared.js'),
118
+ 'module.exports = { shared: true };'
119
+ );
120
+
121
+ // From collection, traverse up to testDir, then into shared directory
122
+ const script = `
123
+ const shared = require('../shared/shared');
124
+ bru.setVar('result', shared.shared);
125
+ `;
126
+
127
+ const context = {
128
+ bru: { setVar: jest.fn() },
129
+ console: console
130
+ };
131
+
132
+ const scriptingConfig = {
133
+ additionalContextRoots: [additionalRoot]
134
+ };
135
+
136
+ await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig });
137
+
138
+ expect(context.bru.setVar).toHaveBeenCalledWith('result', true);
139
+ });
140
+
141
+ it('should handle relative additionalContextRoots path', async () => {
142
+ // Create a sibling directory to collection
143
+ const libsDir = path.join(testDir, 'libs');
144
+ fs.mkdirSync(libsDir);
145
+ fs.writeFileSync(
146
+ path.join(libsDir, 'lib.js'),
147
+ 'module.exports = { fromLib: "yes" };'
148
+ );
149
+
150
+ const script = `
151
+ const lib = require('../libs/lib');
152
+ bru.setVar('result', lib.fromLib);
153
+ `;
154
+
155
+ const context = {
156
+ bru: { setVar: jest.fn() },
157
+ console: console
158
+ };
159
+
160
+ const scriptingConfig = {
161
+ additionalContextRoots: ['../libs']
162
+ };
163
+
164
+ await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig });
165
+
166
+ expect(context.bru.setVar).toHaveBeenCalledWith('result', 'yes');
167
+ });
168
+
169
+ it('should handle nested additional context roots modules', async () => {
170
+ // Create an additional context root
171
+ const additionalRoot = path.join(testDir, 'shared');
172
+ fs.mkdirSync(additionalRoot);
173
+ fs.writeFileSync(
174
+ path.join(additionalRoot, 'allowed.js'),
175
+ 'module.exports = { allowed: true };'
176
+ );
177
+
178
+ // Create a nested module that tries to require from additional root
179
+ fs.writeFileSync(
180
+ path.join(collectionPath, 'parent.js'),
181
+ `
182
+ const allowed = require('../shared/allowed');
183
+ module.exports = { nestedAccess: allowed.allowed };
184
+ `
185
+ );
186
+
187
+ const script = `
188
+ const parent = require('./parent');
189
+ bru.setVar('result', parent.nestedAccess);
190
+ `;
191
+
192
+ const context = {
193
+ bru: { setVar: jest.fn() },
194
+ console: console
195
+ };
196
+
197
+ const scriptingConfig = {
198
+ additionalContextRoots: [additionalRoot]
199
+ };
200
+
201
+ await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig });
202
+
203
+ // Nested module should successfully access the additional root
204
+ expect(context.bru.setVar).toHaveBeenCalledWith('result', true);
205
+ });
206
+ });
207
+
208
+ describe('createCustomRequire - npm modules', () => {
209
+ it('should load npm module', async () => {
210
+ const script = `
211
+ const lodash = require('lodash');
212
+ bru.setVar('result', typeof lodash.get);
213
+ `;
214
+
215
+ const context = {
216
+ bru: { setVar: jest.fn() },
217
+ console: console
218
+ };
219
+
220
+ await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
221
+
222
+ expect(context.bru.setVar).toHaveBeenCalledWith('result', 'function');
223
+ });
224
+ });
225
+
226
+ describe('createCustomRequire - module caching', () => {
227
+ it('should cache loaded modules', async () => {
228
+ let callCount = 0;
229
+ fs.writeFileSync(
230
+ path.join(collectionPath, 'cached.js'),
231
+ `
232
+ module.exports = { count: ${++callCount} };
233
+ `
234
+ );
235
+
236
+ const script = `
237
+ const mod1 = require('./cached');
238
+ const mod2 = require('./cached');
239
+ bru.setVar('same', mod1.count === mod2.count);
240
+ `;
241
+
242
+ const context = {
243
+ bru: { setVar: jest.fn() },
244
+ console: console
245
+ };
246
+
247
+ await runScriptInNodeVm({ script, context, collectionPath, scriptingConfig: {} });
248
+
249
+ expect(context.bru.setVar).toHaveBeenCalledWith('same', true);
250
+ });
251
+ });
252
+ });
@@ -24,7 +24,7 @@ const toNumber = (value) => {
24
24
  };
25
25
 
26
26
  const removeQuotes = (str) => {
27
- if ((str.startsWith('"') && str.endsWith('"')) || (str.startsWith("'") && str.endsWith("'"))) {
27
+ if ((str.startsWith('"') && str.endsWith('"')) || (str.startsWith('\'') && str.endsWith('\''))) {
28
28
  return str.slice(1, -1);
29
29
  }
30
30
  return str;
@@ -36,7 +36,7 @@ const executeQuickJsVm = ({ script: externalScript, context: externalContext, sc
36
36
  }
37
37
  externalScript = externalScript?.trim();
38
38
 
39
- if(scriptType === 'template-literal') {
39
+ if (scriptType === 'template-literal') {
40
40
  if (!isNaN(Number(externalScript))) {
41
41
  const number = Number(externalScript);
42
42
 
@@ -341,7 +341,7 @@ const addBruShimToContext = (vm, bru) => {
341
341
  const promise = vm.newPromise();
342
342
  const dumpedUrl = vm.dump(url);
343
343
  const dumpedNameOrObj = vm.dump(nameOrCookieObj);
344
-
344
+
345
345
  // Check if the second argument is an object (cookie object case)
346
346
  if (typeof dumpedNameOrObj === 'object' && dumpedNameOrObj !== null) {
347
347
  // Cookie object case: setCookie(url, cookieObject, callback)
@@ -363,7 +363,7 @@ const addBruShimToContext = (vm, bru) => {
363
363
  }
364
364
  });
365
365
  }
366
-
366
+
367
367
  promise.settled.then(vm.runtime.executePendingJobs);
368
368
  return promise.handle;
369
369
  });
@@ -371,7 +371,7 @@ const addBruShimToContext = (vm, bru) => {
371
371
 
372
372
  const _setCookiesFn = vm.newFunction('_setCookies', (url, cookiesArray) => {
373
373
  const promise = vm.newPromise();
374
-
374
+
375
375
  nativeJar.setCookies(vm.dump(url), vm.dump(cookiesArray), (err) => {
376
376
  if (err) {
377
377
  promise.reject(marshallToVm(cleanJson(err), vm));
@@ -10,7 +10,7 @@ const addCryptoUtilsShimToContext = async (vm) => {
10
10
  let randomBytesHandle = vm.newFunction('randomBytes', function (sizeHandle) {
11
11
  try {
12
12
  let size = vm.dump(sizeHandle);
13
-
13
+
14
14
  if (typeof size !== 'number') {
15
15
  throw new TypeError('The "size" argument must be of type number');
16
16
  }
@@ -30,15 +30,14 @@ const addCryptoUtilsShimToContext = async (vm) => {
30
30
  }
31
31
 
32
32
  const buffer = crypto.randomBytes(size);
33
-
33
+
34
34
  const byteArray = Array.from(buffer);
35
-
35
+
36
36
  return marshallToVm(byteArray, vm);
37
-
38
37
  } catch (error) {
39
38
  const vmError = vm.newError(error.message);
40
39
  vm.setProp(vmError, 'name', vm.newString(error.name));
41
-
40
+
42
41
  throw vmError;
43
42
  }
44
43
  });
@@ -48,7 +47,7 @@ const addCryptoUtilsShimToContext = async (vm) => {
48
47
  // Receive the serialized array data directly
49
48
  const serializedArray = vm.dump(arrayHandle);
50
49
  const typedArray = deserializeTypedArray(serializedArray);
51
-
50
+
52
51
  if (typedArray.length === 0) {
53
52
  return marshallToVm([], vm);
54
53
  }
@@ -62,11 +61,10 @@ const addCryptoUtilsShimToContext = async (vm) => {
62
61
  const byteArray = Array.from(typedArray);
63
62
 
64
63
  return marshallToVm(byteArray, vm);
65
-
66
64
  } catch (error) {
67
65
  const vmError = vm.newError(error.message);
68
66
  vm.setProp(vmError, 'name', vm.newString(error.name));
69
-
67
+
70
68
  throw vmError;
71
69
  }
72
70
  });
@@ -101,4 +99,4 @@ const addCryptoUtilsShimToContext = async (vm) => {
101
99
  `);
102
100
  };
103
101
 
104
- module.exports = addCryptoUtilsShimToContext;
102
+ module.exports = addCryptoUtilsShimToContext;
@@ -27,7 +27,7 @@ describe('crypto-utils shims tests', () => {
27
27
  const handle = vm.unwrapResult(result);
28
28
  const type = vm.dump(handle);
29
29
  handle.dispose();
30
-
30
+
31
31
  expect(type).toBe('function');
32
32
  });
33
33
 
@@ -36,7 +36,7 @@ describe('crypto-utils shims tests', () => {
36
36
  const handle = vm.unwrapResult(result);
37
37
  const type = vm.dump(handle);
38
38
  handle.dispose();
39
-
39
+
40
40
  expect(type).toBe('function');
41
41
  });
42
42
 
@@ -45,7 +45,7 @@ describe('crypto-utils shims tests', () => {
45
45
  const handle = vm.unwrapResult(result);
46
46
  const length = vm.dump(handle);
47
47
  handle.dispose();
48
-
48
+
49
49
  expect(length).toBe(8);
50
50
  });
51
51
 
@@ -54,7 +54,7 @@ describe('crypto-utils shims tests', () => {
54
54
  const handle = vm.unwrapResult(result);
55
55
  const hexLength = vm.dump(handle);
56
56
  handle.dispose();
57
-
57
+
58
58
  expect(hexLength).toBe(8); // 4 bytes = 8 hex chars
59
59
  });
60
60
 
@@ -67,7 +67,7 @@ describe('crypto-utils shims tests', () => {
67
67
  const handle = vm.unwrapResult(result);
68
68
  const length = vm.dump(handle);
69
69
  handle.dispose();
70
-
70
+
71
71
  expect(length).toBe(5);
72
72
  });
73
- });
73
+ });
@@ -45,4 +45,4 @@ function deserializeTypedArray(obj) {
45
45
  module.exports = {
46
46
  serializeTypedArray,
47
47
  deserializeTypedArray
48
- }
48
+ };
@@ -7,7 +7,7 @@ const getResultsSummary = (results) => {
7
7
  total: results.length,
8
8
  passed: 0,
9
9
  failed: 0,
10
- skipped: 0,
10
+ skipped: 0
11
11
  };
12
12
 
13
13
  results.forEach((r) => {
@@ -34,7 +34,7 @@ const setupBruTestMethods = (bru, __brunoTestResults, assertionResults) => {
34
34
  const summary = getResultsSummary(results);
35
35
  return {
36
36
  summary,
37
- results: results.map(r => ({
37
+ results: results.map((r) => ({
38
38
  status: r.status,
39
39
  description: r.description,
40
40
  expected: r.expected,
@@ -49,7 +49,7 @@ const setupBruTestMethods = (bru, __brunoTestResults, assertionResults) => {
49
49
  const summary = getResultsSummary(results);
50
50
  return {
51
51
  summary,
52
- results: results.map(r => ({
52
+ results: results.map((r) => ({
53
53
  status: r.status,
54
54
  lhsExpr: r.lhsExpr,
55
55
  rhsExpr: r.rhsExpr,
@@ -77,4 +77,4 @@ module.exports = {
77
77
  getResultsSummary,
78
78
  createBruTestResultMethods,
79
79
  setupBruTestMethods
80
- };
80
+ };
package/src/utils.js CHANGED
@@ -90,7 +90,7 @@ const evaluateJsTemplateLiteral = (templateLiteral, context) => {
90
90
  return templateLiteral.slice(1, -1);
91
91
  }
92
92
 
93
- if (templateLiteral.startsWith("'") && templateLiteral.endsWith("'")) {
93
+ if (templateLiteral.startsWith('\'') && templateLiteral.endsWith('\'')) {
94
94
  return templateLiteral.slice(1, -1);
95
95
  }
96
96
 
@@ -129,7 +129,7 @@ const createResponseParser = (response = {}) => {
129
129
  };
130
130
 
131
131
  /**
132
- * Objects that are created inside vm2 execution context result in an serialization error when sent to the renderer process
132
+ * Objects that are created inside developer mode execution context result in an serialization error when sent to the renderer process
133
133
  * Error sending from webFrameMain: Error: Failed to serialize arguments
134
134
  * at s.send (node:electron/js2c/browser_init:169:631)
135
135
  * at g.send (node:electron/js2c/browser_init:165:2156)
@@ -201,7 +201,7 @@ const uuid = () => {
201
201
  const customNanoId = customAlphabet(urlAlphabet, 21);
202
202
 
203
203
  return customNanoId();
204
- }
204
+ };
205
205
 
206
206
  const appendAwaitToTestFunc = (str) => {
207
207
  return str.replace(/(?<!\.\s*)(?<!await\s)(test\()/g, 'await $1');
@@ -211,18 +211,18 @@ const cleanCircularJson = (data) => {
211
211
  try {
212
212
  // Handle circular references by keeping track of seen objects
213
213
  const seen = new WeakSet();
214
-
214
+
215
215
  const replacer = (key, value) => {
216
216
  // Skip non-objects and null
217
217
  if (typeof value !== 'object' || value === null) {
218
218
  return value;
219
219
  }
220
-
220
+
221
221
  // Detect circular reference
222
222
  if (seen.has(value)) {
223
223
  return '[Circular Reference]';
224
224
  }
225
-
225
+
226
226
  seen.add(value);
227
227
  return value;
228
228
  };