Merge pull request #12930 from Budibase/revert-12929-revert-12769-isolated-vm
Migrate from `vm` to `isolated-vm`
This commit is contained in:
commit
37a63404df
|
@ -124,6 +124,8 @@ HEALTHCHECK --interval=15s --timeout=15s --start-period=45s CMD "/healthcheck.sh
|
||||||
|
|
||||||
# must set this just before running
|
# must set this just before running
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
# this is required for isolated-vm to work on Node 20+
|
||||||
|
ENV NODE_OPTIONS="--no-node-snapshot"
|
||||||
WORKDIR /
|
WORKDIR /
|
||||||
|
|
||||||
CMD ["./runner.sh"]
|
CMD ["./runner.sh"]
|
||||||
|
|
|
@ -54,7 +54,7 @@
|
||||||
"sanitize-s3-objectkey": "0.0.1",
|
"sanitize-s3-objectkey": "0.0.1",
|
||||||
"semver": "7.3.7",
|
"semver": "7.3.7",
|
||||||
"tar-fs": "2.1.1",
|
"tar-fs": "2.1.1",
|
||||||
"uuid": "8.3.2"
|
"uuid": "^8.3.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@shopify/jest-koa-mocks": "5.1.1",
|
"@shopify/jest-koa-mocks": "5.1.1",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { IdentityContext } from "@budibase/types"
|
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
|
// keep this out of Budibase types, don't want to expose context info
|
||||||
export type ContextMap = {
|
export type ContextMap = {
|
||||||
|
@ -10,5 +10,9 @@ export type ContextMap = {
|
||||||
isScim?: boolean
|
isScim?: boolean
|
||||||
automationId?: string
|
automationId?: string
|
||||||
isMigrating?: boolean
|
isMigrating?: boolean
|
||||||
jsExecutionTracker?: ExecutionTimeTracker
|
isolateRefs?: {
|
||||||
|
jsIsolate: Isolate
|
||||||
|
jsContext: Context
|
||||||
|
helpersModule: Module
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,41 +20,3 @@ export function cleanup() {
|
||||||
}
|
}
|
||||||
intervals = []
|
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`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -184,7 +184,7 @@
|
||||||
{#if environmentVariablesEnabled}
|
{#if environmentVariablesEnabled}
|
||||||
<div on:click={() => showModal()} class="add-variable">
|
<div on:click={() => showModal()} class="add-variable">
|
||||||
<svg
|
<svg
|
||||||
class="spectrum-Icon spectrum-Icon--sizeS "
|
class="spectrum-Icon spectrum-Icon--sizeS"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
|
@ -195,7 +195,7 @@
|
||||||
{:else}
|
{:else}
|
||||||
<div on:click={() => handleUpgradePanel()} class="add-variable">
|
<div on:click={() => handleUpgradePanel()} class="add-variable">
|
||||||
<svg
|
<svg
|
||||||
class="spectrum-Icon spectrum-Icon--sizeS "
|
class="spectrum-Icon spectrum-Icon--sizeS"
|
||||||
focusable="false"
|
focusable="false"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
>
|
>
|
||||||
|
|
|
@ -82,6 +82,8 @@ EXPOSE 4001
|
||||||
# due to this causing yarn to stop installing dev dependencies
|
# due to this causing yarn to stop installing dev dependencies
|
||||||
# which are actually needed to get this environment up and running
|
# which are actually needed to get this environment up and running
|
||||||
ENV NODE_ENV=production
|
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 CLUSTER_MODE=${CLUSTER_MODE}
|
||||||
ENV TOP_LEVEL_PATH=/app
|
ENV TOP_LEVEL_PATH=/app
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
"../string-templates"
|
"../string-templates"
|
||||||
],
|
],
|
||||||
"ext": "js,ts,json,svelte",
|
"ext": "js,ts,json,svelte",
|
||||||
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../*/dist/**/*"],
|
"ignore": ["**/*.spec.ts", "**/*.spec.js", "../*/dist/**/*"],
|
||||||
"exec": "yarn build && node ./dist/index.js"
|
"exec": "yarn build && node --no-node-snapshot ./dist/index.js"
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
"check:types": "tsc -p tsconfig.json --noEmit --paths null",
|
"check:types": "tsc -p tsconfig.json --noEmit --paths null",
|
||||||
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
"build:dev": "yarn prebuild && tsc --build --watch --preserveWatchOutput",
|
||||||
"debug": "yarn build && node --expose-gc --inspect=9222 dist/index.js",
|
"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": "bash scripts/test.sh",
|
||||||
"test:memory": "jest --maxWorkers=2 --logHeapUsage --forceExit",
|
"test:memory": "jest --maxWorkers=2 --logHeapUsage --forceExit",
|
||||||
"test:watch": "jest --watch",
|
"test:watch": "jest --watch",
|
||||||
|
@ -72,6 +73,7 @@
|
||||||
"google-auth-library": "7.12.0",
|
"google-auth-library": "7.12.0",
|
||||||
"google-spreadsheet": "3.2.0",
|
"google-spreadsheet": "3.2.0",
|
||||||
"ioredis": "5.3.2",
|
"ioredis": "5.3.2",
|
||||||
|
"isolated-vm": "^4.7.2",
|
||||||
"jimp": "0.16.1",
|
"jimp": "0.16.1",
|
||||||
"joi": "17.6.0",
|
"joi": "17.6.0",
|
||||||
"js-yaml": "4.1.0",
|
"js-yaml": "4.1.0",
|
||||||
|
@ -104,9 +106,8 @@
|
||||||
"svelte": "^3.49.0",
|
"svelte": "^3.49.0",
|
||||||
"tar": "6.1.15",
|
"tar": "6.1.15",
|
||||||
"to-json-schema": "0.2.5",
|
"to-json-schema": "0.2.5",
|
||||||
"uuid": "3.3.2",
|
"uuid": "^8.3.2",
|
||||||
"validate.js": "0.13.1",
|
"validate.js": "0.13.1",
|
||||||
"vm2": "^3.9.19",
|
|
||||||
"worker-farm": "1.7.0",
|
"worker-farm": "1.7.0",
|
||||||
"xml2js": "0.5.0"
|
"xml2js": "0.5.0"
|
||||||
},
|
},
|
||||||
|
@ -129,6 +130,7 @@
|
||||||
"@types/server-destroy": "1.0.1",
|
"@types/server-destroy": "1.0.1",
|
||||||
"@types/supertest": "2.0.14",
|
"@types/supertest": "2.0.14",
|
||||||
"@types/tar": "6.1.5",
|
"@types/tar": "6.1.5",
|
||||||
|
"@types/uuid": "8.3.4",
|
||||||
"apidoc": "0.50.4",
|
"apidoc": "0.50.4",
|
||||||
"copyfiles": "2.4.1",
|
"copyfiles": "2.4.1",
|
||||||
"docker-compose": "0.23.17",
|
"docker-compose": "0.23.17",
|
||||||
|
@ -163,6 +165,12 @@
|
||||||
"@budibase/backend-core"
|
"@budibase/backend-core"
|
||||||
],
|
],
|
||||||
"target": "build"
|
"target": "build"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"projects": [
|
||||||
|
"@budibase/string-templates"
|
||||||
|
],
|
||||||
|
"target": "build:esbuild"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -181,6 +189,16 @@
|
||||||
"target": "build"
|
"target": "build"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"dependsOn": [
|
||||||
|
{
|
||||||
|
"projects": [
|
||||||
|
"@budibase/string-templates"
|
||||||
|
],
|
||||||
|
"target": "build:esbuild"
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,12 @@ set -e
|
||||||
|
|
||||||
if [[ -n $CI ]]
|
if [[ -n $CI ]]
|
||||||
then
|
then
|
||||||
# Running in ci, where resources are limited
|
export NODE_OPTIONS="--max-old-space-size=4096 --no-node-snapshot"
|
||||||
export NODE_OPTIONS="--max-old-space-size=4096"
|
|
||||||
echo "jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail $@"
|
echo "jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail $@"
|
||||||
jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail $@
|
jest --coverage --maxWorkers=2 --forceExit --workerIdleMemoryLimit=2000MB --bail $@
|
||||||
else
|
else
|
||||||
# --maxWorkers performs better in development
|
# --maxWorkers performs better in development
|
||||||
|
export NODE_OPTIONS="--no-node-snapshot"
|
||||||
echo "jest --coverage --maxWorkers=2 --forceExit $@"
|
echo "jest --coverage --maxWorkers=2 --forceExit $@"
|
||||||
jest --coverage --maxWorkers=2 --forceExit $@
|
jest --coverage --maxWorkers=2 --forceExit $@
|
||||||
fi
|
fi
|
|
@ -1,12 +1,12 @@
|
||||||
import ScriptRunner from "../../utilities/scriptRunner"
|
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 { script, context } = ctx.request.body
|
||||||
const runner = new ScriptRunner(script, context)
|
const runner = new ScriptRunner(script, context)
|
||||||
ctx.body = runner.execute()
|
ctx.body = runner.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function save(ctx: BBContext) {
|
export async function save(ctx: Ctx) {
|
||||||
ctx.throw(501, "Not currently implemented")
|
ctx.throw(501, "Not currently implemented")
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { InvalidFileExtensions } from "@budibase/shared-core"
|
||||||
require("svelte/register")
|
require("svelte/register")
|
||||||
|
|
||||||
import { join } from "../../../utilities/centralPath"
|
import { join } from "../../../utilities/centralPath"
|
||||||
import uuid from "uuid"
|
import * as uuid from "uuid"
|
||||||
import { ObjectStoreBuckets } from "../../../constants"
|
import { ObjectStoreBuckets } from "../../../constants"
|
||||||
import { processString } from "@budibase/string-templates"
|
import { processString } from "@budibase/string-templates"
|
||||||
import {
|
import {
|
||||||
|
|
|
@ -16,7 +16,7 @@ import {
|
||||||
import { checkBuilderEndpoint } from "./utilities/TestFunctions"
|
import { checkBuilderEndpoint } from "./utilities/TestFunctions"
|
||||||
import * as setup from "./utilities"
|
import * as setup from "./utilities"
|
||||||
import sdk from "../../../sdk"
|
import sdk from "../../../sdk"
|
||||||
import uuid from "uuid"
|
import * as uuid from "uuid"
|
||||||
|
|
||||||
const { basicTable } = setup.structures
|
const { basicTable } = setup.structures
|
||||||
|
|
||||||
|
|
|
@ -9,34 +9,40 @@ describe("test the execute script action", () => {
|
||||||
afterAll(setup.afterAll)
|
afterAll(setup.afterAll)
|
||||||
|
|
||||||
it("should be able to execute a script", async () => {
|
it("should be able to execute a script", async () => {
|
||||||
let res = await setup.runStep(
|
const res = await setup.runStep(setup.actions.EXECUTE_SCRIPT.stepId, {
|
||||||
setup.actions.EXECUTE_SCRIPT.stepId,
|
code: "return 1 + 1",
|
||||||
(inputs = {
|
})
|
||||||
code: "return 1 + 1",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
expect(res.value).toEqual(2)
|
expect(res.value).toEqual(2)
|
||||||
expect(res.success).toEqual(true)
|
expect(res.success).toEqual(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should handle a null value", async () => {
|
it("should handle a null value", async () => {
|
||||||
let res = await setup.runStep(
|
const res = await setup.runStep(setup.actions.EXECUTE_SCRIPT.stepId, {
|
||||||
setup.actions.EXECUTE_SCRIPT.stepId,
|
code: null,
|
||||||
(inputs = {
|
})
|
||||||
code: null,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
expect(res.response.message).toEqual("Invalid inputs")
|
expect(res.response.message).toEqual("Invalid inputs")
|
||||||
expect(res.success).toEqual(false)
|
expect(res.success).toEqual(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("should be able to handle an error gracefully", async () => {
|
it("should be able to get a value from context", async () => {
|
||||||
let res = await setup.runStep(
|
const res = await setup.runStep(
|
||||||
setup.actions.EXECUTE_SCRIPT.stepId,
|
setup.actions.EXECUTE_SCRIPT.stepId,
|
||||||
(inputs = {
|
{
|
||||||
code: "return something.map(x => x.name)",
|
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.response).toEqual("ReferenceError: something is not defined")
|
||||||
expect(res.success).toEqual(false)
|
expect(res.success).toEqual(false)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const { v4 } = require("uuid")
|
import { v4 } from "uuid"
|
||||||
|
|
||||||
export default function (): string {
|
export default function (): string {
|
||||||
return v4().replace(/-/g, "")
|
return v4().replace(/-/g, "")
|
||||||
|
|
|
@ -95,6 +95,8 @@ const environment = {
|
||||||
TOP_LEVEL_PATH:
|
TOP_LEVEL_PATH:
|
||||||
process.env.TOP_LEVEL_PATH || process.env.SERVER_TOP_LEVEL_PATH,
|
process.env.TOP_LEVEL_PATH || process.env.SERVER_TOP_LEVEL_PATH,
|
||||||
APP_MIGRATION_TIMEOUT: parseIntSafe(process.env.APP_MIGRATION_TIMEOUT),
|
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
|
// 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 env from "../../environment"
|
||||||
import tar from "tar"
|
import tar from "tar"
|
||||||
|
|
||||||
const uuid = require("uuid/v4")
|
import { v4 as uuid } from "uuid"
|
||||||
|
|
||||||
export const TOP_LEVEL_PATH =
|
export const TOP_LEVEL_PATH =
|
||||||
env.TOP_LEVEL_PATH || resolve(join(__dirname, "..", "..", ".."))
|
env.TOP_LEVEL_PATH || resolve(join(__dirname, "..", "..", ".."))
|
||||||
|
|
|
@ -1,29 +1,63 @@
|
||||||
import fetch from "node-fetch"
|
import ivm from "isolated-vm"
|
||||||
import { VM, VMScript } from "vm2"
|
|
||||||
|
|
||||||
const JS_TIMEOUT_MS = 1000
|
const JS_TIMEOUT_MS = 1000
|
||||||
|
|
||||||
class ScriptRunner {
|
class ScriptRunner {
|
||||||
vm: VM
|
vm: IsolatedVM
|
||||||
results: { out: string }
|
|
||||||
script: VMScript
|
|
||||||
|
|
||||||
constructor(script: string, context: any) {
|
constructor(script: string, context: any) {
|
||||||
const code = `let fn = () => {\n${script}\n}; results.out = fn();`
|
const code = `let fn = () => {\n${script}\n}; results.out = fn();`
|
||||||
this.vm = new VM({
|
this.vm = new IsolatedVM({ memoryLimit: 8 })
|
||||||
timeout: JS_TIMEOUT_MS,
|
this.vm.context = {
|
||||||
})
|
...context,
|
||||||
this.results = { out: "" }
|
results: { out: "" },
|
||||||
this.vm.setGlobals(context)
|
}
|
||||||
this.vm.setGlobal("fetch", fetch)
|
this.vm.code = code
|
||||||
this.vm.setGlobal("results", this.results)
|
|
||||||
this.script = new VMScript(code)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
execute() {
|
execute() {
|
||||||
this.vm.run(this.script)
|
this.vm.runScript()
|
||||||
return this.results.out
|
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
|
export default ScriptRunner
|
||||||
|
|
|
@ -11,7 +11,9 @@
|
||||||
"require": "./src/index.cjs",
|
"require": "./src/index.cjs",
|
||||||
"import": "./dist/bundle.mjs"
|
"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": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
@ -19,8 +21,9 @@
|
||||||
"manifest.json"
|
"manifest.json"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc && rollup -c",
|
"build:esbuild": "esbuild --minify --bundle src/index-helpers.js --outfile=dist/index-helpers.bundled.js --platform=node --format=esm --external:handlebars",
|
||||||
"dev": "tsc && rollup -cw",
|
"build": "yarn build:esbuild && tsc && rollup -c",
|
||||||
|
"dev": "concurrently \"yarn build:esbuild --watch\" \"tsc && rollup -cw\"",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"manifest": "node ./scripts/gen-collection-info.js"
|
"manifest": "node ./scripts/gen-collection-info.js"
|
||||||
},
|
},
|
||||||
|
@ -34,6 +37,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^17.1.0",
|
"@rollup/plugin-commonjs": "^17.1.0",
|
||||||
"@rollup/plugin-json": "^4.1.0",
|
"@rollup/plugin-json": "^4.1.0",
|
||||||
|
"concurrently": "^8.2.2",
|
||||||
"doctrine": "^3.0.0",
|
"doctrine": "^3.0.0",
|
||||||
"jest": "29.7.0",
|
"jest": "29.7.0",
|
||||||
"marked": "^4.0.10",
|
"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 {
|
try {
|
||||||
// Wrap JS in a function and immediately invoke it.
|
// Wrap JS in a function and immediately invoke it.
|
||||||
// This is required to allow the final `return` statement to be valid.
|
// 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.
|
// Our $ context function gets a value from context.
|
||||||
// We clone the context to avoid mutation in the binding affecting real
|
// We clone the context to avoid mutation in the binding affecting real
|
||||||
|
|
|
@ -1,29 +1,42 @@
|
||||||
const externalHandlebars = require("./external")
|
const { date, duration } = require("./date")
|
||||||
const helperList = require("@budibase/handlebars-helpers")
|
|
||||||
|
|
||||||
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"]
|
const helpersToRemoveForJs = ["sortBy"]
|
||||||
module.exports.helpersToRemoveForJs = helpersToRemoveForJs
|
module.exports.helpersToRemoveForJs = helpersToRemoveForJs
|
||||||
|
|
||||||
|
const addedHelpers = {
|
||||||
|
date: date,
|
||||||
|
duration: duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
let helpers = undefined
|
||||||
|
|
||||||
module.exports.getJsHelperList = () => {
|
module.exports.getJsHelperList = () => {
|
||||||
if (helpers) {
|
if (helpers) {
|
||||||
return helpers
|
return helpers
|
||||||
}
|
}
|
||||||
|
|
||||||
helpers = {}
|
helpers = {}
|
||||||
let constructed = []
|
for (let collection of Object.values(externalCollections)) {
|
||||||
for (let collection of externalHandlebars.externalCollections) {
|
|
||||||
constructed.push(helperList[collection]())
|
|
||||||
}
|
|
||||||
for (let collection of constructed) {
|
|
||||||
for (let [key, func] of Object.entries(collection)) {
|
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
|
// 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, {})
|
helpers[key] = (...props) => func(...props, {})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let key of Object.keys(externalHandlebars.addedHelpers)) {
|
for (let key of Object.keys(addedHelpers)) {
|
||||||
helpers[key] = externalHandlebars.addedHelpers[key]
|
helpers[key] = addedHelpers[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const toRemove of helpersToRemoveForJs) {
|
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)
|
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
|
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
|
module.exports.helpersToRemoveForJs = helpersToRemoveForJs
|
||||||
|
|
|
@ -38,3 +38,5 @@ if (process && !process.env.NO_JS) {
|
||||||
return vm.runInNewContext(js, context, { timeout: 1000 })
|
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 } = require("../src/index.cjs")
|
||||||
const {
|
|
||||||
processString,
|
|
||||||
convertToJS,
|
|
||||||
processStringSync,
|
|
||||||
encodeJSBinding,
|
|
||||||
} = require("../src/index.cjs")
|
|
||||||
|
|
||||||
const tk = require("timekeeper")
|
const tk = require("timekeeper")
|
||||||
const { getJsHelperList } = require("../src/helpers")
|
const { getParsedManifest, runJsHelpersTests } = require("./utils")
|
||||||
|
|
||||||
tk.freeze("2021-01-21T12:00:00")
|
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) {
|
function escapeRegExp(string) {
|
||||||
return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") // $& means the whole matched 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", () => {
|
describe("manifest", () => {
|
||||||
|
const manifest = getParsedManifest()
|
||||||
|
|
||||||
describe("examples are valid", () => {
|
describe("examples are valid", () => {
|
||||||
describe.each(Object.keys(examples))("%s", collection => {
|
describe.each(Object.keys(manifest))("%s", collection => {
|
||||||
it.each(examples[collection])("%s", async (_, { hbs, js }) => {
|
it.each(manifest[collection])("%s", async (_, { hbs, js }) => {
|
||||||
const context = {
|
const context = {
|
||||||
double: i => i * 2,
|
double: i => i * 2,
|
||||||
isString: x => typeof x === "string",
|
isString: x => typeof x === "string",
|
||||||
|
@ -108,36 +50,5 @@ describe("manifest", () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("can be parsed and run as js", () => {
|
runJsHelpersTests()
|
||||||
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)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
# due to this causing yarn to stop installing dev dependencies
|
||||||
# which are actually needed to get this environment up and running
|
# which are actually needed to get this environment up and running
|
||||||
ENV NODE_ENV=production
|
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 CLUSTER_MODE=${CLUSTER_MODE}
|
||||||
ENV SERVICE=worker-service
|
ENV SERVICE=worker-service
|
||||||
ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
ENV POSTHOG_TOKEN=phc_bIjZL7oh2GEUd2vqvTBH8WvrX0fWTFQMs6H5KQxiUxU
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
"../string-templates"
|
"../string-templates"
|
||||||
],
|
],
|
||||||
"ext": "js,ts,json",
|
"ext": "js,ts,json",
|
||||||
"ignore": ["src/**/*.spec.ts", "src/**/*.spec.js", "../*/dist/**/*"],
|
"ignore": ["**/*.spec.ts", "**/*.spec.js", "../*/dist/**/*"],
|
||||||
"exec": "yarn build && node dist/index.js"
|
"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"
|
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
|
||||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
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"
|
version "7.23.8"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.8.tgz#8ee6fe1ac47add7122902f257b8ddf55c898f650"
|
||||||
integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==
|
integrity sha512-Y7KbAP984rn1VGMbGqKmBLio9V7y5Je9GvU4rQPCPinCyNfUcToxIXl06d59URp/F3LwinvODxab5N/G6qggkw==
|
||||||
|
@ -5573,9 +5573,9 @@
|
||||||
integrity sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg==
|
integrity sha512-7GgtHCs/QZrBrDzgIJnQtuSvhFSwhyYSI2uafSwZoNt1iOGhEN5fwNrQMjtONyHm9+/LoA4453jH0CMYcr06Pg==
|
||||||
|
|
||||||
"@types/node@>=8.1.0":
|
"@types/node@>=8.1.0":
|
||||||
version "20.11.6"
|
version "20.11.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.6.tgz#6adf4241460e28be53836529c033a41985f85b6e"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.2.tgz#39cea3fe02fbbc2f80ed283e94e1d24f2d3856fb"
|
||||||
integrity sha512-+EOokTnksGVgip2PbYbr3xnR7kZigh4LbybAfBAw5BpnQ+FqBYUsvCEjYd70IXKlbohQ64mzEYmMtlWUY8q//Q==
|
integrity sha512-cZShBaVa+UO1LjWWBPmWRR4+/eY/JR/UIEcDlVsw3okjWEu+rB7/mH6X3B/L+qJVHDLjk9QW/y2upp9wp1yDXA==
|
||||||
dependencies:
|
dependencies:
|
||||||
undici-types "~5.26.4"
|
undici-types "~5.26.4"
|
||||||
|
|
||||||
|
@ -8355,6 +8355,21 @@ concat-with-sourcemaps@^1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
source-map "^0.6.1"
|
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:
|
condense-newlines@^0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/condense-newlines/-/condense-newlines-0.2.1.tgz#3de985553139475d32502c83b02f60684d24c55f"
|
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-mimetype "^3.0.0"
|
||||||
whatwg-url "^12.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:
|
dateformat@^3.0.3:
|
||||||
version "3.0.3"
|
version "3.0.3"
|
||||||
resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae"
|
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==
|
integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==
|
||||||
|
|
||||||
dotenv@^16.3.1:
|
dotenv@^16.3.1:
|
||||||
version "16.4.0"
|
version "16.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.0.tgz#ac21c3fcaad2e7832a1cd0c0e4e8e52225ecda0e"
|
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
|
||||||
integrity sha512-WvImr5kpN5NGNn7KaDjJnLTh5rDVLZiDf/YLA8T1ZEZEBZNEDOE+mnkS0PVjPax8ZxBP5zC5SLMB3/9VV5de9g==
|
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
|
||||||
|
|
||||||
dotenv@~10.0.0:
|
dotenv@~10.0.0:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
|
||||||
integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
|
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:
|
expand-tilde@^1.2.2:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449"
|
resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449"
|
||||||
|
@ -11441,6 +11468,11 @@ gitconfiglocal@^1.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ini "^1.3.2"
|
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:
|
glob-parent@5.1.2, glob-parent@^5.1.2, glob-parent@~5.1.2:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
|
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"
|
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||||
integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
|
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:
|
isstream@~0.1.2:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
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"
|
is-plain-obj "^1.1.0"
|
||||||
kind-of "^6.0.3"
|
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"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
|
||||||
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==
|
||||||
|
@ -15445,7 +15484,7 @@ minizlib@^2.1.1, minizlib@^2.1.2:
|
||||||
minipass "^3.0.0"
|
minipass "^3.0.0"
|
||||||
yallist "^4.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"
|
version "0.5.3"
|
||||||
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113"
|
||||||
integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==
|
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"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||||
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
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:
|
napi-macros@~2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b"
|
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"
|
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||||
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
|
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:
|
node-abort-controller@^3.0.1, node-abort-controller@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548"
|
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"
|
xtend "^4.0.0"
|
||||||
|
|
||||||
posthog-js@^1.13.4:
|
posthog-js@^1.13.4:
|
||||||
version "1.101.0"
|
version "1.100.0"
|
||||||
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.101.0.tgz#00e0fc6e164addd52b1738f087996bb0d6685943"
|
resolved "https://registry.yarnpkg.com/posthog-js/-/posthog-js-1.100.0.tgz#687b9a6e4ed226aa6572f4040b418ea0c8b3d353"
|
||||||
integrity sha512-mzwYSSWr9FdEMDeVpc+diLfc85+10r/LgELGtsW/HaYk+0du/GEql6szpqG8YXMMgb2dE4dnj0JICZFIJd7K3w==
|
integrity sha512-r2XZEiHQ9mBK7D1G9k57I8uYZ2kZTAJ0OCX6K/OOdCWN8jKPhw3h5F9No5weilP6eVAn+hrsy7NvPV7SCX7gMg==
|
||||||
dependencies:
|
dependencies:
|
||||||
fflate "^0.4.1"
|
fflate "^0.4.1"
|
||||||
preact "^10.19.3"
|
|
||||||
|
|
||||||
posthog-js@^1.36.0:
|
posthog-js@^1.36.0:
|
||||||
version "1.96.1"
|
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"
|
resolved "https://registry.yarnpkg.com/pprof-format/-/pprof-format-2.0.7.tgz#526e4361f8b37d16b2ec4bb0696b5292de5046a4"
|
||||||
integrity sha512-1qWaGAzwMpaXJP9opRa23nPnt2Egi7RMNoNBptEE/XwHbcn4fC2b/4U4bKc5arkGkIh2ZabpF2bEb+c5GNHEKA==
|
integrity sha512-1qWaGAzwMpaXJP9opRa23nPnt2Egi7RMNoNBptEE/XwHbcn4fC2b/4U4bKc5arkGkIh2ZabpF2bEb+c5GNHEKA==
|
||||||
|
|
||||||
preact@^10.19.3:
|
prebuild-install@^7.1.1:
|
||||||
version "10.19.3"
|
version "7.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/preact/-/preact-10.19.3.tgz#7a7107ed2598a60676c943709ea3efb8aaafa899"
|
resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45"
|
||||||
integrity sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==
|
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:
|
precinct@^8.1.0:
|
||||||
version "8.3.1"
|
version "8.3.1"
|
||||||
|
@ -19090,6 +19153,13 @@ rxjs@^7.5.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib "^2.1.0"
|
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:
|
safe-array-concat@^1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c"
|
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"
|
resolved "https://registry.yarnpkg.com/shell-exec/-/shell-exec-1.0.2.tgz#2e9361b0fde1d73f476c4b6671fa17785f696756"
|
||||||
integrity sha512-jyVd+kU2X+mWKMmGhx4fpWbPsjvD53k9ivqetutVW/BQ+WIZoDoP4d8vUMGezV6saZsiNoW2f9GIhg9Dondohg==
|
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:
|
shortid@2.2.15:
|
||||||
version "2.2.15"
|
version "2.2.15"
|
||||||
resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.15.tgz#2b902eaa93a69b11120373cd42a1f1fe4437c122"
|
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"
|
make-fetch-happen "^11.0.1"
|
||||||
tuf-js "^1.1.3"
|
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:
|
simple-lru-cache@^0.0.2:
|
||||||
version "0.0.2"
|
version "0.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz#d59cc3a193c1a5d0320f84ee732f6e4713e511dd"
|
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:
|
dependencies:
|
||||||
memory-pager "^1.0.2"
|
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:
|
spdx-correct@^3.0.0:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9"
|
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:
|
dependencies:
|
||||||
has-flag "^4.0.0"
|
has-flag "^4.0.0"
|
||||||
|
|
||||||
supports-color@^8.0.0:
|
supports-color@^8.0.0, supports-color@^8.1.1:
|
||||||
version "8.1.1"
|
version "8.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
|
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
|
||||||
integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
|
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"
|
resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
|
||||||
integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==
|
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"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
|
resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784"
|
||||||
integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==
|
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"
|
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
|
||||||
integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==
|
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:
|
trim-newlines@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
|
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144"
|
||||||
|
|
Loading…
Reference in New Issue