Merge branch 'master' into nested-context-fix
This commit is contained in:
commit
e6c0cac4d2
|
@ -124,6 +124,8 @@ HEALTHCHECK --interval=15s --timeout=15s --start-period=45s CMD "/healthcheck.sh
|
|||
|
||||
# must set this just before running
|
||||
ENV NODE_ENV=production
|
||||
# this is required for isolated-vm to work on Node 20+
|
||||
ENV NODE_OPTIONS="--no-node-snapshot"
|
||||
WORKDIR /
|
||||
|
||||
CMD ["./runner.sh"]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.17.0",
|
||||
"version": "2.17.1",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*",
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
"sanitize-s3-objectkey": "0.0.1",
|
||||
"semver": "7.3.7",
|
||||
"tar-fs": "2.1.1",
|
||||
"uuid": "8.3.2"
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@shopify/jest-koa-mocks": "5.1.1",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { IdentityContext } from "@budibase/types"
|
||||
import { ExecutionTimeTracker } from "../timers"
|
||||
import { Isolate, Context, Module } from "isolated-vm"
|
||||
|
||||
// keep this out of Budibase types, don't want to expose context info
|
||||
export type ContextMap = {
|
||||
|
@ -10,5 +10,9 @@ export type ContextMap = {
|
|||
isScim?: boolean
|
||||
automationId?: string
|
||||
isMigrating?: boolean
|
||||
jsExecutionTracker?: ExecutionTimeTracker
|
||||
isolateRefs?: {
|
||||
jsIsolate: Isolate
|
||||
jsContext: Context
|
||||
helpersModule: Module
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,41 +20,3 @@ export function cleanup() {
|
|||
}
|
||||
intervals = []
|
||||
}
|
||||
|
||||
export class ExecutionTimeoutError extends Error {
|
||||
public readonly name = "ExecutionTimeoutError"
|
||||
}
|
||||
|
||||
export class ExecutionTimeTracker {
|
||||
static withLimit(limitMs: number) {
|
||||
return new ExecutionTimeTracker(limitMs)
|
||||
}
|
||||
|
||||
constructor(readonly limitMs: number) {}
|
||||
|
||||
private totalTimeMs = 0
|
||||
|
||||
track<T>(f: () => T): T {
|
||||
this.checkLimit()
|
||||
const start = process.hrtime.bigint()
|
||||
try {
|
||||
return f()
|
||||
} finally {
|
||||
const end = process.hrtime.bigint()
|
||||
this.totalTimeMs += Number(end - start) / 1e6
|
||||
this.checkLimit()
|
||||
}
|
||||
}
|
||||
|
||||
get elapsedMS() {
|
||||
return this.totalTimeMs
|
||||
}
|
||||
|
||||
checkLimit() {
|
||||
if (this.totalTimeMs > this.limitMs) {
|
||||
throw new ExecutionTimeoutError(
|
||||
`Execution time limit of ${this.limitMs}ms exceeded: ${this.totalTimeMs}ms`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -82,6 +82,8 @@ EXPOSE 4001
|
|||
# due to this causing yarn to stop installing dev dependencies
|
||||
# which are actually needed to get this environment up and running
|
||||
ENV NODE_ENV=production
|
||||
# this is required for isolated-vm to work on Node 20+
|
||||
ENV NODE_OPTIONS="--no-node-snapshot"
|
||||
ENV CLUSTER_MODE=${CLUSTER_MODE}
|
||||
ENV TOP_LEVEL_PATH=/app
|
||||
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
"../string-templates"
|
||||
],
|
||||
"ext": "js,ts,json,svelte",
|
||||
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../*/dist/**/*"],
|
||||
"exec": "yarn build && node ./dist/index.js"
|
||||
"ignore": ["**/*.spec.ts", "**/*.spec.js", "../*/dist/**/*"],
|
||||
"exec": "yarn build && node --no-node-snapshot ./dist/index.js"
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"check:types": "tsc -p tsconfig.json --noEmit --paths null",
|
||||
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
||||
"debug": "yarn build && node --expose-gc --inspect=9222 dist/index.js",
|
||||
"jest": "NODE_OPTIONS=\"--no-node-snapshot $NODE_OPTIONS\" jest",
|
||||
"test": "bash scripts/test.sh",
|
||||
"test:memory": "jest --maxWorkers=2 --logHeapUsage --forceExit",
|
||||
"test:watch": "jest --watch",
|
||||
|
@ -72,6 +73,7 @@
|
|||
"google-auth-library": "7.12.0",
|
||||
"google-spreadsheet": "3.2.0",
|
||||
"ioredis": "5.3.2",
|
||||
"isolated-vm": "^4.7.2",
|
||||
"jimp": "0.16.1",
|
||||
"joi": "17.6.0",
|
||||
"js-yaml": "4.1.0",
|
||||
|
@ -104,9 +106,8 @@
|
|||
"svelte": "^3.49.0",
|
||||
"tar": "6.1.15",
|
||||
"to-json-schema": "0.2.5",
|
||||
"uuid": "3.3.2",
|
||||
"uuid": "^8.3.2",
|
||||
"validate.js": "0.13.1",
|
||||
"vm2": "^3.9.19",
|
||||
"worker-farm": "1.7.0",
|
||||
"xml2js": "0.5.0"
|
||||
},
|
||||
|
@ -129,6 +130,7 @@
|
|||
"@types/server-destroy": "1.0.1",
|
||||
"@types/supertest": "2.0.14",
|
||||
"@types/tar": "6.1.5",
|
||||
"@types/uuid": "8.3.4",
|
||||
"apidoc": "0.50.4",
|
||||
"copyfiles": "2.4.1",
|
||||
"docker-compose": "0.23.17",
|
||||
|
@ -163,6 +165,12 @@
|
|||
"@budibase/backend-core"
|
||||
],
|
||||
"target": "build"
|
||||
},
|
||||
{
|
||||
"projects": [
|
||||
"@budibase/string-templates"
|
||||
],
|
||||
"target": "build:esbuild"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -181,6 +189,16 @@
|
|||
"target": "build"
|
||||
}
|
||||
]
|
||||
},
|
||||
"test": {
|
||||
"dependsOn": [
|
||||
{
|
||||
"projects": [
|
||||
"@budibase/string-templates"
|
||||
],
|
||||
"target": "build:esbuild"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,12 +3,12 @@ set -e
|
|||
|
||||
if [[ -n $CI ]]
|
||||
then
|
||||
# Running in ci, where resources are limited
|
||||
export NODE_OPTIONS="--max-old-space-size=4096"
|
||||
export NODE_OPTIONS="--max-old-space-size=4096 --no-node-snapshot"
|
||||
echo "jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail $@"
|
||||
jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail $@
|
||||
else
|
||||
# --maxWorkers performs better in development
|
||||
export NODE_OPTIONS="--no-node-snapshot"
|
||||
echo "jest --coverage --maxWorkers=2 --forceExit $@"
|
||||
jest --coverage --maxWorkers=2 --forceExit $@
|
||||
fi
|
|
@ -1,12 +1,12 @@
|
|||
import ScriptRunner from "../../utilities/scriptRunner"
|
||||
import { BBContext } from "@budibase/types"
|
||||
import { Ctx } from "@budibase/types"
|
||||
|
||||
export async function execute(ctx: BBContext) {
|
||||
export async function execute(ctx: Ctx) {
|
||||
const { script, context } = ctx.request.body
|
||||
const runner = new ScriptRunner(script, context)
|
||||
ctx.body = runner.execute()
|
||||
}
|
||||
|
||||
export async function save(ctx: BBContext) {
|
||||
export async function save(ctx: Ctx) {
|
||||
ctx.throw(501, "Not currently implemented")
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { InvalidFileExtensions } from "@budibase/shared-core"
|
|||
require("svelte/register")
|
||||
|
||||
import { join } from "../../../utilities/centralPath"
|
||||
import uuid from "uuid"
|
||||
import * as uuid from "uuid"
|
||||
import { ObjectStoreBuckets } from "../../../constants"
|
||||
import { processString } from "@budibase/string-templates"
|
||||
import {
|
||||
|
|
|
@ -16,7 +16,7 @@ import {
|
|||
import { checkBuilderEndpoint } from "./utilities/TestFunctions"
|
||||
import * as setup from "./utilities"
|
||||
import sdk from "../../../sdk"
|
||||
import uuid from "uuid"
|
||||
import * as uuid from "uuid"
|
||||
|
||||
const { basicTable } = setup.structures
|
||||
|
||||
|
|
|
@ -9,34 +9,40 @@ describe("test the execute script action", () => {
|
|||
afterAll(setup.afterAll)
|
||||
|
||||
it("should be able to execute a script", async () => {
|
||||
let res = await setup.runStep(
|
||||
setup.actions.EXECUTE_SCRIPT.stepId,
|
||||
(inputs = {
|
||||
const res = await setup.runStep(setup.actions.EXECUTE_SCRIPT.stepId, {
|
||||
code: "return 1 + 1",
|
||||
})
|
||||
)
|
||||
expect(res.value).toEqual(2)
|
||||
expect(res.success).toEqual(true)
|
||||
})
|
||||
|
||||
it("should handle a null value", async () => {
|
||||
let res = await setup.runStep(
|
||||
setup.actions.EXECUTE_SCRIPT.stepId,
|
||||
(inputs = {
|
||||
const res = await setup.runStep(setup.actions.EXECUTE_SCRIPT.stepId, {
|
||||
code: null,
|
||||
})
|
||||
)
|
||||
expect(res.response.message).toEqual("Invalid inputs")
|
||||
expect(res.success).toEqual(false)
|
||||
})
|
||||
|
||||
it("should be able to handle an error gracefully", async () => {
|
||||
let res = await setup.runStep(
|
||||
it("should be able to get a value from context", async () => {
|
||||
const res = await setup.runStep(
|
||||
setup.actions.EXECUTE_SCRIPT.stepId,
|
||||
(inputs = {
|
||||
{
|
||||
code: "return steps.map(d => d.value)",
|
||||
},
|
||||
{
|
||||
steps: [{ value: 0 }, { value: 1 }],
|
||||
}
|
||||
)
|
||||
expect(res.value).toEqual([0, 1])
|
||||
expect(res.response).toBeUndefined()
|
||||
expect(res.success).toEqual(true)
|
||||
})
|
||||
|
||||
it("should be able to handle an error gracefully", async () => {
|
||||
const res = await setup.runStep(setup.actions.EXECUTE_SCRIPT.stepId, {
|
||||
code: "return something.map(x => x.name)",
|
||||
})
|
||||
)
|
||||
expect(res.response).toEqual("ReferenceError: something is not defined")
|
||||
expect(res.success).toEqual(false)
|
||||
})
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const { v4 } = require("uuid")
|
||||
import { v4 } from "uuid"
|
||||
|
||||
export default function (): string {
|
||||
return v4().replace(/-/g, "")
|
||||
|
|
|
@ -95,6 +95,8 @@ const environment = {
|
|||
TOP_LEVEL_PATH:
|
||||
process.env.TOP_LEVEL_PATH || process.env.SERVER_TOP_LEVEL_PATH,
|
||||
APP_MIGRATION_TIMEOUT: parseIntSafe(process.env.APP_MIGRATION_TIMEOUT),
|
||||
JS_RUNNER_MEMORY_LIMIT:
|
||||
parseIntSafe(process.env.JS_RUNNER_MEMORY_LIMIT) || 64,
|
||||
}
|
||||
|
||||
// threading can cause memory issues with node-ts in development
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
import vm from "vm"
|
||||
import env from "./environment"
|
||||
import { setJSRunner } from "@budibase/string-templates"
|
||||
import { context, timers } from "@budibase/backend-core"
|
||||
import tracer from "dd-trace"
|
||||
|
||||
type TrackerFn = <T>(f: () => T) => T
|
||||
|
||||
export function init() {
|
||||
setJSRunner((js: string, ctx: vm.Context) => {
|
||||
return tracer.trace("runJS", {}, span => {
|
||||
const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS
|
||||
let track: TrackerFn = f => f()
|
||||
if (perRequestLimit) {
|
||||
const bbCtx = tracer.trace("runJS.getCurrentContext", {}, span =>
|
||||
context.getCurrentContext()
|
||||
)
|
||||
if (bbCtx) {
|
||||
if (!bbCtx.jsExecutionTracker) {
|
||||
span?.addTags({
|
||||
createdExecutionTracker: true,
|
||||
})
|
||||
bbCtx.jsExecutionTracker = tracer.trace(
|
||||
"runJS.createExecutionTimeTracker",
|
||||
{},
|
||||
span => timers.ExecutionTimeTracker.withLimit(perRequestLimit)
|
||||
)
|
||||
}
|
||||
span?.addTags({
|
||||
js: {
|
||||
limitMS: bbCtx.jsExecutionTracker.limitMs,
|
||||
elapsedMS: bbCtx.jsExecutionTracker.elapsedMS,
|
||||
},
|
||||
})
|
||||
// We call checkLimit() here to prevent paying the cost of creating
|
||||
// a new VM context below when we don't need to.
|
||||
tracer.trace("runJS.checkLimitAndBind", {}, span => {
|
||||
bbCtx.jsExecutionTracker!.checkLimit()
|
||||
track = bbCtx.jsExecutionTracker!.track.bind(
|
||||
bbCtx.jsExecutionTracker
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
ctx = {
|
||||
...ctx,
|
||||
alert: undefined,
|
||||
setInterval: undefined,
|
||||
setTimeout: undefined,
|
||||
}
|
||||
|
||||
vm.createContext(ctx)
|
||||
return track(() =>
|
||||
vm.runInNewContext(js, ctx, {
|
||||
timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
import ivm from "isolated-vm"
|
||||
import env from "../environment"
|
||||
import { setJSRunner, JsErrorTimeout } from "@budibase/string-templates"
|
||||
import { context } from "@budibase/backend-core"
|
||||
import tracer from "dd-trace"
|
||||
import fs from "fs"
|
||||
import url from "url"
|
||||
import crypto from "crypto"
|
||||
import querystring from "querystring"
|
||||
|
||||
const helpersSource = fs.readFileSync(
|
||||
`${require.resolve("@budibase/string-templates/index-helpers")}`,
|
||||
"utf8"
|
||||
)
|
||||
|
||||
class ExecutionTimeoutError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message)
|
||||
this.name = "ExecutionTimeoutError"
|
||||
}
|
||||
}
|
||||
|
||||
export function init() {
|
||||
setJSRunner((js: string, ctx: Record<string, any>) => {
|
||||
return tracer.trace("runJS", {}, span => {
|
||||
try {
|
||||
const bbCtx = context.getCurrentContext()!
|
||||
|
||||
const isolateRefs = bbCtx.isolateRefs
|
||||
if (!isolateRefs) {
|
||||
const jsIsolate = new ivm.Isolate({
|
||||
memoryLimit: env.JS_RUNNER_MEMORY_LIMIT,
|
||||
})
|
||||
const jsContext = jsIsolate.createContextSync()
|
||||
|
||||
const injectedRequire = `const require = function(val){
|
||||
switch (val) {
|
||||
case "url":
|
||||
return {
|
||||
resolve: (...params) => urlResolveCb(...params),
|
||||
parse: (...params) => urlParseCb(...params),
|
||||
}
|
||||
case "querystring":
|
||||
return {
|
||||
escape: (...params) => querystringEscapeCb(...params),
|
||||
}
|
||||
}
|
||||
};`
|
||||
|
||||
const global = jsContext.global
|
||||
global.setSync(
|
||||
"urlResolveCb",
|
||||
new ivm.Callback((...params: Parameters<typeof url.resolve>) =>
|
||||
url.resolve(...params)
|
||||
)
|
||||
)
|
||||
|
||||
global.setSync(
|
||||
"urlParseCb",
|
||||
new ivm.Callback((...params: Parameters<typeof url.parse>) =>
|
||||
url.parse(...params)
|
||||
)
|
||||
)
|
||||
|
||||
global.setSync(
|
||||
"querystringEscapeCb",
|
||||
new ivm.Callback(
|
||||
(...params: Parameters<typeof querystring.escape>) =>
|
||||
querystring.escape(...params)
|
||||
)
|
||||
)
|
||||
|
||||
global.setSync(
|
||||
"helpersStripProtocol",
|
||||
new ivm.Callback((str: string) => {
|
||||
var parsed = url.parse(str) as any
|
||||
parsed.protocol = ""
|
||||
return parsed.format()
|
||||
})
|
||||
)
|
||||
|
||||
const helpersModule = jsIsolate.compileModuleSync(
|
||||
`${injectedRequire};${helpersSource}`
|
||||
)
|
||||
|
||||
const cryptoModule = jsIsolate.compileModuleSync(`export default {
|
||||
randomUUID: cryptoRandomUUIDCb,
|
||||
}`)
|
||||
cryptoModule.instantiateSync(jsContext, specifier => {
|
||||
throw new Error(`No imports allowed. Required: ${specifier}`)
|
||||
})
|
||||
|
||||
global.setSync(
|
||||
"cryptoRandomUUIDCb",
|
||||
new ivm.Callback(
|
||||
(...params: Parameters<typeof crypto.randomUUID>) => {
|
||||
return crypto.randomUUID(...params)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
helpersModule.instantiateSync(jsContext, specifier => {
|
||||
if (specifier === "crypto") {
|
||||
return cryptoModule
|
||||
}
|
||||
throw new Error(`No imports allowed. Required: ${specifier}`)
|
||||
})
|
||||
|
||||
for (const [key, value] of Object.entries(ctx)) {
|
||||
if (key === "helpers") {
|
||||
// Can't copy the native helpers into the isolate. We just ignore them as they are handled properly from the helpersSource
|
||||
continue
|
||||
}
|
||||
global.setSync(key, value)
|
||||
}
|
||||
|
||||
bbCtx.isolateRefs = { jsContext, jsIsolate, helpersModule }
|
||||
}
|
||||
|
||||
let { jsIsolate, jsContext, helpersModule } = bbCtx.isolateRefs!
|
||||
|
||||
const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS
|
||||
if (perRequestLimit) {
|
||||
const cpuMs = Number(jsIsolate.cpuTime) / 1e6
|
||||
if (cpuMs > perRequestLimit) {
|
||||
throw new ExecutionTimeoutError(
|
||||
`CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const script = jsIsolate.compileModuleSync(
|
||||
`import helpers from "compiled_module";const result=${js};cb(result)`,
|
||||
{}
|
||||
)
|
||||
|
||||
script.instantiateSync(jsContext, specifier => {
|
||||
if (specifier === "compiled_module") {
|
||||
return helpersModule
|
||||
}
|
||||
|
||||
throw new Error(`"${specifier}" import not allowed`)
|
||||
})
|
||||
|
||||
let result
|
||||
jsContext.global.setSync(
|
||||
"cb",
|
||||
new ivm.Callback((value: any) => {
|
||||
result = value
|
||||
})
|
||||
)
|
||||
|
||||
script.evaluateSync({
|
||||
timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS,
|
||||
})
|
||||
|
||||
return result
|
||||
} catch (error: any) {
|
||||
if (error.message === "Script execution timed out.") {
|
||||
throw new JsErrorTimeout()
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
import { validate as isValidUUID } from "uuid"
|
||||
|
||||
jest.mock("@budibase/handlebars-helpers/lib/math", () => {
|
||||
const actual = jest.requireActual("@budibase/handlebars-helpers/lib/math")
|
||||
|
||||
return {
|
||||
...actual,
|
||||
random: () => 10,
|
||||
}
|
||||
})
|
||||
jest.mock("@budibase/handlebars-helpers/lib/uuid", () => {
|
||||
const actual = jest.requireActual("@budibase/handlebars-helpers/lib/uuid")
|
||||
|
||||
return {
|
||||
...actual,
|
||||
uuid: () => "f34ebc66-93bd-4f7c-b79b-92b5569138bc",
|
||||
}
|
||||
})
|
||||
|
||||
import { processStringSync, encodeJSBinding } from "@budibase/string-templates"
|
||||
|
||||
const { runJsHelpersTests } = require("@budibase/string-templates/test/utils")
|
||||
|
||||
import tk from "timekeeper"
|
||||
import { init } from ".."
|
||||
import TestConfiguration from "../../tests/utilities/TestConfiguration"
|
||||
|
||||
tk.freeze("2021-01-21T12:00:00")
|
||||
|
||||
describe("jsRunner", () => {
|
||||
const config = new TestConfiguration()
|
||||
|
||||
beforeAll(async () => {
|
||||
// Register js runner
|
||||
init()
|
||||
await config.init()
|
||||
})
|
||||
|
||||
const processJS = (js: string, context?: object) => {
|
||||
return config.doInContext(config.getAppId(), async () =>
|
||||
processStringSync(encodeJSBinding(js), context || {})
|
||||
)
|
||||
}
|
||||
|
||||
it("it can run a basic javascript", async () => {
|
||||
const output = await processJS(`return 1 + 2`)
|
||||
expect(output).toBe(3)
|
||||
})
|
||||
|
||||
describe("helpers", () => {
|
||||
runJsHelpersTests({
|
||||
funcWrap: (func: any) => config.doInContext(config.getAppId(), func),
|
||||
testsToSkip: ["random", "uuid"],
|
||||
})
|
||||
|
||||
describe("uuid", () => {
|
||||
it("uuid helper returns a valid uuid", async () => {
|
||||
const result = await processJS("return helpers.uuid()")
|
||||
expect(result).toBeDefined()
|
||||
expect(isValidUUID(result)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("random", () => {
|
||||
it("random helper returns a valid number", async () => {
|
||||
const min = 1
|
||||
const max = 8
|
||||
const result = await processJS(`return helpers.random(${min}, ${max})`)
|
||||
expect(result).toBeDefined()
|
||||
expect(result).toBeGreaterThanOrEqual(min)
|
||||
expect(result).toBeLessThanOrEqual(max)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -4,7 +4,7 @@ import { resolve, join } from "path"
|
|||
import env from "../../environment"
|
||||
import tar from "tar"
|
||||
|
||||
const uuid = require("uuid/v4")
|
||||
import { v4 as uuid } from "uuid"
|
||||
|
||||
export const TOP_LEVEL_PATH =
|
||||
env.TOP_LEVEL_PATH || resolve(join(__dirname, "..", "..", ".."))
|
||||
|
|
|
@ -1,29 +1,63 @@
|
|||
import fetch from "node-fetch"
|
||||
import { VM, VMScript } from "vm2"
|
||||
import ivm from "isolated-vm"
|
||||
|
||||
const JS_TIMEOUT_MS = 1000
|
||||
|
||||
class ScriptRunner {
|
||||
vm: VM
|
||||
results: { out: string }
|
||||
script: VMScript
|
||||
vm: IsolatedVM
|
||||
|
||||
constructor(script: string, context: any) {
|
||||
const code = `let fn = () => {\n${script}\n}; results.out = fn();`
|
||||
this.vm = new VM({
|
||||
timeout: JS_TIMEOUT_MS,
|
||||
})
|
||||
this.results = { out: "" }
|
||||
this.vm.setGlobals(context)
|
||||
this.vm.setGlobal("fetch", fetch)
|
||||
this.vm.setGlobal("results", this.results)
|
||||
this.script = new VMScript(code)
|
||||
this.vm = new IsolatedVM({ memoryLimit: 8 })
|
||||
this.vm.context = {
|
||||
...context,
|
||||
results: { out: "" },
|
||||
}
|
||||
this.vm.code = code
|
||||
}
|
||||
|
||||
execute() {
|
||||
this.vm.run(this.script)
|
||||
return this.results.out
|
||||
this.vm.runScript()
|
||||
const results = this.vm.getValue("results")
|
||||
return results.out
|
||||
}
|
||||
}
|
||||
|
||||
class IsolatedVM {
|
||||
isolate: ivm.Isolate
|
||||
vm: ivm.Context
|
||||
jail: ivm.Reference
|
||||
script: any
|
||||
|
||||
constructor({ memoryLimit }: { memoryLimit: number }) {
|
||||
this.isolate = new ivm.Isolate({ memoryLimit })
|
||||
this.vm = this.isolate.createContextSync()
|
||||
this.jail = this.vm.global
|
||||
this.jail.setSync("global", this.jail.derefInto())
|
||||
}
|
||||
|
||||
getValue(key: string) {
|
||||
const ref = this.vm.global.getSync(key, { reference: true })
|
||||
const result = ref.copySync()
|
||||
ref.release()
|
||||
return result
|
||||
}
|
||||
|
||||
set context(context: Record<string, any>) {
|
||||
for (let key in context) {
|
||||
this.jail.setSync(key, this.copyRefToVm(context[key]))
|
||||
}
|
||||
}
|
||||
|
||||
set code(code: string) {
|
||||
this.script = this.isolate.compileScriptSync(code)
|
||||
}
|
||||
|
||||
runScript() {
|
||||
this.script.runSync(this.vm, { timeout: JS_TIMEOUT_MS })
|
||||
}
|
||||
|
||||
copyRefToVm(value: Object): ivm.Copy<Object> {
|
||||
return new ivm.ExternalCopy(value).copyInto({ release: true })
|
||||
}
|
||||
}
|
||||
export default ScriptRunner
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
"require": "./src/index.cjs",
|
||||
"import": "./dist/bundle.mjs"
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
"./package.json": "./package.json",
|
||||
"./index-helpers": "./dist/index-helpers.bundled.js",
|
||||
"./test/utils": "./test/utils.js"
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
|
@ -19,8 +21,9 @@
|
|||
"manifest.json"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc && rollup -c",
|
||||
"dev": "tsc && rollup -cw",
|
||||
"build:esbuild": "esbuild --minify --bundle src/index-helpers.js --outfile=dist/index-helpers.bundled.js --platform=node --format=esm --external:handlebars",
|
||||
"build": "yarn build:esbuild && tsc && rollup -c",
|
||||
"dev": "concurrently \"yarn build:esbuild --watch\" \"tsc && rollup -cw\"",
|
||||
"test": "jest",
|
||||
"manifest": "node ./scripts/gen-collection-info.js"
|
||||
},
|
||||
|
@ -34,6 +37,7 @@
|
|||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^17.1.0",
|
||||
"@rollup/plugin-json": "^4.1.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"doctrine": "^3.0.0",
|
||||
"jest": "29.7.0",
|
||||
"marked": "^4.0.10",
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
class JsErrorTimeout extends Error {
|
||||
code = "ERR_SCRIPT_EXECUTION_TIMEOUT"
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
JsErrorTimeout,
|
||||
}
|
|
@ -42,7 +42,7 @@ module.exports.processJS = (handlebars, context) => {
|
|||
try {
|
||||
// Wrap JS in a function and immediately invoke it.
|
||||
// This is required to allow the final `return` statement to be valid.
|
||||
const js = `function run(){${atob(handlebars)}};run();`
|
||||
const js = `(function(){${atob(handlebars)}})();`
|
||||
|
||||
// Our $ context function gets a value from context.
|
||||
// We clone the context to avoid mutation in the binding affecting real
|
||||
|
|
|
@ -1,29 +1,42 @@
|
|||
const externalHandlebars = require("./external")
|
||||
const helperList = require("@budibase/handlebars-helpers")
|
||||
const { date, duration } = require("./date")
|
||||
|
||||
let helpers = undefined
|
||||
// https://github.com/evanw/esbuild/issues/56
|
||||
const externalCollections = {
|
||||
math: require("@budibase/handlebars-helpers/lib/math"),
|
||||
array: require("@budibase/handlebars-helpers/lib/array"),
|
||||
number: require("@budibase/handlebars-helpers/lib/number"),
|
||||
url: require("@budibase/handlebars-helpers/lib/url"),
|
||||
string: require("@budibase/handlebars-helpers/lib/string"),
|
||||
comparison: require("@budibase/handlebars-helpers/lib/comparison"),
|
||||
object: require("@budibase/handlebars-helpers/lib/object"),
|
||||
regex: require("@budibase/handlebars-helpers/lib/regex"),
|
||||
uuid: require("@budibase/handlebars-helpers/lib/uuid"),
|
||||
}
|
||||
|
||||
const helpersToRemoveForJs = ["sortBy"]
|
||||
module.exports.helpersToRemoveForJs = helpersToRemoveForJs
|
||||
|
||||
const addedHelpers = {
|
||||
date: date,
|
||||
duration: duration,
|
||||
}
|
||||
|
||||
let helpers = undefined
|
||||
|
||||
module.exports.getJsHelperList = () => {
|
||||
if (helpers) {
|
||||
return helpers
|
||||
}
|
||||
|
||||
helpers = {}
|
||||
let constructed = []
|
||||
for (let collection of externalHandlebars.externalCollections) {
|
||||
constructed.push(helperList[collection]())
|
||||
}
|
||||
for (let collection of constructed) {
|
||||
for (let collection of Object.values(externalCollections)) {
|
||||
for (let [key, func] of Object.entries(collection)) {
|
||||
// Handlebars injects the hbs options to the helpers by default. We are adding an empty {} as a last parameter to simulate it
|
||||
helpers[key] = (...props) => func(...props, {})
|
||||
}
|
||||
}
|
||||
for (let key of Object.keys(externalHandlebars.addedHelpers)) {
|
||||
helpers[key] = externalHandlebars.addedHelpers[key]
|
||||
for (let key of Object.keys(addedHelpers)) {
|
||||
helpers[key] = addedHelpers[key]
|
||||
}
|
||||
|
||||
for (const toRemove of helpersToRemoveForJs) {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
const { getJsHelperList } = require("./helpers/list")
|
||||
|
||||
const helpers = getJsHelperList()
|
||||
module.exports = {
|
||||
...helpers,
|
||||
// pointing stripProtocol to a unexisting function to be able to declare it on isolated-vm
|
||||
// eslint-disable-next-line no-undef
|
||||
stripProtocol: helpersStripProtocol,
|
||||
}
|
|
@ -36,3 +36,8 @@ if (!process.env.NO_JS) {
|
|||
return vm.run(js)
|
||||
})
|
||||
}
|
||||
|
||||
const errors = require("./errors")
|
||||
for (const error in errors) {
|
||||
module.exports[error] = errors[error]
|
||||
}
|
||||
|
|
|
@ -395,4 +395,9 @@ module.exports.convertToJS = hbs => {
|
|||
}
|
||||
|
||||
module.exports.FIND_ANY_HBS_REGEX = FIND_ANY_HBS_REGEX
|
||||
|
||||
const errors = require("./errors")
|
||||
// We cannot use dynamic exports, otherwise the typescript file will not be generating it
|
||||
module.exports.JsErrorTimeout = errors.JsErrorTimeout
|
||||
|
||||
module.exports.helpersToRemoveForJs = helpersToRemoveForJs
|
||||
|
|
|
@ -38,3 +38,5 @@ if (process && !process.env.NO_JS) {
|
|||
return vm.runInNewContext(js, context, { timeout: 1000 })
|
||||
})
|
||||
}
|
||||
|
||||
export * from "./errors.js"
|
||||
|
|
|
@ -15,81 +15,23 @@ jest.mock("@budibase/handlebars-helpers/lib/uuid", () => {
|
|||
}
|
||||
})
|
||||
|
||||
const fs = require("fs")
|
||||
const {
|
||||
processString,
|
||||
convertToJS,
|
||||
processStringSync,
|
||||
encodeJSBinding,
|
||||
} = require("../src/index.cjs")
|
||||
const { processString } = require("../src/index.cjs")
|
||||
|
||||
const tk = require("timekeeper")
|
||||
const { getJsHelperList } = require("../src/helpers")
|
||||
const { getParsedManifest, runJsHelpersTests } = require("./utils")
|
||||
|
||||
tk.freeze("2021-01-21T12:00:00")
|
||||
|
||||
const processJS = (js, context) => {
|
||||
return processStringSync(encodeJSBinding(js), context)
|
||||
}
|
||||
|
||||
const manifest = JSON.parse(
|
||||
fs.readFileSync(require.resolve("../manifest.json"), "utf8")
|
||||
)
|
||||
|
||||
const collections = Object.keys(manifest)
|
||||
const examples = collections.reduce((acc, collection) => {
|
||||
const functions = Object.entries(manifest[collection])
|
||||
.filter(([_, details]) => details.example)
|
||||
.map(([name, details]) => {
|
||||
const example = details.example
|
||||
let [hbs, js] = example.split("->").map(x => x.trim())
|
||||
if (!js) {
|
||||
// The function has no return value
|
||||
return
|
||||
}
|
||||
|
||||
// Trim 's
|
||||
js = js.replace(/^\'|\'$/g, "")
|
||||
if ((parsedExpected = tryParseJson(js))) {
|
||||
if (Array.isArray(parsedExpected)) {
|
||||
if (typeof parsedExpected[0] === "object") {
|
||||
js = JSON.stringify(parsedExpected)
|
||||
} else {
|
||||
js = parsedExpected.join(",")
|
||||
}
|
||||
}
|
||||
}
|
||||
const requiresHbsBody = details.requiresBlock
|
||||
return [name, { hbs, js, requiresHbsBody }]
|
||||
})
|
||||
.filter(x => !!x)
|
||||
|
||||
if (Object.keys(functions).length) {
|
||||
acc[collection] = functions
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") // $& means the whole matched string
|
||||
}
|
||||
|
||||
function tryParseJson(str) {
|
||||
if (typeof str !== "string") {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(str.replace(/\'/g, '"'))
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
describe("manifest", () => {
|
||||
const manifest = getParsedManifest()
|
||||
|
||||
describe("examples are valid", () => {
|
||||
describe.each(Object.keys(examples))("%s", collection => {
|
||||
it.each(examples[collection])("%s", async (_, { hbs, js }) => {
|
||||
describe.each(Object.keys(manifest))("%s", collection => {
|
||||
it.each(manifest[collection])("%s", async (_, { hbs, js }) => {
|
||||
const context = {
|
||||
double: i => i * 2,
|
||||
isString: x => typeof x === "string",
|
||||
|
@ -108,36 +50,5 @@ describe("manifest", () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe("can be parsed and run as js", () => {
|
||||
const jsHelpers = getJsHelperList()
|
||||
const jsExamples = Object.keys(examples).reduce((acc, v) => {
|
||||
acc[v] = examples[v].filter(([key]) => jsHelpers[key])
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
describe.each(Object.keys(jsExamples))("%s", collection => {
|
||||
it.each(
|
||||
jsExamples[collection].filter(
|
||||
([_, { requiresHbsBody }]) => !requiresHbsBody
|
||||
)
|
||||
)("%s", async (_, { hbs, js }) => {
|
||||
const context = {
|
||||
double: i => i * 2,
|
||||
isString: x => typeof x === "string",
|
||||
}
|
||||
|
||||
const arrays = hbs.match(/\[[^/\]]+\]/)
|
||||
arrays?.forEach((arrayString, i) => {
|
||||
hbs = hbs.replace(new RegExp(escapeRegExp(arrayString)), `array${i}`)
|
||||
context[`array${i}`] = JSON.parse(arrayString.replace(/\'/g, '"'))
|
||||
})
|
||||
|
||||
let convertedJs = convertToJS(hbs)
|
||||
|
||||
let result = processJS(convertedJs, context)
|
||||
result = result.replace(/ /g, " ")
|
||||
expect(result).toEqual(js)
|
||||
})
|
||||
})
|
||||
})
|
||||
runJsHelpersTests()
|
||||
})
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
const { getManifest } = require("../src")
|
||||
const { getJsHelperList } = require("../src/helpers")
|
||||
|
||||
const {
|
||||
convertToJS,
|
||||
processStringSync,
|
||||
encodeJSBinding,
|
||||
} = require("../src/index.cjs")
|
||||
|
||||
function tryParseJson(str) {
|
||||
if (typeof str !== "string") {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
return JSON.parse(str.replace(/'/g, '"'))
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const getParsedManifest = () => {
|
||||
const manifest = getManifest()
|
||||
const collections = Object.keys(manifest)
|
||||
const examples = collections.reduce((acc, collection) => {
|
||||
const functions = Object.entries(manifest[collection])
|
||||
.filter(([_, details]) => details.example)
|
||||
.map(([name, details]) => {
|
||||
const example = details.example
|
||||
let [hbs, js] = example.split("->").map(x => x.trim())
|
||||
if (!js) {
|
||||
// The function has no return value
|
||||
return
|
||||
}
|
||||
|
||||
// Trim 's
|
||||
js = js.replace(/^'|'$/g, "")
|
||||
let parsedExpected
|
||||
if ((parsedExpected = tryParseJson(js))) {
|
||||
if (Array.isArray(parsedExpected)) {
|
||||
if (typeof parsedExpected[0] === "object") {
|
||||
js = JSON.stringify(parsedExpected)
|
||||
} else {
|
||||
js = parsedExpected.join(",")
|
||||
}
|
||||
}
|
||||
}
|
||||
const requiresHbsBody = details.requiresBlock
|
||||
return [name, { hbs, js, requiresHbsBody }]
|
||||
})
|
||||
.filter(x => !!x)
|
||||
|
||||
if (Object.keys(functions).length) {
|
||||
acc[collection] = functions
|
||||
}
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
return examples
|
||||
}
|
||||
module.exports.getParsedManifest = getParsedManifest
|
||||
|
||||
module.exports.runJsHelpersTests = ({ funcWrap, testsToSkip } = {}) => {
|
||||
funcWrap = funcWrap || (delegate => delegate())
|
||||
const manifest = getParsedManifest()
|
||||
|
||||
const processJS = (js, context) => {
|
||||
return funcWrap(() => processStringSync(encodeJSBinding(js), context))
|
||||
}
|
||||
|
||||
function escapeRegExp(string) {
|
||||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") // $& means the whole matched string
|
||||
}
|
||||
|
||||
describe("can be parsed and run as js", () => {
|
||||
const jsHelpers = getJsHelperList()
|
||||
const jsExamples = Object.keys(manifest).reduce((acc, v) => {
|
||||
acc[v] = manifest[v].filter(([key]) => jsHelpers[key])
|
||||
return acc
|
||||
}, {})
|
||||
|
||||
describe.each(Object.keys(jsExamples))("%s", collection => {
|
||||
const examplesToRun = jsExamples[collection]
|
||||
.filter(([_, { requiresHbsBody }]) => !requiresHbsBody)
|
||||
.filter(([key]) => !testsToSkip?.includes(key))
|
||||
|
||||
examplesToRun.length &&
|
||||
it.each(examplesToRun)("%s", async (_, { hbs, js }) => {
|
||||
const context = {
|
||||
double: i => i * 2,
|
||||
isString: x => typeof x === "string",
|
||||
}
|
||||
|
||||
const arrays = hbs.match(/\[[^/\]]+\]/)
|
||||
arrays?.forEach((arrayString, i) => {
|
||||
hbs = hbs.replace(
|
||||
new RegExp(escapeRegExp(arrayString)),
|
||||
`array${i}`
|
||||
)
|
||||
context[`array${i}`] = JSON.parse(arrayString.replace(/'/g, '"'))
|
||||
})
|
||||
|
||||
let convertedJs = convertToJS(hbs)
|
||||
|
||||
let result = await processJS(convertedJs, context)
|
||||
result = result.replace(/ /g, " ")
|
||||
expect(result).toEqual(js)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
|
@ -44,6 +44,8 @@ EXPOSE 4001
|
|||
# due to this causing yarn to stop installing dev dependencies
|
||||
# which are actually needed to get this environment up and running
|
||||
ENV NODE_ENV=production
|
||||
# this is required for isolated-vm to work on Node 20+
|
||||
ENV NODE_OPTIONS="--no-node-snapshot"
|
||||
ENV CLUSTER_MODE=${CLUSTER_MODE}
|
||||
ENV SERVICE=worker-service
|
||||
ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
||||
|
|
|
@ -8,6 +8,6 @@
|
|||
"../string-templates"
|
||||
],
|
||||
"ext": "js,ts,json",
|
||||
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../*/dist/**/*"],
|
||||
"exec": "yarn build && node dist/index.js"
|
||||
"ignore": ["**/*.spec.ts", "**/*.spec.js", "../*/dist/**/*"],
|
||||
"exec": "yarn build && node --no-node-snapshot dist/index.js"
|
||||
}
|
||||
|
|
137
yarn.lock
137
yarn.lock
|
@ -1980,7 +1980,7 @@
|
|||
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||
|
||||
"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.21.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
|
||||
version "7.23.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650"
|
||||
integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==
|
||||
|
@ -5573,9 +5573,9 @@
|
|||
integrity sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg==
|
||||
|
||||
"@types/node@>=8.1.0":
|
||||
version "20.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.6.tgz#6adf4241460e28be53836529c033a41985f85b6e"
|
||||
integrity sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==
|
||||
version "20.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.2.tgz#39cea3fe02fbbc2f80ed283e94e1d24f2d3856fb"
|
||||
integrity sha512-cZShBaVa+UO1LjWWBPmWRR4+/eY/JR/UIEcDlVsw3okjWEu+rB7/mH6X3B/L+qJVHDLjk9QW/y2upp9wp1yDXA==
|
||||
dependencies:
|
||||
undici-types "~5.26.4"
|
||||
|
||||
|
@ -8355,6 +8355,21 @@ concat-with-sourcemaps@^1.1.0:
|
|||
dependencies:
|
||||
source-map "^0.6.1"
|
||||
|
||||
concurrently@^8.2.2:
|
||||
version "8.2.2"
|
||||
resolved "https://registry.yarnpkg.com/concurrently/-/concurrently-8.2.2.tgz#353141985c198cfa5e4a3ef90082c336b5851784"
|
||||
integrity sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==
|
||||
dependencies:
|
||||
chalk "^4.1.2"
|
||||
date-fns "^2.30.0"
|
||||
lodash "^4.17.21"
|
||||
rxjs "^7.8.1"
|
||||
shell-quote "^1.8.1"
|
||||
spawn-command "0.0.2"
|
||||
supports-color "^8.1.1"
|
||||
tree-kill "^1.2.2"
|
||||
yargs "^17.7.2"
|
||||
|
||||
condense-newlines@^0.2.1:
|
||||
version "0.2.1"
|
||||
resolved "https://registry.yarnpkg.com/condense-newlines/-/condense-newlines-0.2.1.tgz#3de985553139475d32502c83b02f60684d24c55f"
|
||||
|
@ -8907,6 +8922,13 @@ data-urls@^4.0.0:
|
|||
whatwg-mimetype "^3.0.0"
|
||||
whatwg-url "^12.0.0"
|
||||
|
||||
date-fns@^2.30.0:
|
||||
version "2.30.0"
|
||||
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
|
||||
integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.21.0"
|
||||
|
||||
dateformat@^3.0.3:
|
||||
version "3.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
|
||||
|
@ -9654,9 +9676,9 @@ dotenv@8.6.0, dotenv@^8.2.0:
|
|||
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
|
||||
|
||||
dotenv@^16.3.1:
|
||||
version "16.4.0"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.0.tgz#ac21c3fcaad2e7832a1cd0c0e4e8e52225ecda0e"
|
||||
integrity sha512-WvImr5kpN5NGNn7KaDjJnLTh5rDVLZiDf/YLA8T1ZEZEBZNEDOE+mnkS0PVjPax8ZxBP5zC5SLMB3/9VV5de9g==
|
||||
version "16.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
|
||||
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
|
||||
|
||||
dotenv@~10.0.0:
|
||||
version "10.0.0"
|
||||
|
@ -10530,6 +10552,11 @@ exit@^0.1.2:
|
|||
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
||||
integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
|
||||
|
||||
expand-template@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
|
||||
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
|
||||
|
||||
expand-tilde@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449"
|
||||
|
@ -11441,6 +11468,11 @@ gitconfiglocal@^1.0.0:
|
|||
dependencies:
|
||||
ini "^1.3.2"
|
||||
|
||||
github-from-package@0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/github-from-package/-/github-from-package-0.0.0.tgz#97fb5d96bfde8973313f20e8288ef9a167fa64ce"
|
||||
integrity sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==
|
||||
|
||||
glob-parent@5.1.2, glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
||||
|
@ -13023,6 +13055,13 @@ isobject@^3.0.1:
|
|||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
|
||||
|
||||
isolated-vm@^4.7.2:
|
||||
version "4.7.2"
|
||||
resolved "https://registry.yarnpkg.com/isolated-vm/-/isolated-vm-4.7.2.tgz#5670d5cce1d92004f9b825bec5b0b11fc7501b65"
|
||||
integrity sha512-JVEs5gzWObzZK5+OlBplCdYSpokMcdhLSs/xWYYxmYWVfOOFF4oZJsYh7E/FmfX8e7gMioXMpMMeEyX1afuKrg==
|
||||
dependencies:
|
||||
prebuild-install "^7.1.1"
|
||||
|
||||
isstream@~0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||
|
@ -15352,7 +15391,7 @@ minimist-options@4.1.0:
|
|||
is-plain-obj "^1.1.0"
|
||||
kind-of "^6.0.3"
|
||||
|
||||
minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
|
||||
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6:
|
||||
version "1.2.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||
|
@ -15445,7 +15484,7 @@ minizlib@^2.1.1, minizlib@^2.1.2:
|
|||
minipass "^3.0.0"
|
||||
yallist "^4.0.0"
|
||||
|
||||
mkdirp-classic@^0.5.2:
|
||||
mkdirp-classic@^0.5.2, mkdirp-classic@^0.5.3:
|
||||
version "0.5.3"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
||||
|
@ -15682,6 +15721,11 @@ nanoid@^3.3.6:
|
|||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
||||
|
||||
napi-build-utils@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806"
|
||||
integrity sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==
|
||||
|
||||
napi-macros@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b"
|
||||
|
@ -15732,6 +15776,13 @@ nice-try@^1.0.4:
|
|||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
||||
|
||||
node-abi@^3.3.0:
|
||||
version "3.54.0"
|
||||
resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.54.0.tgz#f6386f7548817acac6434c6cba02999c9aebcc69"
|
||||
integrity sha512-p7eGEiQil0YUV3ItH4/tBb781L5impVmmx2E9FRKF7d18XXzp4PGT2tdYMFY6wQqgxD0IwNZOiSJ0/K0fSi/OA==
|
||||
dependencies:
|
||||
semver "^7.3.5"
|
||||
|
||||
node-abort-controller@^3.0.1, node-abort-controller@^3.1.1:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
|
||||
|
@ -17610,12 +17661,11 @@ postgres-interval@^1.1.0:
|
|||
xtend "^4.0.0"
|
||||
|
||||
posthog-js@^1.13.4:
|
||||
version "1.101.0"
|
||||
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.101.0.tgz#00e0fc6e164addd52b1738f087996bb0d6685943"
|
||||
integrity sha512-mzwYSSWr9FdEMDeVpc+diLfc85+10r/LgELGtsW/HaYk+0du/GEql6szpqG8YXMMgb2dE4dnj0JICZFIJd7K3w==
|
||||
version "1.100.0"
|
||||
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.100.0.tgz#687b9a6e4ed226aa6572f4040b418ea0c8b3d353"
|
||||
integrity sha512-r2XZEiHQ9mBK7D1G9k57I8uYZ2kZTAJ0OCX6K/OOdCWN8jKPhw3h5F9No5weilP6eVAn+hrsy7NvPV7SCX7gMg==
|
||||
dependencies:
|
||||
fflate "^0.4.1"
|
||||
preact "^10.19.3"
|
||||
|
||||
posthog-js@^1.36.0:
|
||||
version "1.96.1"
|
||||
|
@ -17861,10 +17911,23 @@ pprof-format@^2.0.7:
|
|||
resolved "https://registry.yarnpkg.com/pprof-format/-/pprof-format-2.0.7.tgz#526e4361f8b37d16b2ec4bb0696b5292de5046a4"
|
||||
integrity sha512-1qWaGAzwMpaXJP9opRa23nPnt2Egi7RMNoNBptEE/XwHbcn4fC2b/4U4bKc5arkGkIh2ZabpF2bEb+c5GNHEKA==
|
||||
|
||||
preact@^10.19.3:
|
||||
version "10.19.3"
|
||||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.3.tgz#7a7107ed2598a60676c943709ea3efb8aaafa899"
|
||||
integrity sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==
|
||||
prebuild-install@^7.1.1:
|
||||
version "7.1.1"
|
||||
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
|
||||
integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==
|
||||
dependencies:
|
||||
detect-libc "^2.0.0"
|
||||
expand-template "^2.0.3"
|
||||
github-from-package "0.0.0"
|
||||
minimist "^1.2.3"
|
||||
mkdirp-classic "^0.5.3"
|
||||
napi-build-utils "^1.0.1"
|
||||
node-abi "^3.3.0"
|
||||
pump "^3.0.0"
|
||||
rc "^1.2.7"
|
||||
simple-get "^4.0.0"
|
||||
tar-fs "^2.0.0"
|
||||
tunnel-agent "^0.6.0"
|
||||
|
||||
precinct@^8.1.0:
|
||||
version "8.3.1"
|
||||
|
@ -19090,6 +19153,13 @@ rxjs@^7.5.5:
|
|||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
rxjs@^7.8.1:
|
||||
version "7.8.1"
|
||||
resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.1.tgz#6f6f3d99ea8044291efd92e7c7fcf562c4057543"
|
||||
integrity sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==
|
||||
dependencies:
|
||||
tslib "^2.1.0"
|
||||
|
||||
safe-array-concat@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c"
|
||||
|
@ -19405,6 +19475,11 @@ shell-exec@1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/shell-exec/-/shell-exec-1.0.2.tgz#2e9361b0fde1d73f476c4b6671fa17785f696756"
|
||||
integrity sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==
|
||||
|
||||
shell-quote@^1.8.1:
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
|
||||
integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
|
||||
|
||||
shortid@2.2.15:
|
||||
version "2.2.15"
|
||||
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.15.tgz#2b902eaa93a69b11120373cd42a1f1fe4437c122"
|
||||
|
@ -19458,6 +19533,20 @@ sigstore@^1.3.0, sigstore@^1.4.0:
|
|||
make-fetch-happen "^11.0.1"
|
||||
tuf-js "^1.1.3"
|
||||
|
||||
simple-concat@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f"
|
||||
integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==
|
||||
|
||||
simple-get@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543"
|
||||
integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==
|
||||
dependencies:
|
||||
decompress-response "^6.0.0"
|
||||
once "^1.3.1"
|
||||
simple-concat "^1.0.0"
|
||||
|
||||
simple-lru-cache@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz#d59cc3a193c1a5d0320f84ee732f6e4713e511dd"
|
||||
|
@ -19704,6 +19793,11 @@ sparse-bitfield@^3.0.3:
|
|||
dependencies:
|
||||
memory-pager "^1.0.2"
|
||||
|
||||
spawn-command@0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2.tgz#9544e1a43ca045f8531aac1a48cb29bdae62338e"
|
||||
integrity sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==
|
||||
|
||||
spdx-correct@^3.0.0:
|
||||
version "3.1.1"
|
||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
|
||||
|
@ -20266,7 +20360,7 @@ supports-color@^7.0.0, supports-color@^7.1.0:
|
|||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
supports-color@^8.0.0:
|
||||
supports-color@^8.0.0, supports-color@^8.1.1:
|
||||
version "8.1.1"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
|
||||
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
||||
|
@ -20449,7 +20543,7 @@ tapable@^2.1.1, tapable@^2.2.0:
|
|||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
|
||||
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
|
||||
|
||||
tar-fs@2.1.1, tar-fs@^2.1.0:
|
||||
tar-fs@2.1.1, tar-fs@^2.0.0, tar-fs@^2.1.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
|
||||
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
|
||||
|
@ -20896,6 +20990,11 @@ tr46@~0.0.3:
|
|||
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
||||
|
||||
tree-kill@^1.2.2:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
|
||||
integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==
|
||||
|
||||
trim-newlines@^3.0.0:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
|
||||
|
|
Loading…
Reference in New Issue