diff --git a/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte b/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte
index 41245af4f9..984fba9b7a 100644
--- a/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte
+++ b/packages/builder/src/components/common/bindings/EvaluationSidePanel.svelte
@@ -102,7 +102,7 @@
{@html logLine.log}
{#if logLine.line}
- line {logLine.line}
+ :{logLine.line}
{/if}
{/each}
diff --git a/packages/string-templates/src/helpers/javascript.ts b/packages/string-templates/src/helpers/javascript.ts
index a0fdd3cbe5..a1bfb7a824 100644
--- a/packages/string-templates/src/helpers/javascript.ts
+++ b/packages/string-templates/src/helpers/javascript.ts
@@ -1,4 +1,9 @@
-import { atob, isBackendService, isJSAllowed } from "../utilities"
+import {
+ atob,
+ frontendWrapJS,
+ isBackendService,
+ isJSAllowed,
+} from "../utilities"
import { LITERAL_MARKER } from "../helpers/constants"
import { getJsHelperList } from "./list"
import { iifeWrapper } from "../iife"
@@ -117,7 +122,21 @@ export function processJS(handlebars: string, context: any) {
const logs: Log[] = []
// logging only supported on frontend
if (!isBackendService()) {
- const log = (log: string) => logs.push({ log })
+ // this counts the lines in the wrapped JS *before* the user's code, so that we can minus it
+ const jsLineCount = frontendWrapJS(js).split(js)[0].split("\n").length
+ const log = (log: string) => {
+ // quick way to find out what line this is being called from
+ // its an anonymous function and we look for the overall length to find the
+ // line number we care about (from the users function)
+ // JS stack traces are in the format function:line:column
+ const lineNumber = new Error().stack?.match(
+ /:(\d+):\d+/
+ )?.[1]
+ logs.push({
+ log,
+ line: lineNumber ? parseInt(lineNumber) - jsLineCount : undefined,
+ })
+ }
sandboxContext.console = {
log: log,
info: log,
diff --git a/packages/string-templates/src/index.ts b/packages/string-templates/src/index.ts
index 553c0e8861..a21bfdb755 100644
--- a/packages/string-templates/src/index.ts
+++ b/packages/string-templates/src/index.ts
@@ -8,6 +8,7 @@ import {
FIND_ANY_HBS_REGEX,
FIND_HBS_REGEX,
findDoubleHbsInstances,
+ frontendWrapJS,
isBackendService,
prefixStrings,
} from "./utilities"
@@ -511,20 +512,7 @@ export function browserJSSetup() {
setJSRunner((js: string, context: Record) => {
createContext(context)
- const wrappedJs = `
- result = {
- result: null,
- error: null,
- };
-
- try {
- result.result = ${js};
- } catch (e) {
- result.error = e;
- }
-
- result;
- `
+ const wrappedJs = frontendWrapJS(js)
const result = runInNewContext(wrappedJs, context, { timeout: 1000 })
if (result.error) {
diff --git a/packages/string-templates/src/utilities.ts b/packages/string-templates/src/utilities.ts
index 779bef3735..dba1faab17 100644
--- a/packages/string-templates/src/utilities.ts
+++ b/packages/string-templates/src/utilities.ts
@@ -86,3 +86,20 @@ export const prefixStrings = (
const regexPattern = new RegExp(`\\b(${escapedStrings.join("|")})\\b`, "g")
return baseString.replace(regexPattern, `${prefix}$1`)
}
+
+export function frontendWrapJS(js: string) {
+ return `
+ result = {
+ result: null,
+ error: null,
+ };
+
+ try {
+ result.result = ${js};
+ } catch (e) {
+ result.error = e;
+ }
+
+ result;
+ `
+}
diff --git a/packages/string-templates/test/jsLogging.spec.ts b/packages/string-templates/test/jsLogging.spec.ts
new file mode 100644
index 0000000000..9b2bb945d2
--- /dev/null
+++ b/packages/string-templates/test/jsLogging.spec.ts
@@ -0,0 +1,33 @@
+import {
+ processStringWithLogsSync,
+ encodeJSBinding,
+ defaultJSSetup,
+} from "../src/index"
+
+const processJS = (js: string, context?: object) => {
+ return processStringWithLogsSync(encodeJSBinding(js), context)
+}
+
+describe("Javascript", () => {
+ beforeAll(() => {
+ defaultJSSetup()
+ })
+
+ describe("Test logging in JS bindings", () => {
+ it("should execute a simple expression", () => {
+ const output = processJS(
+ `console.log("hello");
+ console.log("world");
+ console.log("foo");
+ return "hello"`
+ )
+ expect(output.result).toBe("hello")
+ expect(output.logs[0].log).toBe("hello")
+ expect(output.logs[0].line).toBe(1)
+ expect(output.logs[1].log).toBe("world")
+ expect(output.logs[1].line).toBe(2)
+ expect(output.logs[2].log).toBe("foo")
+ expect(output.logs[2].line).toBe(3)
+ })
+ })
+})