From d03f96ceb8327d46245700c404831e8afd9c3bbc Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Fri, 28 Apr 2023 09:03:09 +0100 Subject: [PATCH 001/808] Make all blindings global and improve client component performance --- .../builder/src/builderStore/dataBinding.js | 34 ++++++--- .../src/builderStore/store/frontend.js | 7 +- .../controls/DataProviderSelect.svelte | 9 +-- packages/client/manifest.json | 11 --- .../client/src/components/Component.svelte | 71 ++++++++++++++----- .../src/components/context/Provider.svelte | 6 +- packages/client/src/stores/context.js | 61 +++++++--------- packages/client/src/utils/componentProps.js | 16 +---- .../client/src/utils/enrichDataBinding.js | 1 + yarn.lock | 12 ++-- 10 files changed, 127 insertions(+), 101 deletions(-) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 0d41931a55..9f253f9f75 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -199,15 +199,7 @@ export const getContextProviderComponents = ( return [] } - // Get the component tree leading up to this component, ignoring the component - // itself - const path = findComponentPath(asset.props, componentId) - if (!options?.includeSelf) { - path.pop() - } - - // Filter by only data provider components - return path.filter(component => { + return findAllMatchingComponents(asset.props, component => { const def = store.actions.components.getDefinition(component._component) if (!def?.context) { return false @@ -222,6 +214,30 @@ export const getContextProviderComponents = ( const contexts = Array.isArray(def.context) ? def.context : [def.context] return contexts.find(context => context.type === type) != null }) + // + // // Get the component tree leading up to this component, ignoring the component + // // itself + // const path = findComponentPath(asset.props, componentId) + // if (!options?.includeSelf) { + // path.pop() + // } + // + // // Filter by only data provider components + // return path.filter(component => { + // const def = store.actions.components.getDefinition(component._component) + // if (!def?.context) { + // return false + // } + // + // // If no type specified, return anything that exposes context + // if (!type) { + // return true + // } + // + // // Otherwise only match components with the specific context type + // const contexts = Array.isArray(def.context) ? def.context : [def.context] + // return contexts.find(context => context.type === type) != null + // }) } /** diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index e264dc099b..fb3eb8f1d9 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -621,10 +621,9 @@ export const getFrontendStore = () => { else { if (setting.type === "dataProvider") { // Validate data provider exists, or else clear it - const treeId = parent?._id || component._id - const path = findComponentPath(screen?.props, treeId) - const providers = path.filter(component => - component._component?.endsWith("/dataprovider") + const providers = findAllMatchingComponents( + screen?.props, + component => component._component?.endsWith("/dataprovider") ) // Validate non-empty values const valid = providers?.some(dp => value.includes?.(dp._id)) diff --git a/packages/builder/src/components/design/settings/controls/DataProviderSelect.svelte b/packages/builder/src/components/design/settings/controls/DataProviderSelect.svelte index 83255ec325..9fd220e798 100644 --- a/packages/builder/src/components/design/settings/controls/DataProviderSelect.svelte +++ b/packages/builder/src/components/design/settings/controls/DataProviderSelect.svelte @@ -1,15 +1,16 @@ automation._id} + getOptionLabel={automation => automation.name} + /> + + +{#if Object.keys(automationFields)} + {#each Object.keys(automationFields) as field} +
+ +
+ onChange(e, field)} + type="string" + {bindings} + fillWidth={true} + updateOnChange={false} + /> +
+
+ {/each} +{/if} + + diff --git a/packages/server/src/automations/steps/trigger.ts b/packages/server/src/automations/steps/trigger.ts index 3c442267a3..9727a32821 100644 --- a/packages/server/src/automations/steps/trigger.ts +++ b/packages/server/src/automations/steps/trigger.ts @@ -7,6 +7,7 @@ import { AutomationFeature, AutomationResults, Automation, + AutomationCustomIOType, } from "@budibase/types" import * as triggers from "../triggers" import { db as dbCore, context } from "@budibase/backend-core" @@ -24,9 +25,17 @@ export const definition: AutomationStepSchema = { schema: { inputs: { properties: { - automationId: { - type: AutomationIOType.STRING, - title: "Automation ID", + automation: { + type: AutomationIOType.OBJECT, + properties: { + automationId: { + type: AutomationIOType.STRING, + customType: AutomationCustomIOType.AUTOMATION, + }, + }, + customType: AutomationCustomIOType.AUTOMATION_FIELDS, + title: "automatioFields", + required: ["automationId"], }, timeout: { type: AutomationIOType.NUMBER, @@ -52,18 +61,20 @@ export const definition: AutomationStepSchema = { } export async function run({ inputs }: AutomationStepInput) { - if (!inputs.automationId) { + const { automationId, ...fieldParams } = inputs.automation + + if (!inputs.automation.automationId) { return { success: false, } } else { const db = context.getAppDB() - let automation = await db.get(inputs.automationId) + let automation = await db.get(inputs.automation.automationId) const response: AutomationResults = await triggers.externalTrigger( automation, { - fields: {}, + fields: { ...fieldParams }, timeout: inputs.timeout * 1000 || 120000, }, { getResponses: true } diff --git a/packages/types/src/documents/app/automation.ts b/packages/types/src/documents/app/automation.ts index bbaa74bb14..0c3ac0018b 100644 --- a/packages/types/src/documents/app/automation.ts +++ b/packages/types/src/documents/app/automation.ts @@ -28,6 +28,8 @@ export enum AutomationCustomIOType { TRIGGER_SCHEMA = "triggerSchema", CRON = "cron", WEBHOOK_URL = "webhookUrl", + AUTOMATION = "automation", + AUTOMATION_FIELDS = "automationFields", } export enum AutomationTriggerStepId { From e896904af160d760b235e9ac175f7ffe1d959d02 Mon Sep 17 00:00:00 2001 From: Dean Date: Wed, 10 Jan 2024 17:12:02 +0000 Subject: [PATCH 510/808] Added value caching to alleviate too many updates when hovering --- .../controls/FormStepConfiguration.svelte | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte b/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte index bd28347e08..2941821dec 100644 --- a/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte +++ b/packages/builder/src/components/design/settings/controls/FormStepConfiguration.svelte @@ -6,7 +6,7 @@ import { Helpers } from "@budibase/bbui" import { derived, writable } from "svelte/store" import { Utils } from "@budibase/frontend-core" - import { cloneDeep } from "lodash" + import { cloneDeep, isEqual } from "lodash" export let componentInstance export let componentBindings @@ -21,21 +21,32 @@ const currentStep = derived(multiStepStore, state => state.currentStep) const componentType = "@budibase/standard-components/multistepformblockstep" + let cachedValue + let cachedInstance = {} + + $: if (!isEqual(cachedValue, value)) { + cachedValue = value + } + + $: if (!isEqual(componentInstance, cachedInstance)) { + cachedInstance = componentInstance + } + setContext("multi-step-form-block", multiStepStore) - $: stepCount = value?.length || 0 + $: stepCount = cachedValue?.length || 0 $: updateStore(stepCount) - $: dataSource = getDatasourceForProvider($currentAsset, componentInstance) + $: dataSource = getDatasourceForProvider($currentAsset, cachedInstance) $: emitCurrentStep($currentStep) $: stepLabel = getStepLabel($multiStepStore) $: stepDef = getDefinition(stepLabel) - $: stepSettings = value?.[$currentStep] || {} + $: stepSettings = cachedValue?.[$currentStep] || {} $: defaults = Utils.buildMultiStepFormBlockDefaultProps({ - _id: componentInstance._id, + _id: cachedInstance._id, stepCount: $multiStepStore.stepCount, currentStep: $multiStepStore.currentStep, - actionType: componentInstance.actionType, - dataSource: componentInstance.dataSource, + actionType: cachedInstance.actionType, + dataSource: cachedInstance.dataSource, }) $: stepInstance = { _id: Helpers.uuid(), From c3f77f3b42618eddf2007839b0ebd75a7e549939 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 11 Jan 2024 09:47:04 +0000 Subject: [PATCH 511/808] linting --- .../automation/SetupPanel/AutomationBlockSetup.svelte | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index ecc748599f..34c7c44ecd 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -188,7 +188,8 @@ ) { if (name !== "id" && name !== "revision") return `trigger.row.${name}` } - /* end special cases */ + /* End special cases for generating custom schemas based on triggers */ + if (isLoopBlock) { runtimeName = `loop.${name}` } else if (block.name.startsWith("JS")) { From f80e8cc70cf85fe9cfb31c330f81fa854c84fd5a Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 11 Jan 2024 10:45:28 +0000 Subject: [PATCH 512/808] Bump version to 2.14.6 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 81b35e2a6e..9d7b84da36 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.14.5", + "version": "2.14.6", "npmClient": "yarn", "packages": [ "packages/*", From bf12e5bc1e456a0218295cc5a7444ea89900e483 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 11 Jan 2024 11:01:23 +0000 Subject: [PATCH 513/808] revert to empty array on fields object being empty --- .../automation/SetupPanel/AutomationBlockSetup.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 34c7c44ecd..b8aaf10443 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -256,7 +256,7 @@ if (idx === 0 && automation.trigger?.event === "app:trigger") { schema = Object.fromEntries( - Object.keys(automation.trigger.inputs.fields).map(key => [ + Object.keys(automation.trigger.inputs.fields || []).map(key => [ key, { type: automation.trigger.inputs.fields[key] }, ]) From c01abe4c6fbd5e1d1e3fb7191f188e599c74b17e Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 11 Jan 2024 11:20:37 +0000 Subject: [PATCH 514/808] null check --- .../automation/SetupPanel/AutomationSelector.svelte | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationSelector.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationSelector.svelte index ae45bfd8a1..7e3ba92420 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationSelector.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationSelector.svelte @@ -21,9 +21,10 @@ } $: if (value?.automationId == null) value = { automationId: "" } - $: automationFields = $automationStore.automations.find( - automation => automation._id === value?.automationId - )?.definition?.trigger?.inputs?.fields + $: automationFields = + $automationStore.automations.find( + automation => automation._id === value?.automationId + )?.definition?.trigger?.inputs?.fields || [] $: filteredAutomations = $automationStore.automations.filter( automation => From 9c22bee3246c064f4a9289cc4f0b80a73c551c04 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 11 Jan 2024 11:39:04 +0000 Subject: [PATCH 515/808] type fix --- packages/server/src/tests/utilities/structures.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/server/src/tests/utilities/structures.ts b/packages/server/src/tests/utilities/structures.ts index ce08011e76..41a555a0d9 100644 --- a/packages/server/src/tests/utilities/structures.ts +++ b/packages/server/src/tests/utilities/structures.ts @@ -21,7 +21,9 @@ import { Table, INTERNAL_TABLE_SOURCE_ID, TableSourceType, + AutomationIOType, } from "@budibase/types" +import { bool } from "joi" const { BUILTIN_ROLE_IDS } = roles @@ -164,7 +166,7 @@ export function basicAutomation(appId?: string): Automation { inputs: { properties: { text: { - type: "string", + type: AutomationIOType.STRING, title: "Text to log", }, }, @@ -183,6 +185,7 @@ export function basicAutomation(appId?: string): Automation { }, }, id: "y8lkZbeSe", + type: AutomationStepType.ACTION, }, ], }, From 30cbcff1b14a5c0f665729adc38834c4b1a4e677 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 11 Jan 2024 11:57:23 +0000 Subject: [PATCH 516/808] fix height and add null state --- .../AutomationPanel/AutomationPanel.svelte | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte index 2a8f63e50b..03123d481b 100644 --- a/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte +++ b/packages/builder/src/components/automation/AutomationPanel/AutomationPanel.svelte @@ -1,6 +1,6 @@ - -
- {appUrl} -
+
modal.confirm()}> + +
+ {appUrl} +
+
diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index 0a343cac51..30040bfe9c 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -19,36 +19,7 @@ export let onRowClick = null export let buttons = null - const context = getContext("context") - const component = getContext("component") - const { - styleable, - API, - builderStore, - notificationStore, - enrichButtonActions, - ActionTypes, - createContextStore, - Provider, - } = getContext("sdk") - - let grid - - $: columnWhitelist = parsedColumns - ?.filter(col => col.active) - ?.map(col => col.field) - $: schemaOverrides = getSchemaOverrides(parsedColumns) - $: enrichedButtons = enrichButtons(buttons) - $: parsedColumns = getParsedColumns(columns) - $: actions = [ - { - type: ActionTypes.RefreshDatasource, - callback: () => grid?.getContext()?.rows.actions.refreshData(), - metadata: { dataSource: table }, - }, - ] - - // Parses columns to fix older formats + // parses columns to fix older formats const getParsedColumns = columns => { // If the first element has an active key all elements should be in the new format if (columns?.length && columns[0]?.active !== undefined) { @@ -62,6 +33,28 @@ })) } + $: parsedColumns = getParsedColumns(columns) + + const context = getContext("context") + const component = getContext("component") + const { + styleable, + API, + builderStore, + notificationStore, + enrichButtonActions, + ActionTypes, + createContextStore, + } = getContext("sdk") + + let grid + + $: columnWhitelist = parsedColumns + ?.filter(col => col.active) + ?.map(col => col.field) + $: schemaOverrides = getSchemaOverrides(parsedColumns) + $: enrichedButtons = enrichButtons(buttons) + const getSchemaOverrides = columns => { let overrides = {} columns?.forEach(column => { @@ -85,6 +78,11 @@ const id = get(component).id const gridContext = createContextStore(context) gridContext.actions.provideData(id, row) + gridContext.actions.provideAction( + id, + ActionTypes.RefreshDatasource, + () => grid?.getContext()?.rows.actions.refreshData() + ) const fn = enrichButtonActions(settings.onClick, get(gridContext)) return await fn?.({ row }) }, @@ -96,31 +94,29 @@ use:styleable={$component.styles} class:in-builder={$builderStore.inBuilder} > - - onRowClick?.({ row: e.detail })} - /> - + onRowClick?.({ row: e.detail })} + /> diff --git a/packages/client/src/components/app/GridBlock.svelte b/packages/client/src/components/app/GridBlock.svelte index 30040bfe9c..0a343cac51 100644 --- a/packages/client/src/components/app/GridBlock.svelte +++ b/packages/client/src/components/app/GridBlock.svelte @@ -19,7 +19,36 @@ export let onRowClick = null export let buttons = null - // parses columns to fix older formats + const context = getContext("context") + const component = getContext("component") + const { + styleable, + API, + builderStore, + notificationStore, + enrichButtonActions, + ActionTypes, + createContextStore, + Provider, + } = getContext("sdk") + + let grid + + $: columnWhitelist = parsedColumns + ?.filter(col => col.active) + ?.map(col => col.field) + $: schemaOverrides = getSchemaOverrides(parsedColumns) + $: enrichedButtons = enrichButtons(buttons) + $: parsedColumns = getParsedColumns(columns) + $: actions = [ + { + type: ActionTypes.RefreshDatasource, + callback: () => grid?.getContext()?.rows.actions.refreshData(), + metadata: { dataSource: table }, + }, + ] + + // Parses columns to fix older formats const getParsedColumns = columns => { // If the first element has an active key all elements should be in the new format if (columns?.length && columns[0]?.active !== undefined) { @@ -33,28 +62,6 @@ })) } - $: parsedColumns = getParsedColumns(columns) - - const context = getContext("context") - const component = getContext("component") - const { - styleable, - API, - builderStore, - notificationStore, - enrichButtonActions, - ActionTypes, - createContextStore, - } = getContext("sdk") - - let grid - - $: columnWhitelist = parsedColumns - ?.filter(col => col.active) - ?.map(col => col.field) - $: schemaOverrides = getSchemaOverrides(parsedColumns) - $: enrichedButtons = enrichButtons(buttons) - const getSchemaOverrides = columns => { let overrides = {} columns?.forEach(column => { @@ -78,11 +85,6 @@ const id = get(component).id const gridContext = createContextStore(context) gridContext.actions.provideData(id, row) - gridContext.actions.provideAction( - id, - ActionTypes.RefreshDatasource, - () => grid?.getContext()?.rows.actions.refreshData() - ) const fn = enrichButtonActions(settings.onClick, get(gridContext)) return await fn?.({ row }) }, @@ -94,29 +96,31 @@ use:styleable={$component.styles} class:in-builder={$builderStore.inBuilder} > - onRowClick?.({ row: e.detail })} - /> + + onRowClick?.({ row: e.detail })} + /> + From 710083d9fca049b2700d6afcd06b6c1bf7c47908 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 30 Jan 2024 15:12:09 +0100 Subject: [PATCH 755/808] Use require.resolve to get the manifest on generation --- packages/string-templates/scripts/gen-collection-info.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/string-templates/scripts/gen-collection-info.js b/packages/string-templates/scripts/gen-collection-info.js index 004aee4778..48bf503ade 100644 --- a/packages/string-templates/scripts/gen-collection-info.js +++ b/packages/string-templates/scripts/gen-collection-info.js @@ -10,8 +10,8 @@ const marked = require("marked") * https://github.com/budibase/handlebars-helpers */ const { join } = require("path") +const path = require("path") -const DIRECTORY = join(__dirname, "..", "..", "..") const COLLECTIONS = [ "math", "array", @@ -128,7 +128,7 @@ function run() { const foundNames = [] for (let collection of COLLECTIONS) { const collectionFile = fs.readFileSync( - `${DIRECTORY}/node_modules/${HELPER_LIBRARY}/lib/${collection}.js`, + `${path.dirname(require.resolve(HELPER_LIBRARY))}/lib/${collection}.js`, "utf8" ) const collectionInfo = {} From f94e1e105a866dd2688af339cf90fdf66d7b9b6d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 30 Jan 2024 15:49:18 +0100 Subject: [PATCH 756/808] Renames --- packages/string-templates/manifest.json | 266 +++++++++--------- .../scripts/gen-collection-info.js | 4 +- .../string-templates/test/manifest.spec.js | 8 +- 3 files changed, 140 insertions(+), 138 deletions(-) diff --git a/packages/string-templates/manifest.json b/packages/string-templates/manifest.json index cd787bc850..d06a01f606 100644 --- a/packages/string-templates/manifest.json +++ b/packages/string-templates/manifest.json @@ -7,7 +7,7 @@ "numArgs": 1, "example": "{{ abs 12012.1000 }} -> 12012.1", "description": "

Return the magnitude of a.

\n", - "isBlock": false + "requiresBlock": false }, "add": { "args": [ @@ -17,7 +17,7 @@ "numArgs": 2, "example": "{{ add 1 2 }} -> 3", "description": "

Return the sum of a plus b.

\n", - "isBlock": false + "requiresBlock": false }, "avg": { "args": [ @@ -26,7 +26,7 @@ "numArgs": 1, "example": "{{ avg 1 2 3 4 5 }} -> 3", "description": "

Returns the average of all numbers in the given array.

\n", - "isBlock": false + "requiresBlock": false }, "ceil": { "args": [ @@ -35,7 +35,7 @@ "numArgs": 1, "example": "{{ ceil 1.2 }} -> 2", "description": "

Get the Math.ceil() of the given value.

\n", - "isBlock": false + "requiresBlock": false }, "divide": { "args": [ @@ -45,7 +45,7 @@ "numArgs": 2, "example": "{{ divide 10 5 }} -> 2", "description": "

Divide a by b

\n", - "isBlock": false + "requiresBlock": false }, "floor": { "args": [ @@ -54,7 +54,7 @@ "numArgs": 1, "example": "{{ floor 1.2 }} -> 1", "description": "

Get the Math.floor() of the given value.

\n", - "isBlock": false + "requiresBlock": false }, "minus": { "args": [ @@ -64,7 +64,7 @@ "numArgs": 2, "example": "{{ subtract 10 5 }} -> 5", "description": "

Return the product of a minus b.

\n", - "isBlock": false + "requiresBlock": false }, "modulo": { "args": [ @@ -74,7 +74,7 @@ "numArgs": 2, "example": "{{ modulo 10 5 }} -> 0", "description": "

Get the remainder of a division operation.

\n", - "isBlock": false + "requiresBlock": false }, "multiply": { "args": [ @@ -84,7 +84,7 @@ "numArgs": 2, "example": "{{ multiply 10 5 }} -> 50", "description": "

Multiply number a by number b.

\n", - "isBlock": false + "requiresBlock": false }, "plus": { "args": [ @@ -94,7 +94,7 @@ "numArgs": 2, "example": "{{ plus 10 5 }} -> 15", "description": "

Add a by b.

\n", - "isBlock": false + "requiresBlock": false }, "random": { "args": [ @@ -104,7 +104,7 @@ "numArgs": 2, "example": "{{ random 0 20 }} -> 10", "description": "

Generate a random number between two values

\n", - "isBlock": false + "requiresBlock": false }, "remainder": { "args": [ @@ -114,7 +114,7 @@ "numArgs": 2, "example": "{{ remainder 10 6 }} -> 4", "description": "

Get the remainder when a is divided by b.

\n", - "isBlock": false + "requiresBlock": false }, "round": { "args": [ @@ -123,7 +123,7 @@ "numArgs": 1, "example": "{{ round 10.3 }} -> 10", "description": "

Round the given number.

\n", - "isBlock": false + "requiresBlock": false }, "subtract": { "args": [ @@ -133,7 +133,7 @@ "numArgs": 2, "example": "{{ subtract 10 5 }} -> 5", "description": "

Return the product of a minus b.

\n", - "isBlock": false + "requiresBlock": false }, "sum": { "args": [ @@ -142,7 +142,7 @@ "numArgs": 1, "example": "{{ sum [1, 2, 3] }} -> 6", "description": "

Returns the sum of all numbers in the given array.

\n", - "isBlock": false + "requiresBlock": false } }, "array": { @@ -154,7 +154,7 @@ "numArgs": 2, "example": "{{ after ['a', 'b', 'c', 'd'] 2}} -> ['c', 'd']", "description": "

Returns all of the items in an array after the specified index. Opposite of before.

\n", - "isBlock": false + "requiresBlock": false }, "arrayify": { "args": [ @@ -163,7 +163,7 @@ "numArgs": 1, "example": "{{ arrayify 'foo' }} -> ['foo']", "description": "

Cast the given value to an array.

\n", - "isBlock": false + "requiresBlock": false }, "before": { "args": [ @@ -173,7 +173,7 @@ "numArgs": 2, "example": "{{ before ['a', 'b', 'c', 'd'] 3}} -> ['a', 'b']", "description": "

Return all of the items in the collection before the specified count. Opposite of after.

\n", - "isBlock": false + "requiresBlock": false }, "eachIndex": { "args": [ @@ -183,7 +183,7 @@ "numArgs": 2, "example": "{{#eachIndex [1, 2, 3]}} {{item}} is {{index}} {{/eachIndex}} -> ' 1 is 0 2 is 1 3 is 2 '", "description": "

Iterates the array, listing an item and the index of it.

\n", - "isBlock": true + "requiresBlock": true }, "filter": { "args": [ @@ -194,7 +194,7 @@ "numArgs": 3, "example": "{{#filter [1, 2, 3] 2}}2 Found{{else}}2 not found{{/filter}} -> 2 Found", "description": "

Block helper that filters the given array and renders the block for values that evaluate to true, otherwise the inverse block is returned.

\n", - "isBlock": true + "requiresBlock": true }, "first": { "args": [ @@ -204,7 +204,7 @@ "numArgs": 2, "example": "{{first [1, 2, 3, 4] 2}} -> 1,2", "description": "

Returns the first item, or first n items of an array.

\n", - "isBlock": false + "requiresBlock": false }, "forEach": { "args": [ @@ -214,7 +214,7 @@ "numArgs": 2, "example": "{{#forEach [{ 'name': 'John' }] }} {{ name }} {{/forEach}} -> ' John '", "description": "

Iterates over each item in an array and exposes the current item in the array as context to the inner block. In addition to the current array item, the helper exposes the following variables to the inner block: - index - total - isFirst - isLast Also, @index is exposed as a private variable, and additional private variables may be defined as hash arguments.

\n", - "isBlock": true + "requiresBlock": true }, "inArray": { "args": [ @@ -225,7 +225,7 @@ "numArgs": 3, "example": "{{#inArray [1, 2, 3] 2}} 2 exists {{else}} 2 does not exist {{/inArray}} -> ' 2 exists '", "description": "

Block helper that renders the block if an array has the given value. Optionally specify an inverse block to render when the array does not have the given value.

\n", - "isBlock": true + "requiresBlock": true }, "isArray": { "args": [ @@ -234,7 +234,7 @@ "numArgs": 1, "example": "{{isArray [1, 2]}} -> true", "description": "

Returns true if value is an es5 array.

\n", - "isBlock": false + "requiresBlock": false }, "itemAt": { "args": [ @@ -244,7 +244,7 @@ "numArgs": 2, "example": "{{itemAt [1, 2, 3] 1}} -> 2", "description": "

Returns the item from array at index idx.

\n", - "isBlock": false + "requiresBlock": false }, "join": { "args": [ @@ -254,7 +254,7 @@ "numArgs": 2, "example": "{{join [1, 2, 3]}} -> 1, 2, 3", "description": "

Join all elements of array into a string, optionally using a given separator.

\n", - "isBlock": false + "requiresBlock": false }, "equalsLength": { "args": [ @@ -264,7 +264,7 @@ "numArgs": 2, "example": "{{equalsLength [1, 2, 3] 3}} -> true", "description": "

Returns true if the the length of the given value is equal to the given length. Can be used as a block or inline helper.

\n", - "isBlock": true + "requiresBlock": true }, "last": { "args": [ @@ -274,7 +274,7 @@ "numArgs": 2, "example": "{{last [1, 2, 3]}} -> 3", "description": "

Returns the last item, or last n items of an array or string. Opposite of first.

\n", - "isBlock": false + "requiresBlock": false }, "length": { "args": [ @@ -283,7 +283,7 @@ "numArgs": 1, "example": "{{length [1, 2, 3]}} -> 3", "description": "

Returns the length of the given string or array.

\n", - "isBlock": false + "requiresBlock": false }, "lengthEqual": { "args": [ @@ -293,7 +293,7 @@ "numArgs": 2, "example": "{{equalsLength [1, 2, 3] 3}} -> true", "description": "

Returns true if the the length of the given value is equal to the given length. Can be used as a block or inline helper.

\n", - "isBlock": true + "requiresBlock": true }, "map": { "args": [ @@ -303,7 +303,7 @@ "numArgs": 2, "example": "{{map [1, 2, 3] double}} -> [2, 4, 6]", "description": "

Returns a new array, created by calling function on each element of the given array. For example,

\n", - "isBlock": false + "requiresBlock": false }, "pluck": { "args": [ @@ -313,7 +313,7 @@ "numArgs": 2, "example": "{{pluck [{ 'name': 'Bob' }] 'name' }} -> ['Bob']", "description": "

Map over the given object or array or objects and create an array of values from the given prop. Dot-notation may be used (as a string) to get nested properties.

\n", - "isBlock": false + "requiresBlock": false }, "reverse": { "args": [ @@ -322,7 +322,7 @@ "numArgs": 1, "example": "{{reverse [1, 2, 3]}} -> [3, 2, 1]", "description": "

Reverse the elements in an array, or the characters in a string.

\n", - "isBlock": false + "requiresBlock": false }, "some": { "args": [ @@ -333,7 +333,7 @@ "numArgs": 3, "example": "{{#some [1, \"b\", 3] isString}} string found {{else}} No string found {{/some}} -> ' string found '", "description": "

Block helper that returns the block if the callback returns true for some value in the given array.

\n", - "isBlock": true + "requiresBlock": true }, "sort": { "args": [ @@ -343,7 +343,7 @@ "numArgs": 2, "example": "{{ sort ['b', 'a', 'c'] }} -> ['a', 'b', 'c']", "description": "

Sort the given array. If an array of objects is passed, you may optionally pass a key to sort on as the second argument. You may alternatively pass a sorting function as the second argument.

\n", - "isBlock": false + "requiresBlock": false }, "sortBy": { "args": [ @@ -353,7 +353,7 @@ "numArgs": 2, "example": "{{ sortBy [{'a': 'zzz'}, {'a': 'aaa'}] 'a' }} -> [{'a':'aaa'},{'a':'zzz'}]", "description": "

Sort an array. If an array of objects is passed, you may optionally pass a key to sort on as the second argument. You may alternatively pass a sorting function as the second argument.

\n", - "isBlock": false + "requiresBlock": false }, "withAfter": { "args": [ @@ -364,7 +364,7 @@ "numArgs": 3, "example": "{{#withAfter [1, 2, 3] 1 }} {{this}} {{/withAfter}} -> ' 2 3 '", "description": "

Use the items in the array after the specified index as context inside a block. Opposite of withBefore.

\n", - "isBlock": true + "requiresBlock": true }, "withBefore": { "args": [ @@ -375,7 +375,7 @@ "numArgs": 3, "example": "{{#withBefore [1, 2, 3] 2 }} {{this}} {{/withBefore}} -> ' 1 '", "description": "

Use the items in the array before the specified index as context inside a block. Opposite of withAfter.

\n", - "isBlock": true + "requiresBlock": true }, "withFirst": { "args": [ @@ -386,7 +386,7 @@ "numArgs": 3, "example": "{{#withFirst [1, 2, 3] }}{{this}}{{/withFirst}} -> 1", "description": "

Use the first item in a collection inside a handlebars block expression. Opposite of withLast.

\n", - "isBlock": true + "requiresBlock": true }, "withGroup": { "args": [ @@ -397,7 +397,7 @@ "numArgs": 3, "example": "{{#withGroup [1, 2, 3, 4] 2}}{{#each this}}{{.}}{{/each}}
{{/withGroup}} -> 12
34
", "description": "

Block helper that groups array elements by given group size.

\n", - "isBlock": true + "requiresBlock": true }, "withLast": { "args": [ @@ -408,7 +408,7 @@ "numArgs": 3, "example": "{{#withLast [1, 2, 3, 4]}}{{this}}{{/withLast}} -> 4", "description": "

Use the last item or n items in an array as context inside a block. Opposite of withFirst.

\n", - "isBlock": true + "requiresBlock": true }, "withSort": { "args": [ @@ -419,7 +419,7 @@ "numArgs": 3, "example": "{{#withSort ['b', 'a', 'c']}}{{this}}{{/withSort}} -> abc", "description": "

Block helper that sorts a collection and exposes the sorted collection as context inside the block.

\n", - "isBlock": true + "requiresBlock": true }, "unique": { "args": [ @@ -429,7 +429,7 @@ "numArgs": 2, "example": "{{#each (unique ['a', 'a', 'c', 'b', 'e', 'e']) }}{{.}}{{/each}} -> acbe", "description": "

Block helper that return an array with all duplicate values removed. Best used along with a each helper.

\n", - "isBlock": true + "requiresBlock": true } }, "number": { @@ -440,7 +440,7 @@ "numArgs": 1, "example": "{{ bytes 1386 1 }} -> 1.4 kB", "description": "

Format a number to it's equivalent in bytes. If a string is passed, it's length will be formatted and returned. Examples: - 'foo' => 3 B - 13661855 => 13.66 MB - 825399 => 825.39 kB - 1396 => 1.4 kB

\n", - "isBlock": false + "requiresBlock": false }, "addCommas": { "args": [ @@ -449,7 +449,7 @@ "numArgs": 1, "example": "{{ addCommas 1000000 }} -> 1,000,000", "description": "

Add commas to numbers

\n", - "isBlock": false + "requiresBlock": false }, "phoneNumber": { "args": [ @@ -458,7 +458,7 @@ "numArgs": 1, "example": "{{ phoneNumber 8005551212 }} -> (800) 555-1212", "description": "

Convert a string or number to a formatted phone number.

\n", - "isBlock": false + "requiresBlock": false }, "toAbbr": { "args": [ @@ -468,7 +468,7 @@ "numArgs": 2, "example": "{{ toAbbr 10123 2 }} -> 10.12k", "description": "

Abbreviate numbers to the given number of precision. This for general numbers, not size in bytes.

\n", - "isBlock": false + "requiresBlock": false }, "toExponential": { "args": [ @@ -478,7 +478,7 @@ "numArgs": 2, "example": "{{ toExponential 10123 2 }} -> 1.01e+4", "description": "

Returns a string representing the given number in exponential notation.

\n", - "isBlock": false + "requiresBlock": false }, "toFixed": { "args": [ @@ -488,7 +488,7 @@ "numArgs": 2, "example": "{{ toFixed 1.1234 2 }} -> 1.12", "description": "

Formats the given number using fixed-point notation.

\n", - "isBlock": false + "requiresBlock": false }, "toFloat": { "args": [ @@ -496,7 +496,7 @@ ], "numArgs": 1, "description": "

Convert input to a float.

\n", - "isBlock": false + "requiresBlock": false }, "toInt": { "args": [ @@ -504,7 +504,7 @@ ], "numArgs": 1, "description": "

Convert input to an integer.

\n", - "isBlock": false + "requiresBlock": false }, "toPrecision": { "args": [ @@ -514,7 +514,7 @@ "numArgs": 2, "example": "{{toPrecision '1.1234' 2}} -> 1.1", "description": "

Returns a string representing the Number object to the specified precision.

\n", - "isBlock": false + "requiresBlock": false } }, "url": { @@ -525,7 +525,7 @@ "numArgs": 1, "example": "{{ encodeURI 'https://myurl?Hello There' }} -> https%3A%2F%2Fmyurl%3FHello%20There", "description": "

Encodes a Uniform Resource Identifier (URI) component by replacing each instance of certain characters by one, two, three, or four escape sequences representing the UTF-8 encoding of the character.

\n", - "isBlock": false + "requiresBlock": false }, "escape": { "args": [ @@ -534,7 +534,7 @@ "numArgs": 1, "example": "{{ escape 'https://myurl?Hello+There' }} -> https%3A%2F%2Fmyurl%3FHello%2BThere", "description": "

Escape the given string by replacing characters with escape sequences. Useful for allowing the string to be used in a URL, etc.

\n", - "isBlock": false + "requiresBlock": false }, "decodeURI": { "args": [ @@ -543,7 +543,7 @@ "numArgs": 1, "example": "{{ decodeURI 'https://myurl?Hello%20There' }} -> https://myurl?Hello There", "description": "

Decode a Uniform Resource Identifier (URI) component.

\n", - "isBlock": false + "requiresBlock": false }, "urlResolve": { "args": [ @@ -553,7 +553,7 @@ "numArgs": 2, "example": "{{ urlResolve 'https://myurl' '/api/test' }} -> https://myurl/api/test", "description": "

Take a base URL, and a href URL, and resolve them as a browser would for an anchor tag.

\n", - "isBlock": false + "requiresBlock": false }, "urlParse": { "args": [ @@ -562,7 +562,7 @@ "numArgs": 1, "example": "{{ urlParse 'https://myurl/api/test' }}", "description": "

Parses a url string into an object.

\n", - "isBlock": false + "requiresBlock": false }, "stripQuerystring": { "args": [ @@ -571,7 +571,7 @@ "numArgs": 1, "example": "{{ stripQuerystring 'https://myurl/api/test?foo=bar' }} -> 'https://myurl/api/test'", "description": "

Strip the query string from the given url.

\n", - "isBlock": false + "requiresBlock": false }, "stripProtocol": { "args": [ @@ -580,7 +580,7 @@ "numArgs": 1, "example": "{{ stripProtocol 'https://myurl/api/test' }} -> '//myurl/api/test'", "description": "

Strip protocol from a url. Useful for displaying media that may have an 'http' protocol on secure connections.

\n", - "isBlock": false + "requiresBlock": false } }, "string": { @@ -592,7 +592,7 @@ "numArgs": 2, "example": "{{append 'index' '.html'}} -> index.html", "description": "

Append the specified suffix to the given string.

\n", - "isBlock": false + "requiresBlock": false }, "camelcase": { "args": [ @@ -601,7 +601,7 @@ "numArgs": 1, "example": "{{camelcase 'foo bar baz'}} -> fooBarBaz", "description": "

camelCase the characters in the given string.

\n", - "isBlock": false + "requiresBlock": false }, "capitalize": { "args": [ @@ -610,7 +610,7 @@ "numArgs": 1, "example": "{{capitalize 'foo bar baz'}} -> Foo bar baz", "description": "

Capitalize the first word in a sentence.

\n", - "isBlock": false + "requiresBlock": false }, "capitalizeAll": { "args": [ @@ -619,7 +619,7 @@ "numArgs": 1, "example": "{{ capitalizeAll 'foo bar baz'}} -> Foo Bar Baz", "description": "

Capitalize all words in a string.

\n", - "isBlock": false + "requiresBlock": false }, "center": { "args": [ @@ -629,7 +629,7 @@ "numArgs": 2, "example": "{{ center 'test' 1}} -> ' test '", "description": "

Center a string using non-breaking spaces

\n", - "isBlock": false + "requiresBlock": false }, "chop": { "args": [ @@ -638,7 +638,7 @@ "numArgs": 1, "example": "{{ chop ' ABC '}} -> ABC", "description": "

Like trim, but removes both extraneous whitespace and non-word characters from the beginning and end of a string.

\n", - "isBlock": false + "requiresBlock": false }, "dashcase": { "args": [ @@ -647,7 +647,7 @@ "numArgs": 1, "example": "{{dashcase 'a-b-c d_e'}} -> a-b-c-d-e", "description": "

dash-case the characters in string. Replaces non-word characters and periods with hyphens.

\n", - "isBlock": false + "requiresBlock": false }, "dotcase": { "args": [ @@ -656,7 +656,7 @@ "numArgs": 1, "example": "{{dotcase 'a-b-c d_e'}} -> a.b.c.d.e", "description": "

dot.case the characters in string.

\n", - "isBlock": false + "requiresBlock": false }, "downcase": { "args": [ @@ -665,7 +665,7 @@ "numArgs": 1, "example": "{{downcase 'aBcDeF'}} -> abcdef", "description": "

Lowercase all of the characters in the given string. Alias for lowercase.

\n", - "isBlock": false + "requiresBlock": false }, "ellipsis": { "args": [ @@ -675,7 +675,7 @@ "numArgs": 2, "example": "{{ellipsis 'foo bar baz' 7}} -> foo bar…", "description": "

Truncates a string to the specified length, and appends it with an elipsis, .

\n", - "isBlock": false + "requiresBlock": false }, "hyphenate": { "args": [ @@ -684,7 +684,7 @@ "numArgs": 1, "example": "{{hyphenate 'foo bar baz qux'}} -> foo-bar-baz-qux", "description": "

Replace spaces in a string with hyphens.

\n", - "isBlock": false + "requiresBlock": false }, "isString": { "args": [ @@ -693,7 +693,7 @@ "numArgs": 1, "example": "{{isString 'foo'}} -> true", "description": "

Return true if value is a string.

\n", - "isBlock": false + "requiresBlock": false }, "lowercase": { "args": [ @@ -702,7 +702,7 @@ "numArgs": 1, "example": "{{lowercase 'Foo BAR baZ'}} -> foo bar baz", "description": "

Lowercase all characters in the given string.

\n", - "isBlock": false + "requiresBlock": false }, "occurrences": { "args": [ @@ -712,7 +712,7 @@ "numArgs": 2, "example": "{{occurrences 'foo bar foo bar baz' 'foo'}} -> 2", "description": "

Return the number of occurrences of substring within the given string.

\n", - "isBlock": false + "requiresBlock": false }, "pascalcase": { "args": [ @@ -721,7 +721,7 @@ "numArgs": 1, "example": "{{pascalcase 'foo bar baz'}} -> FooBarBaz", "description": "

PascalCase the characters in string.

\n", - "isBlock": false + "requiresBlock": false }, "pathcase": { "args": [ @@ -730,7 +730,7 @@ "numArgs": 1, "example": "{{pathcase 'a-b-c d_e'}} -> a/b/c/d/e", "description": "

path/case the characters in string.

\n", - "isBlock": false + "requiresBlock": false }, "plusify": { "args": [ @@ -739,7 +739,7 @@ "numArgs": 1, "example": "{{plusify 'foo bar baz'}} -> foo+bar+baz", "description": "

Replace spaces in the given string with pluses.

\n", - "isBlock": false + "requiresBlock": false }, "prepend": { "args": [ @@ -749,7 +749,7 @@ "numArgs": 2, "example": "{{prepend 'bar' 'foo-'}} -> foo-bar", "description": "

Prepends the given string with the specified prefix.

\n", - "isBlock": false + "requiresBlock": false }, "remove": { "args": [ @@ -759,7 +759,7 @@ "numArgs": 2, "example": "{{remove 'a b a b a b' 'a '}} -> b b b", "description": "

Remove all occurrences of substring from the given str.

\n", - "isBlock": false + "requiresBlock": false }, "removeFirst": { "args": [ @@ -769,7 +769,7 @@ "numArgs": 2, "example": "{{removeFirst 'a b a b a b' 'a'}} -> ' b a b a b'", "description": "

Remove the first occurrence of substring from the given str.

\n", - "isBlock": false + "requiresBlock": false }, "replace": { "args": [ @@ -780,7 +780,7 @@ "numArgs": 3, "example": "{{replace 'a b a b a b' 'a' 'z'}} -> z b z b z b", "description": "

Replace all occurrences of substring a with substring b.

\n", - "isBlock": false + "requiresBlock": false }, "replaceFirst": { "args": [ @@ -791,7 +791,7 @@ "numArgs": 3, "example": "{{replaceFirst 'a b a b a b' 'a' 'z'}} -> z b a b a b", "description": "

Replace the first occurrence of substring a with substring b.

\n", - "isBlock": false + "requiresBlock": false }, "sentence": { "args": [ @@ -800,7 +800,7 @@ "numArgs": 1, "example": "{{sentence 'hello world. goodbye world.'}} -> Hello world. Goodbye world.", "description": "

Sentence case the given string

\n", - "isBlock": false + "requiresBlock": false }, "snakecase": { "args": [ @@ -809,7 +809,7 @@ "numArgs": 1, "example": "{{snakecase 'a-b-c d_e'}} -> a_b_c_d_e", "description": "

snake_case the characters in the given string.

\n", - "isBlock": false + "requiresBlock": false }, "split": { "args": [ @@ -818,7 +818,7 @@ "numArgs": 1, "example": "{{split 'a,b,c'}} -> ['a', 'b', 'c']", "description": "

Split string by the given character.

\n", - "isBlock": false + "requiresBlock": false }, "startsWith": { "args": [ @@ -829,7 +829,7 @@ "numArgs": 3, "example": "{{#startsWith 'Goodbye' 'Hello, world!'}}Yep{{else}}Nope{{/startsWith}} -> Nope", "description": "

Tests whether a string begins with the given prefix.

\n", - "isBlock": true + "requiresBlock": true }, "titleize": { "args": [ @@ -838,7 +838,7 @@ "numArgs": 1, "example": "{{titleize 'this is title case' }} -> This Is Title Case", "description": "

Title case the given string.

\n", - "isBlock": false + "requiresBlock": false }, "trim": { "args": [ @@ -847,7 +847,7 @@ "numArgs": 1, "example": "{{trim ' ABC ' }} -> ABC", "description": "

Removes extraneous whitespace from the beginning and end of a string.

\n", - "isBlock": false + "requiresBlock": false }, "trimLeft": { "args": [ @@ -856,7 +856,7 @@ "numArgs": 1, "example": "{{trimLeft ' ABC ' }} -> 'ABC '", "description": "

Removes extraneous whitespace from the beginning of a string.

\n", - "isBlock": false + "requiresBlock": false }, "trimRight": { "args": [ @@ -865,7 +865,7 @@ "numArgs": 1, "example": "{{trimRight ' ABC ' }} -> ' ABC'", "description": "

Removes extraneous whitespace from the end of a string.

\n", - "isBlock": false + "requiresBlock": false }, "truncate": { "args": [ @@ -876,7 +876,7 @@ "numArgs": 3, "example": "{{truncate 'foo bar baz' 7 }} -> foo bar", "description": "

Truncate a string to the specified length. Also see ellipsis.

\n", - "isBlock": false + "requiresBlock": false }, "truncateWords": { "args": [ @@ -887,7 +887,7 @@ "numArgs": 3, "example": "{{truncateWords 'foo bar baz' 1 }} -> foo…", "description": "

Truncate a string to have the specified number of words. Also see truncate.

\n", - "isBlock": false + "requiresBlock": false }, "upcase": { "args": [ @@ -896,7 +896,7 @@ "numArgs": 1, "example": "{{upcase 'aBcDef'}} -> ABCDEF", "description": "

Uppercase all of the characters in the given string. Alias for uppercase.

\n", - "isBlock": false + "requiresBlock": false }, "uppercase": { "args": [ @@ -906,7 +906,7 @@ "numArgs": 2, "example": "{{uppercase 'aBcDef'}} -> ABCDEF", "description": "

Uppercase all of the characters in the given string. If used as a block helper it will uppercase the entire block. This helper does not support inverse blocks.

\n", - "isBlock": true + "requiresBlock": true } }, "comparison": { @@ -919,7 +919,7 @@ "numArgs": 3, "example": "{{#and great magnificent}}both{{else}}no{{/and}} -> no", "description": "

Helper that renders the block if both of the given values are truthy. If an inverse block is specified it will be rendered when falsy. Works as a block helper, inline helper or subexpression.

\n", - "isBlock": true + "requiresBlock": true }, "compare": { "args": [ @@ -931,7 +931,7 @@ "numArgs": 4, "example": "{{compare 10 '<' 5 }} -> false", "description": "

Render a block when a comparison of the first and third arguments returns true. The second argument is the [arithemetic operator][operators] to use. You may also optionally specify an inverse block to render when falsy.

\n", - "isBlock": true + "requiresBlock": true }, "contains": { "args": [ @@ -943,7 +943,7 @@ "numArgs": 4, "example": "{{#contains ['a', 'b', 'c'] 'd'}} This will not be rendered. {{else}} This will be rendered. {{/contains}} -> ' This will be rendered. '", "description": "

Block helper that renders the block if collection has the given value, using strict equality (===) for comparison, otherwise the inverse block is rendered (if specified). If a startIndex is specified and is negative, it is used as the offset from the end of the collection.

\n", - "isBlock": true + "requiresBlock": true }, "default": { "args": [ @@ -953,7 +953,7 @@ "numArgs": 2, "example": "{{default null null 'default'}} -> default", "description": "

Returns the first value that is not undefined, otherwise the 'default' value is returned.

\n", - "isBlock": false + "requiresBlock": false }, "eq": { "args": [ @@ -964,7 +964,7 @@ "numArgs": 3, "example": "{{#eq 3 3}}equal{{else}}not equal{{/eq}} -> equal", "description": "

Block helper that renders a block if a is equal to b. If an inverse block is specified it will be rendered when falsy. You may optionally use the compare='' hash argument for the second value.

\n", - "isBlock": true + "requiresBlock": true }, "gt": { "args": [ @@ -975,7 +975,7 @@ "numArgs": 3, "example": "{{#gt 4 3}} greater than{{else}} not greater than{{/gt}} -> ' greater than'", "description": "

Block helper that renders a block if a is greater than b. If an inverse block is specified it will be rendered when falsy. You may optionally use the compare='' hash argument for the second value.

\n", - "isBlock": true + "requiresBlock": true }, "gte": { "args": [ @@ -986,7 +986,7 @@ "numArgs": 3, "example": "{{#gte 4 3}} greater than or equal{{else}} not greater than{{/gte}} -> ' greater than or equal'", "description": "

Block helper that renders a block if a is greater than or equal to b. If an inverse block is specified it will be rendered when falsy. You may optionally use the compare='' hash argument for the second value.

\n", - "isBlock": true + "requiresBlock": true }, "has": { "args": [ @@ -997,7 +997,7 @@ "numArgs": 3, "example": "{{#has 'foobar' 'foo'}}has it{{else}}doesn't{{/has}} -> has it", "description": "

Block helper that renders a block if value has pattern. If an inverse block is specified it will be rendered when falsy.

\n", - "isBlock": true + "requiresBlock": true }, "isFalsey": { "args": [ @@ -1007,7 +1007,7 @@ "numArgs": 2, "example": "{{isFalsey '' }} -> true", "description": "

Returns true if the given value is falsey. Uses the [falsey][] library for comparisons. Please see that library for more information or to report bugs with this helper.

\n", - "isBlock": false + "requiresBlock": false }, "isTruthy": { "args": [ @@ -1017,7 +1017,7 @@ "numArgs": 2, "example": "{{isTruthy '12' }} -> true", "description": "

Returns true if the given value is truthy. Uses the [falsey][] library for comparisons. Please see that library for more information or to report bugs with this helper.

\n", - "isBlock": false + "requiresBlock": false }, "ifEven": { "args": [ @@ -1027,7 +1027,7 @@ "numArgs": 2, "example": "{{#ifEven 2}} even {{else}} odd {{/ifEven}} -> ' even '", "description": "

Return true if the given value is an even number.

\n", - "isBlock": true + "requiresBlock": true }, "ifNth": { "args": [ @@ -1038,7 +1038,7 @@ "numArgs": 3, "example": "{{#ifNth 2 10}}remainder{{else}}no remainder{{/ifNth}} -> remainder", "description": "

Conditionally renders a block if the remainder is zero when b operand is divided by a. If an inverse block is specified it will be rendered when the remainder is not zero.

\n", - "isBlock": true + "requiresBlock": true }, "ifOdd": { "args": [ @@ -1048,7 +1048,7 @@ "numArgs": 2, "example": "{{#ifOdd 3}}odd{{else}}even{{/ifOdd}} -> odd", "description": "

Block helper that renders a block if value is an odd number. If an inverse block is specified it will be rendered when falsy.

\n", - "isBlock": true + "requiresBlock": true }, "is": { "args": [ @@ -1059,7 +1059,7 @@ "numArgs": 3, "example": "{{#is 3 3}} is {{else}} is not {{/is}} -> ' is '", "description": "

Block helper that renders a block if a is equal to b. If an inverse block is specified it will be rendered when falsy. Similar to eq but does not do strict equality.

\n", - "isBlock": true + "requiresBlock": true }, "isnt": { "args": [ @@ -1070,7 +1070,7 @@ "numArgs": 3, "example": "{{#isnt 3 3}} isnt {{else}} is {{/isnt}} -> ' is '", "description": "

Block helper that renders a block if a is not equal to b. If an inverse block is specified it will be rendered when falsy. Similar to unlessEq but does not use strict equality for comparisons.

\n", - "isBlock": true + "requiresBlock": true }, "lt": { "args": [ @@ -1080,7 +1080,7 @@ "numArgs": 2, "example": "{{#lt 2 3}} less than {{else}} more than or equal {{/lt}} -> ' less than '", "description": "

Block helper that renders a block if a is less than b. If an inverse block is specified it will be rendered when falsy. You may optionally use the compare='' hash argument for the second value.

\n", - "isBlock": true + "requiresBlock": true }, "lte": { "args": [ @@ -1091,7 +1091,7 @@ "numArgs": 3, "example": "{{#lte 2 3}} less than or equal {{else}} more than {{/lte}} -> ' less than or equal '", "description": "

Block helper that renders a block if a is less than or equal to b. If an inverse block is specified it will be rendered when falsy. You may optionally use the compare='' hash argument for the second value.

\n", - "isBlock": true + "requiresBlock": true }, "neither": { "args": [ @@ -1102,7 +1102,7 @@ "numArgs": 3, "example": "{{#neither null null}}both falsey{{else}}both not falsey{{/neither}} -> both falsey", "description": "

Block helper that renders a block if neither of the given values are truthy. If an inverse block is specified it will be rendered when falsy.

\n", - "isBlock": true + "requiresBlock": true }, "not": { "args": [ @@ -1112,7 +1112,7 @@ "numArgs": 2, "example": "{{#not undefined }}falsey{{else}}not falsey{{/not}} -> falsey", "description": "

Returns true if val is falsey. Works as a block or inline helper.

\n", - "isBlock": true + "requiresBlock": true }, "or": { "args": [ @@ -1122,7 +1122,7 @@ "numArgs": 2, "example": "{{#or 1 2 undefined }} at least one truthy {{else}} all falsey {{/or}} -> ' at least one truthy '", "description": "

Block helper that renders a block if any of the given values is truthy. If an inverse block is specified it will be rendered when falsy.

\n", - "isBlock": true + "requiresBlock": true }, "unlessEq": { "args": [ @@ -1133,7 +1133,7 @@ "numArgs": 3, "example": "{{#unlessEq 2 1 }} not equal {{else}} equal {{/unlessEq}} -> ' not equal '", "description": "

Block helper that always renders the inverse block unless a is equal to b.

\n", - "isBlock": true + "requiresBlock": true }, "unlessGt": { "args": [ @@ -1144,7 +1144,7 @@ "numArgs": 3, "example": "{{#unlessGt 20 1 }} not greater than {{else}} greater than {{/unlessGt}} -> ' greater than '", "description": "

Block helper that always renders the inverse block unless a is greater than b.

\n", - "isBlock": true + "requiresBlock": true }, "unlessLt": { "args": [ @@ -1155,7 +1155,7 @@ "numArgs": 3, "example": "{{#unlessLt 20 1 }}greater than or equal{{else}}less than{{/unlessLt}} -> greater than or equal", "description": "

Block helper that always renders the inverse block unless a is less than b.

\n", - "isBlock": true + "requiresBlock": true }, "unlessGteq": { "args": [ @@ -1166,7 +1166,7 @@ "numArgs": 3, "example": "{{#unlessGteq 20 1 }} less than {{else}}greater than or equal to{{/unlessGteq}} -> greater than or equal to", "description": "

Block helper that always renders the inverse block unless a is greater than or equal to b.

\n", - "isBlock": true + "requiresBlock": true }, "unlessLteq": { "args": [ @@ -1177,7 +1177,7 @@ "numArgs": 3, "example": "{{#unlessLteq 20 1 }} greater than {{else}} less than or equal to {{/unlessLteq}} -> ' greater than '", "description": "

Block helper that always renders the inverse block unless a is less than or equal to b.

\n", - "isBlock": true + "requiresBlock": true } }, "object": { @@ -1187,7 +1187,7 @@ ], "numArgs": 1, "description": "

Extend the context with the properties of other objects. A shallow merge is performed to avoid mutating the context.

\n", - "isBlock": false + "requiresBlock": false }, "forIn": { "args": [ @@ -1196,7 +1196,7 @@ ], "numArgs": 2, "description": "

Block helper that iterates over the properties of an object, exposing each key and value on the context.

\n", - "isBlock": true + "requiresBlock": true }, "forOwn": { "args": [ @@ -1205,7 +1205,7 @@ ], "numArgs": 2, "description": "

Block helper that iterates over the own properties of an object, exposing each key and value on the context.

\n", - "isBlock": true + "requiresBlock": true }, "toPath": { "args": [ @@ -1213,7 +1213,7 @@ ], "numArgs": 1, "description": "

Take arguments and, if they are string or number, convert them to a dot-delineated object property path.

\n", - "isBlock": false + "requiresBlock": false }, "get": { "args": [ @@ -1223,7 +1223,7 @@ ], "numArgs": 3, "description": "

Use property paths (a.b.c) to get a value or nested value from the context. Works as a regular helper or block helper.

\n", - "isBlock": true + "requiresBlock": true }, "getObject": { "args": [ @@ -1232,7 +1232,7 @@ ], "numArgs": 2, "description": "

Use property paths (a.b.c) to get an object from the context. Differs from the get helper in that this helper will return the actual object, including the given property key. Also, this helper does not work as a block helper.

\n", - "isBlock": false + "requiresBlock": false }, "hasOwn": { "args": [ @@ -1241,7 +1241,7 @@ ], "numArgs": 2, "description": "

Return true if key is an own, enumerable property of the given context object.

\n", - "isBlock": false + "requiresBlock": false }, "isObject": { "args": [ @@ -1249,7 +1249,7 @@ ], "numArgs": 1, "description": "

Return true if value is an object.

\n", - "isBlock": false + "requiresBlock": false }, "JSONparse": { "args": [ @@ -1257,7 +1257,7 @@ ], "numArgs": 1, "description": "

Parses the given string using JSON.parse.

\n", - "isBlock": true + "requiresBlock": true }, "JSONstringify": { "args": [ @@ -1265,7 +1265,7 @@ ], "numArgs": 1, "description": "

Stringify an object using JSON.stringify.

\n", - "isBlock": false + "requiresBlock": false }, "merge": { "args": [ @@ -1274,7 +1274,7 @@ ], "numArgs": 2, "description": "

Deeply merge the properties of the given objects with the context object.

\n", - "isBlock": false + "requiresBlock": false }, "parseJSON": { "args": [ @@ -1282,7 +1282,7 @@ ], "numArgs": 1, "description": "

Parses the given string using JSON.parse.

\n", - "isBlock": true + "requiresBlock": true }, "pick": { "args": [ @@ -1292,7 +1292,7 @@ ], "numArgs": 3, "description": "

Pick properties from the context object.

\n", - "isBlock": true + "requiresBlock": true }, "stringify": { "args": [ @@ -1300,7 +1300,7 @@ ], "numArgs": 1, "description": "

Stringify an object using JSON.stringify.

\n", - "isBlock": false + "requiresBlock": false } }, "uuid": { @@ -1309,7 +1309,7 @@ "numArgs": 0, "example": "{{ uuid }} -> f34ebc66-93bd-4f7c-b79b-92b5569138bc", "description": "

Generates a UUID, using the V4 method (identical to the browser crypto.randomUUID function).

\n", - "isBlock": false + "requiresBlock": false } }, "date": { diff --git a/packages/string-templates/scripts/gen-collection-info.js b/packages/string-templates/scripts/gen-collection-info.js index 48bf503ade..1b540c829c 100644 --- a/packages/string-templates/scripts/gen-collection-info.js +++ b/packages/string-templates/scripts/gen-collection-info.js @@ -115,7 +115,7 @@ function getCommentInfo(file, func) { docs.example = docs.example.replace("product", "multiply") } docs.description = blocks[0].trim() - docs.isBlock = docs.tags.some(el => el.title === "block") + docs.requiresBlock = docs.tags.some(el => el.title === "block") return docs } @@ -160,7 +160,7 @@ function run() { numArgs: args.length, example: jsDocInfo.example || undefined, description: jsDocInfo.description, - isBlock: jsDocInfo.isBlock, + requiresBlock: jsDocInfo.requiresBlock, }) } outputJSON[collection] = collectionInfo diff --git a/packages/string-templates/test/manifest.spec.js b/packages/string-templates/test/manifest.spec.js index 762726e2f3..01fb088bbc 100644 --- a/packages/string-templates/test/manifest.spec.js +++ b/packages/string-templates/test/manifest.spec.js @@ -58,8 +58,8 @@ const examples = collections.reduce((acc, collection) => { } } } - const hasHbsBody = details.isBlock - return [name, { hbs, js, hasHbsBody }] + const requiresHbsBody = details.requiresBlock + return [name, { hbs, js, requiresHbsBody }] }) .filter(x => !!x) @@ -110,7 +110,9 @@ describe("manifest", () => { describe("can be parsed and run as js", () => { describe.each(Object.keys(examples))("%s", collection => { it.each( - examples[collection].filter(([_, { hasHbsBody }]) => !hasHbsBody) + examples[collection].filter( + ([_, { requiresHbsBody }]) => !requiresHbsBody + ) )("%s", async (_, { hbs, js }) => { const context = { double: i => i * 2, From f5157c41847764ad2b41ee974f2f490d6b42afa7 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 30 Jan 2024 15:54:00 +0100 Subject: [PATCH 757/808] Fix other tests --- packages/string-templates/manifest.json | 8 ++++---- packages/string-templates/scripts/gen-collection-info.js | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/string-templates/manifest.json b/packages/string-templates/manifest.json index d06a01f606..dce4af8665 100644 --- a/packages/string-templates/manifest.json +++ b/packages/string-templates/manifest.json @@ -264,7 +264,7 @@ "numArgs": 2, "example": "{{equalsLength [1, 2, 3] 3}} -> true", "description": "

Returns true if the the length of the given value is equal to the given length. Can be used as a block or inline helper.

\n", - "requiresBlock": true + "requiresBlock": false }, "last": { "args": [ @@ -293,7 +293,7 @@ "numArgs": 2, "example": "{{equalsLength [1, 2, 3] 3}} -> true", "description": "

Returns true if the the length of the given value is equal to the given length. Can be used as a block or inline helper.

\n", - "requiresBlock": true + "requiresBlock": false }, "map": { "args": [ @@ -906,7 +906,7 @@ "numArgs": 2, "example": "{{uppercase 'aBcDef'}} -> ABCDEF", "description": "

Uppercase all of the characters in the given string. If used as a block helper it will uppercase the entire block. This helper does not support inverse blocks.

\n", - "requiresBlock": true + "requiresBlock": false } }, "comparison": { @@ -931,7 +931,7 @@ "numArgs": 4, "example": "{{compare 10 '<' 5 }} -> false", "description": "

Render a block when a comparison of the first and third arguments returns true. The second argument is the [arithemetic operator][operators] to use. You may also optionally specify an inverse block to render when falsy.

\n", - "requiresBlock": true + "requiresBlock": false }, "contains": { "args": [ diff --git a/packages/string-templates/scripts/gen-collection-info.js b/packages/string-templates/scripts/gen-collection-info.js index 1b540c829c..ed57fe7fae 100644 --- a/packages/string-templates/scripts/gen-collection-info.js +++ b/packages/string-templates/scripts/gen-collection-info.js @@ -115,7 +115,8 @@ function getCommentInfo(file, func) { docs.example = docs.example.replace("product", "multiply") } docs.description = blocks[0].trim() - docs.requiresBlock = docs.tags.some(el => el.title === "block") + docs.acceptsBlock = docs.tags.some(el => el.title === "block") + docs.acceptsInline = docs.tags.some(el => el.title === "inline") return docs } @@ -160,7 +161,7 @@ function run() { numArgs: args.length, example: jsDocInfo.example || undefined, description: jsDocInfo.description, - requiresBlock: jsDocInfo.requiresBlock, + requiresBlock: jsDocInfo.acceptsBlock && !jsDocInfo.acceptsInline, }) } outputJSON[collection] = collectionInfo From 9c47671032f7d0fa16ad856853886e4a7fb1a24b Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Tue, 30 Jan 2024 15:39:12 +0000 Subject: [PATCH 758/808] fix issue with automation setting sync --- packages/builder/src/builderStore/store/frontend.js | 1 + .../automation/SetupPanel/AutomationBlockSetup.svelte | 1 - .../app/[application]/settings/automations/index.svelte | 5 +---- packages/server/src/api/controllers/application.ts | 3 +++ 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index ff7c0d74b8..c649a58504 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -158,6 +158,7 @@ export const getFrontendStore = () => { ...INITIAL_FRONTEND_STATE.features, ...application.features, }, + automations: application.automations || {}, icon: application.icon || {}, initialised: true, })) diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 5950747237..a9e7ab0d39 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -292,7 +292,6 @@ loopBlockCount++ continue } - console.log(schema) Object.entries(schema).forEach(([name, value]) => addBinding(name, value, icon, idx, isLoopBlock, bindingName) ) diff --git a/packages/builder/src/pages/builder/app/[application]/settings/automations/index.svelte b/packages/builder/src/pages/builder/app/[application]/settings/automations/index.svelte index fde392e31d..9c45eb7eb1 100644 --- a/packages/builder/src/pages/builder/app/[application]/settings/automations/index.svelte +++ b/packages/builder/src/pages/builder/app/[application]/settings/automations/index.svelte @@ -36,15 +36,12 @@ let status = null let timeRange = null let loaded = false - - $: app = $apps.find(app => app.devId === $store.appId?.includes(app.appId)) + $: app = $apps.find(app => $store.appId?.includes(app.appId)) $: licensePlan = $auth.user?.license?.plan $: page = $pageInfo.page $: fetchLogs(automationId, status, page, timeRange) $: isCloud = $admin.cloud - $: chainAutomations = app?.automations?.chainAutomations ?? !isCloud - const timeOptions = [ { value: "90-d", label: "Past 90 days" }, { value: "30-d", label: "Past 30 days" }, diff --git a/packages/server/src/api/controllers/application.ts b/packages/server/src/api/controllers/application.ts index 70298c7172..33582cf656 100644 --- a/packages/server/src/api/controllers/application.ts +++ b/packages/server/src/api/controllers/application.ts @@ -445,6 +445,9 @@ export async function update(ctx: UserCtx) { name: app.name, url: app.url, icon: app.icon, + automations: { + chainAutomations: app.automations?.chainAutomations, + }, }) } From 43e536e7a6f0049528ac33b5850a151544b07412 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 30 Jan 2024 16:52:25 +0100 Subject: [PATCH 759/808] Test only js helpers --- packages/string-templates/src/helpers/list.js | 5 +++++ packages/string-templates/test/manifest.spec.js | 11 +++++++++-- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/string-templates/src/helpers/list.js b/packages/string-templates/src/helpers/list.js index 739d8b50be..c10a732371 100644 --- a/packages/string-templates/src/helpers/list.js +++ b/packages/string-templates/src/helpers/list.js @@ -3,6 +3,8 @@ const helperList = require("@budibase/handlebars-helpers") let helpers = undefined +const helpersToRemove = ["sortBy"] + module.exports.getHelperList = () => { if (helpers) { return helpers @@ -23,6 +25,9 @@ module.exports.getHelperList = () => { helpers[key] = externalHandlebars.addedHelpers[key] } + for (const toRemove of helpersToRemove) { + delete helpers[toRemove] + } Object.freeze(helpers) return helpers } diff --git a/packages/string-templates/test/manifest.spec.js b/packages/string-templates/test/manifest.spec.js index 01fb088bbc..7c794d8c23 100644 --- a/packages/string-templates/test/manifest.spec.js +++ b/packages/string-templates/test/manifest.spec.js @@ -24,6 +24,7 @@ const { } = require("../src/index.cjs") const tk = require("timekeeper") +const { getHelperList } = require("../src/helpers") tk.freeze("2021-01-21T12:00:00") @@ -108,9 +109,15 @@ describe("manifest", () => { }) describe("can be parsed and run as js", () => { - describe.each(Object.keys(examples))("%s", collection => { + const jsHelpers = getHelperList() + 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( - examples[collection].filter( + jsExamples[collection].filter( ([_, { requiresHbsBody }]) => !requiresHbsBody ) )("%s", async (_, { hbs, js }) => { From 82b4af1436d6992a7fd0ce4eaf079409efd31415 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 30 Jan 2024 17:09:41 +0100 Subject: [PATCH 760/808] Update handlebars-helpers package --- packages/string-templates/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index dfbff1a24b..36f95060b4 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -25,7 +25,7 @@ "manifest": "node ./scripts/gen-collection-info.js" }, "dependencies": { - "@budibase/handlebars-helpers": "^0.13.0", + "@budibase/handlebars-helpers": "^0.13.1", "dayjs": "^1.10.8", "handlebars": "^4.7.6", "lodash.clonedeep": "^4.5.0", diff --git a/yarn.lock b/yarn.lock index d2fb6f5236..80d1420edf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2031,10 +2031,10 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@budibase/handlebars-helpers@^0.13.0": - version "0.13.0" - resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.13.0.tgz#224333d14e3900b7dacf48286af1e624a9fd62ea" - integrity sha512-g8+sFrMNxsIDnK+MmdUICTVGr6ReUFtnPp9hJX0VZwz1pN3Ynolpk/Qbu6rEWAvoU1sEqY1mXr9uo/+kEfeGbQ== +"@budibase/handlebars-helpers@^0.13.1": + version "0.13.1" + resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.13.1.tgz#d02e73c0df8305cd675e70dc37f8427eb0842080" + integrity sha512-v4RbXhr3igvK3i2pj5cNltu/4NMxdPIzcUt/o0RoInhesNH1VSLRdweSFr6/Y34fsCR5jHZ6vltdcz2RgrTKgw== dependencies: get-object "^0.2.0" get-value "^3.0.1" From d1712bda5275e57b2496999d135d06000367a232 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Tue, 30 Jan 2024 17:37:09 +0100 Subject: [PATCH 761/808] Remove helpers not available in js --- .../src/components/common/bindings/BindingPicker.svelte | 7 ++++--- packages/builder/src/constants/completions.js | 1 + 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/builder/src/components/common/bindings/BindingPicker.svelte b/packages/builder/src/components/common/bindings/BindingPicker.svelte index 93d9d62021..1f35e3fbab 100644 --- a/packages/builder/src/components/common/bindings/BindingPicker.svelte +++ b/packages/builder/src/components/common/bindings/BindingPicker.svelte @@ -47,9 +47,10 @@ }) $: filteredHelpers = helpers?.filter(helper => { return ( - !search || - helper.label.match(searchRgx) || - helper.description.match(searchRgx) + (!search || + helper.label.match(searchRgx) || + helper.description.match(searchRgx)) && + (mode.name !== "javascript" || !helper.requiresBlock) ) }) diff --git a/packages/builder/src/constants/completions.js b/packages/builder/src/constants/completions.js index 32de934324..ab0e5fd52d 100644 --- a/packages/builder/src/constants/completions.js +++ b/packages/builder/src/constants/completions.js @@ -11,6 +11,7 @@ export function handlebarsCompletions() { label: helperName, displayText: helperName, description: helperConfig.description, + requiresBlock: helperConfig.requiresBlock, })) ) } From 70d49bbd6b25eac519b670b236b805279eab1b58 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 09:59:59 +0100 Subject: [PATCH 762/808] Renames --- .../builder/src/components/common/bindings/BindingPicker.svelte | 2 +- packages/builder/src/constants/completions.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/builder/src/components/common/bindings/BindingPicker.svelte b/packages/builder/src/components/common/bindings/BindingPicker.svelte index 1f35e3fbab..d85a0a2bbb 100644 --- a/packages/builder/src/components/common/bindings/BindingPicker.svelte +++ b/packages/builder/src/components/common/bindings/BindingPicker.svelte @@ -50,7 +50,7 @@ (!search || helper.label.match(searchRgx) || helper.description.match(searchRgx)) && - (mode.name !== "javascript" || !helper.requiresBlock) + (mode.name !== "javascript" || helper.allowsJs) ) }) diff --git a/packages/builder/src/constants/completions.js b/packages/builder/src/constants/completions.js index ab0e5fd52d..4af0bad38d 100644 --- a/packages/builder/src/constants/completions.js +++ b/packages/builder/src/constants/completions.js @@ -11,7 +11,7 @@ export function handlebarsCompletions() { label: helperName, displayText: helperName, description: helperConfig.description, - requiresBlock: helperConfig.requiresBlock, + allowsJs: !helperConfig.requiresBlock, })) ) } From e5d5dea5e626cb593281fb0bb47ce6a17e4dc123 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 10:00:41 +0100 Subject: [PATCH 763/808] Renames --- packages/string-templates/src/conversion/index.js | 4 ++-- packages/string-templates/src/helpers/index.js | 4 ++-- packages/string-templates/src/helpers/javascript.js | 4 ++-- packages/string-templates/src/helpers/list.js | 2 +- packages/string-templates/test/manifest.spec.js | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/string-templates/src/conversion/index.js b/packages/string-templates/src/conversion/index.js index 30e2510b55..10aaef0d2f 100644 --- a/packages/string-templates/src/conversion/index.js +++ b/packages/string-templates/src/conversion/index.js @@ -1,4 +1,4 @@ -const { getHelperList } = require("../helpers") +const { getJsHelperList } = require("../helpers") function getLayers(fullBlock) { let layers = [] @@ -109,7 +109,7 @@ module.exports.convertHBSBlock = (block, blockNumber) => { const layers = getLayers(block) let value = null - const list = getHelperList() + const list = getJsHelperList() for (let layer of layers) { const parts = splitBySpace(layer) if (value || parts.length > 1 || list[parts[0]]) { diff --git a/packages/string-templates/src/helpers/index.js b/packages/string-templates/src/helpers/index.js index bed3d0c3e3..5e6dcbd2b9 100644 --- a/packages/string-templates/src/helpers/index.js +++ b/packages/string-templates/src/helpers/index.js @@ -7,7 +7,7 @@ const { HelperFunctionBuiltin, LITERAL_MARKER, } = require("./constants") -const { getHelperList } = require("./list") +const { getJsHelperList } = require("./list") const HTML_SWAPS = { "<": "<", @@ -97,4 +97,4 @@ module.exports.unregisterAll = handlebars => { externalHandlebars.unregisterAll(handlebars) } -module.exports.getHelperList = getHelperList +module.exports.getJsHelperList = getJsHelperList diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js index 0b63400deb..eff125dd72 100644 --- a/packages/string-templates/src/helpers/javascript.js +++ b/packages/string-templates/src/helpers/javascript.js @@ -1,7 +1,7 @@ const { atob } = require("../utilities") const cloneDeep = require("lodash.clonedeep") const { LITERAL_MARKER } = require("../helpers/constants") -const { getHelperList } = require("./list") +const { getJsHelperList } = require("./list") // The method of executing JS scripts depends on the bundle being built. // This setter is used in the entrypoint (either index.cjs or index.mjs). @@ -49,7 +49,7 @@ module.exports.processJS = (handlebars, context) => { // app context. const sandboxContext = { $: path => getContextValue(path, cloneDeep(context)), - helpers: getHelperList(), + helpers: getJsHelperList(), } // Create a sandbox with our context and run the JS diff --git a/packages/string-templates/src/helpers/list.js b/packages/string-templates/src/helpers/list.js index c10a732371..edbdcbdf05 100644 --- a/packages/string-templates/src/helpers/list.js +++ b/packages/string-templates/src/helpers/list.js @@ -5,7 +5,7 @@ let helpers = undefined const helpersToRemove = ["sortBy"] -module.exports.getHelperList = () => { +module.exports.getJsHelperList = () => { if (helpers) { return helpers } diff --git a/packages/string-templates/test/manifest.spec.js b/packages/string-templates/test/manifest.spec.js index 7c794d8c23..3e39d775f5 100644 --- a/packages/string-templates/test/manifest.spec.js +++ b/packages/string-templates/test/manifest.spec.js @@ -24,7 +24,7 @@ const { } = require("../src/index.cjs") const tk = require("timekeeper") -const { getHelperList } = require("../src/helpers") +const { getJsHelperList } = require("../src/helpers") tk.freeze("2021-01-21T12:00:00") @@ -109,7 +109,7 @@ describe("manifest", () => { }) describe("can be parsed and run as js", () => { - const jsHelpers = getHelperList() + const jsHelpers = getJsHelperList() const jsExamples = Object.keys(examples).reduce((acc, v) => { acc[v] = examples[v].filter(([key]) => jsHelpers[key]) return acc From cd4fccbd6e06c012908537d0dacb19a5181bcfd2 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 10:34:49 +0100 Subject: [PATCH 764/808] Allow excluding js helpers --- packages/builder/src/constants/completions.js | 6 ++++-- packages/string-templates/src/helpers/list.js | 5 +++-- packages/string-templates/src/index.cjs | 1 + packages/string-templates/src/index.js | 2 ++ packages/string-templates/src/index.mjs | 1 + 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/packages/builder/src/constants/completions.js b/packages/builder/src/constants/completions.js index 4af0bad38d..e539a8084a 100644 --- a/packages/builder/src/constants/completions.js +++ b/packages/builder/src/constants/completions.js @@ -1,4 +1,4 @@ -import { getManifest } from "@budibase/string-templates" +import { getManifest, helpersToRemoveForJs } from "@budibase/string-templates" export function handlebarsCompletions() { const manifest = getManifest() @@ -11,7 +11,9 @@ export function handlebarsCompletions() { label: helperName, displayText: helperName, description: helperConfig.description, - allowsJs: !helperConfig.requiresBlock, + allowsJs: + !helperConfig.requiresBlock && + !helpersToRemoveForJs.includes(helperName), })) ) } diff --git a/packages/string-templates/src/helpers/list.js b/packages/string-templates/src/helpers/list.js index edbdcbdf05..e956958865 100644 --- a/packages/string-templates/src/helpers/list.js +++ b/packages/string-templates/src/helpers/list.js @@ -3,7 +3,8 @@ const helperList = require("@budibase/handlebars-helpers") let helpers = undefined -const helpersToRemove = ["sortBy"] +const helpersToRemoveForJs = ["sortBy"] +module.exports.helpersToRemoveForJs = helpersToRemoveForJs module.exports.getJsHelperList = () => { if (helpers) { @@ -25,7 +26,7 @@ module.exports.getJsHelperList = () => { helpers[key] = externalHandlebars.addedHelpers[key] } - for (const toRemove of helpersToRemove) { + for (const toRemove of helpersToRemoveForJs) { delete helpers[toRemove] } Object.freeze(helpers) diff --git a/packages/string-templates/src/index.cjs b/packages/string-templates/src/index.cjs index aedb7fc052..7d3aa02be8 100644 --- a/packages/string-templates/src/index.cjs +++ b/packages/string-templates/src/index.cjs @@ -20,6 +20,7 @@ module.exports.findHBSBlocks = templates.findHBSBlocks module.exports.convertToJS = templates.convertToJS module.exports.setJSRunner = templates.setJSRunner module.exports.FIND_ANY_HBS_REGEX = templates.FIND_ANY_HBS_REGEX +module.exports.helpersToRemoveForJs = templates.helpersToRemoveForJs if (!process.env.NO_JS) { const { VM } = require("vm2") diff --git a/packages/string-templates/src/index.js b/packages/string-templates/src/index.js index 63da7fde4d..eb9a8ac81d 100644 --- a/packages/string-templates/src/index.js +++ b/packages/string-templates/src/index.js @@ -10,6 +10,7 @@ const { } = require("./utilities") const { convertHBSBlock } = require("./conversion") const javascript = require("./helpers/javascript") +const { helpersToRemoveForJs } = require("./helpers/list") const hbsInstance = handlebars.create() registerAll(hbsInstance) @@ -394,3 +395,4 @@ module.exports.convertToJS = hbs => { } module.exports.FIND_ANY_HBS_REGEX = FIND_ANY_HBS_REGEX +module.exports.helpersToRemoveForJs = helpersToRemoveForJs diff --git a/packages/string-templates/src/index.mjs b/packages/string-templates/src/index.mjs index 43cda8183f..ad93a7bed4 100644 --- a/packages/string-templates/src/index.mjs +++ b/packages/string-templates/src/index.mjs @@ -21,6 +21,7 @@ export const findHBSBlocks = templates.findHBSBlocks export const convertToJS = templates.convertToJS export const setJSRunner = templates.setJSRunner export const FIND_ANY_HBS_REGEX = templates.FIND_ANY_HBS_REGEX +export const helpersToRemoveForJs = templates.helpersToRemoveForJs if (process && !process.env.NO_JS) { /** From 60dc6822cff357a1a67480ba52392bc9d9b58479 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 12:41:34 +0100 Subject: [PATCH 765/808] Run only once --- packages/server/src/jsRunner.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/src/jsRunner.ts b/packages/server/src/jsRunner.ts index 14ad5bedaa..237b97b356 100644 --- a/packages/server/src/jsRunner.ts +++ b/packages/server/src/jsRunner.ts @@ -105,8 +105,10 @@ export function init() { } } + // We need to warp up the actual run in a `cb` function to be able to extract its value from isolated-vm + js = js.replace(/run\(\);$/, "cb(run());") const script = jsIsolate.compileModuleSync( - `import helpers from "compiled_module";${js};cb(run());`, + `import helpers from "compiled_module";${js}`, {} ) From f3e0dfd46686a12a031e3484d11d7daa99ad3de6 Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Wed, 31 Jan 2024 11:55:29 +0000 Subject: [PATCH 766/808] Update to @budibase/nano 10.1.5 --- packages/backend-core/package.json | 2 +- packages/types/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index d6325e1de9..85644488f5 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -21,7 +21,7 @@ "test:watch": "jest --watchAll" }, "dependencies": { - "@budibase/nano": "10.1.4", + "@budibase/nano": "10.1.5", "@budibase/pouchdb-replication-stream": "1.2.10", "@budibase/shared-core": "0.0.0", "@budibase/types": "0.0.0", diff --git a/packages/types/package.json b/packages/types/package.json index 5111339c7b..ce4fce95fb 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -15,7 +15,7 @@ }, "jest": {}, "devDependencies": { - "@budibase/nano": "10.1.4", + "@budibase/nano": "10.1.5", "@types/koa": "2.13.4", "@types/pouchdb": "6.4.0", "@types/redlock": "4.0.3", diff --git a/yarn.lock b/yarn.lock index 80d1420edf..2c12097fb5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2050,10 +2050,10 @@ to-gfm-code-block "^0.1.1" uuid "^9.0.1" -"@budibase/nano@10.1.4": - version "10.1.4" - resolved "https://registry.yarnpkg.com/@budibase/nano/-/nano-10.1.4.tgz#5c2670d0b4c12d736ddd6581c57d47c0aa45efad" - integrity sha512-J+IVaAljGideDvJss/AUxXA1599HEIUJo5c0LLlmc1KMA3GZWZjyX+w2fxAw3qF7hqFvX+qAStQgdcD3+/GPMA== +"@budibase/nano@10.1.5": + version "10.1.5" + resolved "https://registry.yarnpkg.com/@budibase/nano/-/nano-10.1.5.tgz#eeaded7bfc707ecabf8fde604425b865a90c06ec" + integrity sha512-q1eKIsYKo+iK17zsJYd3VBl+5ufQMPpHYLec0wVsid8wnJVrTQk7RNpBlBUn/EDgXM7t8XNNHlERqHu+CxJu8Q== dependencies: "@types/tough-cookie" "^4.0.2" axios "^1.1.3" From a23f76b8c813fd4a6043c816bcc79a69d311fee8 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 31 Jan 2024 08:57:54 -0300 Subject: [PATCH 767/808] update expiry time for attachments/images --- packages/backend-core/src/objectStore/cloudfront.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/backend-core/src/objectStore/cloudfront.ts b/packages/backend-core/src/objectStore/cloudfront.ts index 866fe9e880..3bca97d11e 100644 --- a/packages/backend-core/src/objectStore/cloudfront.ts +++ b/packages/backend-core/src/objectStore/cloudfront.ts @@ -23,7 +23,7 @@ const getCloudfrontSignParams = () => { return { keypairId: env.CLOUDFRONT_PUBLIC_KEY_ID!, privateKeyString: getPrivateKey(), - expireTime: new Date().getTime() + 1000 * 60 * 60, // 1 hour + expireTime: new Date().getTime() + 1000 * 60 * 60 * 24, // 1 day } } From f2aeb56671d148cc42f4e5ba9bfe489b3ecc3a1b Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 13:30:41 +0100 Subject: [PATCH 768/808] Lint --- packages/string-templates/src/index.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/string-templates/src/index.js b/packages/string-templates/src/index.js index ef2c2dc303..3636c0a456 100644 --- a/packages/string-templates/src/index.js +++ b/packages/string-templates/src/index.js @@ -400,6 +400,4 @@ 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 - From c5fd92cd94eca5a237e887137a987b8d2fe33e82 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Wed, 31 Jan 2024 13:19:02 +0000 Subject: [PATCH 769/808] Bump version to 2.16.1 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index f91c51d4bb..328e7f720a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.16.0", + "version": "2.16.1", "npmClient": "yarn", "packages": [ "packages/*", From 63cc5447d572017005e4370094b5c3d7d7fecca9 Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 31 Jan 2024 10:47:27 -0300 Subject: [PATCH 770/808] adding lint rule for console.log --- .eslintrc.json | 6 ++++++ packages/builder/src/builderStore/dataBinding.js | 1 - packages/builder/src/builderStore/websocket.js | 2 +- packages/builder/src/components/common/CodeEditor/index.js | 2 +- .../src/components/portal/onboarding/TourPopover.svelte | 2 +- .../builder/src/components/portal/onboarding/tourHandler.js | 4 ++-- packages/builder/src/components/portal/onboarding/tours.js | 2 +- packages/builder/src/helpers/urlStateSync.js | 2 +- .../app/[application]/_components/BuilderSidePanel.svelte | 2 +- .../[componentId]/_components/Screen/GeneralPanel.svelte | 2 +- .../design/_components/NewScreen/CreateScreenModal.svelte | 2 +- packages/builder/src/pages/builder/auth/login.svelte | 2 +- 12 files changed, 17 insertions(+), 12 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 79f7e56712..4f08698fec 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -45,6 +45,12 @@ "no-prototype-builtins": "off", "local-rules/no-budibase-imports": "error" } + }, + { + "files": ["packages/builder/**/*"], + "rules": { + "no-console": ["error", { "allow": ["warn", "error", "debug"] } ] + } } ], "rules": { diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index cc3851c318..86aecd466f 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -364,7 +364,6 @@ const getContextBindings = (asset, componentId) => { * Generates a set of bindings for a given component context */ const generateComponentContextBindings = (asset, componentContext) => { - console.log("Hello ") const { component, definition, contexts } = componentContext if (!component || !definition || !contexts?.length) { return [] diff --git a/packages/builder/src/builderStore/websocket.js b/packages/builder/src/builderStore/websocket.js index ca3f49e9a3..4482e3ae1e 100644 --- a/packages/builder/src/builderStore/websocket.js +++ b/packages/builder/src/builderStore/websocket.js @@ -21,7 +21,7 @@ export const createBuilderWebsocket = appId => { }) }) socket.on("connect_error", err => { - console.log("Failed to connect to builder websocket:", err.message) + console.error("Failed to connect to builder websocket:", err.message) }) socket.on("disconnect", () => { userStore.actions.reset() diff --git a/packages/builder/src/components/common/CodeEditor/index.js b/packages/builder/src/components/common/CodeEditor/index.js index 7987deff52..da0e727e4e 100644 --- a/packages/builder/src/components/common/CodeEditor/index.js +++ b/packages/builder/src/components/common/CodeEditor/index.js @@ -312,7 +312,7 @@ export const insertBinding = (view, from, to, text, mode) => { } else if (mode.name == "handlebars") { parsedInsert = hbInsert(view.state.doc?.toString(), from, to, text) } else { - console.log("Unsupported") + console.warn("Unsupported") return } diff --git a/packages/builder/src/components/portal/onboarding/TourPopover.svelte b/packages/builder/src/components/portal/onboarding/TourPopover.svelte index d959a6ef95..cc56715284 100644 --- a/packages/builder/src/components/portal/onboarding/TourPopover.svelte +++ b/packages/builder/src/components/portal/onboarding/TourPopover.svelte @@ -67,7 +67,7 @@ })) navigateStep(target) } else { - console.log("Could not retrieve step") + console.warn("Could not retrieve step") } } else { if (typeof tourStep.onComplete === "function") { diff --git a/packages/builder/src/components/portal/onboarding/tourHandler.js b/packages/builder/src/components/portal/onboarding/tourHandler.js index d4a564f23a..5b44bdc96f 100644 --- a/packages/builder/src/components/portal/onboarding/tourHandler.js +++ b/packages/builder/src/components/portal/onboarding/tourHandler.js @@ -3,11 +3,11 @@ import { get } from "svelte/store" const registerNode = async (node, tourStepKey) => { if (!node) { - console.log("Tour Handler - an anchor node is required") + console.warn("Tour Handler - an anchor node is required") } if (!get(store).tourKey) { - console.log("Tour Handler - No active tour ", tourStepKey, node) + console.error("Tour Handler - No active tour ", tourStepKey, node) return } diff --git a/packages/builder/src/components/portal/onboarding/tours.js b/packages/builder/src/components/portal/onboarding/tours.js index e82026da94..55fd4c4a9b 100644 --- a/packages/builder/src/components/portal/onboarding/tours.js +++ b/packages/builder/src/components/portal/onboarding/tours.js @@ -45,7 +45,7 @@ const endUserOnboarding = async ({ skipped = false } = {}) => { onboarding: false, })) } catch (e) { - console.log("Onboarding failed", e) + console.error("Onboarding failed", e) return false } return true diff --git a/packages/builder/src/helpers/urlStateSync.js b/packages/builder/src/helpers/urlStateSync.js index 2408dde2f1..9337393f06 100644 --- a/packages/builder/src/helpers/urlStateSync.js +++ b/packages/builder/src/helpers/urlStateSync.js @@ -52,7 +52,7 @@ export const syncURLToState = options => { let cachedPage = get(routify.page) let previousParamsHash = null let debug = false - const log = (...params) => debug && console.log(`[${urlParam}]`, ...params) + const log = (...params) => debug && console.debug(`[${urlParam}]`, ...params) // Navigate to a certain URL const gotoUrl = (url, params) => { diff --git a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte index 33116094eb..cd00b36186 100644 --- a/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_components/BuilderSidePanel.svelte @@ -107,7 +107,7 @@ return } if (!prodAppId) { - console.log("Application id required") + console.error("Application id required") return } await usersFetch.update({ diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte index fb9ee2c8a5..b2bcd3daa4 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/_components/Screen/GeneralPanel.svelte @@ -66,7 +66,7 @@ try { await store.actions.screens.updateSetting(get(selectedScreen), key, value) } catch (error) { - console.log(error) + console.error(error) notifications.error("Error saving screen settings") } } diff --git a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte index a9d64afd19..5e3a7a0f37 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/_components/NewScreen/CreateScreenModal.svelte @@ -71,7 +71,7 @@ $goto(`./${screenId}`) store.actions.screens.select(screenId) } catch (error) { - console.log(error) + console.error(error) notifications.error("Error creating screens") } } diff --git a/packages/builder/src/pages/builder/auth/login.svelte b/packages/builder/src/pages/builder/auth/login.svelte index 0ba7e6448b..7bb2a3ad49 100644 --- a/packages/builder/src/pages/builder/auth/login.svelte +++ b/packages/builder/src/pages/builder/auth/login.svelte @@ -31,7 +31,7 @@ async function login() { form.validate() if (Object.keys(errors).length > 0) { - console.log("errors", errors) + console.error("errors", errors) return } try { From 475070a495778bcc87738225fbddf87e26db734f Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 31 Jan 2024 10:53:53 -0300 Subject: [PATCH 771/808] client and frontend core --- .eslintrc.json | 6 +++++- .../src/components/app/embedded-map/EmbeddedMap.svelte | 2 +- packages/client/src/components/app/forms/CodeScanner.svelte | 2 +- packages/client/src/stores/org.js | 2 +- packages/frontend-core/src/components/grid/lib/websocket.js | 2 +- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 4f08698fec..917443014b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -47,7 +47,11 @@ } }, { - "files": ["packages/builder/**/*"], + "files": [ + "packages/builder/**/*", + "packages/client/**/*", + "packages/frontend-core/**/*" + ], "rules": { "no-console": ["error", { "allow": ["warn", "error", "debug"] } ] } diff --git a/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte index 3bb7d5606d..979cb0e085 100644 --- a/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte +++ b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte @@ -307,7 +307,7 @@ // Reset view resetView() } catch (e) { - console.log("There was a problem with the map", e) + console.error("There was a problem with the map", e) } } diff --git a/packages/client/src/components/app/forms/CodeScanner.svelte b/packages/client/src/components/app/forms/CodeScanner.svelte index 2a546eb64c..008c0f5727 100644 --- a/packages/client/src/components/app/forms/CodeScanner.svelte +++ b/packages/client/src/components/app/forms/CodeScanner.svelte @@ -61,7 +61,7 @@ resolve({ initialised: true }) }) .catch(err => { - console.log("There was a problem scanning the image", err) + console.error("There was a problem scanning the image", err) resolve({ initialised: false }) }) }) diff --git a/packages/client/src/stores/org.js b/packages/client/src/stores/org.js index af0fc955f9..d8a17f7c32 100644 --- a/packages/client/src/stores/org.js +++ b/packages/client/src/stores/org.js @@ -14,7 +14,7 @@ const createOrgStore = () => { const settingsConfigDoc = await API.getTenantConfig(tenantId) set({ logoUrl: settingsConfigDoc.config.logoUrl }) } catch (e) { - console.log("Could not init org ", e) + console.error("Could not init org ", e) } } diff --git a/packages/frontend-core/src/components/grid/lib/websocket.js b/packages/frontend-core/src/components/grid/lib/websocket.js index af34acd530..b0fd236989 100644 --- a/packages/frontend-core/src/components/grid/lib/websocket.js +++ b/packages/frontend-core/src/components/grid/lib/websocket.js @@ -29,7 +29,7 @@ export const createGridWebsocket = context => { connectToDatasource(get(datasource)) }) socket.on("connect_error", err => { - console.log("Failed to connect to grid websocket:", err.message) + console.error("Failed to connect to grid websocket:", err.message) }) // User events From 3c6246e03fed7f71f43f30db2eaa961c3636f69d Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Wed, 31 Jan 2024 15:16:08 +0000 Subject: [PATCH 772/808] Bump version to 2.16.2 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 328e7f720a..38dff33c7e 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.16.1", + "version": "2.16.2", "npmClient": "yarn", "packages": [ "packages/*", From 805b24975b6b677829c01e9b8c0e40d3964e7deb Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 16:55:59 +0100 Subject: [PATCH 773/808] Allow configuring memory limits --- packages/server/src/environment.ts | 2 ++ packages/server/src/jsRunner.ts | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/server/src/environment.ts b/packages/server/src/environment.ts index e300e86dc1..0e94b65df8 100644 --- a/packages/server/src/environment.ts +++ b/packages/server/src/environment.ts @@ -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 diff --git a/packages/server/src/jsRunner.ts b/packages/server/src/jsRunner.ts index 237b97b356..5b7bbf34ae 100644 --- a/packages/server/src/jsRunner.ts +++ b/packages/server/src/jsRunner.ts @@ -27,7 +27,9 @@ export function init() { const isolateRefs = bbCtx.isolateRefs if (!isolateRefs) { - const jsIsolate = new ivm.Isolate({ memoryLimit: 64 }) + const jsIsolate = new ivm.Isolate({ + memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, + }) const jsContext = jsIsolate.createContextSync() const injectedRequire = `const require = function(val){ From 507aa8af935d12544f7bd8aa0d130f2e8ebb7825 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 17:05:20 +0100 Subject: [PATCH 774/808] Simplify and remove magic strings --- packages/server/src/jsRunner.ts | 4 +--- packages/string-templates/src/helpers/javascript.js | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/server/src/jsRunner.ts b/packages/server/src/jsRunner.ts index 5b7bbf34ae..5e8aa2d1e1 100644 --- a/packages/server/src/jsRunner.ts +++ b/packages/server/src/jsRunner.ts @@ -107,10 +107,8 @@ export function init() { } } - // We need to warp up the actual run in a `cb` function to be able to extract its value from isolated-vm - js = js.replace(/run\(\);$/, "cb(run());") const script = jsIsolate.compileModuleSync( - `import helpers from "compiled_module";${js}`, + `import helpers from "compiled_module";const result=${js};cb(result)`, {} ) diff --git a/packages/string-templates/src/helpers/javascript.js b/packages/string-templates/src/helpers/javascript.js index eff125dd72..61a9ed21f1 100644 --- a/packages/string-templates/src/helpers/javascript.js +++ b/packages/string-templates/src/helpers/javascript.js @@ -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 From a5ccb9f0e88410f6f93075a350c2178cb7ae9f40 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 17:47:07 +0100 Subject: [PATCH 775/808] Dry --- packages/string-templates/src/helpers/list.js | 33 ++++++++++----- .../string-templates/src/index-helpers.js | 41 +------------------ 2 files changed, 25 insertions(+), 49 deletions(-) diff --git a/packages/string-templates/src/helpers/list.js b/packages/string-templates/src/helpers/list.js index e956958865..883ab5678e 100644 --- a/packages/string-templates/src/helpers/list.js +++ b/packages/string-templates/src/helpers/list.js @@ -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) { diff --git a/packages/string-templates/src/index-helpers.js b/packages/string-templates/src/index-helpers.js index 079e38078d..3c16a352fc 100644 --- a/packages/string-templates/src/index-helpers.js +++ b/packages/string-templates/src/index-helpers.js @@ -1,40 +1,3 @@ -const { date, duration } = require("./helpers/date") +const { getJsHelperList } = require("./helpers/list") -// 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 addedHelpers = { - date: date, - duration: duration, -} - -let helpers = undefined - -const getHelperList = () => { - if (helpers) { - return helpers - } - - helpers = {} - for (let collection of Object.values(externalCollections)) { - for (let [key, func] of Object.entries(collection)) { - helpers[key] = func - } - } - for (let key of Object.keys(addedHelpers)) { - helpers[key] = addedHelpers[key] - } - return helpers -} - -module.exports = getHelperList() +module.exports = getJsHelperList() From ed7b89f2f9dfddccfd7c6c2524e95b72f5cf7998 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 13:53:20 +0100 Subject: [PATCH 776/808] Move jsRunner to folder --- packages/server/src/{jsRunner.ts => jsRunner/index.ts} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename packages/server/src/{jsRunner.ts => jsRunner/index.ts} (99%) diff --git a/packages/server/src/jsRunner.ts b/packages/server/src/jsRunner/index.ts similarity index 99% rename from packages/server/src/jsRunner.ts rename to packages/server/src/jsRunner/index.ts index 5e8aa2d1e1..477a8c14da 100644 --- a/packages/server/src/jsRunner.ts +++ b/packages/server/src/jsRunner/index.ts @@ -1,5 +1,5 @@ import ivm from "isolated-vm" -import env from "./environment" +import env from "../environment" import { setJSRunner, JsErrorTimeout } from "@budibase/string-templates" import { context } from "@budibase/backend-core" import tracer from "dd-trace" From 786acaa12127dcf21c312dab2d80848dde06e0b8 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 13:59:16 +0100 Subject: [PATCH 777/808] Add basic test --- .../src/jsRunner/tests/jsRunner.spec.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 packages/server/src/jsRunner/tests/jsRunner.spec.ts diff --git a/packages/server/src/jsRunner/tests/jsRunner.spec.ts b/packages/server/src/jsRunner/tests/jsRunner.spec.ts new file mode 100644 index 0000000000..730a92e99b --- /dev/null +++ b/packages/server/src/jsRunner/tests/jsRunner.spec.ts @@ -0,0 +1,21 @@ +import { processStringSync, encodeJSBinding } from "@budibase/string-templates" +import TestConfiguration from "../../tests/utilities/TestConfiguration" + +describe("jsRunner", () => { + const config = new TestConfiguration() + + beforeAll(async () => { + 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) + }) +}) From de4d7d95c39ddc62c5864e7b58585434cf3c8299 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 14:20:49 +0100 Subject: [PATCH 778/808] Move code to test helpers --- .../string-templates/test/manifest.spec.js | 62 +++---------------- packages/string-templates/test/utils.js | 53 ++++++++++++++++ 2 files changed, 60 insertions(+), 55 deletions(-) create mode 100644 packages/string-templates/test/utils.js diff --git a/packages/string-templates/test/manifest.spec.js b/packages/string-templates/test/manifest.spec.js index 3e39d775f5..6dd5e8933d 100644 --- a/packages/string-templates/test/manifest.spec.js +++ b/packages/string-templates/test/manifest.spec.js @@ -15,7 +15,6 @@ jest.mock("@budibase/handlebars-helpers/lib/uuid", () => { } }) -const fs = require("fs") const { processString, convertToJS, @@ -25,6 +24,7 @@ const { const tk = require("timekeeper") const { getJsHelperList } = require("../src/helpers") +const { getParsedManifest } = require("./utils") tk.freeze("2021-01-21T12:00:00") @@ -32,64 +32,16 @@ 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", @@ -110,8 +62,8 @@ 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]) + const jsExamples = Object.keys(manifest).reduce((acc, v) => { + acc[v] = manifest[v].filter(([key]) => jsHelpers[key]) return acc }, {}) diff --git a/packages/string-templates/test/utils.js b/packages/string-templates/test/utils.js new file mode 100644 index 0000000000..1b9d030cc5 --- /dev/null +++ b/packages/string-templates/test/utils.js @@ -0,0 +1,53 @@ +const { getManifest } = require("../src") + +function tryParseJson(str) { + if (typeof str !== "string") { + return + } + + try { + return JSON.parse(str.replace(/\'/g, '"')) + } catch (e) { + return + } +} + +module.exports.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 +} From b96ca1cf54dc31899bd2d8260f19635049f4ad66 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 16:15:53 +0100 Subject: [PATCH 779/808] Run hbs on server tests --- .../src/jsRunner/tests/jsRunner.spec.ts | 38 +++++++++---- packages/string-templates/package.json | 3 +- .../string-templates/test/manifest.spec.js | 47 +--------------- packages/string-templates/test/utils.js | 55 ++++++++++++++++++- 4 files changed, 85 insertions(+), 58 deletions(-) diff --git a/packages/server/src/jsRunner/tests/jsRunner.spec.ts b/packages/server/src/jsRunner/tests/jsRunner.spec.ts index 730a92e99b..35c01a5a8a 100644 --- a/packages/server/src/jsRunner/tests/jsRunner.spec.ts +++ b/packages/server/src/jsRunner/tests/jsRunner.spec.ts @@ -1,21 +1,35 @@ +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" -import TestConfiguration from "../../tests/utilities/TestConfiguration" +const { runJsHelpersTests } = require("@budibase/string-templates/test/utils") + +import tk from "timekeeper" +tk.freeze("2021-01-21T12:00:00") describe("jsRunner", () => { - const config = new TestConfiguration() - - beforeAll(async () => { - await config.init() - }) - const processJS = (js: string, context?: object) => { - return config.doInContext(config.getAppId(), async () => - processStringSync(encodeJSBinding(js), context || {}) - ) + return processStringSync(encodeJSBinding(js), context || {}) } - it("it can run a basic javascript", async () => { - const output = await processJS(`return 1 + 2`) + it("it can run a basic javascript", () => { + const output = processJS(`return 1 + 2`) expect(output).toBe(3) }) + + runJsHelpersTests() }) diff --git a/packages/string-templates/package.json b/packages/string-templates/package.json index 6d0e9298e5..c7a4e79c86 100644 --- a/packages/string-templates/package.json +++ b/packages/string-templates/package.json @@ -12,7 +12,8 @@ "import": "./dist/bundle.mjs" }, "./package.json": "./package.json", - "./index-helpers": "./dist/index-helpers.bundled.js" + "./index-helpers": "./dist/index-helpers.bundled.js", + "./test/utils": "./test/utils.js" }, "files": [ "dist", diff --git a/packages/string-templates/test/manifest.spec.js b/packages/string-templates/test/manifest.spec.js index 6dd5e8933d..fa8683960a 100644 --- a/packages/string-templates/test/manifest.spec.js +++ b/packages/string-templates/test/manifest.spec.js @@ -15,23 +15,13 @@ jest.mock("@budibase/handlebars-helpers/lib/uuid", () => { } }) -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 } = require("./utils") +const { getParsedManifest, runJsHelpersTests } = require("./utils") tk.freeze("2021-01-21T12:00:00") -const processJS = (js, context) => { - return processStringSync(encodeJSBinding(js), context) -} - function escapeRegExp(string) { return string.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") // $& means the whole matched string } @@ -60,36 +50,5 @@ describe("manifest", () => { }) }) - 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 => { - 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() }) diff --git a/packages/string-templates/test/utils.js b/packages/string-templates/test/utils.js index 1b9d030cc5..8b8943c776 100644 --- a/packages/string-templates/test/utils.js +++ b/packages/string-templates/test/utils.js @@ -1,4 +1,11 @@ const { getManifest } = require("../src") +const { getJsHelperList } = require("../src/helpers") + +const { + convertToJS, + processStringSync, + encodeJSBinding, +} = require("../src/index.cjs") function tryParseJson(str) { if (typeof str !== "string") { @@ -12,7 +19,7 @@ function tryParseJson(str) { } } -module.exports.getParsedManifest = () => { +const getParsedManifest = () => { const manifest = getManifest() const collections = Object.keys(manifest) const examples = collections.reduce((acc, collection) => { @@ -51,3 +58,49 @@ module.exports.getParsedManifest = () => { return examples } +module.exports.getParsedManifest = getParsedManifest + +module.exports.runJsHelpersTests = () => { + const manifest = getParsedManifest() + + const processJS = (js, context) => { + return 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 => { + 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 = await processJS(convertedJs, context) + result = result.replace(/ /g, " ") + expect(result).toEqual(js) + }) + }) + }) +} From e6ae3649184a7f74bfdd1f1d88ebe2040dec3424 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Wed, 31 Jan 2024 16:51:34 +0100 Subject: [PATCH 780/808] Register server helpers --- .../src/jsRunner/tests/jsRunner.spec.ts | 19 +++++++++++++++---- packages/string-templates/test/utils.js | 4 ++-- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/server/src/jsRunner/tests/jsRunner.spec.ts b/packages/server/src/jsRunner/tests/jsRunner.spec.ts index 35c01a5a8a..ddef60a80e 100644 --- a/packages/server/src/jsRunner/tests/jsRunner.spec.ts +++ b/packages/server/src/jsRunner/tests/jsRunner.spec.ts @@ -19,17 +19,28 @@ 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 () => { + init() + await config.init() + }) + const processJS = (js: string, context?: object) => { - return processStringSync(encodeJSBinding(js), context || {}) + return config.doInContext(config.getAppId(), async () => + processStringSync(encodeJSBinding(js), context || {}) + ) } - it("it can run a basic javascript", () => { - const output = processJS(`return 1 + 2`) + it("it can run a basic javascript", async () => { + const output = await processJS(`return 1 + 2`) expect(output).toBe(3) }) - runJsHelpersTests() + runJsHelpersTests((func: any) => config.doInContext(config.getAppId(), func)) }) diff --git a/packages/string-templates/test/utils.js b/packages/string-templates/test/utils.js index 8b8943c776..33f6c6d6b6 100644 --- a/packages/string-templates/test/utils.js +++ b/packages/string-templates/test/utils.js @@ -60,11 +60,11 @@ const getParsedManifest = () => { } module.exports.getParsedManifest = getParsedManifest -module.exports.runJsHelpersTests = () => { +module.exports.runJsHelpersTests = (funcWrap = delegate => delegate()) => { const manifest = getParsedManifest() const processJS = (js, context) => { - return processStringSync(encodeJSBinding(js), context) + return funcWrap(() => processStringSync(encodeJSBinding(js), context)) } function escapeRegExp(string) { From 85f7d66a9997ccac2688db48aa0c7b2071ab0d50 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 31 Jan 2024 17:16:51 +0000 Subject: [PATCH 781/808] Handling very large exports/backup downloads. --- packages/backend-core/src/objectStore/objectStore.ts | 4 ++-- packages/pro | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend-core/src/objectStore/objectStore.ts b/packages/backend-core/src/objectStore/objectStore.ts index 57ead0e809..3a3b9cdaab 100644 --- a/packages/backend-core/src/objectStore/objectStore.ts +++ b/packages/backend-core/src/objectStore/objectStore.ts @@ -7,7 +7,7 @@ import tar from "tar-fs" import zlib from "zlib" import { promisify } from "util" import { join } from "path" -import fs from "fs" +import fs, { ReadStream } from "fs" import env from "../environment" import { budibaseTempDir } from "./utils" import { v4 } from "uuid" @@ -184,7 +184,7 @@ export async function upload({ export async function streamUpload( bucketName: string, filename: string, - stream: any, + stream: ReadStream | ReadableStream, extra = {} ) { const objectStore = ObjectStore(bucketName) diff --git a/packages/pro b/packages/pro index eb9565f568..e4dab7e4cc 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit eb9565f568cfef14b336b14eee753119acfdd43b +Subproject commit e4dab7e4ccb4a1e2864a35dd2a851ca1a898e6c6 From 010f160fa54089eef6a8d75ae289bdd93ee5151e Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Wed, 31 Jan 2024 18:22:18 +0000 Subject: [PATCH 782/808] Cleaning up files after import is finished (with large files this saves on tmp space) as well as fixing an issue where menu would appear next to a restore despite having no options. --- .../backups/_components/ActionsRenderer.svelte | 16 ++++++++-------- packages/pro | 2 +- packages/server/src/sdk/app/backups/imports.ts | 7 ++++++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/packages/builder/src/pages/builder/app/[application]/settings/backups/_components/ActionsRenderer.svelte b/packages/builder/src/pages/builder/app/[application]/settings/backups/_components/ActionsRenderer.svelte index 93f0f518ad..ffdf14fdce 100644 --- a/packages/builder/src/pages/builder/app/[application]/settings/backups/_components/ActionsRenderer.svelte +++ b/packages/builder/src/pages/builder/app/[application]/settings/backups/_components/ActionsRenderer.svelte @@ -13,6 +13,7 @@ import CreateRestoreModal from "./CreateRestoreModal.svelte" import { createEventDispatcher } from "svelte" import { isOnlyUser } from "builderStore" + import { BackupType } from "constants/backend/backups" export let row @@ -42,12 +43,11 @@
- -
- -
- - {#if row.type !== "restore"} + {#if row.type !== BackupType.RESTORE} + +
+ +
Delete Download - {/if} -
+
+ {/if}
diff --git a/packages/pro b/packages/pro index e4dab7e4cc..f8ebf7e3e1 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit e4dab7e4ccb4a1e2864a35dd2a851ca1a898e6c6 +Subproject commit f8ebf7e3e14c894cf7ab831fabd2706e94cfb830 diff --git a/packages/server/src/sdk/app/backups/imports.ts b/packages/server/src/sdk/app/backups/imports.ts index 7f76945107..4c5c1e659d 100644 --- a/packages/server/src/sdk/app/backups/imports.ts +++ b/packages/server/src/sdk/app/backups/imports.ts @@ -165,8 +165,9 @@ export async function importApp( const isTar = template.file && template?.file?.type?.endsWith("gzip") const isDirectory = template.file && fs.lstatSync(template.file.path).isDirectory() + let tmpPath: string | undefined = undefined if (template.file && (isTar || isDirectory)) { - const tmpPath = isTar ? await untarFile(template.file) : template.file.path + tmpPath = isTar ? await untarFile(template.file) : template.file.path if (isTar && template.file.password) { await decryptFiles(tmpPath, template.file.password) } @@ -208,5 +209,9 @@ export async function importApp( } await updateAttachmentColumns(prodAppId, db) await updateAutomations(prodAppId, db) + // clear up afterward + if (tmpPath) { + fs.rmSync(tmpPath, { recursive: true, force: true }) + } return ok } From 6fdbeb125868f7cd4d303dbe613340ba69bd9d29 Mon Sep 17 00:00:00 2001 From: Peter Clement Date: Thu, 1 Feb 2024 09:42:38 +0000 Subject: [PATCH 783/808] submodule --- packages/account-portal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/account-portal b/packages/account-portal index 485ec16a9e..52f51dcfb9 160000 --- a/packages/account-portal +++ b/packages/account-portal @@ -1 +1 @@ -Subproject commit 485ec16a9eed48c548a5f1239772139f3319f028 +Subproject commit 52f51dcfb96d3fe58c8cc7a905e7d733f7cd84c2 From 82db76627fbbd60322cc85009ff06b5f1df1e0f0 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 1 Feb 2024 10:56:59 +0100 Subject: [PATCH 784/808] Better typing --- packages/server/src/jsRunner/tests/jsRunner.spec.ts | 5 ++++- packages/string-templates/test/utils.js | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/server/src/jsRunner/tests/jsRunner.spec.ts b/packages/server/src/jsRunner/tests/jsRunner.spec.ts index ddef60a80e..56314c9565 100644 --- a/packages/server/src/jsRunner/tests/jsRunner.spec.ts +++ b/packages/server/src/jsRunner/tests/jsRunner.spec.ts @@ -27,6 +27,7 @@ describe("jsRunner", () => { const config = new TestConfiguration() beforeAll(async () => { + // Register js runner init() await config.init() }) @@ -42,5 +43,7 @@ describe("jsRunner", () => { expect(output).toBe(3) }) - runJsHelpersTests((func: any) => config.doInContext(config.getAppId(), func)) + runJsHelpersTests({ + funcWrap: (func: any) => config.doInContext(config.getAppId(), func), + }) }) diff --git a/packages/string-templates/test/utils.js b/packages/string-templates/test/utils.js index 33f6c6d6b6..04faffb27d 100644 --- a/packages/string-templates/test/utils.js +++ b/packages/string-templates/test/utils.js @@ -60,7 +60,9 @@ const getParsedManifest = () => { } module.exports.getParsedManifest = getParsedManifest -module.exports.runJsHelpersTests = (funcWrap = delegate => delegate()) => { +module.exports.runJsHelpersTests = ( + { funcWrap } = { funcWrap: delegate => delegate() } +) => { const manifest = getParsedManifest() const processJS = (js, context) => { From 4461c49f48e3efb13fef5c4c0f839b2b0275ed1f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 1 Feb 2024 10:58:20 +0100 Subject: [PATCH 785/808] Allow skipping tests --- .../src/jsRunner/tests/jsRunner.spec.ts | 1 + packages/string-templates/test/utils.js | 49 ++++++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/packages/server/src/jsRunner/tests/jsRunner.spec.ts b/packages/server/src/jsRunner/tests/jsRunner.spec.ts index 56314c9565..876ada5ee0 100644 --- a/packages/server/src/jsRunner/tests/jsRunner.spec.ts +++ b/packages/server/src/jsRunner/tests/jsRunner.spec.ts @@ -45,5 +45,6 @@ describe("jsRunner", () => { runJsHelpersTests({ funcWrap: (func: any) => config.doInContext(config.getAppId(), func), + testsToSkip: ["random", "uuid"], }) }) diff --git a/packages/string-templates/test/utils.js b/packages/string-templates/test/utils.js index 04faffb27d..2375c5868f 100644 --- a/packages/string-templates/test/utils.js +++ b/packages/string-templates/test/utils.js @@ -60,9 +60,8 @@ const getParsedManifest = () => { } module.exports.getParsedManifest = getParsedManifest -module.exports.runJsHelpersTests = ( - { funcWrap } = { funcWrap: delegate => delegate() } -) => { +module.exports.runJsHelpersTests = ({ funcWrap, testsToSkip } = {}) => { + funcWrap = funcWrap || (delegate => delegate()) const manifest = getParsedManifest() const processJS = (js, context) => { @@ -81,28 +80,32 @@ module.exports.runJsHelpersTests = ( }, {}) 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 examplesToRun = jsExamples[collection] + .filter(([_, { requiresHbsBody }]) => !requiresHbsBody) + .filter(([key]) => !testsToSkip?.includes(key)) - 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, '"')) + 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) }) - - let convertedJs = convertToJS(hbs) - - let result = await processJS(convertedJs, context) - result = result.replace(/ /g, " ") - expect(result).toEqual(js) - }) }) }) } From 181d462cdf711ff89d31ca45de1a76b1264d716c Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 1 Feb 2024 11:15:21 +0100 Subject: [PATCH 786/808] Update uuid --- packages/backend-core/package.json | 2 +- packages/server/package.json | 3 ++- packages/server/src/api/controllers/static/index.ts | 2 +- packages/server/src/utilities/fileSystem/filesystem.ts | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index d6325e1de9..93dc911f4e 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -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", diff --git a/packages/server/package.json b/packages/server/package.json index 2f5c3db6e9..a52623802a 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -106,7 +106,7 @@ "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", "worker-farm": "1.7.0", "xml2js": "0.5.0" @@ -130,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", diff --git a/packages/server/src/api/controllers/static/index.ts b/packages/server/src/api/controllers/static/index.ts index 5f383e837d..ec45361604 100644 --- a/packages/server/src/api/controllers/static/index.ts +++ b/packages/server/src/api/controllers/static/index.ts @@ -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 { diff --git a/packages/server/src/utilities/fileSystem/filesystem.ts b/packages/server/src/utilities/fileSystem/filesystem.ts index be656f54d1..add587cdea 100644 --- a/packages/server/src/utilities/fileSystem/filesystem.ts +++ b/packages/server/src/utilities/fileSystem/filesystem.ts @@ -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, "..", "..", "..")) From 2899c4f7f8b94a302bdbd2f646b3f70029cdfa31 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 1 Feb 2024 11:15:36 +0100 Subject: [PATCH 787/808] Test uuid helper --- .../server/src/jsRunner/tests/jsRunner.spec.ts | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/server/src/jsRunner/tests/jsRunner.spec.ts b/packages/server/src/jsRunner/tests/jsRunner.spec.ts index 876ada5ee0..e16734db93 100644 --- a/packages/server/src/jsRunner/tests/jsRunner.spec.ts +++ b/packages/server/src/jsRunner/tests/jsRunner.spec.ts @@ -1,3 +1,5 @@ +import { validate as isValidUUID } from "uuid" + jest.mock("@budibase/handlebars-helpers/lib/math", () => { const actual = jest.requireActual("@budibase/handlebars-helpers/lib/math") @@ -43,8 +45,18 @@ describe("jsRunner", () => { expect(output).toBe(3) }) - runJsHelpersTests({ - funcWrap: (func: any) => config.doInContext(config.getAppId(), func), - testsToSkip: ["random", "uuid"], + 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) + }) + }) }) }) From 1b88700d0267e32ebee0848c9eb89176404568f0 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 1 Feb 2024 11:17:55 +0100 Subject: [PATCH 788/808] Test random helper --- packages/server/src/jsRunner/tests/jsRunner.spec.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/server/src/jsRunner/tests/jsRunner.spec.ts b/packages/server/src/jsRunner/tests/jsRunner.spec.ts index e16734db93..3545abfe4e 100644 --- a/packages/server/src/jsRunner/tests/jsRunner.spec.ts +++ b/packages/server/src/jsRunner/tests/jsRunner.spec.ts @@ -58,5 +58,16 @@ describe("jsRunner", () => { 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) + }) + }) }) }) From e61d52ee8ba7b741aa4c3060c4085b7faf7cfd5c Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 1 Feb 2024 10:18:14 +0000 Subject: [PATCH 789/808] Updating pro ref to master. --- packages/pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pro b/packages/pro index f8ebf7e3e1..4f9616f163 160000 --- a/packages/pro +++ b/packages/pro @@ -1 +1 @@ -Subproject commit f8ebf7e3e14c894cf7ab831fabd2706e94cfb830 +Subproject commit 4f9616f163039a0eea81319d8e2288340a2ebc79 From d1cf13707d814ba9afca664a0403b8b990ba8f0d Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 1 Feb 2024 11:57:22 +0100 Subject: [PATCH 790/808] Fix url.escape --- packages/server/src/jsRunner/index.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index 477a8c14da..b7e63ce63c 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -6,6 +6,7 @@ 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")}`, @@ -39,6 +40,10 @@ export function init() { resolve: (...params) => urlResolveCb(...params), parse: (...params) => urlParseCb(...params), } + case "querystring": + return { + escape: (...params) => querystringEscapeCb(...params), + } } };` @@ -57,6 +62,14 @@ export function init() { ) ) + global.setSync( + "querystringEscapeCb", + new ivm.Callback( + (...params: Parameters) => + querystring.escape(...params) + ) + ) + const helpersModule = jsIsolate.compileModuleSync( `${injectedRequire};${helpersSource}` ) From c4eae33b6718a722da67c65c96f9817c2c755db0 Mon Sep 17 00:00:00 2001 From: mike12345567 Date: Thu, 1 Feb 2024 11:09:28 +0000 Subject: [PATCH 791/808] Fixing mock to cover getReadStream in object store. --- packages/server/__mocks__/aws-sdk.ts | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/server/__mocks__/aws-sdk.ts b/packages/server/__mocks__/aws-sdk.ts index fa6d099f56..e6a624c83e 100644 --- a/packages/server/__mocks__/aws-sdk.ts +++ b/packages/server/__mocks__/aws-sdk.ts @@ -1,7 +1,13 @@ +import fs from "fs" +import { join } from "path" + module AwsMock { const aws: any = {} - const response = (body: any) => () => ({ promise: () => body }) + const response = (body: any, extra?: any) => () => ({ + promise: () => body, + ...extra, + }) function DocumentClient() { // @ts-ignore @@ -73,9 +79,18 @@ module AwsMock { // @ts-ignore this.getObject = jest.fn( - response({ - Body: "", - }) + response( + { + Body: "", + }, + { + createReadStream: jest + .fn() + .mockReturnValue( + fs.createReadStream(join(__dirname, "aws-sdk.ts")) + ), + } + ) ) } From 1439eb9b271c84949c50dfc82b128e9f601452fd Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 1 Feb 2024 12:20:31 +0100 Subject: [PATCH 792/808] Fix stripProtocol --- packages/server/src/jsRunner/index.ts | 9 +++++++++ packages/string-templates/src/index-helpers.js | 7 ++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/packages/server/src/jsRunner/index.ts b/packages/server/src/jsRunner/index.ts index b7e63ce63c..8e529d533d 100644 --- a/packages/server/src/jsRunner/index.ts +++ b/packages/server/src/jsRunner/index.ts @@ -70,6 +70,15 @@ export function init() { ) ) + 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}` ) diff --git a/packages/string-templates/src/index-helpers.js b/packages/string-templates/src/index-helpers.js index 3c16a352fc..b0d9ac5c68 100644 --- a/packages/string-templates/src/index-helpers.js +++ b/packages/string-templates/src/index-helpers.js @@ -1,3 +1,8 @@ const { getJsHelperList } = require("./helpers/list") -module.exports = getJsHelperList() +const helpers = getJsHelperList() +module.exports = { + ...helpers, + // point stripProtocol to a unexisting function to be able to declare it on isolated-vm + stripProtocol: helpersStripProtocol, +} From 843d95c3bbb074144d1f5baba7f3adb98d1567cc Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 1 Feb 2024 12:30:59 +0100 Subject: [PATCH 793/808] Lint --- packages/server/src/jsRunner/tests/jsRunner.spec.ts | 2 ++ packages/string-templates/test/utils.js | 6 +++--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/server/src/jsRunner/tests/jsRunner.spec.ts b/packages/server/src/jsRunner/tests/jsRunner.spec.ts index 3545abfe4e..cddab3c9b4 100644 --- a/packages/server/src/jsRunner/tests/jsRunner.spec.ts +++ b/packages/server/src/jsRunner/tests/jsRunner.spec.ts @@ -18,11 +18,13 @@ jest.mock("@budibase/handlebars-helpers/lib/uuid", () => { }) 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", () => { diff --git a/packages/string-templates/test/utils.js b/packages/string-templates/test/utils.js index 2375c5868f..3a54502a3d 100644 --- a/packages/string-templates/test/utils.js +++ b/packages/string-templates/test/utils.js @@ -13,7 +13,7 @@ function tryParseJson(str) { } try { - return JSON.parse(str.replace(/\'/g, '"')) + return JSON.parse(str.replace(/'/g, '"')) } catch (e) { return } @@ -34,7 +34,7 @@ const getParsedManifest = () => { } // Trim 's - js = js.replace(/^\'|\'$/g, "") + js = js.replace(/^'|'$/g, "") let parsedExpected if ((parsedExpected = tryParseJson(js))) { if (Array.isArray(parsedExpected)) { @@ -97,7 +97,7 @@ module.exports.runJsHelpersTests = ({ funcWrap, testsToSkip } = {}) => { new RegExp(escapeRegExp(arrayString)), `array${i}` ) - context[`array${i}`] = JSON.parse(arrayString.replace(/\'/g, '"')) + context[`array${i}`] = JSON.parse(arrayString.replace(/'/g, '"')) }) let convertedJs = convertToJS(hbs) From 480476bcfe1143adaa6cf7d03adb779d84ced128 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 1 Feb 2024 12:32:39 +0100 Subject: [PATCH 794/808] Lint --- packages/string-templates/src/index-helpers.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/string-templates/src/index-helpers.js b/packages/string-templates/src/index-helpers.js index b0d9ac5c68..c71f8a9853 100644 --- a/packages/string-templates/src/index-helpers.js +++ b/packages/string-templates/src/index-helpers.js @@ -3,6 +3,7 @@ const { getJsHelperList } = require("./helpers/list") const helpers = getJsHelperList() module.exports = { ...helpers, - // point stripProtocol to a unexisting function to be able to declare it on isolated-vm + // pointing stripProtocol to a unexisting function to be able to declare it on isolated-vm + // eslint-disable-next-line no-undef stripProtocol: helpersStripProtocol, } From 380871800444b4509ce78a3388ef5db5e7acb219 Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 1 Feb 2024 12:42:23 +0100 Subject: [PATCH 795/808] Fix tests --- packages/server/src/api/routes/tests/table.spec.ts | 2 +- packages/server/src/db/newid.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/src/api/routes/tests/table.spec.ts b/packages/server/src/api/routes/tests/table.spec.ts index 9129fd14c3..62efdda448 100644 --- a/packages/server/src/api/routes/tests/table.spec.ts +++ b/packages/server/src/api/routes/tests/table.spec.ts @@ -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 diff --git a/packages/server/src/db/newid.ts b/packages/server/src/db/newid.ts index 0037bc0353..bc8f3bb04b 100644 --- a/packages/server/src/db/newid.ts +++ b/packages/server/src/db/newid.ts @@ -1,4 +1,4 @@ -const { v4 } = require("uuid") +import { v4 } from "uuid" export default function (): string { return v4().replace(/-/g, "") From 2c8254ba0fceed0257b4a7924bbc84d5a33d678f Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 1 Feb 2024 12:11:47 +0000 Subject: [PATCH 796/808] Bump version to 2.16.3 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 38dff33c7e..6c3b971a3a 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.16.2", + "version": "2.16.3", "npmClient": "yarn", "packages": [ "packages/*", From 9ab726f9b298a1dd35b393fcd61559bea2656d50 Mon Sep 17 00:00:00 2001 From: Budibase Staging Release Bot <> Date: Thu, 1 Feb 2024 14:48:35 +0000 Subject: [PATCH 797/808] Bump version to 2.17.0 --- lerna.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lerna.json b/lerna.json index 6c3b971a3a..d89d826599 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.16.3", + "version": "2.17.0", "npmClient": "yarn", "packages": [ "packages/*", From 16749ec3dd5024d88e11fcd3e63f4be81039d23f Mon Sep 17 00:00:00 2001 From: Adria Navarro Date: Thu, 1 Feb 2024 16:30:34 +0100 Subject: [PATCH 798/808] Revert "Migrate from `vm` to `isolated-vm`" --- hosting/single/Dockerfile | 2 - packages/backend-core/package.json | 2 +- packages/backend-core/src/context/types.ts | 8 +- packages/backend-core/src/timers/timers.ts | 38 ++++ .../bbui/src/Form/Core/EnvDropdown.svelte | 4 +- packages/server/Dockerfile | 2 - packages/server/nodemon.json | 4 +- packages/server/package.json | 22 +-- packages/server/scripts/test.sh | 4 +- .../src/api/controllers/static/index.ts | 2 +- .../server/src/api/routes/tests/table.spec.ts | 2 +- packages/server/src/db/newid.ts | 2 +- packages/server/src/environment.ts | 2 - packages/server/src/jsRunner.ts | 61 +++++++ packages/server/src/jsRunner/index.ts | 167 ------------------ .../src/jsRunner/tests/jsRunner.spec.ts | 75 -------- .../src/utilities/fileSystem/filesystem.ts | 2 +- packages/server/src/utilities/scriptRunner.ts | 65 ++----- packages/string-templates/package.json | 10 +- packages/string-templates/src/errors.js | 11 -- .../src/helpers/javascript.js | 2 +- packages/string-templates/src/helpers/list.js | 33 ++-- .../string-templates/src/index-helpers.js | 9 - packages/string-templates/src/index.cjs | 5 - packages/string-templates/src/index.js | 5 - packages/string-templates/src/index.mjs | 2 - .../string-templates/test/manifest.spec.js | 103 ++++++++++- packages/string-templates/test/utils.js | 111 ------------ packages/worker/Dockerfile | 2 - packages/worker/nodemon.json | 4 +- yarn.lock | 137 ++------------ 31 files changed, 260 insertions(+), 638 deletions(-) create mode 100644 packages/server/src/jsRunner.ts delete mode 100644 packages/server/src/jsRunner/index.ts delete mode 100644 packages/server/src/jsRunner/tests/jsRunner.spec.ts delete mode 100644 packages/string-templates/src/errors.js delete mode 100644 packages/string-templates/src/index-helpers.js delete mode 100644 packages/string-templates/test/utils.js diff --git a/hosting/single/Dockerfile b/hosting/single/Dockerfile index f9044cd124..67ac677984 100644 --- a/hosting/single/Dockerfile +++ b/hosting/single/Dockerfile @@ -124,8 +124,6 @@ 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"] diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 13bc7011b5..85644488f5 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -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", diff --git a/packages/backend-core/src/context/types.ts b/packages/backend-core/src/context/types.ts index cc052ca505..f73dc9f5c7 100644 --- a/packages/backend-core/src/context/types.ts +++ b/packages/backend-core/src/context/types.ts @@ -1,5 +1,5 @@ import { IdentityContext } from "@budibase/types" -import { Isolate, Context, Module } from "isolated-vm" +import { ExecutionTimeTracker } from "../timers" // keep this out of Budibase types, don't want to expose context info export type ContextMap = { @@ -10,9 +10,5 @@ export type ContextMap = { isScim?: boolean automationId?: string isMigrating?: boolean - isolateRefs?: { - jsIsolate: Isolate - jsContext: Context - helpersModule: Module - } + jsExecutionTracker?: ExecutionTimeTracker } diff --git a/packages/backend-core/src/timers/timers.ts b/packages/backend-core/src/timers/timers.ts index 000be74821..9121c576cd 100644 --- a/packages/backend-core/src/timers/timers.ts +++ b/packages/backend-core/src/timers/timers.ts @@ -20,3 +20,41 @@ 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(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` + ) + } + } +} diff --git a/packages/bbui/src/Form/Core/EnvDropdown.svelte b/packages/bbui/src/Form/Core/EnvDropdown.svelte index c690ffbc6b..2edf8a5f9d 100644 --- a/packages/bbui/src/Form/Core/EnvDropdown.svelte +++ b/packages/bbui/src/Form/Core/EnvDropdown.svelte @@ -184,7 +184,7 @@ {#if environmentVariablesEnabled}
showModal()} class="add-variable">
handleUpgradePanel()} class="add-variable">
showModal()} class="add-variable">
handleUpgradePanel()} class="add-variable">
showModal()} class="add-variable">
handleUpgradePanel()} class="add-variable">