@flowerforce/flowerbase 1.8.4-beta.2 → 1.8.4-beta.3
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/dist/features/functions/interface.d.ts +1 -0
- package/dist/features/functions/interface.d.ts.map +1 -1
- package/dist/features/functions/utils.js +1 -1
- package/dist/utils/context/index.d.ts.map +1 -1
- package/dist/utils/context/index.js +91 -14
- package/package.json +1 -1
- package/src/features/functions/interface.ts +4 -1
- package/src/features/functions/utils.ts +3 -3
- package/src/utils/__tests__/contextExecuteCompatibility.test.ts +40 -0
- package/src/utils/context/index.ts +142 -13
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/features/functions/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAE1C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG;
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../../src/features/functions/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAClC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oCAAoC,CAAA;AACzE,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAA;AAE1C,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAC3B;AAED,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,MAAM,CAAC,GAAG;IACpD,IAAI,EAAE,MAAM,CAAA;IACZ,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;AAEhD,MAAM,MAAM,uBAAuB,GAAG;IACpC,GAAG,EAAE,eAAe,CAAA;IACpB,aAAa,EAAE,SAAS,CAAA;IACxB,SAAS,EAAE,KAAK,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,aAAa,EAAE,UAAU,CAAC,oBAAoB,CAAC,CAAC,MAAM,UAAU,CAAC,oBAAoB,CAAC,CAAC,CAAA;IACvF,KAAK,EAAE,UAAU,CAAC,oBAAoB,CAAC,CAAA;IACvC,MAAM,EAAE,QAAQ,CAAA;IAChB,MAAM,CAAC,EAAE,QAAQ,CAAA;IACjB,UAAU,CAAC,EAAE,QAAQ,CAAA;IACrB,OAAO,CAAC,EAAE,QAAQ,CAAA;IAClB,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,QAAQ,EAAE,QAAQ,CAAA;IAClB,SAAS,EAAE,QAAQ,EAAE,CAAA;IACrB,QAAQ,EAAE,QAAQ,EAAE,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,KAAK,0BAA0B,GAAG;IAChC,aAAa,EAAE,SAAS,CAAA;IACxB,KAAK,EAAE,KAAK,CAAA;CACb,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG,CAC/B,GAAG,EAAE,eAAe,EACpB,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,0BAA0B,KACjD,OAAO,CAAC,IAAI,CAAC,CAAA"}
|
|
@@ -46,7 +46,7 @@ const loadFunctions = (...args_1) => __awaiter(void 0, [...args_1], void 0, func
|
|
|
46
46
|
throw new Error(`File ${name}.js or ${name}.ts not found`);
|
|
47
47
|
}
|
|
48
48
|
code = fs_1.default.readFileSync(fnPath, 'utf-8');
|
|
49
|
-
acc[name] = Object.assign({ code }, opts);
|
|
49
|
+
acc[name] = Object.assign({ code, sourcePath: fnPath }, opts);
|
|
50
50
|
return acc;
|
|
51
51
|
}, {});
|
|
52
52
|
return functions;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/context/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/context/index.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AA8RnD;;;;;;;;;;GAUG;AACH,wBAAsB,eAAe,CAAC,EACpC,IAAI,EACJ,GAAG,EACH,KAAK,EACL,IAAI,EACJ,eAAe,EACf,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,eAAsB,EACtB,OAAO,EACP,OAAO,EACR,EAAE,qBAAqB,GAAG,OAAO,CAAC,OAAO,CAAC,CA2G1C;AAED,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,GAAG,EACH,KAAK,EACL,IAAI,EACJ,eAAe,EACf,aAAa,EACb,QAAQ,EACR,YAAY,EACZ,WAAW,EACX,eAAsB,EACtB,OAAO,EACR,EAAE,qBAAqB,GAAG,OAAO,CA0BjC"}
|
|
@@ -14,6 +14,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
14
14
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
15
|
exports.GenerateContext = GenerateContext;
|
|
16
16
|
exports.GenerateContextSync = GenerateContextSync;
|
|
17
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
17
18
|
const node_module_1 = require("node:module");
|
|
18
19
|
const node_path_1 = __importDefault(require("node:path"));
|
|
19
20
|
const node_url_1 = require("node:url");
|
|
@@ -89,6 +90,27 @@ const wrapEsmModule = (code) => {
|
|
|
89
90
|
].join('\n');
|
|
90
91
|
return `${prelude}\n${code}\n${trailer}`;
|
|
91
92
|
};
|
|
93
|
+
const transpileSandboxModule = (code) => {
|
|
94
|
+
const exportedNames = [];
|
|
95
|
+
let transformed = code.includes('import ')
|
|
96
|
+
? transformImportsToRequire(code)
|
|
97
|
+
: code;
|
|
98
|
+
transformed = transformed.replace(/^\s*export\s+function\s+([A-Za-z_$][\w$]*)\s*\(/gm, (_match, name) => {
|
|
99
|
+
exportedNames.push(name);
|
|
100
|
+
return `function ${name}(`;
|
|
101
|
+
});
|
|
102
|
+
transformed = transformed.replace(/^\s*export\s+(const|let|var|class)\s+([A-Za-z_$][\w$]*)/gm, (_match, kind, name) => {
|
|
103
|
+
exportedNames.push(name);
|
|
104
|
+
return `${kind} ${name}`;
|
|
105
|
+
});
|
|
106
|
+
transformed = transformed.replace(/^\s*export\s+default\s+/gm, 'module.exports = ');
|
|
107
|
+
if (exportedNames.length === 0) {
|
|
108
|
+
return transformed;
|
|
109
|
+
}
|
|
110
|
+
return `${transformed}\n${[...new Set(exportedNames)]
|
|
111
|
+
.map((name) => `exports.${name} = ${name}`)
|
|
112
|
+
.join('\n')}`;
|
|
113
|
+
};
|
|
92
114
|
const resolveImportTarget = (specifier, customRequire) => {
|
|
93
115
|
try {
|
|
94
116
|
const resolved = customRequire.resolve(specifier);
|
|
@@ -109,6 +131,53 @@ const shouldFallbackFromVmModules = (error) => {
|
|
|
109
131
|
const code = error.code;
|
|
110
132
|
return code === 'ERR_VM_MODULES_DISABLED' || code === 'ERR_VM_MODULES_NOT_SUPPORTED';
|
|
111
133
|
};
|
|
134
|
+
const resolveModulePath = (specifier, parentFile) => {
|
|
135
|
+
const parentDir = node_path_1.default.dirname(parentFile);
|
|
136
|
+
const basePath = node_path_1.default.resolve(parentDir, specifier);
|
|
137
|
+
const candidates = [
|
|
138
|
+
basePath,
|
|
139
|
+
`${basePath}.js`,
|
|
140
|
+
`${basePath}.ts`,
|
|
141
|
+
node_path_1.default.join(basePath, 'index.js'),
|
|
142
|
+
node_path_1.default.join(basePath, 'index.ts')
|
|
143
|
+
];
|
|
144
|
+
return candidates.find((candidate) => {
|
|
145
|
+
try {
|
|
146
|
+
return node_fs_1.default.statSync(candidate).isFile();
|
|
147
|
+
}
|
|
148
|
+
catch (_a) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
};
|
|
153
|
+
const executeSandboxModule = ({ code, contextData, filePath, moduleCache }) => {
|
|
154
|
+
var _a;
|
|
155
|
+
if (moduleCache.has(filePath)) {
|
|
156
|
+
return moduleCache.get(filePath);
|
|
157
|
+
}
|
|
158
|
+
const sandboxModule = { exports: {} };
|
|
159
|
+
moduleCache.set(filePath, sandboxModule.exports);
|
|
160
|
+
const baseRequire = (0, node_module_1.createRequire)(filePath);
|
|
161
|
+
const localRequire = ((specifier) => {
|
|
162
|
+
if (specifier.startsWith('.') || specifier.startsWith('/')) {
|
|
163
|
+
const resolvedPath = resolveModulePath(specifier, filePath);
|
|
164
|
+
if (resolvedPath) {
|
|
165
|
+
return executeSandboxModule({
|
|
166
|
+
code: node_fs_1.default.readFileSync(resolvedPath, 'utf-8'),
|
|
167
|
+
contextData,
|
|
168
|
+
filePath: resolvedPath,
|
|
169
|
+
moduleCache
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return baseRequire(specifier);
|
|
174
|
+
});
|
|
175
|
+
const vmContext = vm_1.default.createContext(Object.assign(Object.assign({}, contextData), { require: localRequire, exports: sandboxModule.exports, module: sandboxModule, __filename: filePath, __dirname: node_path_1.default.dirname(filePath), __fb_require: localRequire, __fb_filename: filePath, __fb_dirname: node_path_1.default.dirname(filePath) }));
|
|
176
|
+
vm_1.default.runInContext(transpileSandboxModule(code), vmContext, { filename: filePath });
|
|
177
|
+
sandboxModule.exports = (_a = resolveExport(vmContext)) !== null && _a !== void 0 ? _a : sandboxModule.exports;
|
|
178
|
+
moduleCache.set(filePath, sandboxModule.exports);
|
|
179
|
+
return sandboxModule.exports;
|
|
180
|
+
};
|
|
112
181
|
const isExportedFunction = (value) => typeof value === 'function';
|
|
113
182
|
const getDefaultExport = (value) => {
|
|
114
183
|
if (!value || typeof value !== 'object')
|
|
@@ -128,11 +197,25 @@ const resolveExport = (ctx) => {
|
|
|
128
197
|
return contextExports;
|
|
129
198
|
return (_e = getDefaultExport(moduleExports)) !== null && _e !== void 0 ? _e : getDefaultExport(contextExports);
|
|
130
199
|
};
|
|
131
|
-
const buildVmContext = (contextData) => {
|
|
132
|
-
var _a, _b;
|
|
200
|
+
const buildVmContext = (contextData, currentFunction) => {
|
|
201
|
+
var _a, _b, _c;
|
|
133
202
|
const sandboxModule = { exports: {} };
|
|
134
|
-
const entryFile = (
|
|
135
|
-
const
|
|
203
|
+
const entryFile = (_c = (_a = currentFunction === null || currentFunction === void 0 ? void 0 : currentFunction.sourcePath) !== null && _a !== void 0 ? _a : (_b = require.main) === null || _b === void 0 ? void 0 : _b.filename) !== null && _c !== void 0 ? _c : process.cwd();
|
|
204
|
+
const moduleCache = new Map();
|
|
205
|
+
const customRequire = ((specifier) => {
|
|
206
|
+
if ((specifier.startsWith('.') || specifier.startsWith('/')) && (currentFunction === null || currentFunction === void 0 ? void 0 : currentFunction.sourcePath)) {
|
|
207
|
+
const resolvedPath = resolveModulePath(specifier, currentFunction.sourcePath);
|
|
208
|
+
if (resolvedPath) {
|
|
209
|
+
return executeSandboxModule({
|
|
210
|
+
code: node_fs_1.default.readFileSync(resolvedPath, 'utf-8'),
|
|
211
|
+
contextData,
|
|
212
|
+
filePath: resolvedPath,
|
|
213
|
+
moduleCache
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return (0, node_module_1.createRequire)(entryFile)(specifier);
|
|
218
|
+
});
|
|
136
219
|
const vmContext = vm_1.default.createContext(Object.assign(Object.assign({}, contextData), { require: customRequire, exports: sandboxModule.exports, module: sandboxModule, __filename,
|
|
137
220
|
__dirname, __fb_require: customRequire, __fb_filename: __filename, __fb_dirname: __dirname }));
|
|
138
221
|
return { sandboxModule, entryFile, customRequire, vmContext };
|
|
@@ -169,7 +252,7 @@ function GenerateContext(_a) {
|
|
|
169
252
|
GenerateContextSync,
|
|
170
253
|
request
|
|
171
254
|
});
|
|
172
|
-
const { sandboxModule, entryFile, customRequire, vmContext } = buildVmContext(contextData);
|
|
255
|
+
const { sandboxModule, entryFile, customRequire, vmContext } = buildVmContext(contextData, functionToRun);
|
|
173
256
|
const vmModules = vm_1.default;
|
|
174
257
|
const hasStaticImport = /\bimport\s+/.test(functionToRun.code);
|
|
175
258
|
let usedVmModules = false;
|
|
@@ -214,10 +297,7 @@ function GenerateContext(_a) {
|
|
|
214
297
|
}
|
|
215
298
|
}
|
|
216
299
|
if (!usedVmModules) {
|
|
217
|
-
|
|
218
|
-
? transformImportsToRequire(functionToRun.code)
|
|
219
|
-
: functionToRun.code;
|
|
220
|
-
vm_1.default.runInContext(codeToRun, vmContext);
|
|
300
|
+
vm_1.default.runInContext(transpileSandboxModule(functionToRun.code), vmContext, { filename: entryFile });
|
|
221
301
|
}
|
|
222
302
|
sandboxModule.exports = (_a = resolveExport(vmContext)) !== null && _a !== void 0 ? _a : sandboxModule.exports;
|
|
223
303
|
if (deserializeArgs) {
|
|
@@ -247,11 +327,8 @@ function GenerateContextSync({ args, app, rules, user, currentFunction, function
|
|
|
247
327
|
GenerateContextSync,
|
|
248
328
|
request
|
|
249
329
|
});
|
|
250
|
-
const { sandboxModule, vmContext } = buildVmContext(contextData);
|
|
251
|
-
|
|
252
|
-
? transformImportsToRequire(functionToRun.code)
|
|
253
|
-
: functionToRun.code;
|
|
254
|
-
vm_1.default.runInContext(codeToRun, vmContext);
|
|
330
|
+
const { sandboxModule, entryFile, vmContext } = buildVmContext(contextData, functionToRun);
|
|
331
|
+
vm_1.default.runInContext(transpileSandboxModule(functionToRun.code), vmContext, { filename: entryFile });
|
|
255
332
|
sandboxModule.exports = (_a = resolveExport(vmContext)) !== null && _a !== void 0 ? _a : sandboxModule.exports;
|
|
256
333
|
const fn = sandboxModule.exports;
|
|
257
334
|
if (deserializeArgs) {
|
package/package.json
CHANGED
|
@@ -10,7 +10,10 @@ export interface FunctionConfig {
|
|
|
10
10
|
disable_arg_logs?: boolean
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export type Function = Omit<FunctionConfig, 'name'> & {
|
|
13
|
+
export type Function = Omit<FunctionConfig, 'name'> & {
|
|
14
|
+
code: string
|
|
15
|
+
sourcePath?: string
|
|
16
|
+
}
|
|
14
17
|
|
|
15
18
|
export type Functions = Record<string, Function>
|
|
16
19
|
|
|
@@ -27,7 +27,7 @@ export const loadFunctions = async (rootDir = process.cwd()): Promise<Functions>
|
|
|
27
27
|
throw new Error(`File ${name}.js or ${name}.ts not found`)
|
|
28
28
|
}
|
|
29
29
|
code = fs.readFileSync(fnPath, 'utf-8')
|
|
30
|
-
acc[name] = { code, ...opts }
|
|
30
|
+
acc[name] = { code, sourcePath: fnPath, ...opts }
|
|
31
31
|
|
|
32
32
|
return acc
|
|
33
33
|
}, {} as Functions)
|
|
@@ -74,8 +74,8 @@ export const executeQuery = async ({
|
|
|
74
74
|
typeof projection !== 'undefined'
|
|
75
75
|
? parsedProjection
|
|
76
76
|
: parsedOptions &&
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
typeof parsedOptions === 'object' &&
|
|
78
|
+
'projection' in parsedOptions
|
|
79
79
|
? (parsedOptions as Document).projection
|
|
80
80
|
: undefined
|
|
81
81
|
return {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { GenerateContextSync } from '../context'
|
|
2
2
|
import { Functions } from '../../features/functions/interface'
|
|
3
|
+
import fs from 'node:fs'
|
|
4
|
+
import os from 'node:os'
|
|
5
|
+
import path from 'node:path'
|
|
3
6
|
|
|
4
7
|
const mockServices = {
|
|
5
8
|
api: jest.fn().mockReturnValue({}),
|
|
@@ -117,4 +120,41 @@ describe('context.functions.execute compatibility', () => {
|
|
|
117
120
|
|
|
118
121
|
expect(result).toBe(true)
|
|
119
122
|
})
|
|
123
|
+
|
|
124
|
+
it('loads same-directory helper modules for sandboxed functions', () => {
|
|
125
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'flowerbase-context-'))
|
|
126
|
+
const helperPath = path.join(tempDir, 'getFreightRate.ts')
|
|
127
|
+
|
|
128
|
+
fs.writeFileSync(
|
|
129
|
+
helperPath,
|
|
130
|
+
'export function getFreightRate([address, amount, freightRateValues]) { return { address, total: amount * freightRateValues.multiplier } }'
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const functionsList = {
|
|
134
|
+
caller: {
|
|
135
|
+
sourcePath: path.join(tempDir, 'caller.ts'),
|
|
136
|
+
code: `
|
|
137
|
+
import { getFreightRate } from './getFreightRate'
|
|
138
|
+
|
|
139
|
+
module.exports = function() {
|
|
140
|
+
return getFreightRate(['rome', 4, { multiplier: 2.5 }])
|
|
141
|
+
}
|
|
142
|
+
`
|
|
143
|
+
}
|
|
144
|
+
} as Functions
|
|
145
|
+
|
|
146
|
+
const result = GenerateContextSync({
|
|
147
|
+
args: [],
|
|
148
|
+
app: {} as any,
|
|
149
|
+
rules: {} as any,
|
|
150
|
+
user: {} as any,
|
|
151
|
+
currentFunction: functionsList.caller,
|
|
152
|
+
functionsList,
|
|
153
|
+
services: mockServices,
|
|
154
|
+
functionName: 'caller'
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
expect(result).toEqual({ address: 'rome', total: 10 })
|
|
158
|
+
fs.rmSync(tempDir, { recursive: true, force: true })
|
|
159
|
+
})
|
|
120
160
|
})
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
1
2
|
import { createRequire } from 'node:module'
|
|
2
3
|
import path from 'node:path'
|
|
3
4
|
import { pathToFileURL } from 'node:url'
|
|
4
5
|
import vm from 'vm'
|
|
5
6
|
import { EJSON } from 'bson'
|
|
6
7
|
import { StateManager } from '../../state'
|
|
8
|
+
import { Function as AppFunction } from '../../features/functions/interface'
|
|
7
9
|
import { generateContextData } from './helpers'
|
|
8
10
|
import { GenerateContextParams } from './interface'
|
|
9
11
|
|
|
@@ -92,6 +94,42 @@ const wrapEsmModule = (code: string): string => {
|
|
|
92
94
|
return `${prelude}\n${code}\n${trailer}`
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
const transpileSandboxModule = (code: string): string => {
|
|
98
|
+
const exportedNames: string[] = []
|
|
99
|
+
let transformed = code.includes('import ')
|
|
100
|
+
? transformImportsToRequire(code)
|
|
101
|
+
: code
|
|
102
|
+
|
|
103
|
+
transformed = transformed.replace(
|
|
104
|
+
/^\s*export\s+function\s+([A-Za-z_$][\w$]*)\s*\(/gm,
|
|
105
|
+
(_match, name: string) => {
|
|
106
|
+
exportedNames.push(name)
|
|
107
|
+
return `function ${name}(`
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
transformed = transformed.replace(
|
|
112
|
+
/^\s*export\s+(const|let|var|class)\s+([A-Za-z_$][\w$]*)/gm,
|
|
113
|
+
(_match, kind: string, name: string) => {
|
|
114
|
+
exportedNames.push(name)
|
|
115
|
+
return `${kind} ${name}`
|
|
116
|
+
}
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
transformed = transformed.replace(
|
|
120
|
+
/^\s*export\s+default\s+/gm,
|
|
121
|
+
'module.exports = '
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
if (exportedNames.length === 0) {
|
|
125
|
+
return transformed
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return `${transformed}\n${[...new Set(exportedNames)]
|
|
129
|
+
.map((name) => `exports.${name} = ${name}`)
|
|
130
|
+
.join('\n')}`
|
|
131
|
+
}
|
|
132
|
+
|
|
95
133
|
const resolveImportTarget = (specifier: string, customRequire: NodeRequire): string => {
|
|
96
134
|
try {
|
|
97
135
|
const resolved = customRequire.resolve(specifier)
|
|
@@ -123,6 +161,82 @@ type SandboxContext = vm.Context & {
|
|
|
123
161
|
__fb_dirname?: string
|
|
124
162
|
}
|
|
125
163
|
|
|
164
|
+
type SandboxExecutionContext = ReturnType<typeof generateContextData>
|
|
165
|
+
|
|
166
|
+
const resolveModulePath = (specifier: string, parentFile: string): string | undefined => {
|
|
167
|
+
const parentDir = path.dirname(parentFile)
|
|
168
|
+
const basePath = path.resolve(parentDir, specifier)
|
|
169
|
+
const candidates = [
|
|
170
|
+
basePath,
|
|
171
|
+
`${basePath}.js`,
|
|
172
|
+
`${basePath}.ts`,
|
|
173
|
+
path.join(basePath, 'index.js'),
|
|
174
|
+
path.join(basePath, 'index.ts')
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
return candidates.find((candidate) => {
|
|
178
|
+
try {
|
|
179
|
+
return fs.statSync(candidate).isFile()
|
|
180
|
+
} catch {
|
|
181
|
+
return false
|
|
182
|
+
}
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const executeSandboxModule = ({
|
|
187
|
+
code,
|
|
188
|
+
contextData,
|
|
189
|
+
filePath,
|
|
190
|
+
moduleCache
|
|
191
|
+
}: {
|
|
192
|
+
code: string
|
|
193
|
+
contextData: SandboxExecutionContext
|
|
194
|
+
filePath: string
|
|
195
|
+
moduleCache: Map<string, unknown>
|
|
196
|
+
}): unknown => {
|
|
197
|
+
if (moduleCache.has(filePath)) {
|
|
198
|
+
return moduleCache.get(filePath)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const sandboxModule: SandboxModule = { exports: {} }
|
|
202
|
+
moduleCache.set(filePath, sandboxModule.exports)
|
|
203
|
+
const baseRequire = createRequire(filePath)
|
|
204
|
+
|
|
205
|
+
const localRequire = ((specifier: string) => {
|
|
206
|
+
if (specifier.startsWith('.') || specifier.startsWith('/')) {
|
|
207
|
+
const resolvedPath = resolveModulePath(specifier, filePath)
|
|
208
|
+
if (resolvedPath) {
|
|
209
|
+
return executeSandboxModule({
|
|
210
|
+
code: fs.readFileSync(resolvedPath, 'utf-8'),
|
|
211
|
+
contextData,
|
|
212
|
+
filePath: resolvedPath,
|
|
213
|
+
moduleCache
|
|
214
|
+
})
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return baseRequire(specifier)
|
|
219
|
+
}) as NodeRequire
|
|
220
|
+
|
|
221
|
+
const vmContext = vm.createContext({
|
|
222
|
+
...contextData,
|
|
223
|
+
require: localRequire,
|
|
224
|
+
exports: sandboxModule.exports,
|
|
225
|
+
module: sandboxModule,
|
|
226
|
+
__filename: filePath,
|
|
227
|
+
__dirname: path.dirname(filePath),
|
|
228
|
+
__fb_require: localRequire,
|
|
229
|
+
__fb_filename: filePath,
|
|
230
|
+
__fb_dirname: path.dirname(filePath)
|
|
231
|
+
}) as SandboxContext
|
|
232
|
+
|
|
233
|
+
vm.runInContext(transpileSandboxModule(code), vmContext, { filename: filePath })
|
|
234
|
+
sandboxModule.exports = resolveExport(vmContext) ?? sandboxModule.exports
|
|
235
|
+
moduleCache.set(filePath, sandboxModule.exports)
|
|
236
|
+
|
|
237
|
+
return sandboxModule.exports
|
|
238
|
+
}
|
|
239
|
+
|
|
126
240
|
const isExportedFunction = (value: unknown): value is ExportedFunction =>
|
|
127
241
|
typeof value === 'function'
|
|
128
242
|
|
|
@@ -141,10 +255,28 @@ const resolveExport = (ctx: SandboxContext): ExportedFunction | undefined => {
|
|
|
141
255
|
return getDefaultExport(moduleExports) ?? getDefaultExport(contextExports)
|
|
142
256
|
}
|
|
143
257
|
|
|
144
|
-
const buildVmContext = (
|
|
258
|
+
const buildVmContext = (
|
|
259
|
+
contextData: ReturnType<typeof generateContextData>,
|
|
260
|
+
currentFunction?: AppFunction
|
|
261
|
+
) => {
|
|
145
262
|
const sandboxModule: SandboxModule = { exports: {} }
|
|
146
|
-
const entryFile = require.main?.filename ?? process.cwd()
|
|
147
|
-
const
|
|
263
|
+
const entryFile = currentFunction?.sourcePath ?? require.main?.filename ?? process.cwd()
|
|
264
|
+
const moduleCache = new Map<string, unknown>()
|
|
265
|
+
const customRequire = ((specifier: string) => {
|
|
266
|
+
if ((specifier.startsWith('.') || specifier.startsWith('/')) && currentFunction?.sourcePath) {
|
|
267
|
+
const resolvedPath = resolveModulePath(specifier, currentFunction.sourcePath)
|
|
268
|
+
if (resolvedPath) {
|
|
269
|
+
return executeSandboxModule({
|
|
270
|
+
code: fs.readFileSync(resolvedPath, 'utf-8'),
|
|
271
|
+
contextData,
|
|
272
|
+
filePath: resolvedPath,
|
|
273
|
+
moduleCache
|
|
274
|
+
})
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return createRequire(entryFile)(specifier)
|
|
279
|
+
}) as NodeRequire
|
|
148
280
|
|
|
149
281
|
const vmContext: SandboxContext = vm.createContext({
|
|
150
282
|
...contextData,
|
|
@@ -206,7 +338,10 @@ export async function GenerateContext({
|
|
|
206
338
|
GenerateContextSync,
|
|
207
339
|
request
|
|
208
340
|
})
|
|
209
|
-
const { sandboxModule, entryFile, customRequire, vmContext } = buildVmContext(
|
|
341
|
+
const { sandboxModule, entryFile, customRequire, vmContext } = buildVmContext(
|
|
342
|
+
contextData,
|
|
343
|
+
functionToRun
|
|
344
|
+
)
|
|
210
345
|
|
|
211
346
|
const vmModules = vm as typeof vm & {
|
|
212
347
|
SourceTextModule?: typeof vm.SourceTextModule
|
|
@@ -271,10 +406,7 @@ export async function GenerateContext({
|
|
|
271
406
|
}
|
|
272
407
|
|
|
273
408
|
if (!usedVmModules) {
|
|
274
|
-
|
|
275
|
-
? transformImportsToRequire(functionToRun.code)
|
|
276
|
-
: functionToRun.code
|
|
277
|
-
vm.runInContext(codeToRun, vmContext)
|
|
409
|
+
vm.runInContext(transpileSandboxModule(functionToRun.code), vmContext, { filename: entryFile })
|
|
278
410
|
}
|
|
279
411
|
|
|
280
412
|
sandboxModule.exports = resolveExport(vmContext) ?? sandboxModule.exports
|
|
@@ -323,12 +455,9 @@ export function GenerateContextSync({
|
|
|
323
455
|
GenerateContextSync,
|
|
324
456
|
request
|
|
325
457
|
})
|
|
326
|
-
const { sandboxModule, vmContext } = buildVmContext(contextData)
|
|
327
|
-
const codeToRun = functionToRun.code.includes('import ')
|
|
328
|
-
? transformImportsToRequire(functionToRun.code)
|
|
329
|
-
: functionToRun.code
|
|
458
|
+
const { sandboxModule, entryFile, vmContext } = buildVmContext(contextData, functionToRun)
|
|
330
459
|
|
|
331
|
-
vm.runInContext(
|
|
460
|
+
vm.runInContext(transpileSandboxModule(functionToRun.code), vmContext, { filename: entryFile })
|
|
332
461
|
sandboxModule.exports = resolveExport(vmContext) ?? sandboxModule.exports
|
|
333
462
|
const fn = sandboxModule.exports as ExportedFunction
|
|
334
463
|
if (deserializeArgs) {
|