Merge branch 'master' of github.com:Budibase/budibase

This commit is contained in:
mike12345567 2021-02-01 17:09:53 +00:00
commit e898e9efb2
45 changed files with 2500 additions and 1104 deletions

View File

@ -5,6 +5,7 @@ version: "3"
services:
app-service:
restart: always
#build: ./build/server
image: budibase/budibase-apps
ports:
- "${APP_PORT}:4002"
@ -22,6 +23,7 @@ services:
worker-service:
restart: always
#build: ./build/worker
image: budibase/budibase-worker
ports:
- "${WORKER_PORT}:4003"

View File

@ -21,6 +21,11 @@ static_resources:
cluster: app-service
prefix_rewrite: "/"
# special case for presenting our static self hosting page
- match: { path: "/" }
route:
cluster: app-service
# special case for when API requests are made, can just forward, not to minio
- match: { prefix: "/api/" }
route:

View File

@ -2,6 +2,7 @@ import { cloneDeep } from "lodash/fp"
import { get } from "svelte/store"
import { backendUiStore, store } from "builderStore"
import { findAllMatchingComponents, findComponentPath } from "./storeUtils"
import { makePropSafe } from "@budibase/string-templates"
import { TableNames } from "../constants"
// Regex to match all instances of template strings
@ -106,7 +107,9 @@ export const getContextBindings = (rootComponent, componentId) => {
contextBindings.push({
type: "context",
runtimeBinding: `${component._id}.${runtimeBoundKey}`,
runtimeBinding: `${makePropSafe(component._id)}.${makePropSafe(
runtimeBoundKey
)}`,
readableBinding: `${component._instanceName}.${table.name}.${key}`,
fieldSchema,
providerId: component._id,
@ -167,7 +170,7 @@ export const getComponentBindings = rootComponent => {
return {
type: "instance",
providerId: component._id,
runtimeBinding: `${component._id}`,
runtimeBinding: `${makePropSafe(component._id)}`,
readableBinding: `${component._instanceName}`,
}
})
@ -199,43 +202,52 @@ export const getSchemaForDatasource = datasource => {
}
/**
* Converts a readable data binding into a runtime data binding
* utility function for the readableToRuntimeBinding and runtimeToReadableBinding.
*/
export function readableToRuntimeBinding(bindableProperties, textWithBindings) {
function bindingReplacement(bindableProperties, textWithBindings, convertTo) {
const convertFrom =
convertTo === "runtimeBinding" ? "readableBinding" : "runtimeBinding"
if (typeof textWithBindings !== "string") {
return textWithBindings
}
const convertFromProps = bindableProperties
.map(el => el[convertFrom])
.sort((a, b) => {
return b.length - a.length
})
const boundValues = textWithBindings.match(CAPTURE_VAR_INSIDE_TEMPLATE) || []
let result = textWithBindings
boundValues.forEach(boundValue => {
const binding = bindableProperties.find(({ readableBinding }) => {
return boundValue === `{{ ${readableBinding} }}`
})
if (binding) {
result = result.replace(boundValue, `{{ ${binding.runtimeBinding} }}`)
for (let boundValue of boundValues) {
let newBoundValue = boundValue
for (let from of convertFromProps) {
if (newBoundValue.includes(from)) {
const binding = bindableProperties.find(el => el[convertFrom] === from)
newBoundValue = newBoundValue.replace(from, binding[convertTo])
}
}
result = result.replace(boundValue, newBoundValue)
}
})
return result
}
/**
* Converts a readable data binding into a runtime data binding
*/
export function readableToRuntimeBinding(bindableProperties, textWithBindings) {
return bindingReplacement(
bindableProperties,
textWithBindings,
"runtimeBinding"
)
}
/**
* Converts a runtime data binding into a readable data binding
*/
export function runtimeToReadableBinding(bindableProperties, textWithBindings) {
if (typeof textWithBindings !== "string") {
return textWithBindings
}
const boundValues = textWithBindings.match(CAPTURE_VAR_INSIDE_TEMPLATE) || []
let result = textWithBindings
boundValues.forEach(boundValue => {
const binding = bindableProperties.find(({ runtimeBinding }) => {
return boundValue === `{{ ${runtimeBinding} }}`
})
// Show invalid bindings as invalid rather than a long ID
result = result.replace(
boundValue,
`{{ ${binding?.readableBinding ?? "Invalid binding"} }}`
return bindingReplacement(
bindableProperties,
textWithBindings,
"readableBinding"
)
})
return result
}

View File

@ -2,6 +2,7 @@ import sanitizeUrl from "./utils/sanitizeUrl"
import { rowListUrl } from "./rowListScreen"
import { Screen } from "./utils/Screen"
import { Component } from "./utils/Component"
import { makePropSafe } from "@budibase/string-templates"
import {
makeMainContainer,
makeBreadcrumbContainer,
@ -12,7 +13,7 @@ import {
export default function(tables) {
return tables.map(table => {
const heading = table.primaryDisplay
? `{{ data.${table.primaryDisplay} }}`
? `{{ data.${makePropSafe(table.primaryDisplay)} }}`
: null
return {
name: `${table.name} - Detail`,
@ -60,8 +61,8 @@ function generateTitleContainer(table, title, providerId) {
onClick: [
{
parameters: {
rowId: `{{ ${providerId}._id }}`,
revId: `{{ ${providerId}._rev }}`,
rowId: `{{ ${makePropSafe(providerId)}._id }}`,
revId: `{{ ${makePropSafe(providerId)}._rev }}`,
tableId: table._id,
},
"##eventHandlerType": "Delete Row",

View File

@ -3,6 +3,7 @@
import {
TextArea,
Label,
Input,
Heading,
Body,
Spacer,
@ -10,6 +11,9 @@
Popover,
} from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { isValid } from "@budibase/string-templates"
import { handlebarsCompletions } from "constants/completions"
const dispatch = createEventDispatcher()
export let value = ""
@ -18,9 +22,14 @@
export let align
export let popover = null
let helpers = handlebarsCompletions()
let getCaretPosition
let validity = true
let search = ""
$: categories = Object.entries(groupBy("category", bindings))
$: value && checkValid()
$: searchRgx = new RegExp(search, "ig")
function onClickBinding(binding) {
const position = getCaretPosition()
@ -34,18 +43,27 @@
value += toAdd
}
}
function checkValid() {
validity = isValid(value)
}
</script>
<Popover {anchor} {align} bind:this={popover}>
<div class="container">
<div class="bindings">
<Heading small>Available bindings</Heading>
<Spacer medium />
<Input extraThin placeholder="Search" bind:value={search} />
<Spacer medium />
<div class="bindings__wrapper">
<div class="bindings__list">
{#each categories as [categoryName, bindings]}
<Heading extraSmall>{categoryName}</Heading>
<Spacer extraSmall />
{#each bindings as binding}
{#each bindings.filter(binding =>
binding.label.match(searchRgx)
) as binding}
<div class="binding" on:click={() => onClickBinding(binding)}>
<span class="binding__label">{binding.label}</span>
<span class="binding__type">{binding.type}</span>
@ -56,6 +74,17 @@
</div>
{/each}
{/each}
<Heading extraSmall>Helpers</Heading>
<Spacer extraSmall />
{#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper}
<div class="binding" on:click={() => onClickBinding(helper)}>
<span class="binding__label">{helper.label}</span>
<br />
<div class="binding__description">
{@html helper.description || ''}
</div>
</div>
{/each}
</div>
</div>
</div>
@ -70,11 +99,20 @@
bind:getCaretPosition
bind:value
placeholder="Add options from the left, type text, or do both" />
{#if !validity}
<p class="syntax-error">
Current Handlebars syntax is invalid, please check the guide
<a href="https://handlebarsjs.com/guide/">here</a>
for more details.
</p>
{/if}
<div class="controls">
<a href="https://docs.budibase.com/design/binding">
<Body small>Learn more about binding</Body>
</a>
<Button on:click={popover.hide} primary>Done</Button>
<Button on:click={popover.hide} disabled={!validity} primary>
Done
</Button>
</div>
</div>
</div>
@ -152,4 +190,14 @@
align-items: center;
margin-top: var(--spacing-m);
}
.syntax-error {
color: var(--red);
font-size: 12px;
}
.syntax-error a {
color: var(--red);
text-decoration: underline;
}
</style>

View File

@ -9,7 +9,7 @@
let permissions = []
let selectedRole = {}
let errors = []
let builtInRoles = ['Admin', 'Power', 'Basic', 'Public']
let builtInRoles = ["Admin", "Power", "Basic", "Public"]
$: selectedRoleId = selectedRole._id
$: otherRoles = $backendUiStore.roles.filter(
role => role._id !== selectedRoleId
@ -103,7 +103,11 @@
{/each}
</Select>
{#if selectedRole}
<Input label="Name" bind:value={selectedRole.name} thin disabled={builtInRoles.includes(selectedRole.name)}/>
<Input
label="Name"
bind:value={selectedRole.name}
thin
disabled={builtInRoles.includes(selectedRole.name)} />
<Select
thin
secondary

View File

@ -1,6 +1,7 @@
<script>
import api from "builderStore/api"
import { Button, Select } from "@budibase/bbui"
import download from "downloadjs"
const FORMATS = [
{

View File

@ -41,7 +41,7 @@
.icon {
right: 2px;
top: 2px;
top: 26px;
bottom: 2px;
position: absolute;
align-items: center;

View File

@ -1,59 +1,127 @@
<script>
import groupBy from "lodash/fp/groupBy"
import { Button, TextArea, Drawer, Heading, Spacer } from "@budibase/bbui"
import { Input, TextArea, Heading, Spacer, Label } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { isValid } from "@budibase/string-templates"
import {
getBindableProperties,
readableToRuntimeBinding,
} from "builderStore/dataBinding"
import { currentAsset, store } from "../../../builderStore"
import { handlebarsCompletions } from "constants/completions"
const dispatch = createEventDispatcher()
export let bindableProperties
export let value = ""
export let bindingDrawer
export let valid = true
let originalValue = value
let helpers = handlebarsCompletions()
let getCaretPosition
let search = ""
$: value && checkValid()
$: bindableProperties = getBindableProperties(
$currentAsset.props,
$store.selectedComponentId
)
$: dispatch("update", value)
$: ({ instance, context } = groupBy("type", bindableProperties))
$: searchRgx = new RegExp(search, "ig")
function checkValid() {
// TODO: need to convert the value to the runtime binding
const runtimeBinding = readableToRuntimeBinding(bindableProperties, value)
valid = isValid(runtimeBinding)
}
function addToText(readableBinding) {
value = `${value || ""}{{ ${readableBinding} }}`
const position = getCaretPosition()
const toAdd = `{{ ${readableBinding} }}`
if (position.start) {
value =
value.substring(0, position.start) +
toAdd +
value.substring(position.end, value.length)
} else {
value += toAdd
}
}
let originalValue = value
$: dispatch("update", value)
export function cancel() {
dispatch("update", originalValue)
bindingDrawer.close()
}
$: ({ instance, context } = groupBy("type", bindableProperties))
function updateValue({ detail }) {
value = detail.value
}
</script>
<div class="drawer-contents">
<div class="container" data-cy="binding-dropdown-modal">
<div class="list">
<Input extraThin placeholder="Search" bind:value={search} />
<Spacer medium />
{#if context}
<Heading extraSmall>Columns</Heading>
<Spacer small />
<ul>
{#each context as { readableBinding }}
{#each context.filter(context =>
context.readableBinding.match(searchRgx)
) as { readableBinding }}
<li on:click={() => addToText(readableBinding)}>
{readableBinding}
</li>
{/each}
</ul>
{/if}
<Spacer small />
{#if instance}
<Heading extraSmall>Components</Heading>
<Spacer small />
<ul>
{#each instance as { readableBinding }}
{#each instance.filter(instance =>
instance.readableBinding.match(searchRgx)
) as { readableBinding }}
<li on:click={() => addToText(readableBinding)}>
{readableBinding}
</li>
{/each}
</ul>
{/if}
<Spacer small />
<Heading extraSmall>Helpers</Heading>
<Spacer small />
<ul>
{#each helpers.filter(helper => helper.label.match(searchRgx) || helper.description.match(searchRgx)) as helper}
<li on:click={() => addToText(helper.text)}>
<div>
<Label extraSmall>{helper.displayText}</Label>
<div class="description">
{@html helper.description}
</div>
</div>
</li>
{/each}
</ul>
</div>
<div class="text">
<TextArea
bind:getCaretPosition
thin
bind:value
placeholder="Add text, or click the objects on the left to add them to
the textbox." />
{#if !valid}
<p class="syntax-error">
Current Handlebars syntax is invalid, please check the guide
<a href="https://handlebarsjs.com/guide/">here</a>
for more details.
</p>
{/if}
</div>
</div>
</div>
@ -114,4 +182,27 @@
height: 40vh;
overflow-y: auto;
}
.syntax-error {
padding-top: var(--spacing-m);
color: var(--red);
font-size: 12px;
}
.syntax-error a {
color: var(--red);
text-decoration: underline;
}
.description :global(p) {
color: var(--grey-7);
}
.description :global(p:hover) {
color: var(--ink);
}
.description :global(p a) {
color: var(--grey-7);
}
</style>

View File

@ -21,6 +21,7 @@
let bindingDrawer
let temporaryBindableValue = value
let anchor
let valid
$: bindableProperties = getBindableProperties(
$currentAsset.props,
@ -90,10 +91,11 @@
</Body>
</div>
<heading slot="buttons">
<Button thin blue on:click={handleClose}>Save</Button>
<Button thin blue disabled={!valid} on:click={handleClose}>Save</Button>
</heading>
<div slot="body">
<BindingPanel
bind:valid
value={safeValue}
close={handleClose}
on:update={e => (temporaryBindableValue = e.detail)}

View File

@ -2,6 +2,7 @@
import CodeMirror from "./codemirror"
import { onMount, createEventDispatcher } from "svelte"
import { themeStore } from "builderStore"
import { handlebarsCompletions } from "constants/completions"
const dispatch = createEventDispatcher()
@ -15,6 +16,14 @@
export let lineNumbers = true
export let tab = true
export let mode
// export let parameters = []
let completions = handlebarsCompletions()
// $: completions = parameters.map(param => ({
// text: `{{ ${param.name} }}`,
// displayText: param.name,
// }))
let width
let height
@ -109,6 +118,7 @@
mode: modes[mode] || {
name: mode,
},
readOnly,
autoCloseBrackets: true,
autoCloseTags: true,
@ -136,6 +146,18 @@
}
})
// editor.on("cursorActivity", function() {
// editor.showHint({
// hint: function() {
// return {
// from: editor.getDoc().getCursor(),
// to: editor.getDoc().getCursor(),
// list: completions,
// }
// },
// })
// })
if (first) await sleep(50)
editor.refresh()

View File

@ -1,10 +1,12 @@
import CodeMirror from "codemirror"
import "codemirror/lib/codemirror.css"
import "codemirror/theme/tomorrow-night-eighties.css"
import "codemirror/addon/hint/show-hint.css"
import "codemirror/theme/neo.css"
import "codemirror/mode/sql/sql"
import "codemirror/mode/css/css"
import "codemirror/mode/handlebars/handlebars"
import "codemirror/mode/javascript/javascript"
import "codemirror/addon/hint/show-hint"
export default CodeMirror

View File

@ -28,14 +28,16 @@
mode="sql"
on:change={updateQuery}
readOnly={!editable}
value={query.fields.sql} />
value={query.fields.sql}
parameters={query.parameters} />
{:else if schema.type === QueryTypes.JSON}
<Editor
label="Query"
mode="json"
on:change={updateQuery}
readOnly={!editable}
value={query.fields.json} />
value={query.fields.json}
parameters={query.parameters} />
{:else if schema.type === QueryTypes.FIELDS}
<FieldsBuilder bind:fields={query.fields} {schema} {editable} />
{/if}

View File

@ -0,0 +1,15 @@
import { getManifest } from "@budibase/string-templates"
export function handlebarsCompletions() {
const manifest = getManifest()
return Object.keys(manifest).flatMap(key =>
Object.entries(manifest[key]).map(([helperName, helperConfig]) => ({
text: helperName,
path: helperName,
label: helperName,
displayText: helperName,
description: helperConfig.description,
}))
)
}

File diff suppressed because it is too large Load Diff

View File

@ -26,7 +26,7 @@
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-svelte": "^6.1.1",
"rollup-plugin-terser": "^4.0.4",
"rollup-plugin-terser": "^7.0.2",
"svelte": "^3.30.0",
"svelte-jester": "^1.0.6"
},

View File

@ -2,6 +2,7 @@ import commonjs from "@rollup/plugin-commonjs"
import resolve from "@rollup/plugin-node-resolve"
import builtins from "rollup-plugin-node-builtins"
import svelte from "rollup-plugin-svelte"
import { terser } from "rollup-plugin-terser"
const production = !process.env.ROLLUP_WATCH
@ -25,6 +26,7 @@ export default {
}),
commonjs(),
builtins(),
production && terser(),
],
watch: {
clearScreen: false,

View File

@ -9,6 +9,13 @@
dependencies:
"@babel/highlight" "^7.10.4"
"@babel/code-frame@^7.10.4":
version "7.12.11"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.12.11.tgz#f4ad435aa263db935b8f10f2c552d23fb716a63f"
integrity sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==
dependencies:
"@babel/highlight" "^7.10.4"
"@babel/helper-validator-identifier@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2"
@ -373,7 +380,7 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@^2.19.0:
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
@ -847,6 +854,11 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has-symbols@^1.0.0, has-symbols@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
@ -1097,13 +1109,14 @@ isstream@~0.1.2:
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
jest-worker@^24.0.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-24.9.0.tgz#5dbfdb5b2d322e98567898238a9697bcce67b3e5"
integrity sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==
jest-worker@^26.2.1:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
dependencies:
"@types/node" "*"
merge-stream "^2.0.0"
supports-color "^6.1.0"
supports-color "^7.0.0"
js-tokens@^4.0.0:
version "4.0.0"
@ -1564,7 +1577,7 @@ qs@~6.5.2:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
@ -1744,15 +1757,15 @@ rollup-plugin-svelte@^6.1.1:
rollup-pluginutils "^2.8.2"
sourcemap-codec "^1.4.8"
rollup-plugin-terser@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-4.0.4.tgz#6f661ef284fa7c27963d242601691dc3d23f994e"
integrity sha512-wPANT5XKVJJ8RDUN0+wIr7UPd0lIXBo4UdJ59VmlPCtlFsE20AM+14pe+tk7YunCsWEiuzkDBY3QIkSCjtrPXg==
rollup-plugin-terser@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==
dependencies:
"@babel/code-frame" "^7.0.0"
jest-worker "^24.0.0"
serialize-javascript "^1.6.1"
terser "^3.14.1"
"@babel/code-frame" "^7.10.4"
jest-worker "^26.2.1"
serialize-javascript "^4.0.0"
terser "^5.0.0"
rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.8.1, rollup-pluginutils@^2.8.2:
version "2.8.2"
@ -1795,10 +1808,12 @@ semver@~2.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52"
integrity sha1-uYSPJdbPNjMwc+ye+IVtQvEjPlI=
serialize-javascript@^1.6.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.9.1.tgz#cfc200aef77b600c47da9bb8149c943e798c2fdb"
integrity sha512-0Vb/54WJ6k5v8sSWN09S0ora+Hnr+cX40r9F170nT+mSkaxltoE/7R3OrIdBSUv1OoiobH1QoWQbCnAO+e8J1A==
serialize-javascript@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
dependencies:
randombytes "^2.1.0"
sha.js@^2.4.0, sha.js@^2.4.8:
version "2.4.11"
@ -1823,7 +1838,7 @@ side-channel@^1.0.2:
es-abstract "^1.18.0-next.0"
object-inspect "^1.8.0"
source-map-support@~0.5.10:
source-map-support@~0.5.19:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
@ -1836,6 +1851,11 @@ source-map@^0.6.0, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@~0.7.2:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
sourcemap-codec@^1.4.4, sourcemap-codec@^1.4.8:
version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
@ -1908,12 +1928,12 @@ supports-color@^5.3.0:
dependencies:
has-flag "^3.0.0"
supports-color@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3"
integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==
supports-color@^7.0.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^3.0.0"
has-flag "^4.0.0"
svelte-jester@^1.0.6:
version "1.1.5"
@ -1939,14 +1959,14 @@ symbol-tree@^3.2.4:
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
terser@^3.14.1:
version "3.17.0"
resolved "https://registry.yarnpkg.com/terser/-/terser-3.17.0.tgz#f88ffbeda0deb5637f9d24b0da66f4e15ab10cb2"
integrity sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==
terser@^5.0.0:
version "5.5.1"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289"
integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==
dependencies:
commander "^2.19.0"
source-map "~0.6.1"
source-map-support "~0.5.10"
commander "^2.20.0"
source-map "~0.7.2"
source-map-support "~0.5.19"
tough-cookie@^2.3.3, tough-cookie@~2.5.0:
version "2.5.0"

View File

@ -45,7 +45,7 @@ exports.authenticate = async ctx => {
expiresIn: "1 day",
})
setCookie(ctx, appId, token)
setCookie(ctx, token, appId)
delete dbUser.password
ctx.body = {

View File

@ -49,6 +49,17 @@ exports.serveBuilder = async function(ctx) {
await send(ctx, ctx.file, { root: ctx.devPath || builderPath })
}
exports.serveSelfHostPage = async function(ctx) {
const logo = fs.readFileSync(resolve(__dirname, "selfhost/logo.svg"), "utf8")
const hostingHbs = fs.readFileSync(
resolve(__dirname, "selfhost/index.hbs"),
"utf8"
)
ctx.body = await processString(hostingHbs, {
logo,
})
}
exports.uploadFile = async function(ctx) {
let files
files =

View File

@ -0,0 +1,173 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Budibase self hosting</title>
<style>
body {
font-family: Inter, -apple-system, BlinkMacSystemFont, Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
height: 100%;
width: 100%;
margin: 0;
padding: 0;
background: #fafafa;
}
.main {
padding: 0 20px;
margin: 30px auto;
width: 60%;
}
h2 {
font-size: clamp(24px, 1.5vw, 30px);
text-align: center;
line-height: 1.3;
font-weight: bold;
}
.card-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
}
.card {
display: grid;
background-color: #222222;
grid-template-columns: 1fr;
align-items: center;
padding: 2.5rem 1.75rem;
border-radius: 12px;
color: white;
}
.card h3 {
margin: 0;
font-size: 24px;
font-family: sans-serif;
font-weight: 600;
}
.card h3 b {
text-wrap: normal;
font-size: 36px;
padding-right: 14px;
}
.card p {
color: #ffffff;
opacity: 0.8;
font-size: 18px;
text-align: left;
line-height: 1.3;
margin-top: 1rem;
}
.logo {
width: 60px;
height: 60px;
margin: auto;
}
.top-text {
text-align: center;
color: #707070;
margin: 0 0 1.5rem 0;
}
.button {
cursor: pointer;
display: block;
background: #4285f4;
color: white;
padding: 12px 16px;
font-size: 16px;
font-weight: 600;
border-radius: 6px;
max-width: 120px;
text-align: center;
transition: 200ms background ease;
text-decoration: none;
}
.info {
background: #f5f5f5;
padding: 1rem 1rem 1rem 1rem;
border: #ccc 1px solid;
border-radius: 6px;
margin-top: 40px;
display: flex;
align-items: center;
}
.info p {
margin-left: 20px;
color: #222222;
font-family: sans-serif;
}
.info p {
margin-right: 20px;
}
.info svg {
margin-left: 20px;
}
.info a {
color: #4285f4;
}
</style>
</head>
<body>
<div class="main">
<div class="logo">
{{logo}}
</div>
<h2>Get started with Budibase Self Hosting</h2>
<p class="top-text">Use the address <b id="url"></b> in your Builder</p>
<div class="card-grid">
<div class="card">
<h3><b>📚</b>Documentation</h3>
<p>
Find out more about your self hosted platform.
</p>
<a class="button"
href="https://docs.budibase.com/self-hosting/introduction-to-self-hosting">
Documentation
</a>
</div>
<div class="card">
<h3><b>💻</b>Next steps</h3>
<p>
Find out how to make use of your self hosted Budibase platform.
</p>
<a class="button"
href="https://docs.budibase.com/self-hosting/builder-settings">
Next steps
</a>
</div>
</div>
<div class="info">
<svg preserveAspectRatio="xMidYMid meet" height="28px" width="28px" fill="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" stroke="none" class="icon-7f6730be--text-3f89f380">
<g>
<path d="M12.2 8.98c.06-.01.12-.03.18-.06.06-.02.12-.05.18-.09l.15-.12c.18-.19.29-.45.29-.71 0-.06-.01-.13-.02-.19a.603.603 0 0 0-.06-.19.757.757 0 0 0-.09-.18c-.03-.05-.08-.1-.12-.15-.28-.27-.72-.37-1.09-.21-.13.05-.23.12-.33.21-.04.05-.09.1-.12.15-.04.06-.07.12-.09.18-.03.06-.05.12-.06.19-.01.06-.02.13-.02.19 0 .26.11.52.29.71.1.09.2.16.33.21.12.05.25.08.38.08.06 0 .13-.01.2-.02M13 16v-4a1 1 0 1 0-2 0v4a1 1 0 1 0 2 0M12 3c-4.962 0-9 4.038-9 9 0 4.963 4.038 9 9 9 4.963 0 9-4.037 9-9 0-4.962-4.037-9-9-9m0 20C5.935 23 1 18.065 1 12S5.935 1 12 1c6.066 0 11 4.935 11 11s-4.934 11-11 11" fill-rule="evenodd">
</path>
</g>
</svg>
<p>A <b>Hosting Key</b> will also be required, this can be found in your hosting properties, info found <a href="https://docs.budibase.com/self-hosting/hosting-settings">here</a>.</p>
</div>
</div>
<script>
window.addEventListener("load", () => {
let url = document.URL.split("//")[1]
if (url.substring(url.length - 1) === "/") {
url = url.substring(0, url.length - 1)
}
document.getElementById("url").innerHTML = url
})
</script>
</body>
</html>

View File

@ -0,0 +1,17 @@
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" x="0" y="0" viewBox="0 0 48 48" xml:space="preserve">
<style>
.st0{fill:#393c44}.st1{fill:#fff}
</style>
<path class="st0" d="M-152.17-24.17H-56V72h-96.17z"/>
<path class="st1" d="M-83.19 48h-41.79c-1.76 0-3.19-1.43-3.19-3.19V3.02c0-1.76 1.43-3.19 3.19-3.19h41.79c1.76 0 3.19 1.43 3.19 3.19v41.79c0 1.76-1.43 3.19-3.19 3.19z"/>
<path class="st0" d="M-99.62 12.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V35h-4.89V12.57h4.9zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .17-.39.26-.79.26-1.2z"/>
<path class="st0" d="M-114.76 12.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V35h-4.89V12.57h4.9zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .18-.39.26-.79.26-1.2z"/>
<path d="M44.81 159H3.02c-1.76 0-3.19-1.43-3.19-3.19v-41.79c0-1.76 1.43-3.19 3.19-3.19h41.79c1.76 0 3.19 1.43 3.19 3.19v41.79c0 1.76-1.43 3.19-3.19 3.19z" fill="#4285f4"/>
<path class="st1" d="M28.38 123.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V146h-4.89v-22.43h4.9zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .17-.39.26-.79.26-1.2z"/>
<path class="st1" d="M13.24 123.57v9.94c1.15-1.21 2.59-1.81 4.32-1.81 1.03 0 1.97.19 2.82.58.86.39 1.59.91 2.19 1.57.6.66 1.08 1.43 1.42 2.32.34.89.51 1.84.51 2.85 0 1.03-.18 1.99-.53 2.89-.35.9-.84 1.68-1.47 2.35-.63.67-1.37 1.19-2.23 1.58-.86.39-1.78.58-2.77.58-1.8 0-3.22-.66-4.27-1.97V146H8.35v-22.43h4.89zm6.16 15.54c0-.43-.08-.84-.24-1.23-.16-.39-.39-.72-.68-1.01-.29-.29-.62-.52-1-.69-.38-.17-.79-.26-1.24-.26-.43 0-.84.08-1.22.24-.38.16-.71.39-.99.68-.28.29-.5.63-.68 1.01-.17.39-.26.8-.26 1.23 0 .43.08.84.24 1.22.16.38.39.71.68.99.29.28.63.5 1.01.68.39.17.8.26 1.23.26.43 0 .84-.08 1.22-.24.38-.16.71-.39.99-.68.28-.29.5-.62.68-1 .18-.39.26-.79.26-1.2z"/>
<g>
<path class="st0" d="M44 48H4c-2.21 0-4-1.79-4-4V4c0-2.21 1.79-4 4-4h40c2.21 0 4 1.79 4 4v40c0 2.21-1.79 4-4 4z"/>
<path class="st1" d="M28.48 12v10.44c1.18-1.27 2.65-1.9 4.42-1.9 1.05 0 2.01.2 2.89.61.87.41 1.62.96 2.24 1.65.62.69 1.1 1.5 1.45 2.44.35.94.52 1.93.52 2.99 0 1.08-.18 2.09-.54 3.04-.36.95-.86 1.77-1.51 2.47-.64.7-1.4 1.25-2.28 1.66-.87.4-1.81.6-2.83.6-1.84 0-3.3-.69-4.37-2.07v1.62h-5V12h5.01zm6.3 16.31c0-.45-.08-.88-.25-1.29-.17-.41-.4-.76-.69-1.06-.3-.3-.64-.54-1.02-.72-.39-.18-.81-.27-1.27-.27-.44 0-.86.09-1.24.26-.39.17-.72.41-1.01.71-.29.3-.52.66-.69 1.06-.18.41-.26.84-.26 1.29s.08.88.25 1.28c.17.4.4.74.69 1.04.29.29.64.53 1.04.71.4.18.82.27 1.26.27.44 0 .86-.09 1.24-.26.39-.17.72-.41 1.01-.71.29-.3.52-.65.69-1.05.16-.41.25-.82.25-1.26z"/>
<path class="st1" d="M13 12v10.44c1.18-1.27 2.65-1.9 4.42-1.9 1.05 0 2.01.2 2.89.61.87.41 1.62.96 2.24 1.65.62.69 1.1 1.5 1.45 2.44.35.94.52 1.93.52 2.99 0 1.08-.18 2.09-.54 3.04-.36.95-.86 1.77-1.51 2.47-.64.7-1.4 1.25-2.28 1.66-.87.4-1.81.6-2.82.6-1.84 0-3.3-.69-4.37-2.07v1.62H8V12h5zm6.3 16.31c0-.45-.08-.88-.25-1.29-.17-.41-.4-.76-.69-1.06-.3-.3-.64-.54-1.02-.72-.39-.18-.81-.27-1.27-.27-.44 0-.86.09-1.24.26-.39.17-.72.41-1.01.71-.29.3-.52.66-.69 1.06-.18.41-.26.84-.26 1.29s.08.88.25 1.28c.17.4.4.74.69 1.04.29.29.64.53 1.04.71.4.18.82.27 1.26.27.44 0 .86-.09 1.24-.26.39-.17.72-.41 1.01-.71.29-.3.52-.65.69-1.05.16-.41.25-.82.25-1.26z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@ -65,6 +65,8 @@ for (let route of mainRoutes) {
router.use(staticRoutes.routes())
router.use(staticRoutes.allowedMethods())
router.redirect("/", "/_builder")
if (!env.SELF_HOSTED && !env.CLOUD) {
router.redirect("/", "/_builder")
}
module.exports = router

View File

@ -23,6 +23,10 @@ if (env.NODE_ENV !== "production") {
router.get("/_builder/:file*", controller.serveBuilder)
}
if (env.SELF_HOSTED) {
router.get("/", controller.serveSelfHostPage)
}
router
.post(
"/api/attachments/process",

View File

@ -2,7 +2,13 @@ const jwt = require("jsonwebtoken")
const STATUS_CODES = require("../utilities/statusCodes")
const { getRole, BUILTIN_ROLES } = require("../utilities/security/roles")
const { AuthTypes } = require("../constants")
const { getAppId, getCookieName, setCookie, isClient } = require("../utilities")
const {
getAppId,
getCookieName,
clearCookie,
setCookie,
isClient,
} = require("../utilities")
module.exports = async (ctx, next) => {
if (ctx.path === "/_builder") {
@ -15,16 +21,18 @@ module.exports = async (ctx, next) => {
let appId = getAppId(ctx)
const cookieAppId = ctx.cookies.get(getCookieName("currentapp"))
if (appId && cookieAppId !== appId) {
setCookie(ctx, "currentapp", appId)
setCookie(ctx, appId, "currentapp")
} else if (cookieAppId) {
appId = cookieAppId
}
let token = ctx.cookies.get(getCookieName(appId))
let authType = AuthTypes.APP
if (!token && !isClient(ctx)) {
authType = AuthTypes.BUILDER
let token, authType
if (!isClient(ctx)) {
token = ctx.cookies.get(getCookieName())
authType = AuthTypes.BUILDER
}
if (!token && appId) {
token = ctx.cookies.get(getCookieName(appId))
authType = AuthTypes.APP
}
if (!token) {
@ -49,10 +57,14 @@ module.exports = async (ctx, next) => {
role: await getRole(appId, jwtPayload.roleId),
}
} catch (err) {
// TODO - this can happen if the JWT secret is changed and can never login
// TODO: wipe cookies if they exist
if (authType === AuthTypes.BUILDER) {
clearCookie(ctx)
ctx.status = 200
return
} else {
ctx.throw(err.status || STATUS_CODES.FORBIDDEN, err.text)
}
}
await next()
}

View File

@ -3,7 +3,7 @@ const env = require("../../environment")
const CouchDB = require("../../db")
const jwt = require("jsonwebtoken")
const { DocumentTypes, SEPARATOR } = require("../../db/utils")
const { setCookie } = require("../index")
const { setCookie, clearCookie } = require("../index")
const APP_PREFIX = DocumentTypes.APP + SEPARATOR
module.exports = async (ctx, appId, version) => {
@ -20,13 +20,13 @@ module.exports = async (ctx, appId, version) => {
})
// set the builder token
setCookie(ctx, "builder", token)
setCookie(ctx, "currentapp", appId)
setCookie(ctx, token, "builder")
setCookie(ctx, appId, "currentapp")
// need to clear all app tokens or else unable to use the app in the builder
let allDbNames = await CouchDB.allDbs()
allDbNames.map(dbName => {
if (dbName.startsWith(APP_PREFIX)) {
setCookie(ctx, dbName, "")
clearCookie(ctx, dbName)
}
})
}

View File

@ -111,16 +111,28 @@ exports.getCookieName = (name = "builder") => {
* @param {string} name The name of the cookie to set.
* @param {string|object} value The value of cookie which will be set.
*/
exports.setCookie = (ctx, name, value) => {
exports.setCookie = (ctx, value, name = "builder") => {
const expires = new Date()
expires.setDate(expires.getDate() + 1)
ctx.cookies.set(exports.getCookieName(name), value, {
const cookieName = exports.getCookieName(name)
if (!value) {
ctx.cookies.set(cookieName)
} else {
ctx.cookies.set(cookieName, value, {
expires,
path: "/",
httpOnly: false,
overwrite: true,
})
}
}
/**
* Utility function, simply calls setCookie with an empty string for value
*/
exports.clearCookie = (ctx, name) => {
exports.setCookie(ctx, "", name)
}
exports.isClient = ctx => {

View File

@ -0,0 +1,92 @@
# String templating
This package provides a common system for string templating across the Budibase Builder, client and server.
The templating is provided through the use of [Handlebars](https://handlebarsjs.com/) an extension of Mustache
which is capable of carrying out logic. We have also extended the base Handlebars functionality through the use
of a set of helpers provided through the [handlebars-helpers](https://github.com/helpers/handlebars-helpers) package.
We have not implemented all the helpers provided by the helpers package as some of them provide functionality
we felt would not be beneficial. The following collections of helpers have been implemented:
1. [Math](https://github.com/helpers/handlebars-helpers/tree/master#math) - a set of useful helpers for
carrying out logic pertaining to numbers e.g. `avg`, `add`, `abs` and so on.
2. [Array](https://github.com/helpers/handlebars-helpers/tree/master#array) - some very specific helpers
for use with arrays, useful for example in automations. Helpers like `first`, `last`, `after` and `join`
can be useful for getting particular portions of arrays or turning them into strings.
3. [Number](https://github.com/helpers/handlebars-helpers/tree/master#number) - unlike the math helpers these
are useful for converting numbers into useful formats for display, e.g. `bytes`, `addCommas` and `toPrecision`.
4. [URL](https://github.com/helpers/handlebars-helpers/tree/master#url) - very specific helpers for dealing with URLs,
such as `encodeURI`, `escape`, `stripQueryString` and `stripProtocol`. These are primarily useful
for building up particular URLs to hit as say part of an automation.
5. [String](https://github.com/helpers/handlebars-helpers/tree/master#string) - these helpers are useful for building
strings and preparing them for display, e.g. `append`, `camelcase`, `capitalize` and `ellipsis`.
6. [Comparison](https://github.com/helpers/handlebars-helpers/tree/master#comparison) - these helpers are mainly for
building strings when particular conditions are met, for example `and`, `or`, `gt`, `lt`, `not` and so on. This is a very
extensive set of helpers but is mostly as would be expected from a set of logical operators.
7. [Date](https://github.com/helpers/helper-date) - last but certainly not least is a moment based date helper, which can
format ISO/timestamp based dates into something human-readable. An example of this would be `{{date dateProperty "DD-MM-YYYY"}}`.
## Date formatting
This package uses the standard method for formatting date times, using the following syntax:
| Input | Example | Description |
| ----- | ------- | ----------- |
| YYYY | 2014 | 4 or 2 digit year. Note: Only 4 digit can be parsed on strict mode |
| YY | 14 | 2 digit year |
| Y | -25 | Year with any number of digits and sign |
| Q | 1..4 | Quarter of year. Sets month to first month in quarter. |
| M MM | 1..12 | Month number |
| MMM MMMM | Jan..December | Month name in locale set by moment.locale() |
| D DD | 1..31 | Day of month |
| Do | 1st..31st | Day of month with ordinal |
| DDD DDDD | 1..365 | Day of year |
| X | 1410715640.579 | Unix timestamp |
| x | 1410715640579 | Unix ms timestamp |
## Template format
There are two main ways that the templating system can be used, the first is very similar to that which
would be produced by Mustache - a single statement:
```
Hello I'm building a {{uppercase adjective}} string with Handlebars!
```
In the statement above provided a context of `{adjective: "cool"}` will produce a string of `Hello I'm building a COOL string with Handlebars!`.
Here we can see an example of how string helpers can be used to make a string exactly as we need it. These statements are relatively
simple; we can also stack helpers as such: `{{ uppercase (remove string "bad") }}` with the use of parenthesis.
The other type of statement that can be made with the templating system is conditional ones, that appear as such:
```
Hello I'm building a {{ #gte score "50" }}Great{{ else }}Bad{{ /gte }} string with Handlebars!
```
In this string we can see that the string `Great` or `Bad` will be inserted depending on the state of the
`score` context variable. The comparison, string and array helpers all provide some conditional operators which can be used
in this way. There will also be some operators which will be built with a very similar syntax but will produce an
iterative operation, like a for each - an example of this would be the `forEach` array helper.
## Usage
Usage of this templating package is through one of the primary functions provided by the package - these functions are
as follows:
1. `processString` - `async (string, object)` - given a template string and a context object this will build a string
using our pre-processors, post-processors and handlebars.
2. `processObject` - `async (object, object)` - carries out the functionality provided by `processString` for any string
inside the given object. This will recurse deeply into the provided object so for very large objects this could be slow.
3. `processStringSync` - `(string, object)` - a reduced functionality processing of strings which is synchronous, like
functions provided by Node (e.g. `readdirSync`)
4. `processObjectSync` - `(object, object)` - as with the sync'd string, recurses an object to process it synchronously.
5. `makePropSafe` - `(string)` - some properties cannot be handled by Handlebars, for example `Table 1` is not valid due
to spaces found in the property name. This will update the property name to `[Table 1]` wrapping it in literal
specifiers so that it is safe for use in Handlebars. Ideally this function should be called for every level of an object
being accessed, for example `[Table 1].[property name]` is the syntax that is required for Handlebars.
6. `isValid` - `(string)` - checks the given string for any templates and provides a boolean stating whether it is a valid
template.
7. `getManifest` - returns the manifest JSON which has been generated for the helpers, describing them and their params.
## Development
This library is built with [Rollup](https://rollupjs.org/guide/en/) as many of the packages built by Budibase are. We have
built the string templating package as a UMD so that it can be used by Node and Browser based applications. This package also
builds Typescript stubs which when making use of the library will be used by your IDE to provide code completion. The following
commands are provided for development purposes:
1. `yarn build` - will build the Typescript stubs and the bundle into the `dist` directory.
2. `yarn test` - runs the test suite which will check various helpers are still functioning as
expected and a few expected use cases.
3. `yarn dev:builder` - an internal command which is used by lerna to watch and build any changes
to the package as part of the main `yarn dev` of the repo.
It is also important to note this package is managed in the same manner as all other in the mono-repo,
through lerna.

File diff suppressed because it is too large Load Diff

View File

@ -2,30 +2,35 @@
"name": "@budibase/string-templates",
"version": "0.6.2",
"description": "Handlebars wrapper for Budibase templating.",
"main": "dist/bundle.js",
"module": "dist/bundle.js",
"main": "src/index.js",
"module": "src/index.js",
"browser": "dist/bundle.js",
"license": "AGPL-3.0",
"types": "dist/index.d.ts",
"scripts": {
"build": "tsc && rollup -c",
"dev:builder": "tsc && rollup -cw",
"test": "jest"
"test": "jest",
"manifest": "node ./scripts/gen-collection-info.js"
},
"dependencies": {
"@budibase/handlebars-helpers": "^0.11.1",
"handlebars": "^4.7.6",
"handlebars-helpers": "^0.10.0",
"handlebars-utils": "^1.0.6",
"helper-date": "^1.0.1",
"lodash": "^4.17.20"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^17.1.0",
"@rollup/plugin-json": "^4.1.0",
"doctrine": "^3.0.0",
"jest": "^26.6.3",
"marked": "^1.2.8",
"rollup": "^2.36.2",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-globals": "^1.4.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-terser": "^7.0.2",
"typescript": "^4.1.3"
}
}

View File

@ -1,28 +1,32 @@
import commonjs from "rollup-plugin-commonjs"
import commonjs from "@rollup/plugin-commonjs"
import resolve from "rollup-plugin-node-resolve"
import builtins from "rollup-plugin-node-builtins"
import globals from "rollup-plugin-node-globals"
import json from "@rollup/plugin-json"
import { terser } from "rollup-plugin-terser"
const production = !process.env.ROLLUP_WATCH
export default {
input: "src/index.js",
input: "src/esIndex.js",
output: [
{
sourcemap: true,
format: "umd",
format: "esm",
file: "./dist/bundle.js",
name: "string-templates",
name: "templates",
exports: "named",
},
],
plugins: [
resolve({
mainFields: ["module", "main"],
preferBuiltins: true,
browser: true,
}),
commonjs(),
globals(),
builtins(),
production && terser(),
json(),
],
}

View File

@ -0,0 +1,152 @@
const HELPER_LIBRARY = "@budibase/handlebars-helpers"
const helpers = require(HELPER_LIBRARY)
const { HelperFunctionBuiltin } = require("../src/helpers/constants")
const fs = require("fs")
const doctrine = require("doctrine")
const marked = require("marked")
const DIRECTORY = fs.existsSync("node_modules") ? "." : ".."
const FILENAME = `${DIRECTORY}/manifest.json`
/**
* full list of supported helpers can be found here:
* https://github.com/helpers/handlebars-helpers
*/
const COLLECTIONS = ["math", "array", "number", "url", "string", "comparison"]
const outputJSON = {}
function fixSpecialCases(name, obj) {
const args = obj.args
if (name === "ifNth") {
args[0] = "a"
args[1] = "b"
}
if (name === "eachIndex") {
obj.description = "Iterates the array, listing an item and the index of it."
}
if (name === "toFloat") {
obj.description = "Convert input to a float."
}
if (name === "toInt") {
obj.description = "Convert input to an integer."
}
return obj
}
function lookForward(lines, funcLines, idx) {
const funcLen = funcLines.length
for (let i = idx, j = 0; i < idx + funcLen; ++i, j++) {
if (!lines[i].includes(funcLines[j])) {
return false
}
}
return true
}
function getCommentInfo(file, func) {
const lines = file.split("\n")
const funcLines = func.split("\n")
let comment = null
for (let idx = 0; idx < lines.length; ++idx) {
// from here work back until we have the comment
if (lookForward(lines, funcLines, idx)) {
let fromIdx = idx
let start = 0,
end = 0
do {
if (lines[fromIdx].includes("*/")) {
end = fromIdx
} else if (lines[fromIdx].includes("/*")) {
start = fromIdx
}
if (start && end) {
break
}
fromIdx--
} while (fromIdx > 0)
comment = lines.slice(start, end + 1).join("\n")
}
}
if (comment == null) {
return { description: "" }
}
const docs = doctrine.parse(comment, { unwrap: true })
// some hacky fixes
docs.description = docs.description.replace(/\n/g, " ")
docs.description = docs.description.replace(/[ ]{2,}/g, " ")
docs.description = docs.description.replace(/is is/g, "is")
const example = docs.description.split("```")
if (example.length > 1) {
docs.example = example[1]
}
docs.description = example[0].trim()
return docs
}
/**
* This script is very specific to purpose, parsing the handlebars-helpers files to attempt to get information about them.
*/
function run() {
const foundNames = []
for (let collection of COLLECTIONS) {
const collectionFile = fs.readFileSync(
`${DIRECTORY}/node_modules/${HELPER_LIBRARY}/lib/${collection}.js`,
"utf8"
)
const collectionInfo = {}
// collect information about helper
let hbsHelperInfo = helpers[collection]()
for (let entry of Object.entries(hbsHelperInfo)) {
const name = entry[0]
// skip built in functions and ones seen already
if (
HelperFunctionBuiltin.indexOf(name) !== -1 ||
foundNames.indexOf(name) !== -1
) {
continue
}
foundNames.push(name)
// this is ridiculous, but it parse the function header
const fnc = entry[1].toString()
const jsDocInfo = getCommentInfo(collectionFile, fnc)
let args = jsDocInfo.tags
.filter(tag => tag.title === "param")
.map(
tag =>
tag.description &&
tag.description
.replace(/`/g, "")
.split(" ")[0]
.trim()
)
collectionInfo[name] = fixSpecialCases(name, {
args,
numArgs: args.length,
example: jsDocInfo.example || undefined,
description: jsDocInfo.description,
})
}
outputJSON[collection] = collectionInfo
}
// add the date helper
outputJSON["date"] = {
date: {
args: ["datetime", "format"],
numArgs: 2,
example: '{{date now "YYYY"}}',
description: "Format a date using moment.js data formatting.",
},
}
// convert all markdown to HTML
for (let collection of Object.values(outputJSON)) {
for (let helper of Object.values(collection)) {
helper.description = marked(helper.description)
}
}
fs.writeFileSync(FILENAME, JSON.stringify(outputJSON, null, 2))
}
run()

View File

@ -0,0 +1,12 @@
import templates from "./index"
/**
* This file is simply an entrypoint for rollup - makes a lot of cjs problems go away
*/
export const isValid = templates.isValid
export const makePropSafe = templates.makePropSafe
export const getManifest = templates.getManifest
export const processStringSync = templates.processStringSync
export const processObjectSync = templates.processObjectSync
export const processString = templates.processString
export const processObject = templates.processObject

View File

@ -18,4 +18,7 @@ module.exports.HelperFunctionBuiltin = [
module.exports.HelperFunctionNames = {
OBJECT: "object",
ALL: "all",
LITERAL: "literal",
}
module.exports.LITERAL_MARKER = "%LITERAL%"

View File

@ -1,10 +1,10 @@
const helpers = require("handlebars-helpers")
const helpers = require("@budibase/handlebars-helpers")
const dateHelper = require("helper-date")
const { HelperFunctionBuiltin } = require("./constants")
/**
* full list of supported helpers can be found here:
* https://github.com/helpers/handlebars-helpers
* https://github.com/Budibase/handlebars-helpers
*/
const EXTERNAL_FUNCTION_COLLECTIONS = [
@ -13,7 +13,7 @@ const EXTERNAL_FUNCTION_COLLECTIONS = [
"number",
"url",
"string",
"markdown",
"comparison",
]
const DATE_NAME = "date"

View File

@ -1,7 +1,11 @@
const Helper = require("./Helper")
const { SafeString } = require("handlebars")
const externalHandlebars = require("./external")
const { HelperFunctionNames, HelperFunctionBuiltin } = require("./constants")
const {
HelperFunctionNames,
HelperFunctionBuiltin,
LITERAL_MARKER,
} = require("./constants")
const HTML_SWAPS = {
"<": "&lt;",
@ -27,6 +31,12 @@ const HELPERS = [
return HTML_SWAPS[tag] || tag
})
}),
// adds a note for post-processor
new Helper(HelperFunctionNames.LITERAL, value => {
const type = typeof value
const outputVal = type === "object" ? JSON.stringify(value) : value
return `{{-${LITERAL_MARKER}-${type}-${outputVal}-}}`
}),
]
module.exports.HelperNames = () => {

View File

@ -2,7 +2,8 @@ const handlebars = require("handlebars")
const { registerAll } = require("./helpers/index")
const processors = require("./processors")
const { cloneDeep } = require("lodash/fp")
const { removeNull } = require("./utilities")
const { removeNull, addConstants } = require("./utilities")
const manifest = require("../manifest.json")
const hbsInstance = handlebars.create()
registerAll(hbsInstance)
@ -82,24 +83,52 @@ module.exports.processObjectSync = (object, context) => {
* @returns {string} The enriched string, all templates should have been replaced if they can be.
*/
module.exports.processStringSync = (string, context) => {
const clonedContext = removeNull(cloneDeep(context))
let clonedContext = removeNull(cloneDeep(context))
clonedContext = addConstants(clonedContext)
// remove any null/undefined properties
if (typeof string !== "string") {
throw "Cannot process non-string types."
}
let template
string = processors.preprocess(string)
// this does not throw an error when template can't be fulfilled, have to try correct beforehand
template = hbsInstance.compile(string)
const template = hbsInstance.compile(string)
return processors.postprocess(template(clonedContext))
}
/**
* Errors can occur if a user of this library attempts to use a helper that has not been added to the system, these errors
* can be captured to alert the user of the mistake.
* @param {function} handler a function which will be called every time an error occurs when processing a handlebars
* statement.
* Simple utility function which makes sure that a templating property has been wrapped in literal specifiers correctly.
* @param {string} property The property which is to be wrapped.
* @returns {string} The wrapped property ready to be added to a templating string.
*/
module.exports.errorEvents = handler => {
hbsInstance.registerHelper("helperMissing", handler)
module.exports.makePropSafe = property => {
return `[${property}]`.replace("[[", "[").replace("]]", "]")
}
/**
* Checks whether or not a template string contains totally valid syntax (simply tries running it)
* @param string The string to test for valid syntax - this may contain no templates and will be considered valid.
* @returns {boolean} Whether or not the input string is valid.
*/
module.exports.isValid = string => {
const specialCases = ["isNumber", "expected a number"]
// don't really need a real context to check if its valid
const context = {}
try {
hbsInstance.compile(processors.preprocess(string, false))(context)
return true
} catch (err) {
const msg = err ? err.message : ""
const foundCase = specialCases.find(spCase => msg.includes(spCase))
// special case for maths functions - don't have inputs yet
return !!foundCase
}
}
/**
* We have generated a static manifest file from the helpers that this string templating package makes use of.
* This manifest provides information about each of the helpers and how it can be used.
* @returns The manifest JSON which has been generated from the helpers.
*/
module.exports.getManifest = () => {
return manifest
}

View File

@ -2,23 +2,35 @@ const { FIND_HBS_REGEX } = require("../utilities")
const preprocessor = require("./preprocessor")
const postprocessor = require("./postprocessor")
function process(string, processors) {
function process(output, processors) {
for (let processor of processors) {
// if a literal statement has occurred stop
if (typeof output !== "string") {
break
}
// re-run search each time incase previous processor updated/removed a match
let regex = new RegExp(FIND_HBS_REGEX)
let matches = string.match(regex)
let regexp = new RegExp(FIND_HBS_REGEX)
let matches = output.match(regexp)
if (matches == null) {
continue
}
for (let match of matches) {
string = processor.process(string, match)
output = processor.process(output, match)
}
}
return string
return output
}
module.exports.preprocess = string => {
return process(string, preprocessor.processors)
module.exports.preprocess = (string, finalise = true) => {
let processors = preprocessor.processors
// the pre-processor finalisation stops handlebars from ever throwing an error
// might want to pre-process for other benefits but still want to see errors
if (!finalise) {
processors = processors.filter(
processor => processor.name !== preprocessor.PreprocessorNames.FINALISE
)
}
return process(string, processors)
}
module.exports.postprocess = string => {

View File

@ -1,9 +1,43 @@
const { LITERAL_MARKER } = require("../helpers/constants")
const PostProcessorNames = {
CONVERT_LITERALS: "convert-literals",
}
/* eslint-disable no-unused-vars */
class Postprocessor {
constructor(name, fn) {
this.name = name
this.fn = fn
}
process(statement) {
return this.fn(statement)
}
}
module.exports.processors = []
module.exports.processors = [
new Postprocessor(PostProcessorNames.CONVERT_LITERALS, statement => {
if (!statement.includes(LITERAL_MARKER)) {
return statement
}
const components = statement.split("-")
// pop and shift remove the empty array elements from the first and last dash
components.pop()
components.shift()
const type = components[1]
const value = components[2]
switch (type) {
case "string":
return value
case "number":
return parseFloat(value)
case "boolean":
return value === "true"
case "object":
return JSON.parse(value)
}
return value
}),
]

View File

@ -1,9 +1,11 @@
const { HelperNames } = require("../helpers")
const { swapStrings, isAlphaNumeric } = require("../utilities")
const FUNCTION_CASES = ["#", "else", "/"]
const PreprocessorNames = {
SWAP_TO_DOT: "swap-to-dot-notation",
HANDLE_SPACES: "handle-spaces-in-properties",
FIX_FUNCTIONS: "fix-functions",
FINALISE: "finalise",
}
@ -37,42 +39,16 @@ module.exports.processors = [
return statement
}),
new Preprocessor(PreprocessorNames.HANDLE_SPACES, statement => {
// exclude helpers and brackets, regex will only find double brackets
const exclusions = HelperNames()
// find all the parts split by spaces
const splitBySpaces = statement
.split(" ")
.filter(el => el !== "{{" && el !== "}}")
// remove braces if they are found and weren't spaced out
splitBySpaces[0] = splitBySpaces[0].replace("{", "")
splitBySpaces[splitBySpaces.length - 1] = splitBySpaces[
splitBySpaces.length - 1
].replace("}", "")
// remove the excluded elements
const propertyParts = splitBySpaces.filter(
part => exclusions.indexOf(part) === -1
)
// rebuild to get the full property
const fullProperty = propertyParts.join(" ")
// now work out the dot notation layers and split them up
const propertyLayers = fullProperty.split(".")
// find the layers which need to be wrapped and wrap them
for (let layer of propertyLayers) {
if (layer.indexOf(" ") !== -1) {
statement = swapStrings(
statement,
statement.indexOf(layer),
layer.length,
`[${layer}]`
)
new Preprocessor(PreprocessorNames.FIX_FUNCTIONS, statement => {
for (let specialCase of FUNCTION_CASES) {
const toFind = `{ ${specialCase}`,
replacement = `{${specialCase}`
statement = statement.replace(new RegExp(toFind, "g"), replacement)
}
}
// remove the edge case of double brackets being entered (in-case user already has specified)
return statement.replace(/\[\[/g, "[").replace(/]]/g, "]")
return statement
}),
new Preprocessor(Preprocessor.FINALISE, statement => {
new Preprocessor(PreprocessorNames.FINALISE, statement => {
let insideStatement = statement.slice(2, statement.length - 2)
if (insideStatement.charAt(0) === " ") {
insideStatement = insideStatement.slice(1)
@ -81,9 +57,17 @@ module.exports.processors = [
insideStatement = insideStatement.slice(0, insideStatement.length - 1)
}
const possibleHelper = insideStatement.split(" ")[0]
if (HelperNames().some(option => possibleHelper === option)) {
// function helpers can't be wrapped
for (let specialCase of FUNCTION_CASES) {
if (possibleHelper.includes(specialCase)) {
return statement
}
}
if (HelperNames().some(option => possibleHelper.includes(option))) {
insideStatement = `(${insideStatement})`
}
return `{{ all ${insideStatement} }}`
}),
]
module.exports.PreprocessorNames = PreprocessorNames

View File

@ -1,6 +1,7 @@
const _ = require("lodash")
const ALPHA_NUMERIC_REGEX = /^[A-Za-z0-9]+$/g
module.exports.FIND_HBS_REGEX = /{{([^{}])+}}/g
module.exports.FIND_HBS_REGEX = /{{([^{].*?)}}/g
module.exports.isAlphaNumeric = char => {
return char.match(ALPHA_NUMERIC_REGEX)
@ -12,12 +13,22 @@ module.exports.swapStrings = (string, start, length, swap) => {
// removes null and undefined
module.exports.removeNull = obj => {
return Object.fromEntries(
Object.entries(obj)
.filter(entry => entry[1] != null)
.map(([key, value]) => [
key,
value === Object(value) ? module.exports.removeNull(value) : value,
])
)
obj = _(obj)
.omitBy(_.isUndefined)
.omitBy(_.isNull)
.value()
for (let [key, value] of Object.entries(obj)) {
// only objects
if (typeof value === "object" && !Array.isArray(value)) {
obj[key] = module.exports.removeNull(value)
}
}
return obj
}
module.exports.addConstants = obj => {
if (obj.now == null) {
obj.now = new Date()
}
return obj
}

View File

@ -1,6 +1,9 @@
const {
processObject,
processString,
isValid,
makePropSafe,
getManifest,
} = require("../src/index")
describe("Test that the string processing works correctly", () => {
@ -82,3 +85,28 @@ describe("Test that the object processing works correctly", () => {
expect(error).not.toBeNull()
})
})
describe("check the utility functions", () => {
it("should return false for an invalid template string", () => {
const valid = isValid("{{ table1.thing prop }}")
expect(valid).toBe(false)
})
it("should state this template is valid", () => {
const valid = isValid("{{ thing }}")
expect(valid).toBe(true)
})
it("should make a property safe", () => {
const property = makePropSafe("thing")
expect(property).toEqual("[thing]")
})
})
describe("check manifest", () => {
it("should be able to retrieve the manifest", () => {
const manifest = getManifest()
expect(manifest.math).not.toBeNull()
expect(manifest.math.abs.description).toBe("<p>Return the magnitude of <code>a</code>.</p>\n")
})
})

View File

@ -18,14 +18,14 @@ describe("Handling context properties with spaces in their name", () => {
})
it("should be able to handle a property with a space in its name", async () => {
const output = await processString("hello my name is {{ person name }}", {
const output = await processString("hello my name is {{ [person name] }}", {
"person name": "Mike",
})
expect(output).toBe("hello my name is Mike")
})
it("should be able to handle an object with layers that requires escaping", async () => {
const output = await processString("testcase {{ testing.test case }}", {
const output = await processString("testcase {{ testing.[test case] }}", {
testing: {
"test case": 1
}
@ -44,8 +44,19 @@ describe("attempt some complex problems", () => {
},
},
}
const hbs = "{{ New Repeater.Get Actors.first_name }} {{ New Repeater.Get Actors.last_name }}"
const hbs = "{{ [New Repeater].[Get Actors].[first_name] }} {{ [New Repeater].[Get Actors].[last_name] }}"
const output = await processString(hbs, context)
expect(output).toBe("Bob Bobert")
})
it("should be able to process an odd string produced by builder", async () => {
const context = {
"c306d140d7e854f388bae056db380a0eb": {
"test prop": "test",
}
}
const hbs = "null{{ [c306d140d7e854f388bae056db380a0eb].[test prop] }}"
const output = await processString(hbs, context)
expect(output).toBe("nulltest")
})
})

View File

@ -1,5 +1,6 @@
const {
processString,
isValid,
} = require("../src/index")
describe("test the custom helpers we have applied", () => {
@ -9,5 +10,295 @@ describe("test the custom helpers we have applied", () => {
})
expect(output).toBe("object is {\"a\":1}")
})
})
describe("test the math helpers", () => {
it("should be able to produce an absolute", async () => {
const output = await processString("{{abs a}}", {
a: -10,
})
expect(parseInt(output)).toBe(10)
})
it("should be able to add", async () => {
const output = await processString("{{add a b}}", {
a: 10,
b: 10,
})
expect(parseInt(output)).toBe(20)
})
it("should be able to average", async () => {
const output = await processString("{{avg a b c}}", {
a: 1,
b: 2,
c: 3,
})
expect(parseInt(output)).toBe(2)
})
it("should be able to times", async () => {
const output = await processString("{{times a b}}", {
a: 5,
b: 5,
})
expect(parseInt(output)).toBe(25)
})
})
describe("test the array helpers", () => {
const array = ["hi", "person", "how", "are", "you"]
it("should allow use of the after helper", async () => {
const output = await processString("{{after array 1}}", {
array,
})
expect(output).toBe("person,how,are,you")
})
it("should allow use of the before helper", async () => {
const output = await processString("{{before array 2}}", {
array,
})
expect(output).toBe("hi,person,how")
})
it("should allow use of the filter helper", async () => {
const output = await processString("{{#filter array \"person\"}}THING{{else}}OTHER{{/filter}}", {
array,
})
expect(output).toBe("THING")
})
it("should allow use of the itemAt helper", async () => {
const output = await processString("{{itemAt array 1}}", {
array,
})
expect(output).toBe("person")
})
it("should allow use of the join helper", async () => {
const output = await processString("{{join array \"-\"}}", {
array,
})
expect(output).toBe("hi-person-how-are-you")
})
it("should allow use of the sort helper", async () => {
const output = await processString("{{sort array}}", {
array: ["d", "a", "c", "e"]
})
expect(output).toBe("a,c,d,e")
})
it("should allow use of the unique helper", async () => {
const output = await processString("{{unique array}}", {
array: ["a", "a", "b"]
})
expect(output).toBe("a,b")
})
})
describe("test the number helpers", () => {
it("should allow use of the addCommas helper", async () => {
const output = await processString("{{ addCommas number }}", {
number: 10000000
})
expect(output).toBe("10,000,000")
})
it("should allow use of the phoneNumber helper", async () => {
const output = await processString("{{ phoneNumber number }}", {
number: 4490102030,
})
expect(output).toBe("(449) 010-2030")
})
it("should allow use of the toPrecision helper", async () => {
const output = await processString("{{ toPrecision number 2 }}", {
number: 1.222222222,
})
expect(output).toBe("1.2")
})
it("should allow use of the bytes helper", async () => {
const output = await processString("{{ bytes number }}", {
number: 1000000,
})
expect(output).toBe("1 MB")
})
})
describe("test the url helpers", () => {
const url = "http://example.com?query=1"
it("should allow use of the stripQueryString helper", async () => {
const output = await processString('{{stripQuerystring url }}', {
url,
})
expect(output).toBe("http://example.com")
})
it("should allow use of the stripProtocol helper", async () => {
const output = await processString("{{ stripProtocol url }}", {
url,
})
expect(output).toBe("//example.com/?query=1")
})
it("should allow use of the urlParse helper", async () => {
const output = await processString("{{ object ( urlParse url ) }}", {
url,
})
expect(output).toBe("{\"protocol\":\"http:\",\"slashes\":true,\"auth\":null,\"host\":\"example.com\"," +
"\"port\":null,\"hostname\":\"example.com\",\"hash\":null,\"search\":\"?query=1\"," +
"\"query\":\"query=1\",\"pathname\":\"/\",\"path\":\"/?query=1\"," +
"\"href\":\"http://example.com/?query=1\"}")
})
})
describe("test the date helpers", () => {
it("should allow use of the date helper", async () => {
const date = new Date(1611577535000)
const output = await processString("{{ date time 'YYYY-MM-DD' }}", {
time: date.toISOString(),
})
expect(output).toBe("2021-01-25")
})
it("should allow use of the date helper with now time", async () => {
const date = new Date()
const output = await processString("{{ date now 'DD' }}", {})
expect(parseInt(output)).toBe(date.getDate())
})
})
describe("test the string helpers", () => {
it("should allow use of the append helper", async () => {
const output = await processString("{{ append filename '.txt' }}", {
filename: "yummy",
})
expect(output).toBe("yummy.txt")
})
it("should allow use of the camelcase helper", async () => {
const output = await processString("{{ camelcase camel }}", {
camel: "testing this thing",
})
expect(output).toBe("testingThisThing")
})
it("should allow use of the capitalize helper", async () => {
const output = await processString("{{ capitalize string }}", {
string: "this is a string",
})
expect(output).toBe("This is a string")
})
it("should allow use of the capitalizeAll helper", async () => {
const output = await processString("{{ capitalizeAll string }}", {
string: "this is a string",
})
expect(output).toBe("This Is A String")
})
it("should allow use of the replace helper", async () => {
const output = await processString("{{ replace string 'Mike' name }}", {
string: "Hello my name is Mike",
name: "David",
})
expect(output).toBe("Hello my name is David")
})
it("should allow use of the split helper", async () => {
const output = await processString("{{ first ( split string ' ' ) }}", {
string: "this is a string",
})
expect(output).toBe("this")
})
it("should allow use of the remove helper", async () => {
const output = await processString("{{ remove string 'string' }}", {
string: "this is a string",
})
expect(output).toBe("this is a ")
})
it("should allow use of the startsWith helper", async () => {
const output = await processString("{{ #startsWith 'Hello' string }}Hi!{{ else }}Goodbye!{{ /startsWith }}", {
string: "Hello my name is Mike",
})
expect(output).toBe("Hi!")
})
})
describe("test the comparison helpers", () => {
async function compare(func, a, b) {
const output = await processString(`{{ #${func} a b }}Success{{ else }}Fail{{ /${func} }}`, {
a,
b,
})
expect(output).toBe("Success")
}
it("should allow use of the lt helper", async () => {
await compare("lt", 10, 15)
})
it("should allow use of the gt helper", async () => {
await compare("gt", 15, 10)
})
it("should allow use of the and helper", async () => {
await compare("and", true, true)
})
it("should allow use of the or helper", async () => {
await compare("or", false, true)
})
it("should allow use of gte with a literal value", async () => {
const output = await processString(`{{ #gte a "50" }}s{{ else }}f{{ /gte }}`, {
a: 51,
})
expect(output).toBe("s")
})
})
describe("Test the literal helper", () => {
it("should allow use of the literal specifier for a number", async () => {
const output = await processString(`{{literal a}}`, {
a: 51,
})
expect(output).toBe(51)
})
it("should allow use of the literal specifier for an object", async () => {
const output = await processString(`{{literal a}}`, {
a: {b: 1},
})
expect(output.b).toBe(1)
})
})
describe("Cover a few complex use cases", () => {
it("should allow use of three different collection helpers", async () => {
const output = await processString(`{{ join ( after ( split "My name is: Joe Smith" " " ) 3 ) " " }}`, {})
expect(output).toBe("Joe Smith")
})
it("should allow a complex array case", async () => {
const output = await processString("{{ last ( sort ( unique array ) ) }}", {
array: ["a", "a", "d", "c", "e"]
})
expect(output).toBe("e")
})
it("should make sure case is valid", () => {
const validity = isValid("{{ avg [c355ec2b422e54f988ae553c8acd811ea].[a] [c355ec2b422e54f988ae553c8acd811ea].[b] }}")
expect(validity).toBe(true)
})
it("should be able to solve an example from docs", async () => {
const output = await processString(`{{first ( split "a-b-c" "-") 2}}`, {})
expect(output).toBe(`a,b`)
})
})

View File

@ -270,6 +270,38 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
"@budibase/handlebars-helpers@^0.11.1":
version "0.11.1"
resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.1.tgz#fe8672612fb4ad8fd3bad338ee15759f45e421eb"
integrity sha512-s72LhOhlHk43QYGVcqjogaGa4h4e6y6X1AfshqWBWceTRYBs/9tUnoYDUiRJ/Mt3WoTmx8Hj5GOZekSqrUsOFQ==
dependencies:
arr-flatten "^1.1.0"
array-sort "^0.1.4"
define-property "^1.0.0"
extend-shallow "^3.0.2"
"falsey" "^0.3.2"
for-in "^1.0.2"
for-own "^1.0.0"
get-object "^0.2.0"
get-value "^2.0.6"
handlebars "^4.0.11"
handlebars-utils "^1.0.6"
has-value "^1.0.0"
helper-date "^1.0.1"
helper-markdown "^1.0.0"
helper-md "^0.2.2"
html-tag "^2.0.0"
is-even "^1.0.0"
is-glob "^4.0.0"
is-number "^4.0.0"
kind-of "^6.0.0"
logging-helpers "^1.0.0"
micromatch "^3.1.4"
relative "^3.0.2"
striptags "^3.1.0"
to-gfm-code-block "^0.1.1"
year "^0.2.1"
"@cnakazawa/watch@^1.0.3":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@ -465,6 +497,19 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
"@rollup/plugin-commonjs@^17.1.0":
version "17.1.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz#757ec88737dffa8aa913eb392fade2e45aef2a2d"
integrity sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew==
dependencies:
"@rollup/pluginutils" "^3.1.0"
commondir "^1.0.1"
estree-walker "^2.0.1"
glob "^7.1.6"
is-reference "^1.2.1"
magic-string "^0.25.7"
resolve "^1.17.0"
"@rollup/plugin-json@^4.1.0":
version "4.1.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-json/-/plugin-json-4.1.0.tgz#54e09867ae6963c593844d8bd7a9c718294496f3"
@ -472,7 +517,7 @@
dependencies:
"@rollup/pluginutils" "^3.0.8"
"@rollup/pluginutils@^3.0.8":
"@rollup/pluginutils@^3.0.8", "@rollup/pluginutils@^3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-3.1.0.tgz#706b4524ee6dc8b103b3c995533e5ad680c02b9b"
integrity sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==
@ -1389,6 +1434,16 @@ combined-stream@^1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
commander@^2.20.0:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
component-emitter@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
@ -1441,16 +1496,6 @@ create-ecdh@^4.0.0:
bn.js "^4.1.0"
elliptic "^6.5.3"
create-frame@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/create-frame/-/create-frame-1.0.0.tgz#8b95f2691e3249b6080443e33d0bad9f8f6975aa"
integrity sha1-i5XyaR4ySbYIBEPjPQutn49pdao=
dependencies:
define-property "^0.2.5"
extend-shallow "^2.0.1"
isobject "^3.0.0"
lazy-cache "^2.0.2"
create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
@ -1665,6 +1710,13 @@ diffie-hellman@^5.0.0:
miller-rabin "^4.0.0"
randombytes "^2.0.0"
doctrine@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
dependencies:
esutils "^2.0.2"
domexception@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304"
@ -1781,6 +1833,11 @@ estree-walker@^1.0.1:
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700"
integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==
estree-walker@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
@ -2074,7 +2131,7 @@ getpass@^0.1.1:
dependencies:
assert-plus "^1.0.0"
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@ -2110,48 +2167,6 @@ gulp-header@^1.7.1:
lodash.template "^4.4.0"
through2 "^2.0.0"
handlebars-helper-create-frame@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/handlebars-helper-create-frame/-/handlebars-helper-create-frame-0.1.0.tgz#8aa51d10aeb6408fcc6605d40d77356288487a03"
integrity sha1-iqUdEK62QI/MZgXUDXc1YohIegM=
dependencies:
create-frame "^1.0.0"
isobject "^3.0.0"
handlebars-helpers@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/handlebars-helpers/-/handlebars-helpers-0.10.0.tgz#663d49e718928eafbead1473419ed7bc24bcd45a"
integrity sha512-QiyhQz58u/DbuV41VnfpE0nhy6YCH4vB514ajysV8SoKmP+DxU+pR+fahVyNECHj+jiwEN2VrvxD/34/yHaLUg==
dependencies:
arr-flatten "^1.1.0"
array-sort "^0.1.4"
create-frame "^1.0.0"
define-property "^1.0.0"
"falsey" "^0.3.2"
for-in "^1.0.2"
for-own "^1.0.0"
get-object "^0.2.0"
get-value "^2.0.6"
handlebars "^4.0.11"
handlebars-helper-create-frame "^0.1.0"
handlebars-utils "^1.0.6"
has-value "^1.0.0"
helper-date "^1.0.1"
helper-markdown "^1.0.0"
helper-md "^0.2.2"
html-tag "^2.0.0"
is-even "^1.0.0"
is-glob "^4.0.0"
is-number "^4.0.0"
kind-of "^6.0.0"
lazy-cache "^2.0.2"
logging-helpers "^1.0.0"
micromatch "^3.1.4"
relative "^3.0.2"
striptags "^3.1.0"
to-gfm-code-block "^0.1.1"
year "^0.2.1"
handlebars-utils@^1.0.2, handlebars-utils@^1.0.4, handlebars-utils@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9"
@ -2553,7 +2568,7 @@ is-potential-custom-element-name@^1.0.0:
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397"
integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c=
is-reference@^1.1.2:
is-reference@^1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7"
integrity sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==
@ -3037,7 +3052,7 @@ jest-watcher@^26.6.2:
jest-util "^26.6.2"
string-length "^4.0.1"
jest-worker@^26.6.2:
jest-worker@^26.2.1, jest-worker@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed"
integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==
@ -3176,7 +3191,7 @@ kleur@^3.0.3:
resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
lazy-cache@^2.0.1, lazy-cache@^2.0.2:
lazy-cache@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-2.0.2.tgz#b9190a4f913354694840859f8a8f7084d8822264"
integrity sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=
@ -3371,7 +3386,7 @@ magic-string@^0.22.5:
dependencies:
vlq "^0.2.2"
magic-string@^0.25.2:
magic-string@^0.25.7:
version "0.25.7"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051"
integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==
@ -3404,6 +3419,11 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
marked@^1.2.8:
version "1.2.8"
resolved "https://registry.yarnpkg.com/marked/-/marked-1.2.8.tgz#5008ece15cfa43e653e85845f3525af4beb6bdd4"
integrity sha512-lzmFjGnzWHkmbk85q/ILZjFoHHJIQGF+SxGEfIdGk/XhiTPhqGs37gbru6Kkd48diJnEyYwnG67nru0Z2gQtuQ==
md5.js@^1.3.4:
version "1.3.5"
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
@ -3886,7 +3906,7 @@ qs@~6.5.2:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5:
randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a"
integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==
@ -4074,7 +4094,7 @@ resolve-url@^0.2.1:
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
resolve@^1.10.0, resolve@^1.11.0, resolve@^1.11.1, resolve@^1.18.1:
resolve@^1.10.0, resolve@^1.11.1, resolve@^1.17.0, resolve@^1.18.1:
version "1.19.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
@ -4102,17 +4122,6 @@ ripemd160@^2.0.0, ripemd160@^2.0.1:
hash-base "^3.0.0"
inherits "^2.0.1"
rollup-plugin-commonjs@^10.1.0:
version "10.1.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-commonjs/-/rollup-plugin-commonjs-10.1.0.tgz#417af3b54503878e084d127adf4d1caf8beb86fb"
integrity sha512-jlXbjZSQg8EIeAAvepNwhJj++qJWNJw1Cl0YnOqKtP5Djx+fFGkp3WRh+W0ASCaFG5w1jhmzDxgu3SJuVxPF4Q==
dependencies:
estree-walker "^0.6.1"
is-reference "^1.1.2"
magic-string "^0.25.2"
resolve "^1.11.0"
rollup-pluginutils "^2.8.1"
rollup-plugin-node-builtins@^2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-node-builtins/-/rollup-plugin-node-builtins-2.1.2.tgz#24a1fed4a43257b6b64371d8abc6ce1ab14597e9"
@ -4146,6 +4155,16 @@ rollup-plugin-node-resolve@^5.2.0:
resolve "^1.11.1"
rollup-pluginutils "^2.8.1"
rollup-plugin-terser@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
integrity sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==
dependencies:
"@babel/code-frame" "^7.10.4"
jest-worker "^26.2.1"
serialize-javascript "^4.0.0"
terser "^5.0.0"
rollup-pluginutils@^2.3.1, rollup-pluginutils@^2.8.1:
version "2.8.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"
@ -4236,6 +4255,13 @@ semver@~2.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-2.3.2.tgz#b9848f25d6cf36333073ec9ef8856d42f1233e52"
integrity sha1-uYSPJdbPNjMwc+ye+IVtQvEjPlI=
serialize-javascript@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa"
integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==
dependencies:
randombytes "^2.1.0"
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@ -4351,7 +4377,7 @@ source-map-resolve@^0.5.0:
source-map-url "^0.4.0"
urix "^0.1.0"
source-map-support@^0.5.6:
source-map-support@^0.5.6, source-map-support@~0.5.19:
version "0.5.19"
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
@ -4374,7 +4400,7 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.7.3:
source-map@^0.7.3, source-map@~0.7.2:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
@ -4565,6 +4591,15 @@ terminal-link@^2.0.0:
ansi-escapes "^4.2.1"
supports-hyperlinks "^2.0.0"
terser@^5.0.0:
version "5.5.1"
resolved "https://registry.yarnpkg.com/terser/-/terser-5.5.1.tgz#540caa25139d6f496fdea056e414284886fb2289"
integrity sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==
dependencies:
commander "^2.20.0"
source-map "~0.7.2"
source-map-support "~0.5.19"
test-exclude@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
@ -4732,9 +4767,9 @@ typescript@^4.1.3:
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
uglify-js@^3.1.4:
version "3.12.4"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.4.tgz#93de48bb76bb3ec0fc36563f871ba46e2ee5c7ee"
integrity sha512-L5i5jg/SHkEqzN18gQMTWsZk3KelRsfD1wUVNqtq0kzqWQqcJjyL8yc1o8hJgRrWqrAl2mUFbhfznEIoi7zi2A==
version "3.12.6"
resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.12.6.tgz#f884584fcc42e10bca70db5cb32e8625c2c42535"
integrity sha512-aqWHe3DfQmZUDGWBbabZ2eQnJlQd1fKlMUu7gV+MiTuDzdgDw31bI3wA2jLLsV/hNcDP26IfyEgSVoft5+0SVw==
union-value@^1.0.0:
version "1.0.1"