diff --git a/src/compile/build.js b/src/compile/build.js index 4109100..6a1eae6 100644 --- a/src/compile/build.js +++ b/src/compile/build.js @@ -12,7 +12,7 @@ const MINIFY = (() => { } })() -module.exports = ({ content, cwd }) => +module.exports = ({ content, cwd, nodePaths = [] }) => esbuild.build({ stdin: { contents: content, @@ -24,5 +24,6 @@ module.exports = ({ content, cwd }) => platform: 'node', legalComments: 'eof', target: 'node24', + nodePaths, ...MINIFY }) diff --git a/src/compile/index.js b/src/compile/index.js index 1d4ad23..1f10248 100644 --- a/src/compile/index.js +++ b/src/compile/index.js @@ -1,6 +1,6 @@ 'use strict' -const { mkdirSync } = require('fs') +const { mkdirSync, readFileSync } = require('fs') const path = require('path') const transformDependencies = require('./transform-dependencies') @@ -24,22 +24,40 @@ const enqueueInstall = (tmpdir, dependencies, allow) => { return next } -module.exports = async (snippet, { tmpdir = DEFAULT_TMPDIR, allow = {} } = {}) => { +module.exports = async (snippet, { tmpdir = DEFAULT_TMPDIR, allow = {}, nodePaths = [] } = {}) => { let content = template(snippet) const phases = { install: 0 } - const dependencies = detectDependencies(content) + const allDependencies = detectDependencies(content) + installDependencies.validateDependencies(allDependencies, allow.dependencies) + const dependencies = nodePaths.length + ? allDependencies.filter(dep => { + const name = installDependencies.extractPackageName(dep) + const version = dep.slice(name.length + 1) + try { + const pkgPath = require.resolve(path.join(name, 'package.json'), { paths: nodePaths }) + if (version === 'latest') return false + return JSON.parse(readFileSync(pkgPath, 'utf8')).version !== version + } catch { + return true + } + }) + : allDependencies + if (dependencies.length) { content = transformDependencies(content) mkdirSync(tmpdir, { recursive: true }) const elapsed = timeSpan() await enqueueInstall(tmpdir, dependencies, allow) phases.install = elapsed() + } else if (allDependencies.length) { + content = transformDependencies(content) + mkdirSync(tmpdir, { recursive: true }) } - const cwd = dependencies.length ? tmpdir : process.cwd() + const cwd = allDependencies.length ? tmpdir : process.cwd() const elapsed = timeSpan() - const result = await build({ content, cwd }) + const result = await build({ content, cwd, nodePaths }) phases.build = elapsed() content = result.outputFiles[0].text diff --git a/src/compile/install-dependencies.js b/src/compile/install-dependencies.js index 42d9f59..73bebcf 100644 --- a/src/compile/install-dependencies.js +++ b/src/compile/install-dependencies.js @@ -56,3 +56,6 @@ module.exports = async ({ dependencies, cwd, allow = {} }) => { validateDependencies(dependencies, allow.dependencies) return $(`${install} ${dependencies.join(' ')}`, { cwd, env: { ...process.env, CI: true } }) } + +module.exports.validateDependencies = validateDependencies +module.exports.extractPackageName = extractPackageName diff --git a/src/index.d.ts b/src/index.d.ts index 6b2fbc5..4074756 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -73,6 +73,8 @@ export interface AllowOptions { export interface CreateOptions { /** Directory for installing code dependencies. Reused across invocations. */ tmpdir?: string + /** Additional directories for resolving dependencies. Dependencies found here with a matching version skip npm install. */ + nodePaths?: string[] } /** diff --git a/src/index.js b/src/index.js index ea382c7..d9f4151 100644 --- a/src/index.js +++ b/src/index.js @@ -40,11 +40,11 @@ const spawn = ({ args, env, timeout }) => { return $('node', ['-', args], spawnOpts) } -module.exports = ({ tmpdir } = {}) => { +module.exports = ({ tmpdir, nodePaths } = {}) => { const isolatedFunction = (snippet, { timeout, memory, throwError = true, allow = {} } = {}) => { if (!['function', 'string'].includes(typeof snippet)) throw new TypeError('Expected a function') const { permissions = [] } = allow - const compilePromise = compile(snippet, { tmpdir, allow }) + const compilePromise = compile(snippet, { tmpdir, allow, nodePaths }) return async (...args) => { let total