Skip to content

Commit f6ceeaa

Browse files
committed
Add pm.require API
1 parent 903677b commit f6ceeaa

File tree

7 files changed

+561
-6
lines changed

7 files changed

+561
-6
lines changed

lib/postman-sandbox.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ class PostmanSandbox extends UniversalVM {
9393
executionEventName = 'execution.result.' + id,
9494
executionTimeout = _.get(options, 'timeout', this.executionTimeout),
9595
cursor = _.clone(_.get(options, 'cursor', {})), // clone the cursor as it travels through IPC for mutation
96+
requireFileCache = _.get(options, 'requireFileResolver'),
9697
debugMode = _.has(options, 'debug') ? options.debug : this.debug;
9798

9899
let waiting;
@@ -126,6 +127,7 @@ class PostmanSandbox extends UniversalVM {
126127
cursor: cursor,
127128
debug: debugMode,
128129
timeout: executionTimeout,
130+
requireFileCache: requireFileCache,
129131
legacy: _.get(options, 'legacy')
130132
});
131133
}

lib/sandbox/execute.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const _ = require('lodash'),
99
PostmanTimers = require('./timers'),
1010
PostmanAPI = require('./pmapi'),
1111
PostmanCookieStore = require('./cookie-store'),
12+
createPostmanRequire = require('./pm-require'),
1213

1314
EXECUTION_RESULT_EVENT_BASE = 'execution.result.',
1415
EXECUTION_REQUEST_EVENT_BASE = 'execution.request.',
@@ -228,6 +229,7 @@ module.exports = function (bridge, glob) {
228229
},
229230
dispatchAssertions,
230231
new PostmanCookieStore(id, bridge, timers),
232+
createPostmanRequire(options.requireFileCache, scope),
231233
{
232234
disabledAPIs: initializationOptions.disabledAPIs
233235
})

lib/sandbox/pm-require.js

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
const MODULE_KEY = '__module_obj', // why not use `module`?
2+
MODULE_WRAPPER = [
3+
'(function (exports, module) {\n',
4+
`\n})(${MODULE_KEY}.exports, ${MODULE_KEY});`
5+
];
6+
7+
class PostmanRequireStore {
8+
constructor (fileCache) {
9+
this.fileCache = fileCache || {};
10+
}
11+
12+
getResolvedPath (path) {
13+
if (this.hasFile(path)) {
14+
return path;
15+
}
16+
}
17+
18+
hasFile (path) {
19+
return Boolean(this.fileCache[path]);
20+
}
21+
22+
getFileData (path) {
23+
return this.hasFile(path) && this.fileCache[path].data;
24+
}
25+
}
26+
27+
function createPostmanRequire (fileCache, scope) {
28+
const store = new PostmanRequireStore(fileCache),
29+
cache = {};
30+
31+
return function postmanRequire (name) {
32+
const path = store.getResolvedPath(name);
33+
34+
// Any module should not be evaluated twice, so we use it from the
35+
// cache. If there's a circular dependency, the partially evaluated
36+
// module will be returned from the cache.
37+
if (cache[path]) {
38+
// Always use the resolved path as the ID of the module. This
39+
// ensures that relative paths are handled correctly.
40+
return cache[path].exports;
41+
}
42+
43+
/* eslint-disable-next-line one-var */
44+
const file = path && store.getFileData(path);
45+
46+
if (!file) {
47+
// Error should contain the name exactly as the user specified,
48+
// and not the resolved path.
49+
throw new Error(`Cannot find module '${name}'`);
50+
}
51+
52+
/* eslint-disable-next-line one-var */
53+
const moduleObj = {
54+
id: path,
55+
exports: {}
56+
};
57+
58+
// Add to cache before executing. This ensures that any dependency
59+
// that tries to import it's parent/ancestor gets the cached
60+
// version and not end up in infinite loop.
61+
cache[moduleObj.id] = moduleObj;
62+
63+
/* eslint-disable-next-line one-var */
64+
const wrappedModule = MODULE_WRAPPER[0] + file + MODULE_WRAPPER[1];
65+
66+
scope.import({
67+
[MODULE_KEY]: moduleObj
68+
});
69+
70+
// Note: We're executing the code in the same scope as the one
71+
// which called the `pm.require` function. This is because we want
72+
// to share the global scope across all the required modules. Any
73+
// locals are available inside the required modules and any locals
74+
// created inside the required modules are available to the parent.
75+
//
76+
// Why `async` = true?
77+
// - We want to allow execution of async code like setTimeout etc.
78+
scope.exec(wrappedModule, true, (err) => {
79+
// Bubble up the error to be caught as execution error
80+
if (err) {
81+
throw err;
82+
}
83+
});
84+
85+
scope.unset(MODULE_KEY);
86+
87+
return moduleObj.exports;
88+
};
89+
}
90+
91+
module.exports = createPostmanRequire;

lib/sandbox/pmapi.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,10 +47,11 @@ const _ = require('lodash'),
4747
* @param {Function} onSkipRequest - callback to execute when pm.execution.skipRequest() called
4848
* @param {Function} onAssertion - callback to execute when pm.expect() called
4949
* @param {Object} cookieStore - cookie store
50+
* @param {Function} require - require
5051
* @param {Object} [options] - options
5152
* @param {Array.<String>} [options.disabledAPIs] - list of disabled APIs
5253
*/
53-
function Postman (execution, onRequest, onSkipRequest, onAssertion, cookieStore, options = {}) {
54+
function Postman (execution, onRequest, onSkipRequest, onAssertion, cookieStore, require, options = {}) {
5455
// @todo - ensure runtime passes data in a scope format
5556
let iterationData = new VariableScope();
5657

@@ -291,7 +292,9 @@ function Postman (execution, onRequest, onSkipRequest, onAssertion, cookieStore,
291292
*/
292293
current: execution.legacy._eventItemName
293294
})
294-
}
295+
},
296+
297+
require: require
295298
}, options.disabledAPIs);
296299

297300
// extend pm api with test runner abilities

package-lock.json

Lines changed: 2 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
"terser": "^5.24.0",
9393
"tsd-jsdoc": "^2.5.0",
9494
"tv4": "1.3.0",
95-
"uniscope": "2.0.1",
95+
"uniscope": "github:postmanlabs/uniscope#feat/options-reset-locals",
9696
"watchify": "^4.0.0",
9797
"xml2js": "0.4.23"
9898
},

0 commit comments

Comments
 (0)