Repository: redpanda-data/docs-ui
Pull request #289: Update Bloblang playground
Author: login: mihaitodor
Created: 2025-05-12T19:36:08Z | Merged: 2025-05-13T13:11:27Z

- Only update Benthos and Connect automatically
- Make sure we parse the output correctly when the mapping returns a simple byte array
- Make sure we pull in the latest wasm_exec.js
- Update the Readme


Diff:
diff --git a/.github/workflows/update-go-mod.yml b/.github/workflows/update-go-mod.yml
index 87da5f7f..58ef81d5 100644
--- a/.github/workflows/update-go-mod.yml
+++ b/.github/workflows/update-go-mod.yml
@@ -30,7 +30,7 @@ jobs:
           token: ${{ env.ACTIONS_BOT_TOKEN }}
 
       - name: Set up Go
-        uses: actions/setup-go@v4
+        uses: actions/setup-go@v5
         with:
           go-version: stable
 
@@ -39,8 +39,10 @@ jobs:
 
       - name: Update dependencies
         run: |
+          cp $(go env GOROOT)/lib/wasm/wasm_exec.js ./redpanda-docs/src/js/vendor/wasm_exec.js
           cd ./redpanda-docs/blobl-editor/wasm
-          go get -u ./...
+          go get github.com/redpanda-data/benthos/v4
+          go get github.com/redpanda-data/connect/v4
           go mod tidy
 
       - name: Create pull request
diff --git a/.github/workflows/validate-build.yml b/.github/workflows/validate-build.yml
index 83df45cf..d3d2acfe 100644
--- a/.github/workflows/validate-build.yml
+++ b/.github/workflows/validate-build.yml
@@ -9,9 +9,23 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - uses: actions/checkout@v4
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: stable
+      - name: Lint Go code
+        uses: golangci/golangci-lint-action@v8
+        with:
+          working-directory: "./blobl-editor/wasm"
+        env:
+          GOOS: js
+          GOARCH: wasm
+      - name: Ensure wasm_exec.js is up to date
+        run: |
+          diff -q $(go env GOROOT)/lib/wasm/wasm_exec.js ./src/js/vendor/wasm_exec.js &>/dev/null || { >&2 echo "Stale wasm_exec.js detected."; exit 1; }
       - uses: actions/setup-node@v4
         with:
-          node-version: 10
+          node-version: 18
       - name: Bundle UI
         run: |
           npm i
diff --git a/blobl-editor/README.md b/blobl-editor/README.md
index b37568c1..84d0f079 100644
--- a/blobl-editor/README.md
+++ b/blobl-editor/README.md
@@ -1,15 +1,16 @@
 # Bloblang Playground
 
 This directory hosts the source code for the Wasm module that allows the Redpanda docs to execute Bloblang mappings.
-The Wasm module is used in the Bloblang playground defined in [`/src/partials/bloblang-playground.hbs`](/src/partials/bloblang-playground.hbs). 
+The Wasm module is used in the Bloblang playground defined in [`/src/partials/bloblang-playground.hbs`](/src/partials/bloblang-playground.hbs).
 
 The Wasm module is built automatically when you bundle the UI with `gulp bundle`.
 
 To build the Wasm module manually:
 
 ```shell
-GOOS=js GOARCH=wasm go build -o ./src/static/blobl.wasm ./cmd/wasm
-cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./static
+cd ./blobl-editor/wasm
+GOOS=js GOARCH=wasm go build -o ../../src/static/blobl.wasm .
+cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" ../../src/js/vendor
 ```
 
 ## Update Go modules for the Wasm module
diff --git a/blobl-editor/wasm/go.mod b/blobl-editor/wasm/go.mod
index becda80f..a9544b09 100644
--- a/blobl-editor/wasm/go.mod
+++ b/blobl-editor/wasm/go.mod
@@ -1,4 +1,4 @@
-module blobl-playground
+module blobl
 
 go 1.24.1
 
diff --git a/blobl-editor/wasm/main.go b/blobl-editor/wasm/main.go
index 818ae112..155be7f8 100644
--- a/blobl-editor/wasm/main.go
+++ b/blobl-editor/wasm/main.go
@@ -1,206 +1,227 @@
 package main
 
 import (
-    "encoding/json"
-    "fmt"
-    "syscall/js"
+	"encoding/json"
+	"fmt"
+	"syscall/js"
 
-    "github.com/redpanda-data/benthos/v4/public/bloblang"
-    "github.com/redpanda-data/benthos/v4/public/service"
-    _ "github.com/redpanda-data/connect/v4/public/components/pure/extended"
+	"github.com/redpanda-data/benthos/v4/public/bloblang"
+	"github.com/redpanda-data/benthos/v4/public/service"
+	_ "github.com/redpanda-data/connect/v4/public/components/pure/extended"
 )
 
 var globalEnv *bloblang.Environment
 
 func main() {
-    // Initialize the global Bloblang environment
-    globalEnv = createMockEnvironment()
+	// Initialize the global Bloblang environment
+	globalEnv = createMockEnvironment()
 
-    js.Global().Set("blobl", js.FuncOf(blobl))
+	js.Global().Set("blobl", js.FuncOf(blobl))
 
-    // Wait for a signal to shut down
-    select {}
+	// Wait for a signal to shut down
+	select {}
 }
 
-func blobl(_ js.Value, args []js.Value) any {
-    if len(args) < 2 || len(args) > 3 {
-        return fmt.Sprintf("Expected 2 or 3 arguments, received %d instead", len(args))
-    }
-
-    // Parse the mapping
-    mapping, err := globalEnv.Parse(args[0].String())
-    if err != nil {
-        return fmt.Sprintf("Failed to parse mapping: %s", err)
-    }
-
-    // Take the raw data without unmarshaling
-    payloadBytes := []byte(args[1].String())
-
-    // Create the message from the raw bytes
-    msg := service.NewMessage(payloadBytes)
-
-    // Parse the optional metadata (this can still be JSON)
-    metadata := map[string]any{}
-    if len(args) == 3 {
-        if err := json.Unmarshal([]byte(args[2].String()), &metadata); err != nil {
-            return fmt.Sprintf("Failed to parse metadata: %s", err)
-        }
-    }
-
-    // Apply metadata to the message
-    for key, value := range metadata {
-        strValue, ok := value.(string)
-        if !ok {
-            return fmt.Errorf("metadata value for key '%s' must be a string, got %T", key, value)
-        }
-        msg.MetaSet(key, strValue)
-    }
-
-    // Execute the mapping
-    result, err := msg.BloblangQuery(mapping)
-    if err != nil {
-        return fmt.Sprintf("Failed to execute mapping: %s", err)
-    }
-
-    message, err := result.AsStructured()
-    if err != nil {
-        return fmt.Sprintf("Failed to marshal message: %s", err)
-    }
-
-    // Extract metadata
-    var extractedMetadata map[string]any
-    result.MetaWalkMut(func(key string, value any) error {
-        if extractedMetadata == nil {
-            extractedMetadata = make(map[string]any)
-        }
-        extractedMetadata[key] = value
-        return nil
-    })
-
-    output, err := json.MarshalIndent(struct {
-        Msg  any            `json:"msg"`
-        Meta map[string]any `json:"meta,omitempty"`
-    }{
-        Msg:  message,
-        Meta: extractedMetadata,
-    }, "", "  ")
-    if err != nil {
-        return fmt.Sprintf("Failed to marshal output: %s", err)
-    }
-
-    return string(output)
+func blobl(_ js.Value, args []js.Value) (output any) {
+	defer func() {
+		// Make sure we return a string instead of an error
+		if o, ok := output.(error); ok {
+			output = "Error: " + o.Error()
+		}
+	}()
+
+	if len(args) < 2 || len(args) > 3 {
+		return fmt.Errorf("expected 2 or 3 arguments, received %d instead", len(args))
+	}
+
+	// Parse the mapping
+	mapping, err := globalEnv.Parse(args[0].String())
+	if err != nil {
+		return fmt.Errorf("failed to parse mapping: %s", err)
+	}
+
+	// Take the raw data without unmarshaling
+	payloadBytes := []byte(args[1].String())
+
+	// Create the message from the raw bytes
+	msg := service.NewMessage(payloadBytes)
+
+	// Parse the optional metadata (this can still be JSON)
+	metadata := map[string]any{}
+	if len(args) == 3 {
+		if err := json.Unmarshal([]byte(args[2].String()), &metadata); err != nil {
+			return fmt.Errorf("failed to parse metadata: %s", err)
+		}
+	}
+
+	// Apply metadata to the message
+	for key, value := range metadata {
+		strValue, ok := value.(string)
+		if !ok {
+			return fmt.Errorf("metadata value for key '%s' must be a string, got %T", key, value)
+		}
+		msg.MetaSetMut(key, strValue)
+	}
+
+	// Execute the mapping
+	result, err := msg.BloblangQuery(mapping)
+	if err != nil {
+		return fmt.Errorf("failed to execute mapping: %s", err)
+	}
+
+	message, err := result.AsStructured()
+	if err != nil {
+		res, err := result.AsBytes()
+		if err != nil {
+			return fmt.Errorf("failed to extract message: %s", err)
+		}
+		message = string(res)
+	}
+
+	// Extract metadata
+	var extractedMetadata map[string]any
+	if err = result.MetaWalkMut(func(key string, value any) error {
+		if extractedMetadata == nil {
+			extractedMetadata = make(map[string]any)
+		}
+		extractedMetadata[key] = value
+		return nil
+	}); err != nil {
+		return fmt.Errorf("failed to extract metadata: %s", err)
+	}
+
+	payload, err := json.MarshalIndent(struct {
+		Msg  any            `json:"msg"`
+		Meta map[string]any `json:"meta,omitempty"`
+	}{
+		Msg:  message,
+		Meta: extractedMetadata,
+	}, "", "  ")
+	if err != nil {
+		return fmt.Errorf("failed to marshal output: %s", err)
+	}
+
+	return string(payload)
 }
 
 // createMockEnvironment creates a shared Bloblang environment with mocked I/O functions.
 func createMockEnvironment() *bloblang.Environment {
-    env := bloblang.NewEnvironment()
-
-    // Mock `env` function
-    env.RegisterFunction("env", func(args ...any) (bloblang.Function, error) {
-        return func() (any, error) {
-            var name string
-            var noCache bool
-
-            if len(args) == 1 {
-                name, _ = args[0].(string)
-            } else if len(args) == 2 {
-                switch v := args[0].(type) {
-                case string:
-                    name = v
-                    noCache, _ = args[1].(bool)
-                case map[string]any:
-                    name, _ = v["name"].(string)
-                    noCache, _ = v["no_cache"].(bool)
-                default:
-                    return nil, fmt.Errorf("invalid argument format for `env`")
-                }
-            } else {
-                return nil, fmt.Errorf("invalid number of arguments for `env`")
-            }
-
-            mockValues := map[string]string{
-                "key": "mocked_value",
-            }
-            if val, exists := mockValues[name]; exists {
-                if noCache {
-                    return val + " (no cache)", nil
-                }
-                return val, nil
-            }
-            return nil, nil
-        }, nil
-    })
-
-    // Mock `file` function
-    env.RegisterFunction("file", func(args ...any) (bloblang.Function, error) {
-        return func() (any, error) {
-            var path string
-            var noCache bool
-
-            if len(args) == 1 {
-                path, _ = args[0].(string)
-            } else if len(args) == 2 {
-                params, ok := args[0].(map[string]any)
-                if !ok {
-                    return nil, fmt.Errorf("invalid argument format for `file`")
-                }
-                path, _ = params["path"].(string)
-                noCache, _ = params["no_cache"].(bool)
-            } else {
-                return nil, fmt.Errorf("invalid number of arguments for `file`")
-            }
-
-            mockFiles := map[string]string{
-                "/mock/path/file.json": `{"hello": "world"}`,
-            }
-            if content, exists := mockFiles[path]; exists {
-                if noCache {
-                    return content + " (no_cache)", nil
-                }
-                return content, nil
-            }
-            return nil, fmt.Errorf("file not found: %s", path)
-        }, nil
-    })
-
-    // Mock `file_rel` function
-    env.RegisterFunction("file_rel", func(args ...any) (bloblang.Function, error) {
-        return func() (any, error) {
-            var path string
-            var noCache bool
-
-            if len(args) == 1 {
-                path, _ = args[0].(string)
-            } else if len(args) == 2 {
-                params, ok := args[0].(map[string]any)
-                if !ok {
-                    return nil, fmt.Errorf("invalid argument format for `file_rel`")
-                }
-                path, _ = params["path"].(string)
-                noCache, _ = params["no_cache"].(bool)
-            } else {
-                return nil, fmt.Errorf("invalid number of arguments for `file_rel`")
-            }
-
-            mockFiles := map[string]string{
-                "relative/path/file.json": `{"hello": "world"}`,
-            }
-            if content, exists := mockFiles[path]; exists {
-                if noCache {
-                    return content + " (no_cache)", nil
-                }
-                return content, nil
-            }
-            return nil, fmt.Errorf("file not found: %s", path)
-        }, nil
-    })
-
-    // Mock `hostname` function
-    env.RegisterFunction("hostname", func(args ...any) (bloblang.Function, error) {
-        return func() (any, error) {
-            return "mocked-hostname", nil
-        }, nil
-    })
-
-    return env
+	env := bloblang.NewEnvironment()
+
+	// Mock `env` function
+	if err := env.RegisterFunction("env", func(args ...any) (bloblang.Function, error) {
+		return func() (any, error) {
+			var name string
+			var noCache bool
+
+			if len(args) == 1 {
+				name, _ = args[0].(string)
+			} else if len(args) == 2 {
+				switch v := args[0].(type) {
+				case string:
+					name = v
+					noCache, _ = args[1].(bool)
+				case map[string]any:
+					name, _ = v["name"].(string)
+					noCache, _ = v["no_cache"].(bool)
+				default:
+					return nil, fmt.Errorf("invalid argument format for `env`")
+				}
+			} else {
+				return nil, fmt.Errorf("invalid number of arguments for `env`")
+			}
+
+			mockValues := map[string]string{
+				"key": "mocked_value",
+			}
+			if val, exists := mockValues[name]; exists {
+				if noCache {
+					return val + " (no cache)", nil
+				}
+				return val, nil
+			}
+			return nil, nil
+		}, nil
+	}); err != nil {
+		panic(err)
+	}
+
+	// Mock `file` function
+	if err := env.RegisterFunction("file", func(args ...any) (bloblang.Function, error) {
+		return func() (any, error) {
+			var path string
+			var noCache bool
+
+			if len(args) == 1 {
+				path, _ = args[0].(string)
+			} else if len(args) == 2 {
+				params, ok := args[0].(map[string]any)
+				if !ok {
+					return nil, fmt.Errorf("invalid argument format for `file`")
+				}
+				path, _ = params["path"].(string)
+				noCache, _ = params["no_cache"].(bool)
+			} else {
+				return nil, fmt.Errorf("invalid number of arguments for `file`")
+			}
+
+			mockFiles := map[string]string{
+				"/mock/path/file.json": `{"hello": "world"}`,
+			}
+			if content, exists := mockFiles[path]; exists {
+				if noCache {
+					return content + " (no_cache)", nil
+				}
+				return content, nil
+			}
+			return nil, fmt.Errorf("file not found: %s", path)
+		}, nil
+	}); err != nil {
+		panic(err)
+	}
+
+	// Mock `file_rel` function
+	if err := env.RegisterFunction("file_rel", func(args ...any) (bloblang.Function, error) {
+		return func() (any, error) {
+			var path string
+			var noCache bool
+
+			if len(args) == 1 {
+				path, _ = args[0].(string)
+			} else if len(args) == 2 {
+				params, ok := args[0].(map[string]any)
+				if !ok {
+					return nil, fmt.Errorf("invalid argument format for `file_rel`")
+				}
+				path, _ = params["path"].(string)
+				noCache, _ = params["no_cache"].(bool)
+			} else {
+				return nil, fmt.Errorf("invalid number of arguments for `file_rel`")
+			}
+
+			mockFiles := map[string]string{
+				"relative/path/file.json": `{"hello": "world"}`,
+			}
+			if content, exists := mockFiles[path]; exists {
+				if noCache {
+					return content + " (no_cache)", nil
+				}
+				return content, nil
+			}
+			return nil, fmt.Errorf("file not found: %s", path)
+		}, nil
+	}); err != nil {
+		panic(err)
+	}
+
+	// Mock `hostname` function
+	if err := env.RegisterFunction("hostname", func(args ...any) (bloblang.Function, error) {
+		return func() (any, error) {
+			return "mocked-hostname", nil
+		}, nil
+	}); err != nil {
+		panic(err)
+	}
+
+	return env
 }
diff --git a/gulpfile.js b/gulpfile.js
index 906ae1a4..ed2b860e 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -87,7 +87,7 @@ const buildWasmTask = createTask({
       GOARCH: 'wasm',
     }
 
-    const command = `go build -o "${wasmOutputPath}" main.go`
+    const command = `go build -o "${wasmOutputPath}" .`
 
     exec(command, { cwd: wasmDir, env: envVars }, (err, stdout, stderr) => {
       if (err) {
diff --git a/src/js/vendor/wasm_exec.js b/src/js/vendor/wasm_exec.js
index bc6f2102..d71af9e9 100644
--- a/src/js/vendor/wasm_exec.js
+++ b/src/js/vendor/wasm_exec.js
@@ -14,7 +14,7 @@
 	if (!globalThis.fs) {
 		let outputBuf = "";
 		globalThis.fs = {
-			constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
+			constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused
 			writeSync(fd, buf) {
 				outputBuf += decoder.decode(buf);
 				const nl = outputBuf.lastIndexOf("\n");
@@ -73,6 +73,14 @@
 		}
 	}
 
+	if (!globalThis.path) {
+		globalThis.path = {
+			resolve(...pathSegments) {
+				return pathSegments.join("/");
+			}
+		}
+	}
+
 	if (!globalThis.crypto) {
 		throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
 	}
@@ -208,10 +216,16 @@
 				return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
 			}
 
+			const testCallExport = (a, b) => {
+				this._inst.exports.testExport0();
+				return this._inst.exports.testExport(a, b);
+			}
+
 			const timeOrigin = Date.now() - performance.now();
 			this.importObject = {
 				_gotest: {
 					add: (a, b) => a + b,
+					callExport: testCallExport,
 				},
 				gojs: {
 					// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
diff --git a/src/partials/bloblang-playground.hbs b/src/partials/bloblang-playground.hbs
index 88454702..7d85ce7f 100644
--- a/src/partials/bloblang-playground.hbs
+++ b/src/partials/bloblang-playground.hbs
@@ -304,6 +304,9 @@ function execute() {
   aceOutputMetadataEditor.setValue("");
 
   try {
+    if (typeof blobl !== 'function') {
+      throw new Error("WebAssembly module not properly loaded. Please refresh the page.");
+    }
     const result = blobl(getMapping(), getInput(), getInputMetadata());
 
     if (isValidJSON(result)) {
@@ -311,7 +314,7 @@ function execute() {
       const parsedResult = JSON.parse(result);
 
       // Separate message and metadata
-      const message = parsedResult.msg || parsedResult;
+      const message = parsedResult.msg;
       const metadata = parsedResult.meta || {};
 
       // Display message output


Reviews:
user: login: mihaitodor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T19:36:54Z
user: login: mihaitodor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T19:37:36Z
user: login: mihaitodor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T19:42:40Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>gulpfile.js (1)</summary><blockquote>

`90-107`: _⚠️ Potential issue_

**`done()` is invoked twice – Gulp will error out**

Both `exec` calls use the same callback:

1. Copy `wasm_exec.js` → `done()`
2. Build `.wasm` → `done()`

When the first finishes, the task signals completion, but the second will later call `done()` again, causing “`task callback called too many times`”.

```diff
-  exec('cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" ./src/js/vendor', { cwd: __dirname }, (err, stdout, stderr) => {
-    if (err) { …; done(err); return }
-    done()
-  })
-
-  exec(`go build -o "${wasmOutputPath}" .`, { cwd: wasmDir, env: envVars }, (err, stdout, stderr) => {
-    if (err) { …; done(err); return }
-    …
-    done()
-  })
+  exec(
+    'cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" ./src/js/vendor',
+    { cwd: __dirname },
+    (copyErr, _1, copyStderr) => {
+      if (copyErr) {
+        log.error('Error copying wasm_exec.js:', copyStderr);
+        return done(copyErr);
+      }
+      exec(
+        `go build -o "${wasmOutputPath}" .`,
+        { cwd: wasmDir, env: envVars },
+        (buildErr, stdout, stderr) => {
+          if (buildErr) {
+            log.error('Error building WASM:', stderr);
+            return done(buildErr);
+          }
+          log.info('WebAssembly built successfully:', stdout);
+          done(); // called exactly once
+        }
+      );
+    }
+  );
```

While here, consider replacing the shell-specific `cp "$(go env …)"` with a Node copy (`fs.copyFileSync`) plus `child_process.execSync('go env GOROOT')`; this is portable across Windows and *nix.

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>src/partials/bloblang-playground.hbs (1)</summary><blockquote>

`307-309`: **Verify WebAssembly integration.**

The `blobl` function is called here, but there's no clear error handling for when the WebAssembly module isn't properly loaded. Consider adding a check before calling this function.

```diff
-    const result = blobl(getMapping(), getInput(), getInputMetadata());
+    if (typeof blobl !== 'function') {
+      throw new Error("WebAssembly module not properly loaded. Please refresh the page.");
+    }
+    const result = blobl(getMapping(), getInput(), getInputMetadata());
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 3cc0ab82b690127e8345428a56f3cb29ea037adb and 0142fdac11b9a4e1443dca697ec1d9200531f9bb.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `.github/workflows/update-go-mod.yml` (1 hunks)
* `.github/workflows/validate-build.yml` (1 hunks)
* `blobl-editor/README.md` (1 hunks)
* `blobl-editor/wasm/go.mod` (1 hunks)
* `blobl-editor/wasm/main.go` (1 hunks)
* `gulpfile.js` (1 hunks)
* `src/js/vendor/wasm_exec.js` (3 hunks)
* `src/partials/bloblang-playground.hbs` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🪛 actionlint (1.7.4)</summary>

<details>
<summary>.github/workflows/validate-build.yml</summary>

13-13: the runner of "actions/setup-go@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

</details>

</details>
<details>
<summary>🪛 golangci-lint (1.64.8)</summary>

<details>
<summary>blobl-editor/wasm/main.go</summary>

6-6: could not import syscall/js (-: build constraints exclude all Go files in /tmp/go/.go-mod-cache/golang.org/toolchain@v0.0.1-go1.24.2.linux-amd64/src/syscall/js)

(typecheck)

</details>

</details>
<details>
<summary>🪛 ESLint</summary>

<details>
<summary>src/js/vendor/wasm_exec.js</summary>

[error] 17-17: Unexpected tab character.

(no-tabs)

---

[error] 17-17: Expected indentation of 6 spaces but found 3 tabs.

(indent)

---

[error] 76-76: Unexpected tab character.

(no-tabs)

---

[error] 76-76: Expected indentation of 2 spaces but found 1 tab.

(indent)

---

[error] 76-76: 'globalThis' is not defined.

(no-undef)

---

[error] 77-77: Unexpected tab character.

(no-tabs)

---

[error] 77-77: Expected indentation of 4 spaces but found 2 tabs.

(indent)

---

[error] 77-77: 'globalThis' is not defined.

(no-undef)

---

[error] 78-78: Unexpected tab character.

(no-tabs)

---

[error] 78-78: Expected indentation of 6 spaces but found 3 tabs.

(indent)

---

[error] 78-78: Missing space before function parentheses.

(space-before-function-paren)

---

[error] 79-79: Unexpected tab character.

(no-tabs)

---

[error] 79-79: Expected indentation of 8 spaces but found 4 tabs.

(indent)

---

[error] 79-79: Strings must use singlequote.

(quotes)

---

[error] 79-79: Extra semicolon.

(semi)

---

[error] 80-80: Unexpected tab character.

(no-tabs)

---

[error] 80-80: Expected indentation of 6 spaces but found 3 tabs.

(indent)

---

[error] 80-81: Missing trailing comma.

(comma-dangle)

---

[error] 81-81: Unexpected tab character.

(no-tabs)

---

[error] 81-81: Expected indentation of 4 spaces but found 2 tabs.

(indent)

---

[error] 82-82: Unexpected tab character.

(no-tabs)

---

[error] 82-82: Expected indentation of 2 spaces but found 1 tab.

(indent)

---

[error] 219-219: Unexpected tab character.

(no-tabs)

---

[error] 219-219: Expected indentation of 6 spaces but found 3 tabs.

(indent)

---

[error] 220-220: Unexpected tab character.

(no-tabs)

---

[error] 220-220: Expected indentation of 8 spaces but found 4 tabs.

(indent)

---

[error] 220-220: Extra semicolon.

(semi)

---

[error] 221-221: Unexpected tab character.

(no-tabs)

---

[error] 221-221: Expected indentation of 8 spaces but found 4 tabs.

(indent)

---

[error] 221-221: Extra semicolon.

(semi)

---

[error] 222-222: Unexpected tab character.

(no-tabs)

---

[error] 222-222: Expected indentation of 6 spaces but found 3 tabs.

(indent)

---

[error] 224-224: Unexpected tab character.

(no-tabs)

---

[error] 224-224: Expected indentation of 6 spaces but found 3 tabs.

(indent)

---

[error] 224-224: 'performance' is not defined.

(no-undef)

---

[error] 224-224: Extra semicolon.

(semi)

---

[error] 225-225: Unexpected tab character.

(no-tabs)

---

[error] 225-225: Expected indentation of 6 spaces but found 3 tabs.

(indent)

---

[error] 226-226: Unexpected tab character.

(no-tabs)

---

[error] 226-226: Expected indentation of 8 spaces but found 4 tabs.

(indent)

---

[error] 227-227: Unexpected tab character.

(no-tabs)

---

[error] 227-227: Expected indentation of 10 spaces but found 5 tabs.

(indent)

---

[error] 228-228: Unexpected tab character.

(no-tabs)

---

[error] 228-228: Expected indentation of 10 spaces but found 5 tabs.

(indent)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>blobl-editor/wasm/go.mod (3)</summary>

`1-1`: **Module name change looks good.**

The module name has been simplified from `blobl-playground` to `blobl`, which aligns well with the other updates in this PR. The change is simple but provides better consistency across the codebase.

---

`8-10`: **Explicit dependency versioning looks good.**

Direct dependencies on Benthos and Connect are clearly specified, which aligns with the PR objectives to automatically update only these components.

---

`3-5`: <details>
<summary>❓ Verification inconclusive</summary>

**Confirm Go version compatibility with your deployment environment.**

The file specifies Go 1.24.1 with toolchain 1.24.2, which are very recent versions of Go. Make sure your deployment environment supports these versions.

---

🏁 Script executed:

```shell
#!/bin/bash
# Verify Go version compatibility in deployment environments
go version
```

Length of output: 44

---



</details>

**Ensure Go version consistency in go.mod (blobl-editor/wasm/go.mod lines 3–5)**

Your go.mod declares:  
```go
go 1.24.1
toolchain go1.24.2
```  
Local environment reports:  
```
go version go1.24.1 linux/amd64
```

• There’s a mismatch: the `toolchain` directive requires Go 1.24.2, but your local (and possibly CI/deploy) is on 1.24.1.  
• Either upgrade all environments (CI, containers, servers) to Go 1.24.2 to match the directive,  
  or change the `toolchain` line to `go1.24.1` if you intend to stay on that version.

</details>
<details>
<summary>.github/workflows/update-go-mod.yml (1)</summary>

`43-44`: **Improved dependency update strategy.**

The workflow now explicitly updates only the Benthos and Connect packages rather than updating all dependencies recursively. This provides more controlled dependency management and aligns perfectly with the PR objectives.

</details>
<details>
<summary>src/partials/bloblang-playground.hbs (1)</summary>

`314-315`: <details>
<summary>✅ Verification successful</summary>

**Ensure consistent message handling with the backend changes.**

The code now strictly uses `parsedResult.msg` without falling back to the entire `parsedResult` object. While this change aligns with backend updates for more consistent message handling, it could potentially lead to issues if `msg` is not present in certain responses.

---

🏁 Script executed:

```shell
#!/bin/bash
# Look for places where blobl function returns values without a msg property
cd blobl-editor/wasm
grep -r "return" --include="*.go" . | grep -v "msg"
```

Length of output: 1825

---

🏁 Script executed:

```shell
#!/bin/bash
# Extract the relevant JavaScript logic around parsedResult usage
sed -n '250,350p' src/partials/bloblang-playground.hbs
```

Length of output: 3234

---

🏁 Script executed:

```shell
#!/bin/bash
# Find and print context around payload return in WASM main.go
file="blobl-editor/wasm/main.go"
match=$(grep -n "return string(payload)" "$file")
echo "$match"
if [ -n "$match" ]; then
  line=${match%%:*}
  start=$((line-10))
  end=$((line+10))
  sed -n "${start},${end}p" "$file"
fi
```

Length of output: 891

---



</details>

**No changes needed for `msg` handling**  
The WASM backend always includes a top‐level `msg` field (it isn’t tagged `omitempty`), so `parsedResult.msg` will always be defined when `JSON.parse(result)` succeeds. Non‐JSON outputs and errors are handled in separate branches.

</details>
<details>
<summary>.github/workflows/validate-build.yml (1)</summary>

`16-19`: **Go linting integration looks good.**

Adding golangci-lint to validate Go code quality is a great improvement. This ensures the WebAssembly code meets quality standards before building.

</details>
<details>
<summary>blobl-editor/wasm/main.go (2)</summary>

`25-32`: **Nice touch: converting returned errors to strings avoids JS panics**

The deferred conversion guarantees the JS caller always receives a string, simplifying the playground’s error handling. 👍

---

`72-80`: **Graceful fallback from structured → raw bytes is helpful**

The extra `AsBytes()` branch ensures mappings that return plain byte arrays still render correctly, matching the PR objective. Looks good.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-12T19:44:23Z
user: login: JakeSCahill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T06:23:37Z
user: login: JakeSCahill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T06:28:55Z
user: login: JakeSCahill
body: Amazing, thanks for the changes ❤️ 
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T06:29:43Z
user: login: JakeSCahill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T06:43:26Z
user: login: JakeSCahill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T06:43:45Z
user: login: JakeSCahill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T10:00:04Z
user: login: JakeSCahill
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-13T11:24:28Z

Review comments:
diff_hunk: @@ -40,7 +40,8 @@ jobs:
       - name: Update dependencies
         run: |
           cd ./redpanda-docs/blobl-editor/wasm
-          go get -u ./...
+          go get github.com/redpanda-data/benthos/v4
+          go get github.com/redpanda-data/connect/v4
path: .github/workflows/update-go-mod.yml
user: login: mihaitodor
body: I think this should be fine... All the other dependencies are implicit so they should be updated when we update these.
created_at: 2025-05-12T19:36:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,206 +1,217 @@
 package main
 
 import (
-    "encoding/json"
-    "fmt"
-    "syscall/js"
+	"encoding/json"
path: blobl-editor/wasm/main.go
user: login: mihaitodor
body: Somehow, this file had the tabs converted to spaces in a previous PR. Not sure why...
created_at: 2025-05-12T19:37:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -311,7 +311,7 @@ function execute() {
       const parsedResult = JSON.parse(result);
 
       // Separate message and metadata
-      const message = parsedResult.msg || parsedResult;
+      const message = parsedResult.msg;
path: src/partials/bloblang-playground.hbs
user: login: mihaitodor
body: We should get a `msg` field in here if `isValidJSON(result) == true`, but maybe there are some edge cases I'm not thinking of right now... Not sure.

The `parsedResult.msg || parsedResult` approach causes trouble if the mapping is just `root = "foobar"` for example.
created_at: 2025-05-12T19:42:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,6 +9,14 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - uses: actions/checkout@v4
+      - name: Set up Go
+        uses: actions/setup-go@v4
+        with:
+          go-version: stable
path: .github/workflows/validate-build.yml
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Update actions/setup-go compatibility.**

The runner might be too old for actions/setup-go@v4 based on static analysis. Consider updating to a newer runner or checking compatibility.

```diff
-      - uses: actions/checkout@v4
-      - name: Set up Go
-        uses: actions/setup-go@v4
+      - uses: actions/checkout@v4
+      - name: Set up Go
+        uses: actions/setup-go@v5
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      - name: Set up Go
        uses: actions/setup-go@v5
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 actionlint (1.7.4)</summary>

13-13: the runner of "actions/setup-go@v4" action is too old to run on GitHub Actions. update the action's version to fix this issue

(action)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-12T19:44:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,15 +1,16 @@
 # Bloblang Playground
 
 This directory hosts the source code for the Wasm module that allows the Redpanda docs to execute Bloblang mappings.
-The Wasm module is used in the Bloblang playground defined in [`/src/partials/bloblang-playground.hbs`](/src/partials/bloblang-playground.hbs). 
+The Wasm module is used in the Bloblang playground defined in [`/src/partials/bloblang-playground.hbs`](/src/partials/bloblang-playground.hbs).
 
 The Wasm module is built automatically when you bundle the UI with `gulp bundle`.
 
 To build the Wasm module manually:
 
 ```shell
-GOOS=js GOARCH=wasm go build -o ./src/static/blobl.wasm ./cmd/wasm
-cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./static
+cd ./blobl-editor/wasm
+GOOS=js GOARCH=wasm go build -o ../../src/static/blobl.wasm .
+cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" ../../src/static
 ```
path: blobl-editor/README.md
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Inconsistent destination for `wasm_exec.js` between README and build script**

The README instructs users to copy `wasm_exec.js` into `../../src/static`, while `gulpfile.js` now copies the same file into `src/js/vendor`.  Divergent destinations will confuse people who follow the README manually and then run the automated gulp task (or vice-versa), potentially loading two different versions of the runtime.

Please update one of the two locations so they are aligned, and add a short note explaining why the chosen directory is the canonical one.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-12T19:44:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -73,6 +73,14 @@
 		}
 	}
 
+	if (!globalThis.path) {
+		globalThis.path = {
+			resolve(...pathSegments) {
+				return pathSegments.join("/");
+			}
+		}
+	}
path: src/js/vendor/wasm_exec.js
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**`path.resolve` polyfill is too naïve and may break 3rd-party code**

The stub implementation simply joins the segments with “/”.  
Real `path.resolve` normalises “.”/“..”, removes duplicate slashes, and preserves absolute paths.  Any module (or Go code via `syscall/js`) that relies on those semantics will mis-resolve paths in the browser.

```diff
-if (!globalThis.path) {
-  globalThis.path = {
-    resolve(...pathSegments) {
-      return pathSegments.join("/");
-    }
-  }
-}
+// Use a proper browser-friendly polyfill to avoid surprises.
+if (!globalThis.path) {
+  // `path-browserify` is ~3 KB and matches Node semantics.
+  globalThis.path = await import(/* webpackChunkName: "path-polyfill" */ 'path-browserify');
+}
```
Alternatively, write a more complete resolver (handling absolute paths, “..”, etc.).  
This will save you hard-to-debug issues later.



> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 76-76: Unexpected tab character.

(no-tabs)

---

[error] 76-76: Expected indentation of 2 spaces but found 1 tab.

(indent)

---

[error] 76-76: 'globalThis' is not defined.

(no-undef)

---

[error] 77-77: Unexpected tab character.

(no-tabs)

---

[error] 77-77: Expected indentation of 4 spaces but found 2 tabs.

(indent)

---

[error] 77-77: 'globalThis' is not defined.

(no-undef)

---

[error] 78-78: Unexpected tab character.

(no-tabs)

---

[error] 78-78: Expected indentation of 6 spaces but found 3 tabs.

(indent)

---

[error] 78-78: Missing space before function parentheses.

(space-before-function-paren)

---

[error] 79-79: Unexpected tab character.

(no-tabs)

---

[error] 79-79: Expected indentation of 8 spaces but found 4 tabs.

(indent)

---

[error] 79-79: Strings must use singlequote.

(quotes)

---

[error] 79-79: Extra semicolon.

(semi)

---

[error] 80-80: Unexpected tab character.

(no-tabs)

---

[error] 80-80: Expected indentation of 6 spaces but found 3 tabs.

(indent)

---

[error] 80-81: Missing trailing comma.

(comma-dangle)

---

[error] 81-81: Unexpected tab character.

(no-tabs)

---

[error] 81-81: Expected indentation of 4 spaces but found 2 tabs.

(indent)

---

[error] 82-82: Unexpected tab character.

(no-tabs)

---

[error] 82-82: Expected indentation of 2 spaces but found 1 tab.

(indent)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-12T19:44:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -208,10 +216,16 @@
 				return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
 			}
 
+			const testCallExport = (a, b) => {
+				this._inst.exports.testExport0();
+				return this._inst.exports.testExport(a, b);
+			}
+
path: src/js/vendor/wasm_exec.js
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Directly calling `testExport0` / `testExport` without guards risks runtime crashes**

`testCallExport` unconditionally invokes two Wasm exports. If either symbol is absent in a release build (very likely outside a test binary), a TypeError will be thrown the first time Go calls into `_gotest.callExport`, killing the runtime.

```diff
-const testCallExport = (a, b) => {
-  this._inst.exports.testExport0();
-  return this._inst.exports.testExport(a, b);
-}
+const testCallExport = (a, b) => {
+  const { testExport0, testExport } = this._inst.exports;
+  if (typeof testExport !== 'function') {
+    throw new Error('testExport is not available in this WASM build');
+  }
+  if (typeof testExport0 === 'function') {
+    testExport0();
+  }
+  return testExport(a, b);
+};
```

Please either (a) compile these exports in every build, or (b) guard the calls as above to avoid production breakages.



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
			const testCallExport = (a, b) => {
				const { testExport0, testExport } = this._inst.exports;
				if (typeof testExport !== 'function') {
					throw new Error('testExport is not available in this WASM build');
				}
				if (typeof testExport0 === 'function') {
					testExport0();
				}
				return testExport(a, b);
			};
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 219-219: Unexpected tab character.

(no-tabs)

---

[error] 219-219: Expected indentation of 6 spaces but found 3 tabs.

(indent)

---

[error] 220-220: Unexpected tab character.

(no-tabs)

---

[error] 220-220: Expected indentation of 8 spaces but found 4 tabs.

(indent)

---

[error] 220-220: Extra semicolon.

(semi)

---

[error] 221-221: Unexpected tab character.

(no-tabs)

---

[error] 221-221: Expected indentation of 8 spaces but found 4 tabs.

(indent)

---

[error] 221-221: Extra semicolon.

(semi)

---

[error] 222-222: Unexpected tab character.

(no-tabs)

---

[error] 222-222: Expected indentation of 6 spaces but found 3 tabs.

(indent)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-12T19:44:22Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,15 +1,16 @@
 # Bloblang Playground
 
 This directory hosts the source code for the Wasm module that allows the Redpanda docs to execute Bloblang mappings.
-The Wasm module is used in the Bloblang playground defined in [`/src/partials/bloblang-playground.hbs`](/src/partials/bloblang-playground.hbs). 
+The Wasm module is used in the Bloblang playground defined in [`/src/partials/bloblang-playground.hbs`](/src/partials/bloblang-playground.hbs).
 
 The Wasm module is built automatically when you bundle the UI with `gulp bundle`.
 
 To build the Wasm module manually:
 
 ```shell
-GOOS=js GOARCH=wasm go build -o ./src/static/blobl.wasm ./cmd/wasm
-cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./static
+cd ./blobl-editor/wasm
+GOOS=js GOARCH=wasm go build -o ../../src/static/blobl.wasm .
+cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" ../../src/static
path: blobl-editor/README.md
user: login: JakeSCahill
body: ```suggestion
cp "$(go env GOROOT)/lib/wasm/wasm_exec.js" ../../src/js/vendor
```
created_at: 2025-05-13T06:23:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9,6 +9,20 @@ jobs:
     runs-on: ubuntu-22.04
     steps:
       - uses: actions/checkout@v4
+      - name: Set up Go
+        uses: actions/setup-go@v5
+        with:
+          go-version: stable
+      - name: Lint Go code
+        uses: golangci/golangci-lint-action@v8
+        with:
+          working-directory: "./blobl-editor/wasm"
+        env:
+          GOOS: js
+          GOARCH: wasm
+      - name: Ensure wasm_exec.js is up to date
+        run: |
+          diff -q $(go env GOROOT)/lib/wasm/wasm_exec.js ./src/js/vendor/wasm_exec.js &>/dev/null || { >&2 echo "Stale wasm_exec.js detected."; exit 1; }
       - uses: actions/setup-node@v4
         with:
           node-version: 10
path: .github/workflows/validate-build.yml
user: login: JakeSCahill
body: ```suggestion
          node-version: 18
```
created_at: 2025-05-13T06:28:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -311,7 +311,7 @@ function execute() {
       const parsedResult = JSON.parse(result);
 
       // Separate message and metadata
-      const message = parsedResult.msg || parsedResult;
+      const message = parsedResult.msg;
path: src/partials/bloblang-playground.hbs
user: login: JakeSCahill
body: In this case, I'm seeing: `Error: failed to parse mapping: expected query, got: 'foob`
created_at: 2025-05-13T06:43:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -311,7 +311,7 @@ function execute() {
       const parsedResult = JSON.parse(result);
 
       // Separate message and metadata
-      const message = parsedResult.msg || parsedResult;
+      const message = parsedResult.msg;
path: src/partials/bloblang-playground.hbs
user: login: JakeSCahill
body: Do we want to tackle this issue in this PR or should be open a separate Jira?
created_at: 2025-05-13T06:43:45Z
author_association: CONTRIBUTOR
diff_hunk: @@ -311,7 +311,7 @@ function execute() {
       const parsedResult = JSON.parse(result);
 
       // Separate message and metadata
-      const message = parsedResult.msg || parsedResult;
+      const message = parsedResult.msg;
path: src/partials/bloblang-playground.hbs
user: login: JakeSCahill
body: https://deploy-preview-289--docs-ui.netlify.app/playground
created_at: 2025-05-13T10:00:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -311,7 +311,7 @@ function execute() {
       const parsedResult = JSON.parse(result);
 
       // Separate message and metadata
-      const message = parsedResult.msg || parsedResult;
+      const message = parsedResult.msg;
path: src/partials/bloblang-playground.hbs
user: login: JakeSCahill
body: `root = “foobar”` works 🙂

looks like I was using the wrong quotes
created_at: 2025-05-13T11:24:28Z
author_association: CONTRIBUTOR

PR comments:
user: login: netlify[bot]
created_at: 2025-05-12T19:36:13Z
body: ### <span aria-hidden="true">✅</span> Deploy Preview for *docs-ui* ready!


|  Name | Link |
|:-:|------------------------|
|<span aria-hidden="true">🔨</span> Latest commit | 46da9ac51abf7e9e1204d12058c0982dda90ae28 |
|<span aria-hidden="true">🔍</span> Latest deploy log | https://app.netlify.com/sites/docs-ui/deploys/6822eab8bed03f0008b2fea8 |
|<span aria-hidden="true">😎</span> Deploy Preview | [https://deploy-preview-289--docs-ui.netlify.app](https://deploy-preview-289--docs-ui.netlify.app) |
|<span aria-hidden="true">📱</span> Preview on mobile | <details><summary> Toggle QR Code... </summary><br /><br />![QR Code](https://app.netlify.com/qr-code/eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1cmwiOiJodHRwczovL2RlcGxveS1wcmV2aWV3LTI4OS0tZG9jcy11aS5uZXRsaWZ5LmFwcCJ9.Cbxo0y889IzyGCTC2aKpCx9wxLjKbX3hpsXnDeBlb1E)<br /><br />_Use your smartphone camera to open QR code link._</details> |
---
<!-- [docs-ui Preview](https://deploy-preview-289--docs-ui.netlify.app) -->
_To edit notification comments on pull requests, go to your [Netlify site configuration](https://app.netlify.com/sites/docs-ui/configuration/notifications#deploy-webhooks)._
author_association: NONE
user: login: coderabbitai[bot]
created_at: 2025-05-12T19:36:16Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto incremental reviews are disabled on this repository.
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- walkthrough_start -->

<details>
<summary>📝 Walkthrough</summary>

## Walkthrough

This set of changes updates the build, dependency management, and code quality workflows for the Bloblang Playground's WebAssembly (Wasm) module and related frontend integration. The GitHub Actions workflows are modified to target specific Go dependencies for updates and to introduce Go linting. The Wasm module's Go code is refactored for improved error handling and output formatting, and its module name is changed. The frontend JavaScript handling of Wasm output is adjusted, and the build process now copies the Go runtime's `wasm_exec.js` script before building the Wasm binary. Additional polyfills and helper functions are introduced in the local `wasm_exec.js` file.

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant Frontend (JS)
    participant Wasm Module (Go)
    participant GitHub Actions

    User->>Frontend (JS): Initiates Bloblang Playground action
    Frontend (JS)->>Wasm Module (Go): Calls blobl() with args
    Wasm Module (Go)->>Wasm Module (Go): Processes args, applies mapping, handles metadata
    Wasm Module (Go)->>Frontend (JS): Returns JSON result (msg, meta)
    Frontend (JS)->>Frontend (JS): Extracts msg and meta from result
    Frontend (JS)->>User: Displays output

    Note over GitHub Actions: Workflow for build/validation
    GitHub Actions->>GitHub Actions: Checkout, setup Go, update specific dependencies, tidy modules
    GitHub Actions->>GitHub Actions: Run Go linter
    GitHub Actions->>GitHub Actions: Setup Node, bundle UI
```

## Possibly related PRs

- redpanda-data/docs-ui#281: Updates multiple Go dependencies, including one also updated in this PR, focusing on Go module management.
- redpanda-data/docs-ui#280: Updates the same Go module dependencies' versions in the `go.mod` file, related to dependency management for the Bloblang Wasm editor.
- redpanda-data/docs-ui#282: Updates the version of `github.com/redpanda-data/benthos/v4` in the `go.mod` file, related through management of the same dependency.

## Suggested reviewers

- JakeSCahill

</details>

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=redpanda-data/docs-ui&utm_content=289):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR