Use vm2 for JS execution in node, and a vm polyfill for the browser. Use 2 standalone entrypoints for string-templates depending on env
This commit is contained in:
parent
9a21a0b2db
commit
403f64d1ac
|
@ -24,7 +24,8 @@
|
|||
"dayjs": "^1.10.4",
|
||||
"handlebars": "^4.7.6",
|
||||
"handlebars-utils": "^1.0.6",
|
||||
"lodash": "^4.17.20"
|
||||
"lodash": "^4.17.20",
|
||||
"vm2": "^3.9.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.1.0",
|
||||
|
|
|
@ -7,7 +7,15 @@ import globals from "rollup-plugin-node-globals"
|
|||
|
||||
const production = !process.env.ROLLUP_WATCH
|
||||
|
||||
const plugins = [
|
||||
export default [
|
||||
{
|
||||
input: "src/index.mjs",
|
||||
output: {
|
||||
sourcemap: !production,
|
||||
format: "esm",
|
||||
file: "./dist/bundle.mjs",
|
||||
},
|
||||
plugins: [
|
||||
resolve({
|
||||
preferBuiltins: true,
|
||||
browser: true,
|
||||
|
@ -17,28 +25,6 @@ const plugins = [
|
|||
builtins(),
|
||||
json(),
|
||||
production && terser(),
|
||||
]
|
||||
|
||||
export default [
|
||||
{
|
||||
input: "src/index.mjs",
|
||||
output: {
|
||||
sourcemap: !production,
|
||||
format: "esm",
|
||||
file: "./dist/bundle.mjs",
|
||||
],
|
||||
},
|
||||
plugins,
|
||||
},
|
||||
// This is the valid configuration for a CommonJS bundle, but since we have
|
||||
// no use for this, it's better to leave it out.
|
||||
// {
|
||||
// input: "src/index.cjs",
|
||||
// output: {
|
||||
// sourcemap: !production,
|
||||
// format: "cjs",
|
||||
// file: "./dist/bundle.cjs",
|
||||
// exports: "named",
|
||||
// },
|
||||
// plugins,
|
||||
// },
|
||||
]
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
const CAPTURE_JS = new RegExp(/{{ js "(.*)" }}/)
|
||||
const vm = require("vm")
|
||||
const { atob } = require("../utilities")
|
||||
|
||||
// The method of executing JS scripts depends on the bundle being built.
|
||||
// This setter is used in the entrypoint (either index.cjs or index.mjs).
|
||||
let runJS
|
||||
module.exports.setJSRunner = runner => (runJS = runner)
|
||||
|
||||
// Helper utility to strip square brackets from a value
|
||||
const removeSquareBrackets = value => {
|
||||
|
@ -27,23 +31,8 @@ const getContextValue = (path, context) => {
|
|||
return data
|
||||
}
|
||||
|
||||
// Node polyfill for base64 encoding
|
||||
const btoa = plainText => {
|
||||
return Buffer.from(plainText, "utf-8").toString("base64")
|
||||
}
|
||||
|
||||
// Node polyfill for base64 decoding
|
||||
const atob = base64 => {
|
||||
return Buffer.from(base64, "base64").toString("utf-8")
|
||||
}
|
||||
|
||||
// Evaluates JS code against a certain context
|
||||
module.exports.processJS = (handlebars, context) => {
|
||||
// Do not evaluate JS in a node environment
|
||||
if (typeof window === "undefined") {
|
||||
return "JS bindings are not executed in a Node environment"
|
||||
}
|
||||
|
||||
try {
|
||||
// Wrap JS in a function and immediately invoke it.
|
||||
// This is required to allow the final `return` statement to be valid.
|
||||
|
@ -58,37 +47,8 @@ module.exports.processJS = (handlebars, context) => {
|
|||
}
|
||||
|
||||
// Create a sandbox with out context and run the JS
|
||||
vm.createContext(sandboxContext)
|
||||
return vm.runInNewContext(js, sandboxContext, { timeout: 1000 })
|
||||
return runJS(js, sandboxContext)
|
||||
} catch (error) {
|
||||
return "Error while executing JS"
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if a HBS expression is a valid JS HBS expression
|
||||
module.exports.isJSBinding = handlebars => {
|
||||
return module.exports.decodeJSBinding(handlebars) != null
|
||||
}
|
||||
|
||||
// Encodes a raw JS string as a JS HBS expression
|
||||
module.exports.encodeJSBinding = javascript => {
|
||||
return `{{ js "${btoa(javascript)}" }}`
|
||||
}
|
||||
|
||||
// Decodes a JS HBS expression to the raw JS string
|
||||
module.exports.decodeJSBinding = handlebars => {
|
||||
if (!handlebars || typeof handlebars !== "string") {
|
||||
return null
|
||||
}
|
||||
|
||||
// JS is only valid if it is the only HBS expression
|
||||
if (!handlebars.trim().startsWith("{{ js ")) {
|
||||
return null
|
||||
}
|
||||
|
||||
const match = handlebars.match(CAPTURE_JS)
|
||||
if (!match || match.length < 2) {
|
||||
return null
|
||||
}
|
||||
return atob(match[1])
|
||||
}
|
||||
|
|
|
@ -1,169 +1,28 @@
|
|||
const handlebars = require("handlebars")
|
||||
const { registerAll } = require("./helpers/index")
|
||||
const processors = require("./processors")
|
||||
const { removeHandlebarsStatements } = require("./utilities")
|
||||
const manifest = require("../manifest.json")
|
||||
const JS = require("./helpers/javascript")
|
||||
|
||||
const hbsInstance = handlebars.create()
|
||||
registerAll(hbsInstance)
|
||||
const { VM } = require("vm2")
|
||||
const templates = require("./index.js")
|
||||
const { setJSRunner } = require("./helpers/javascript")
|
||||
|
||||
/**
|
||||
* utility function to check if the object is valid
|
||||
* CJS entrypoint for rollup
|
||||
*/
|
||||
function testObject(object) {
|
||||
// JSON stringify will fail if there are any cycles, stops infinite recursion
|
||||
try {
|
||||
JSON.stringify(object)
|
||||
} catch (err) {
|
||||
throw "Unable to process inputs to JSON, cannot recurse"
|
||||
}
|
||||
}
|
||||
module.exports.isValid = templates.isValid
|
||||
module.exports.makePropSafe = templates.makePropSafe
|
||||
module.exports.getManifest = templates.getManifest
|
||||
module.exports.isJSBinding = templates.isJSBinding
|
||||
module.exports.encodeJSBinding = templates.encodeJSBinding
|
||||
module.exports.decodeJSBinding = templates.decodeJSBinding
|
||||
module.exports.processStringSync = templates.processStringSync
|
||||
module.exports.processObjectSync = templates.processObjectSync
|
||||
module.exports.processString = templates.processString
|
||||
module.exports.processObject = templates.processObject
|
||||
|
||||
/**
|
||||
* Given an input object this will recurse through all props to try and update any handlebars statements within.
|
||||
* @param {object|array} object The input structure which is to be recursed, it is important to note that
|
||||
* if the structure contains any cycles then this will fail.
|
||||
* @param {object} context The context that handlebars should fill data from.
|
||||
* @returns {Promise<object|array>} The structure input, as fully updated as possible.
|
||||
* Use vm2 to run JS scripts in a node env
|
||||
*/
|
||||
module.exports.processObject = async (object, context) => {
|
||||
testObject(object)
|
||||
for (let key of Object.keys(object || {})) {
|
||||
if (object[key] != null) {
|
||||
let val = object[key]
|
||||
if (typeof val === "string") {
|
||||
object[key] = await module.exports.processString(object[key], context)
|
||||
} else if (typeof val === "object") {
|
||||
object[key] = await module.exports.processObject(object[key], context)
|
||||
}
|
||||
}
|
||||
}
|
||||
return object
|
||||
}
|
||||
|
||||
/**
|
||||
* This will process a single handlebars containing string. If the string passed in has no valid handlebars statements
|
||||
* then nothing will occur.
|
||||
* @param {string} string The template string which is the filled from the context object.
|
||||
* @param {object} context An object of information which will be used to enrich the string.
|
||||
* @returns {Promise<string>} The enriched string, all templates should have been replaced if they can be.
|
||||
*/
|
||||
module.exports.processString = async (string, context) => {
|
||||
// TODO: carry out any async calls before carrying out async call
|
||||
return module.exports.processStringSync(string, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an input object this will recurse through all props to try and update any handlebars statements within. This is
|
||||
* a pure sync call and therefore does not have the full functionality of the async call.
|
||||
* @param {object|array} object The input structure which is to be recursed, it is important to note that
|
||||
* if the structure contains any cycles then this will fail.
|
||||
* @param {object} context The context that handlebars should fill data from.
|
||||
* @returns {object|array} The structure input, as fully updated as possible.
|
||||
*/
|
||||
module.exports.processObjectSync = (object, context) => {
|
||||
testObject(object)
|
||||
for (let key of Object.keys(object || {})) {
|
||||
let val = object[key]
|
||||
if (typeof val === "string") {
|
||||
object[key] = module.exports.processStringSync(object[key], context)
|
||||
} else if (typeof val === "object") {
|
||||
object[key] = module.exports.processObjectSync(object[key], context)
|
||||
}
|
||||
}
|
||||
return object
|
||||
}
|
||||
|
||||
/**
|
||||
* This will process a single handlebars containing string. If the string passed in has no valid handlebars statements
|
||||
* then nothing will occur. This is a pure sync call and therefore does not have the full functionality of the async call.
|
||||
* @param {string} string The template string which is the filled from the context object.
|
||||
* @param {object} context An object of information which will be used to enrich the string.
|
||||
* @returns {string} The enriched string, all templates should have been replaced if they can be.
|
||||
*/
|
||||
module.exports.processStringSync = (string, context) => {
|
||||
if (!exports.isValid(string)) {
|
||||
return string
|
||||
}
|
||||
// take a copy of input incase error
|
||||
const input = string
|
||||
if (typeof string !== "string") {
|
||||
throw "Cannot process non-string types."
|
||||
}
|
||||
try {
|
||||
string = processors.preprocess(string)
|
||||
// this does not throw an error when template can't be fulfilled, have to try correct beforehand
|
||||
const template = hbsInstance.compile(string, {
|
||||
strict: false,
|
||||
setJSRunner((js, context) => {
|
||||
const vm = new VM({
|
||||
sandbox: context,
|
||||
timeout: 1000
|
||||
})
|
||||
return vm.run(js)
|
||||
})
|
||||
return processors.postprocess(template({
|
||||
now: new Date().toISOString(),
|
||||
...context,
|
||||
}))
|
||||
} catch (err) {
|
||||
return removeHandlebarsStatements(input)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple utility function which makes sure that a templating property has been wrapped in literal specifiers correctly.
|
||||
* @param {string} property The property which is to be wrapped.
|
||||
* @returns {string} The wrapped property ready to be added to a templating string.
|
||||
*/
|
||||
module.exports.makePropSafe = property => {
|
||||
return `[${property}]`.replace("[[", "[").replace("]]", "]")
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a template string contains totally valid syntax (simply tries running it)
|
||||
* @param string The string to test for valid syntax - this may contain no templates and will be considered valid.
|
||||
* @returns {boolean} Whether or not the input string is valid.
|
||||
*/
|
||||
module.exports.isValid = string => {
|
||||
const validCases = [
|
||||
"string",
|
||||
"number",
|
||||
"object",
|
||||
"array",
|
||||
"cannot read property",
|
||||
"undefined",
|
||||
]
|
||||
// this is a portion of a specific string always output by handlebars in the case of a syntax error
|
||||
const invalidCases = [`expecting '`]
|
||||
// don't really need a real context to check if its valid
|
||||
const context = {}
|
||||
try {
|
||||
hbsInstance.compile(processors.preprocess(string, false))(context)
|
||||
return true
|
||||
} catch (err) {
|
||||
const msg = err && err.message ? err.message : err
|
||||
if (!msg) {
|
||||
return false
|
||||
}
|
||||
const invalidCase = invalidCases.some(invalidCase =>
|
||||
msg.toLowerCase().includes(invalidCase)
|
||||
)
|
||||
const validCase = validCases.some(validCase =>
|
||||
msg.toLowerCase().includes(validCase)
|
||||
)
|
||||
// special case for maths functions - don't have inputs yet
|
||||
return validCase && !invalidCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We have generated a static manifest file from the helpers that this string templating package makes use of.
|
||||
* This manifest provides information about each of the helpers and how it can be used.
|
||||
* @returns The manifest JSON which has been generated from the helpers.
|
||||
*/
|
||||
module.exports.getManifest = () => {
|
||||
return manifest
|
||||
}
|
||||
|
||||
/**
|
||||
* Export utilities for working with JS bindings
|
||||
*/
|
||||
module.exports.isJSBinding = JS.isJSBinding
|
||||
module.exports.decodeJSBinding = JS.decodeJSBinding
|
||||
module.exports.encodeJSBinding = JS.encodeJSBinding
|
|
@ -0,0 +1,204 @@
|
|||
const handlebars = require("handlebars")
|
||||
const { registerAll } = require("./helpers/index")
|
||||
const processors = require("./processors")
|
||||
const { removeHandlebarsStatements, atob, btoa } = require("./utilities")
|
||||
const manifest = require("../manifest.json")
|
||||
|
||||
const hbsInstance = handlebars.create()
|
||||
registerAll(hbsInstance)
|
||||
|
||||
/**
|
||||
* utility function to check if the object is valid
|
||||
*/
|
||||
function testObject(object) {
|
||||
// JSON stringify will fail if there are any cycles, stops infinite recursion
|
||||
try {
|
||||
JSON.stringify(object)
|
||||
} catch (err) {
|
||||
throw "Unable to process inputs to JSON, cannot recurse"
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an input object this will recurse through all props to try and update any handlebars statements within.
|
||||
* @param {object|array} object The input structure which is to be recursed, it is important to note that
|
||||
* if the structure contains any cycles then this will fail.
|
||||
* @param {object} context The context that handlebars should fill data from.
|
||||
* @returns {Promise<object|array>} The structure input, as fully updated as possible.
|
||||
*/
|
||||
module.exports.processObject = async (object, context) => {
|
||||
testObject(object)
|
||||
for (let key of Object.keys(object || {})) {
|
||||
if (object[key] != null) {
|
||||
let val = object[key]
|
||||
if (typeof val === "string") {
|
||||
object[key] = await module.exports.processString(object[key], context)
|
||||
} else if (typeof val === "object") {
|
||||
object[key] = await module.exports.processObject(object[key], context)
|
||||
}
|
||||
}
|
||||
}
|
||||
return object
|
||||
}
|
||||
|
||||
/**
|
||||
* This will process a single handlebars containing string. If the string passed in has no valid handlebars statements
|
||||
* then nothing will occur.
|
||||
* @param {string} string The template string which is the filled from the context object.
|
||||
* @param {object} context An object of information which will be used to enrich the string.
|
||||
* @returns {Promise<string>} The enriched string, all templates should have been replaced if they can be.
|
||||
*/
|
||||
module.exports.processString = async (string, context) => {
|
||||
// TODO: carry out any async calls before carrying out async call
|
||||
return module.exports.processStringSync(string, context)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an input object this will recurse through all props to try and update any handlebars statements within. This is
|
||||
* a pure sync call and therefore does not have the full functionality of the async call.
|
||||
* @param {object|array} object The input structure which is to be recursed, it is important to note that
|
||||
* if the structure contains any cycles then this will fail.
|
||||
* @param {object} context The context that handlebars should fill data from.
|
||||
* @returns {object|array} The structure input, as fully updated as possible.
|
||||
*/
|
||||
module.exports.processObjectSync = (object, context) => {
|
||||
testObject(object)
|
||||
for (let key of Object.keys(object || {})) {
|
||||
let val = object[key]
|
||||
if (typeof val === "string") {
|
||||
object[key] = module.exports.processStringSync(object[key], context)
|
||||
} else if (typeof val === "object") {
|
||||
object[key] = module.exports.processObjectSync(object[key], context)
|
||||
}
|
||||
}
|
||||
return object
|
||||
}
|
||||
|
||||
/**
|
||||
* This will process a single handlebars containing string. If the string passed in has no valid handlebars statements
|
||||
* then nothing will occur. This is a pure sync call and therefore does not have the full functionality of the async call.
|
||||
* @param {string} string The template string which is the filled from the context object.
|
||||
* @param {object} context An object of information which will be used to enrich the string.
|
||||
* @returns {string} The enriched string, all templates should have been replaced if they can be.
|
||||
*/
|
||||
module.exports.processStringSync = (string, context) => {
|
||||
if (!exports.isValid(string)) {
|
||||
return string
|
||||
}
|
||||
// take a copy of input incase error
|
||||
const input = string
|
||||
if (typeof string !== "string") {
|
||||
throw "Cannot process non-string types."
|
||||
}
|
||||
try {
|
||||
string = processors.preprocess(string)
|
||||
// this does not throw an error when template can't be fulfilled, have to try correct beforehand
|
||||
const template = hbsInstance.compile(string, {
|
||||
strict: false,
|
||||
})
|
||||
return processors.postprocess(
|
||||
template({
|
||||
now: new Date().toISOString(),
|
||||
...context,
|
||||
})
|
||||
)
|
||||
} catch (err) {
|
||||
return removeHandlebarsStatements(input)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple utility function which makes sure that a templating property has been wrapped in literal specifiers correctly.
|
||||
* @param {string} property The property which is to be wrapped.
|
||||
* @returns {string} The wrapped property ready to be added to a templating string.
|
||||
*/
|
||||
module.exports.makePropSafe = property => {
|
||||
return `[${property}]`.replace("[[", "[").replace("]]", "]")
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a template string contains totally valid syntax (simply tries running it)
|
||||
* @param string The string to test for valid syntax - this may contain no templates and will be considered valid.
|
||||
* @returns {boolean} Whether or not the input string is valid.
|
||||
*/
|
||||
module.exports.isValid = string => {
|
||||
const validCases = [
|
||||
"string",
|
||||
"number",
|
||||
"object",
|
||||
"array",
|
||||
"cannot read property",
|
||||
"undefined",
|
||||
]
|
||||
// this is a portion of a specific string always output by handlebars in the case of a syntax error
|
||||
const invalidCases = [`expecting '`]
|
||||
// don't really need a real context to check if its valid
|
||||
const context = {}
|
||||
try {
|
||||
hbsInstance.compile(processors.preprocess(string, false))(context)
|
||||
return true
|
||||
} catch (err) {
|
||||
const msg = err && err.message ? err.message : err
|
||||
if (!msg) {
|
||||
return false
|
||||
}
|
||||
const invalidCase = invalidCases.some(invalidCase =>
|
||||
msg.toLowerCase().includes(invalidCase)
|
||||
)
|
||||
const validCase = validCases.some(validCase =>
|
||||
msg.toLowerCase().includes(validCase)
|
||||
)
|
||||
// special case for maths functions - don't have inputs yet
|
||||
return validCase && !invalidCase
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We have generated a static manifest file from the helpers that this string templating package makes use of.
|
||||
* This manifest provides information about each of the helpers and how it can be used.
|
||||
* @returns The manifest JSON which has been generated from the helpers.
|
||||
*/
|
||||
module.exports.getManifest = () => {
|
||||
return manifest
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a HBS expression is a valid JS HBS expression
|
||||
* @param handlebars the HBS expression to check
|
||||
* @returns {boolean} whether the expression is JS or not
|
||||
*/
|
||||
module.exports.isJSBinding = handlebars => {
|
||||
return module.exports.decodeJSBinding(handlebars) != null
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes a raw JS string as a JS HBS expression
|
||||
* @param javascript the JS code to encode
|
||||
* @returns {string} the JS HBS expression
|
||||
*/
|
||||
module.exports.encodeJSBinding = javascript => {
|
||||
return `{{ js "${btoa(javascript)}" }}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a JS HBS expression to the raw JS code
|
||||
* @param handlebars the JS HBS expression
|
||||
* @returns {string|null} the raw JS code
|
||||
*/
|
||||
module.exports.decodeJSBinding = handlebars => {
|
||||
if (!handlebars || typeof handlebars !== "string") {
|
||||
return null
|
||||
}
|
||||
|
||||
// JS is only valid if it is the only HBS expression
|
||||
if (!handlebars.trim().startsWith("{{ js ")) {
|
||||
return null
|
||||
}
|
||||
|
||||
const captureJSRegex = new RegExp(/{{ js "(.*)" }}/)
|
||||
const match = handlebars.match(captureJSRegex)
|
||||
if (!match || match.length < 2) {
|
||||
return null
|
||||
}
|
||||
return atob(match[1])
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
import templates from "./index.cjs"
|
||||
import vm from "vm"
|
||||
import templates from "./index.js"
|
||||
import { setJSRunner } from "./helpers/javascript"
|
||||
|
||||
/**
|
||||
* This file is simply an entrypoint for rollup - makes a lot of cjs problems go away
|
||||
* ES6 entrypoint for rollup
|
||||
*/
|
||||
export const isValid = templates.isValid
|
||||
export const makePropSafe = templates.makePropSafe
|
||||
|
@ -13,3 +15,11 @@ export const processStringSync = templates.processStringSync
|
|||
export const processObjectSync = templates.processObjectSync
|
||||
export const processString = templates.processString
|
||||
export const processObject = templates.processObject
|
||||
|
||||
/**
|
||||
* Use polyfilled vm to run JS scripts in a browser Env
|
||||
*/
|
||||
setJSRunner((js, context) => {
|
||||
vm.createContext(context)
|
||||
return vm.runInNewContext(js, context, { timeout: 1000 })
|
||||
})
|
|
@ -22,3 +22,11 @@ module.exports.removeHandlebarsStatements = string => {
|
|||
}
|
||||
return string
|
||||
}
|
||||
|
||||
module.exports.btoa = plainText => {
|
||||
return Buffer.from(plainText, "utf-8").toString("base64")
|
||||
}
|
||||
|
||||
module.exports.atob = base64 => {
|
||||
return Buffer.from(base64, "base64").toString("utf-8")
|
||||
}
|
||||
|
|
|
@ -4,21 +4,7 @@ const processJS = (js, context) => {
|
|||
return processStringSync(encodeJSBinding(js), context)
|
||||
}
|
||||
|
||||
describe("Test the JavaScript helper in Node", () => {
|
||||
it("should not execute JS in Node", () => {
|
||||
const output = processJS(`return 1`)
|
||||
expect(output).toBe("JS bindings are not executed in a Node environment")
|
||||
})
|
||||
})
|
||||
|
||||
describe("Test the JavaScript helper", () => {
|
||||
// JS bindings do not get evaluated on the server for safety.
|
||||
// Since we want to run SJ for tests, we fake a window object to make
|
||||
// it think that we're in the browser
|
||||
beforeEach(() => {
|
||||
window = {}
|
||||
})
|
||||
|
||||
it("should execute a simple expression", () => {
|
||||
const output = processJS(`return 1 + 2`)
|
||||
expect(output).toBe("3")
|
||||
|
@ -84,4 +70,16 @@ describe("Test the JavaScript helper", () => {
|
|||
const output = processJS(`while (true) {}`)
|
||||
expect(output).toBe("Error while executing JS")
|
||||
})
|
||||
|
||||
it("should prevent access to the process global", () => {
|
||||
const output = processJS(`return process`)
|
||||
expect(output).toBe("Error while executing JS")
|
||||
})
|
||||
|
||||
it("should prevent sandbox escape", () => {
|
||||
const output = processJS(
|
||||
`return this.constructor.constructor("return process")()`
|
||||
)
|
||||
expect(output).toBe("Error while executing JS")
|
||||
})
|
||||
})
|
||||
|
|
|
@ -4572,6 +4572,11 @@ vlq@^0.2.2:
|
|||
resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.3.tgz#8f3e4328cf63b1540c0d67e1b2778386f8975b26"
|
||||
integrity sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==
|
||||
|
||||
vm2@^3.9.4:
|
||||
version "3.9.4"
|
||||
resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.4.tgz#2e118290fefe7bd8ea09ebe2f5faf53730dbddaa"
|
||||
integrity sha512-sOdharrJ7KEePIpHekiWaY1DwgueuiBeX/ZBJUPgETsVlJsXuEx0K0/naATq2haFvJrvZnRiORQRubR0b7Ye6g==
|
||||
|
||||
w3c-hr-time@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
|
||||
|
|
Loading…
Reference in New Issue