isolated-function 0.1.14 → 0.1.16

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/README.md CHANGED
@@ -35,6 +35,9 @@
35
35
  - [=\> (fn(\[...args\]), teardown())](#-fnargs-teardown)
36
36
  - [fn](#fn)
37
37
  - [teardown](#teardown)
38
+ - [Environment Variables](#environment-variables)
39
+ - [`ISOLATED_FUNCTIONS_MINIFY`](#isolated_functions_minify)
40
+ - [`DEBUG`](#debug)
38
41
  - [License](#license)
39
42
 
40
43
  ## Install
@@ -262,9 +265,19 @@ Timeout after a specified amount of time, in milliseconds.
262
265
  ##### tmpdir
263
266
 
264
267
  Type: `function`<br>
265
- Default: `fs.mkdtemp(path.join(require('os').tmpdir(), 'compile-'))`
266
268
 
267
- The temporal folder to use for installing code dependencies.
269
+ It setup the temporal folder to be used for installing code dependencies.
270
+
271
+ The default implementation is:
272
+
273
+ ```js
274
+ const tmpdir = async () => {
275
+ const cwd = await fs.mkdtemp(path.join(require('os').tmpdir(), 'compile-'))
276
+ await fs.mkdir(cwd, { recursive: true })
277
+ const cleanup = () => fs.rm(cwd, { recursive: true, force: true })
278
+ return { cwd, cleanup }
279
+ }
280
+ ```
268
281
 
269
282
  ### => (fn([...args]), teardown())
270
283
 
@@ -280,6 +293,18 @@ Type: `function`
280
293
 
281
294
  A function to be called to release resources associated with the **isolated-function**.
282
295
 
296
+ ## Environment Variables
297
+
298
+ #### `ISOLATED_FUNCTIONS_MINIFY`
299
+
300
+ Default: `true`
301
+
302
+ When is `false`, it disabled minify the compiled code.
303
+
304
+ #### `DEBUG`
305
+
306
+ Pass `DEBUG=isolated-function` for enabling debug timing output.
307
+
283
308
  ## License
284
309
 
285
310
  **isolated-function** © [Kiko Beats](https://kikobeats.com), released under the [MIT](https://github.com/Kikobeats/isolated-function/blob/master/LICENSE.md) License.<br>
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "isolated-function",
3
3
  "description": "Runs untrusted code in a Node.js v8 sandbox.",
4
4
  "homepage": "https://github.com/Kikobeats/isolated-function",
5
- "version": "0.1.14",
5
+ "version": "0.1.16",
6
6
  "main": "src/index.js",
7
7
  "exports": {
8
8
  ".": "./src/index.js"
@@ -36,6 +36,7 @@
36
36
  "@kikobeats/time-span": "~1.0.5",
37
37
  "acorn": "~8.12.1",
38
38
  "acorn-walk": "~8.3.3",
39
+ "debug-logfmt": "~1.2.3",
39
40
  "ensure-error": "~3.0.1",
40
41
  "esbuild": "~0.23.1",
41
42
  "serialize-error": "8",
@@ -9,6 +9,7 @@ const path = require('path')
9
9
  const transformDependencies = require('./transform-dependencies')
10
10
  const detectDependencies = require('./detect-dependencies')
11
11
  const generateTemplate = require('../template')
12
+ const { duration } = require('../debug')
12
13
 
13
14
  const MINIFY = (() => {
14
15
  return process.env.ISOLATED_FUNCTIONS_MINIFY !== 'false'
@@ -20,48 +21,52 @@ const MINIFY = (() => {
20
21
  }
21
22
  })()
22
23
 
23
- const packageManager = (() => {
24
+ const install = (() => {
24
25
  try {
25
26
  execSync('which pnpm').toString().trim()
26
- return { init: 'pnpm init', install: 'pnpm install' }
27
+ return 'pnpm install'
27
28
  } catch {
28
- return { init: 'npm init --yes', install: 'npm install' }
29
+ return 'npm install'
29
30
  }
30
31
  })()
31
32
 
32
- const tmpdirDefault = () => fs.mkdtemp(path.join(require('os').tmpdir(), 'compile-'))
33
-
34
- const getTmp = async (content, tmpdir) => {
35
- const cwd = await tmpdir()
33
+ const tmpdirDefault = async () => {
34
+ const cwd = await fs.mkdtemp(path.join(require('os').tmpdir(), 'compile-'))
36
35
  await fs.mkdir(cwd, { recursive: true })
37
-
38
- const filepath = path.join(cwd, 'index.js')
39
- await fs.writeFile(filepath, content)
40
-
41
36
  const cleanup = () => fs.rm(cwd, { recursive: true, force: true })
42
- return { filepath, cwd, content, cleanup }
37
+ return { cwd, cleanup }
43
38
  }
44
39
 
45
40
  module.exports = async (snippet, tmpdir = tmpdirDefault) => {
46
41
  const compiledTemplate = generateTemplate(snippet)
47
42
  const dependencies = detectDependencies(compiledTemplate)
48
- const tmp = await getTmp(transformDependencies(compiledTemplate), tmpdir)
49
43
 
50
- await $(packageManager.init, { cwd: tmp.cwd })
51
- await $(`${packageManager.install} ${dependencies.join(' ')}`, {
52
- cwd: tmp.cwd
53
- })
44
+ const content = transformDependencies(compiledTemplate)
45
+ const tmpDir = await duration('tmpdir', tmpdir)
54
46
 
55
- const result = await esbuild.build({
56
- entryPoints: [tmp.filepath],
57
- bundle: true,
58
- ...MINIFY,
59
- write: false,
60
- platform: 'node'
61
- })
47
+ await duration('npm:init', () => fs.writeFile(path.join(tmpDir.cwd, 'package.json'), '{}'))
48
+ await duration('npm:install', () =>
49
+ $(`${install} ${dependencies.join(' ')}`, { cwd: tmpDir.cwd })
50
+ )
62
51
 
63
- await tmp.cleanup()
64
- return getTmp(result.outputFiles[0].text, tmpdir)
52
+ const result = await duration('esbuild', () =>
53
+ esbuild.build({
54
+ stdin: {
55
+ contents: content,
56
+ resolveDir: tmpDir.cwd,
57
+ sourcefile: 'index.js'
58
+ },
59
+ bundle: true,
60
+ ...MINIFY,
61
+ write: false,
62
+ platform: 'node'
63
+ })
64
+ )
65
+
66
+ return {
67
+ content: result.outputFiles[0].text,
68
+ cleanupPromise: duration('tmpDir:cleanup', tmpDir.cleanup)
69
+ }
65
70
  }
66
71
 
67
72
  module.exports.detectDependencies = detectDependencies
package/src/debug.js ADDED
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ const debug = require('debug-logfmt')('isolated-function')
4
+
5
+ const duration = async (name, fn) => {
6
+ const duration = debug.duration(name)
7
+
8
+ return Promise.resolve(fn())
9
+ .then(result => {
10
+ duration()
11
+ return result
12
+ })
13
+ .catch(error => {
14
+ duration.error()
15
+ throw error
16
+ })
17
+ }
18
+
19
+ module.exports = { debug, duration }
package/src/index.js CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
  const { deserializeError } = require('serialize-error')
4
4
  const timeSpan = require('@kikobeats/time-span')()
5
+ const { Readable } = require('node:stream')
5
6
  const $ = require('tinyspawn')
6
- const path = require('path')
7
7
 
8
8
  const compile = require('./compile')
9
+ const { debug } = require('./debug')
9
10
 
10
11
  const createError = ({ name, message, ...props }) => {
11
12
  const error = new Error(message)
@@ -14,12 +15,8 @@ const createError = ({ name, message, ...props }) => {
14
15
  return error
15
16
  }
16
17
 
17
- const flags = ({ filename, memory }) => {
18
- const flags = [
19
- '--disable-warning=ExperimentalWarning',
20
- '--experimental-permission',
21
- `--allow-fs-read=${filename}`
22
- ]
18
+ const flags = ({ memory }) => {
19
+ const flags = ['--disable-warning=ExperimentalWarning', '--experimental-permission']
23
20
  if (memory) flags.push(`--max-old-space-size=${memory}`)
24
21
  return flags.join(' ')
25
22
  }
@@ -31,22 +28,26 @@ module.exports = (snippet, { tmpdir, timeout, memory, throwError = true } = {})
31
28
  const fn = async (...args) => {
32
29
  let duration
33
30
  try {
34
- const { filepath } = await compilePromise
31
+ const { content, cleanupPromise } = await compilePromise
35
32
 
36
- const cwd = path.dirname(filepath)
37
- const filename = path.basename(filepath)
38
33
  duration = timeSpan()
39
- const { stdout } = await $('node', [filename, JSON.stringify(args)], {
40
- cwd,
34
+ const subprocess = $('node', ['-', JSON.stringify(args)], {
41
35
  env: {
42
36
  ...process.env,
43
- NODE_OPTIONS: flags({ filename, memory })
37
+ NODE_OPTIONS: flags({ memory })
44
38
  },
45
39
  timeout,
46
40
  killSignal: 'SIGKILL'
47
41
  })
42
+ Readable.from(content).pipe(subprocess.stdin)
43
+ const [{ stdout }] = await Promise.all([subprocess, cleanupPromise])
48
44
  const { isFulfilled, value, profiling, logging } = JSON.parse(stdout)
49
45
  profiling.duration = duration()
46
+ debug('node', {
47
+ duration: `${Math.round(profiling.duration / 100)}s`,
48
+ memory: `${Math.round(profiling.memory / (1024 * 1024))}MiB`
49
+ })
50
+
50
51
  return isFulfilled
51
52
  ? { isFulfilled, value, profiling, logging }
52
53
  : throwError