@budibase/string-templates 3.2.5 → 3.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,168 +0,0 @@
1
- import { atob, isBackendService, isJSAllowed } from "../utilities"
2
- import { LITERAL_MARKER } from "../helpers/constants"
3
- import { getJsHelperList } from "./list"
4
- import { iifeWrapper } from "../iife"
5
- import { JsTimeoutError, UserScriptError } from "../errors"
6
- import { cloneDeep } from "lodash/fp"
7
-
8
- // The method of executing JS scripts depends on the bundle being built.
9
- // This setter is used in the entrypoint (either index.js or index.mjs).
10
- let runJS: ((js: string, context: Record<string, any>) => any) | undefined =
11
- undefined
12
- export const setJSRunner = (runner: typeof runJS) => (runJS = runner)
13
-
14
- export const removeJSRunner = () => {
15
- runJS = undefined
16
- }
17
-
18
- let onErrorLog: (message: Error) => void
19
- export const setOnErrorLog = (delegate: typeof onErrorLog) =>
20
- (onErrorLog = delegate)
21
-
22
- // Helper utility to strip square brackets from a value
23
- const removeSquareBrackets = (value: string) => {
24
- if (!value || typeof value !== "string") {
25
- return value
26
- }
27
- const regex = /\[+(.+)]+/
28
- const matches = value.match(regex)
29
- if (matches && matches[1]) {
30
- return matches[1]
31
- }
32
- return value
33
- }
34
-
35
- const isReservedKey = (key: string) =>
36
- key === "snippets" ||
37
- key === "helpers" ||
38
- key.startsWith("snippets.") ||
39
- key.startsWith("helpers.")
40
-
41
- // Our context getter function provided to JS code as $.
42
- // Extracts a value from context.
43
- const getContextValue = (path: string, context: any) => {
44
- // We populate `snippets` ourselves, don't allow access to it.
45
- if (isReservedKey(path)) {
46
- return undefined
47
- }
48
- const literalStringRegex = /^(["'`]).*\1$/
49
- let data = context
50
- // check if it's a literal string - just return path if its quoted
51
- if (literalStringRegex.test(path)) {
52
- return path.substring(1, path.length - 1)
53
- }
54
- path.split(".").forEach(key => {
55
- if (data == null || typeof data !== "object") {
56
- return null
57
- }
58
- data = data[removeSquareBrackets(key)]
59
- })
60
-
61
- return data
62
- }
63
-
64
- // Evaluates JS code against a certain context
65
- export function processJS(handlebars: string, context: any) {
66
- if (!isJSAllowed() || !runJS) {
67
- throw new Error("JS disabled in environment.")
68
- }
69
- try {
70
- // Wrap JS in a function and immediately invoke it.
71
- // This is required to allow the final `return` statement to be valid.
72
- const js = iifeWrapper(atob(handlebars))
73
-
74
- // Transform snippets into an object for faster access, and cache previously
75
- // evaluated snippets
76
- let snippetMap: any = {}
77
- let snippetCache: any = {}
78
- for (let snippet of context.snippets || []) {
79
- snippetMap[snippet.name] = snippet.code
80
- }
81
-
82
- let clonedContext: Record<string, any>
83
- if (isBackendService()) {
84
- // On the backned, values are copied across the isolated-vm boundary and
85
- // so we don't need to do any cloning here. This does create a fundamental
86
- // difference in how JS executes on the frontend vs the backend, e.g.
87
- // consider this snippet:
88
- //
89
- // $("array").push(2)
90
- // return $("array")[1]
91
- //
92
- // With the context of `{ array: [1] }`, the backend will return
93
- // `undefined` whereas the frontend will return `2`. We should fix this.
94
- clonedContext = context
95
- } else {
96
- clonedContext = cloneDeep(context)
97
- }
98
-
99
- const sandboxContext = {
100
- $: (path: string) => getContextValue(path, clonedContext),
101
- helpers: getJsHelperList(),
102
-
103
- // Proxy to evaluate snippets when running in the browser
104
- snippets: new Proxy(
105
- {},
106
- {
107
- get: function (_, name) {
108
- if (!(name in snippetCache)) {
109
- snippetCache[name] = eval(iifeWrapper(snippetMap[name]))
110
- }
111
- return snippetCache[name]
112
- },
113
- }
114
- ),
115
- }
116
-
117
- // Create a sandbox with our context and run the JS
118
- const res = { data: runJS(js, sandboxContext) }
119
- return `{{${LITERAL_MARKER} js_result-${JSON.stringify(res)}}}`
120
- } catch (error: any) {
121
- onErrorLog && onErrorLog(error)
122
-
123
- const { noThrow = true } = context.__opts || {}
124
-
125
- // The error handling below is quite messy, because it has fallen to
126
- // string-templates to handle a variety of types of error specific to usages
127
- // above it in the stack. It would be nice some day to refactor this to
128
- // allow each user of processStringSync to handle errors in the way they see
129
- // fit.
130
-
131
- // This is to catch the error vm.runInNewContext() throws when the timeout
132
- // is exceeded.
133
- if (error.code === "ERR_SCRIPT_EXECUTION_TIMEOUT") {
134
- return "Timed out while executing JS"
135
- }
136
-
137
- // This is to catch the JsRequestTimeoutError we throw when we detect a
138
- // timeout across an entire request in the backend. We use a magic string
139
- // because we can't import from the backend into string-templates.
140
- if (error.code === "JS_REQUEST_TIMEOUT_ERROR") {
141
- return error.message
142
- }
143
-
144
- // This is to catch the JsTimeoutError we throw when we detect a timeout in
145
- // a single JS execution.
146
- if (error.code === JsTimeoutError.code) {
147
- return JsTimeoutError.message
148
- }
149
-
150
- // This is to catch the error that happens if a user-supplied JS script
151
- // throws for reasons introduced by the user.
152
- if (error.code === UserScriptError.code) {
153
- if (noThrow) {
154
- return error.userScriptError.toString()
155
- }
156
- throw error
157
- }
158
-
159
- if (error.name === "SyntaxError") {
160
- if (noThrow) {
161
- return error.toString()
162
- }
163
- throw error
164
- }
165
-
166
- return "Error while executing JS"
167
- }
168
- }
@@ -1,72 +0,0 @@
1
- import { date, duration } from "./date"
2
-
3
- /*
4
- @budibase/handlebars-helpers is not treeshakeable, so we can't use the barrel files.
5
- Otherwise, we have issues when generating the isolated-vm bundle because of the treeshaking
6
- */
7
- /* eslint-disable local-rules/no-budibase-imports */
8
- // @ts-expect-error
9
- import math from "@budibase/handlebars-helpers/lib/math"
10
- // @ts-expect-error
11
- import array from "@budibase/handlebars-helpers/lib/array"
12
- // @ts-expect-error
13
- import number from "@budibase/handlebars-helpers/lib/number"
14
- // @ts-expect-error
15
- import url from "@budibase/handlebars-helpers/lib/url"
16
- // @ts-expect-error
17
- import string from "@budibase/handlebars-helpers/lib/string"
18
- // @ts-expect-error
19
- import comparison from "@budibase/handlebars-helpers/lib/comparison"
20
- // @ts-expect-error
21
- import object from "@budibase/handlebars-helpers/lib/object"
22
- // @ts-expect-error
23
- import regex from "@budibase/handlebars-helpers/lib/regex"
24
- // @ts-expect-error
25
- import uuid from "@budibase/handlebars-helpers/lib/uuid"
26
- /* eslint-enable local-rules/no-budibase-imports */
27
-
28
- // https://github.com/evanw/esbuild/issues/56
29
- const externalCollections = {
30
- math,
31
- array,
32
- number,
33
- url,
34
- string,
35
- comparison,
36
- object,
37
- regex,
38
- uuid,
39
- }
40
-
41
- export const helpersToRemoveForJs = ["sortBy"]
42
-
43
- const addedHelpers = {
44
- date: date,
45
- duration: duration,
46
- }
47
-
48
- let helpers: Record<string, any>
49
-
50
- export function getJsHelperList() {
51
- if (helpers) {
52
- return helpers
53
- }
54
-
55
- helpers = {}
56
- for (let collection of Object.values(externalCollections)) {
57
- for (let [key, func] of Object.entries<any>(collection)) {
58
- // Handlebars injects the hbs options to the helpers by default. We are adding an empty {} as a last parameter to simulate it
59
- helpers[key] = (...props: any) => func(...props, {})
60
- }
61
- }
62
- helpers = {
63
- ...helpers,
64
- ...addedHelpers,
65
- }
66
-
67
- for (const toRemove of helpersToRemoveForJs) {
68
- delete helpers[toRemove]
69
- }
70
- Object.freeze(helpers)
71
- return helpers
72
- }
package/src/iife.ts DELETED
@@ -1,3 +0,0 @@
1
- export const iifeWrapper = (script: string) => {
2
- return `(function(){\n${script}\n})();`
3
- }