@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.
@@ -10,6 +10,7 @@ export interface FunctionConfig {
10
10
  }
11
11
  export type Function = Omit<FunctionConfig, 'name'> & {
12
12
  code: string;
13
+ sourcePath?: string;
13
14
  };
14
15
  export type Functions = Record<string, Function>;
15
16
  export type RegisterFunctionsParams = {
@@ -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;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEtE,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"}
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":"AAOA,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AA4JnD;;;;;;;;;;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,CA6BjC"}
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 = (_b = (_a = require.main) === null || _a === void 0 ? void 0 : _a.filename) !== null && _b !== void 0 ? _b : process.cwd();
135
- const customRequire = (0, node_module_1.createRequire)(entryFile);
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
- const codeToRun = functionToRun.code.includes('import ')
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
- const codeToRun = functionToRun.code.includes('import ')
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowerforce/flowerbase",
3
- "version": "1.8.4-beta.2",
3
+ "version": "1.8.4-beta.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -10,7 +10,10 @@ export interface FunctionConfig {
10
10
  disable_arg_logs?: boolean
11
11
  }
12
12
 
13
- export type Function = Omit<FunctionConfig, 'name'> & { code: string }
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
- typeof parsedOptions === 'object' &&
78
- 'projection' in parsedOptions
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 = (contextData: ReturnType<typeof generateContextData>) => {
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 customRequire = createRequire(entryFile)
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(contextData)
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
- const codeToRun = functionToRun.code.includes('import ')
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(codeToRun, vmContext)
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) {