diff --git a/.github/workflows/build-cli-binaries.yml b/.github/workflows/build-cli-binaries.yml
new file mode 100644
index 0000000000..3b16809358
--- /dev/null
+++ b/.github/workflows/build-cli-binaries.yml
@@ -0,0 +1,259 @@
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+name: Build CLI Binaries
+
+on:
+ push:
+ branches:
+ - '@invertase/cli-binary'
+ workflow_dispatch:
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ include:
+ - os: ubuntu-latest
+ target: linux-x64
+ - os: ubuntu-24.04-arm
+ target: linux-arm64
+ - os: macos-13 # x64/Intel
+ target: darwin-x64
+ - os: macos-latest # ARM64/M1
+ target: darwin-arm64
+ - os: windows-latest
+ target: win32-x64
+ # Note: Windows ARM64 currently runs x64 binaries through emulation
+ # Native ARM64 support is not yet available in Bun
+ # See: https://github.com/oven-sh/bun/pull/11430
+ # - os: windows-11-arm
+ # target: win32-arm64
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup Bun
+ uses: oven-sh/setup-bun@v2
+ with:
+ bun-version: latest
+
+ - name: Setup pnpm
+ uses: pnpm/action-setup@v3
+ with:
+ version: 10.11.0
+
+ - name: Setup Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'pnpm'
+
+ - name: Install root dependencies
+ run: pnpm i
+
+ - name: Install genkit-tools dependencies
+ run: pnpm pnpm-install-genkit-tools
+
+ - name: Build genkit-tools
+ run: pnpm build:genkit-tools
+
+ - name: Set binary extension
+ id: binary
+ shell: bash
+ run: |
+ if [[ "${{ matrix.target }}" == win32-* ]]; then
+ echo "ext=.exe" >> $GITHUB_OUTPUT
+ else
+ echo "ext=" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Compile CLI binary
+ shell: bash
+ run: |
+ cd genkit-tools/cli
+ pnpm compile:bun
+
+ # Handle the binary name based on OS
+ if [[ "${{ matrix.os }}" == windows-* ]]; then
+ # On Windows, Bun outputs genkit.exe
+ mv dist/bin/genkit.exe "dist/bin/genkit-${{ matrix.target }}.exe"
+ else
+ # On Unix-like systems, no extension
+ mv dist/bin/genkit "dist/bin/genkit-${{ matrix.target }}"
+ fi
+
+ - name: Upload binary artifact
+ uses: actions/upload-artifact@v4
+ with:
+ name: genkit-${{ matrix.target }}
+ path: genkit-tools/cli/dist/bin/genkit-${{ matrix.target }}${{ steps.binary.outputs.ext }}
+ retention-days: 1 # TODO: Consider increasing to 7 days for better debugging capability
+
+ test:
+ needs: build
+ strategy:
+ matrix:
+ include:
+ - os: ubuntu-latest
+ target: linux-x64
+ - os: ubuntu-24.04-arm
+ target: linux-arm64
+ - os: macos-13
+ target: darwin-x64
+ - os: macos-latest
+ target: darwin-arm64
+ - os: windows-latest
+ target: win32-x64
+
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - name: Set binary extension
+ id: binary
+ shell: bash
+ run: |
+ if [[ "${{ matrix.target }}" == win32-* ]]; then
+ echo "ext=.exe" >> $GITHUB_OUTPUT
+ else
+ echo "ext=" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Download binary artifact
+ uses: actions/download-artifact@v4
+ with:
+ name: genkit-${{ matrix.target }}
+ path: ./
+
+ - name: Make binary executable (Unix)
+ if: runner.os != 'Windows'
+ run: chmod +x genkit-${{ matrix.target }}
+
+ - name: Test --help command
+ shell: bash
+ run: |
+ echo "Testing genkit --help"
+ ./genkit-${{ matrix.target }}${{ steps.binary.outputs.ext }} --help
+
+ - name: Test --version command
+ shell: bash
+ run: |
+ echo "Testing genkit --version"
+ ./genkit-${{ matrix.target }}${{ steps.binary.outputs.ext }} --version
+
+ - name: Verify UI commands exist
+ shell: bash
+ run: |
+ echo "Verifying UI commands are available"
+ ./genkit-${{ matrix.target }}${{ steps.binary.outputs.ext }} ui:start --help
+ ./genkit-${{ matrix.target }}${{ steps.binary.outputs.ext }} ui:stop --help
+
+ - name: Test UI start functionality (Unix only)
+ if: runner.os != 'Windows'
+ shell: bash
+ run: |
+ echo "Testing genkit ui:start"
+
+ # Start UI in background, piping any prompts to accept them
+ (echo "" | ./genkit-${{ matrix.target }} ui:start 2>&1 | tee ui_output.log) &
+ UI_PID=$!
+
+ # Give it time to start
+ sleep 5
+
+ # Check if it started successfully by looking for the expected output
+ if grep -q "Genkit Developer UI started at:" ui_output.log 2>/dev/null; then
+ echo "✓ UI started successfully"
+ cat ui_output.log
+
+ # Try to stop it gracefully
+ echo "Testing genkit ui:stop"
+ ./genkit-${{ matrix.target }} ui:stop || true
+
+ # Give it time to stop
+ sleep 2
+ else
+ echo "UI output:"
+ cat ui_output.log 2>/dev/null || echo "No output captured"
+
+ # Check if process is still running
+ if ps -p $UI_PID > /dev/null 2>&1; then
+ echo "Process is running but didn't produce expected output"
+ kill $UI_PID 2>/dev/null || true
+ else
+ echo "Process exited (might be due to cookie prompt or missing project)"
+ fi
+ fi
+
+ # Clean up any remaining processes
+ pkill -f "genkit.*ui:start" 2>/dev/null || true
+
+ - name: Test UI start functionality (Windows only)
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ Write-Host "Testing genkit ui:start"
+
+ # Create empty input file first for redirecting stdin
+ "" | Out-File -FilePath ".\empty.txt"
+
+ # Start UI in background, redirecting input to handle prompts
+ $process = Start-Process -FilePath ".\genkit-${{ matrix.target }}.exe" `
+ -ArgumentList "ui:start" `
+ -RedirectStandardInput ".\empty.txt" `
+ -RedirectStandardOutput ".\ui_output.log" `
+ -RedirectStandardError ".\ui_error.log" `
+ -PassThru `
+ -NoNewWindow
+
+ # Give it time to start
+ Start-Sleep -Seconds 5
+
+ # Read the output
+ $output = Get-Content ".\ui_output.log" -ErrorAction SilentlyContinue
+ $errorOutput = Get-Content ".\ui_error.log" -ErrorAction SilentlyContinue
+
+ if ($output -match "Genkit Developer UI started at:") {
+ Write-Host "✓ UI started successfully"
+ Write-Host "Output:"
+ $output | Write-Host
+
+ # Try to stop it gracefully
+ Write-Host "Testing genkit ui:stop"
+ & ".\genkit-${{ matrix.target }}.exe" ui:stop
+
+ Start-Sleep -Seconds 2
+ } else {
+ Write-Host "UI did not start as expected"
+ Write-Host "Output:"
+ $output | Write-Host
+ Write-Host "Error:"
+ $errorOutput | Write-Host
+
+ # Check if process is still running
+ if (-not $process.HasExited) {
+ Write-Host "Process is still running, terminating..."
+ Stop-Process -Id $process.Id -Force -ErrorAction SilentlyContinue
+ } else {
+ Write-Host "Process exited (might be due to cookie prompt or missing project)"
+ }
+ }
+
+ # Clean up any remaining genkit processes
+ Get-Process | Where-Object { $_.ProcessName -match "genkit" } | Stop-Process -Force -ErrorAction SilentlyContinue
\ No newline at end of file
diff --git a/bin/install_cli b/bin/install_cli
new file mode 100644
index 0000000000..35873ec603
--- /dev/null
+++ b/bin/install_cli
@@ -0,0 +1,349 @@
+#!/usr/bin/env bash
+# Copyright 2025 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# SPDX-License-Identifier: Apache-2.0
+
+
+##
+##
+##
+##
+##
+
+# Configuration variables
+DOMAIN="genkit.tools"
+TRACKING_ID="UA-XXXXXXXXX-X" # Not used when analytics is commented out
+
+: ==========================================
+: Introduction
+: ==========================================
+
+# This script allows you to install the latest version of the
+# "genkit" command by running:
+#
+: curl -sL $DOMAIN | bash
+#
+# If you do not want to use this script, you can manually
+# download the latest "genkit" binary.
+#
+: curl -Lo ./genkit_bin https://$DOMAIN/bin/linux/latest
+#
+# Alternatively, you can download a specific version.
+#
+: curl -Lo ./genkit_bin https://$DOMAIN/bin/linux/v1.12.0
+#
+# Note: On Mac, replace "linux" with "macos" in the URL.
+#
+# For full details about installation options for the Genkit CLI
+# please see our documentation.
+# https://firebase.google.com/docs/genkit/
+#
+# Please report bugs / issues with this script on GitHub.
+# https://github.com/firebase/genkit
+#
+
+: ==========================================
+: Advanced Usage
+: ==========================================
+
+# The behavior of this script can be modified at runtime by passing environmental
+# variables to the `bash` process.
+#
+# For example, passing an argument called arg1 set to true and one called arg2 set
+# to false would look like this.
+#
+: curl -sL $DOMAIN | arg1=true arg2=false bash
+#
+# These arguments are optional, but be aware that explicitly setting them will help
+# ensure consistent behavior if / when defaults are changed.
+#
+
+: -----------------------------------------
+: Upgrading - default: false
+: -----------------------------------------
+
+# By default, this script will not replace an existing "genkit" install.
+# If you'd like to upgrade an existing install, set the "upgrade" variable to true.
+#
+: curl -sL $DOMAIN | upgrade=true bash
+#
+# This operation could (potentially) break an existing install, so use it with caution.
+#
+
+: -----------------------------------------
+: Uninstalling - default false
+: -----------------------------------------
+
+# You can remove the binary by passing the "uninstall" flag.
+#
+: curl -sL $DOMAIN | uninstall=true bash
+#
+# This will remove the binary file and any cached data.
+#
+
+: -----------------------------------------
+: Analytics - default true
+: -----------------------------------------
+
+# This script reports anonymous success / failure analytics.
+# You can disable this reporting by setting the "analytics" variable to false.
+#
+: curl -sL $DOMAIN | analytics=false bash
+#
+# By default we report all data anonymously and do not collect any information
+# except platform type (Darwin, Win, etc) in the case of an unsupported platform
+# error.
+#
+
+: ==========================================
+: Source Code
+: ==========================================
+
+# This script contains a large amount of comments so you can understand
+# how it interacts with your system. If you're not interested in the
+# technical details, you can just run the command above.
+
+# We begin by generating a unique ID for tracking the anonymous session.
+CID=$(head -80 /dev/urandom | LC_ALL=c tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1)
+# Credit: https://gist.github.com/earthgecko/3089509
+
+# We can use this CID in all calls to the Google Analytics endpoint via
+# this reusable function.
+send_analytics_event()
+{
+ # Analytics tracking is currently disabled
+ # Uncomment the block below to enable analytics
+
+ # if [ ! "$analytics" = "false" ]; then
+ # curl -s https://www.google-analytics.com/collect \
+ # -d "tid=$TRACKING_ID" \
+ # -d "t=event" \
+ # -d "ec=$DOMAIN" \
+ # -d "ea=$1" \
+ # -d "v=1" \
+ # -d "cid=$CID" \
+ # -o /dev/null
+ # fi
+
+ # For now, just return success
+ return 0
+}
+
+# We send one event to count the number of times this script is ran. At the
+# end we also report success / failure, but it's possible that the script
+# will crash before we get to that point, so we manually count invocations here.
+send_analytics_event start
+
+# We try to detect any existing binaries on $PATH or two common locations.
+GENKIT_BINARY=${GENKIT_BINARY:-$(which genkit)}
+LOCAL_BINARY="$HOME/.local/bin/genkit"
+# For info about why we place the binary at this location, see
+# https://unix.stackexchange.com/a/8658
+GLOBAL_BINARY="/usr/local/bin/genkit"
+if [[ -z "$GENKIT_BINARY" ]]; then
+ if [ -e "$LOCAL_BINARY" ]; then
+ GENKIT_BINARY="$LOCAL_BINARY"
+ elif [ -e "$GLOBAL_BINARY" ]; then
+ GENKIT_BINARY="$GLOBAL_BINARY"
+ fi
+fi
+
+# If the user asked for us to uninstall genkit, then do so.
+if [ "$uninstall" = "true" ]; then
+ if [[ -z "$GENKIT_BINARY" ]]; then
+ echo "Cannot detect any Genkit CLI installations."
+ echo "Please manually remove any \"genkit\" binaries not in \$PATH."
+ else
+ # Assuming binary install, skip npm check
+ echo "-- Removing binary file..."
+ sudo rm -- "$GENKIT_BINARY"
+ fi
+ echo "-- Removing genkit cache..."
+ rm -rf ~/.cache/genkit
+
+ echo "-- genkit has been uninstalled"
+ echo "-- All Done!"
+
+ send_analytics_event uninstall
+ exit 0
+fi
+
+# We need to ensure that we don't mess up an existing "genkit"
+# install, so before doing anything we check to see if this system
+# has "genkit" installed and if so, we exit out.
+echo "-- Checking for existing genkit installation..."
+
+if [[ ! -z "$GENKIT_BINARY" ]]; then
+ INSTALLED_GENKIT_VERSION=$("$GENKIT_BINARY" --version)
+
+ # In the case of a corrupt genkit install, we wont be able to
+ # retrieve a version number, so to keep the logs correct, we refer to
+ # your existing install as either the CLI version or as a "corrupt install"
+ if [[ ! -z "$INSTALLED_GENKIT_VERSION" ]]; then
+ GENKIT_NICKNAME="genkit@$INSTALLED_GENKIT_VERSION"
+ else
+ GENKIT_NICKNAME="a corrupted genkit binary"
+ fi
+
+ # Skip npm check - assume binary install
+ # If the user didn't pass upgrade=true, then we print the command to do an upgrade and exit
+ if [ ! "$upgrade" = "true" ]; then
+ echo "Your machine has $GENKIT_NICKNAME installed."
+ echo "If you would like to upgrade your install run: curl -sL $DOMAIN | upgrade=true bash"
+
+ send_analytics_event already_installed
+ exit 0
+ else
+ # If the user did pass upgrade=true, then we allow the script to continue and overwrite the install.
+ echo "-- Your machine has $GENKIT_NICKNAME, attempting upgrade..."
+
+ send_analytics_event upgrade
+ fi
+fi
+
+echo "-- Checking your machine type..."
+
+# Now we need to detect the platform we're running on (Linux / Mac / Other)
+# so we can fetch the correct binary and place it in the correct location
+# on the machine.
+
+# We use "tr" to translate the uppercase "uname" output into lowercase
+UNAME=$(uname -s | tr '[:upper:]' '[:lower:]')
+
+# Detect architecture
+ARCH=$(uname -m)
+case "$ARCH" in
+ x86_64) ARCH_SUFFIX="x64";;
+ aarch64|arm64) ARCH_SUFFIX="arm64";;
+ *) ARCH_SUFFIX="x64";; # Default to x64
+esac
+
+# Then we map the output to the names used on the GitHub releases page
+case "$UNAME" in
+ linux*) MACHINE="linux-${ARCH_SUFFIX}";;
+ darwin*) MACHINE="darwin-${ARCH_SUFFIX}";;
+ mingw*|msys*|cygwin*) MACHINE="win32-x64";;
+esac
+
+# If we never define the $MACHINE variable (because our platform is neither Mac,
+# Linux, or Windows), then we can't finish our job, so just log out a helpful message
+# and close.
+if [[ -z "$MACHINE" ]]; then
+ echo "Your operating system is not supported, if you think it should be please file a bug."
+ echo "https://github.com/firebase/genkit/"
+ echo "-- All done!"
+
+ send_analytics_event "missing_platform_${UNAME}_${ARCH}"
+ exit 0
+fi
+
+# We have enough information to generate the binary's download URL.
+DOWNLOAD_URL="https://$DOMAIN/bin/$MACHINE/latest"
+echo "-- Downloading binary from $DOWNLOAD_URL"
+
+# We use "curl" to download the binary with a flag set to follow redirects
+# (GitHub download URLs redirect to CDNs) and a flag to show a progress bar.
+curl -o "/tmp/genkit_standalone.tmp" -L --progress-bar $DOWNLOAD_URL
+
+GENKIT_BINARY=${GENKIT_BINARY:-$GLOBAL_BINARY}
+INSTALL_DIR=$(dirname -- "$GENKIT_BINARY")
+
+# We need to ensure that the INSTALL_DIR exists.
+# On some platforms like the Windows Subsystem for Linux it may not.
+# We created it using a non-destructive mkdir command.
+mkdir -p -- "$INSTALL_DIR" 2> /dev/null
+
+# If the directory does not exist or is not writable, we resort to sudo.
+sudo=""
+if [ ! -w "$INSTALL_DIR" ]; then
+ sudo="sudo"
+fi
+
+$sudo mkdir -p -- "$INSTALL_DIR"
+$sudo mv -f "/tmp/genkit_standalone.tmp" "$GENKIT_BINARY"
+
+# Once the download is complete, we mark the binary file as readable
+# and executable (+rx).
+echo "-- Setting permissions on binary... $GENKIT_BINARY"
+$sudo chmod +rx "$GENKIT_BINARY"
+
+# If all went well, the "genkit" binary should be located on our PATH so
+# we'll run it once, asking it to print out the version. This is helpful as
+# standalone genkit binaries do a small amount of setup on the initial run
+# so this not only allows us to make sure we got the right version, but it
+# also does the setup so the first time the developer runs the binary, it'll
+# be faster.
+VERSION=$("$GENKIT_BINARY" --version)
+
+# If no version is detected then clearly the binary failed to install for
+# some reason, so we'll log out an error message and report the failure
+# to headquarters via an analytics event.
+if [[ -z "$VERSION" ]]; then
+ echo "Something went wrong, genkit has not been installed."
+ echo "Please file a bug with your system information on GitHub."
+ echo "https://github.com/firebase/genkit/"
+ echo "-- All done!"
+
+ send_analytics_event failure
+ exit 1
+fi
+
+# In order for the user to be able to actually run the "genkit" command
+# without specifying the absolute location, the INSTALL_DIR path must
+# be present inside of the PATH environment variable.
+
+echo "-- Checking your PATH variable..."
+if [[ ! ":$PATH:" == *":$INSTALL_DIR:"* ]]; then
+ echo ""
+ echo "It looks like $INSTALL_DIR isn't on your PATH."
+ echo "Please add the following line to either your ~/.profile or ~/.bash_profile, then restart your terminal."
+ echo ""
+ echo "PATH=\$PATH:$INSTALL_DIR"
+ echo ""
+ echo "For more information about modifying PATHs, see https://unix.stackexchange.com/a/26059"
+ echo ""
+ send_analytics_event missing_path
+fi
+
+# We also try to upgrade the local binary if it exists.
+# This helps prevent having two mismatching versions of "genkit".
+if [[ "$GENKIT_BINARY" != "$LOCAL_BINARY" ]] && [ -e "$LOCAL_BINARY" ]; then
+ echo "-- Upgrading the local binary installation $LOCAL_BINARY..."
+ cp "$GENKIT_BINARY" "$LOCAL_BINARY" # best effort, okay if it fails.
+ chmod +x "$LOCAL_BINARY"
+fi
+
+# Since we've gotten this far we know everything succeeded. We'll just
+# let the developer know everything is ready and take our leave.
+echo "-- genkit@$VERSION is now installed"
+echo "-- All Done!"
+
+send_analytics_event success
+exit 0
+
+# ------------------------------------------
+# Notes
+# ------------------------------------------
+#
+# This script contains hidden JavaScript which is used to improve
+# readability in the browser (via syntax highlighting, etc), right-click
+# and "View source" of this page to see the entire bash script!
+#
+# You'll also notice that we use the ":" character in the Introduction
+# which allows our copy/paste commands to be syntax highlighted, but not
+# ran. In bash : is equal to `true` and true can take infinite arguments
+# while still returning true. This turns these commands into no-ops so
+# when ran as a script, they're totally ignored.
+#
\ No newline at end of file
diff --git a/genkit-tools/cli/package.json b/genkit-tools/cli/package.json
index 6e0a5854d9..485b0e607a 100644
--- a/genkit-tools/cli/package.json
+++ b/genkit-tools/cli/package.json
@@ -17,6 +17,7 @@
"scripts": {
"build": "pnpm genversion && tsc",
"build:watch": "tsc --watch",
+ "compile:bun": "bun build src/bin/genkit.ts --compile --outfile dist/bin/genkit --minify",
"test": "jest --verbose",
"genversion": "genversion -esf src/utils/version.ts"
},
@@ -42,9 +43,11 @@
"@types/inquirer": "^8.1.3",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.19",
+ "bun-types": "^1.2.16",
"genversion": "^3.2.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
+ "ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
}
diff --git a/genkit-tools/cli/src/cli.ts b/genkit-tools/cli/src/cli.ts
index b725582bd9..e44776ffd3 100644
--- a/genkit-tools/cli/src/cli.ts
+++ b/genkit-tools/cli/src/cli.ts
@@ -31,6 +31,10 @@ import { flowRun } from './commands/flow-run';
import { getPluginCommands, getPluginSubCommand } from './commands/plugins';
import { start } from './commands/start';
import { uiStart } from './commands/ui-start';
+import {
+ UI_START_SERVER_COMMAND,
+ uiStartServer,
+} from './commands/ui-start-server';
import { uiStop } from './commands/ui-stop';
import { version } from './utils/version';
@@ -79,6 +83,12 @@ export async function startCLI(): Promise {
await record(new RunCommandEvent(commandName));
});
+ // When running as a spawned UI server process, argv[1] will be '__ui:start-server'
+ // instead of a normal command. This allows the same binary to serve both CLI and server roles.
+ if (process.argv[1] === UI_START_SERVER_COMMAND) {
+ program.addCommand(uiStartServer);
+ }
+
for (const command of commands) program.addCommand(command);
for (const command of await getPluginCommands()) program.addCommand(command);
diff --git a/genkit-tools/cli/src/commands/ui-start-server.ts b/genkit-tools/cli/src/commands/ui-start-server.ts
new file mode 100644
index 0000000000..65ca2a7a40
--- /dev/null
+++ b/genkit-tools/cli/src/commands/ui-start-server.ts
@@ -0,0 +1,56 @@
+/**
+ * Copyright 2024 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { startServer } from '@genkit-ai/tools-common/server';
+import { Command } from 'commander';
+import fs from 'fs';
+import { startManager } from '../utils/manager-utils';
+
+function redirectStdoutToFile(logFile: string) {
+ const myLogFileStream = fs.createWriteStream(logFile);
+
+ const originalStdout = process.stdout.write;
+ function writeStdout() {
+ originalStdout.apply(process.stdout, arguments as any);
+ myLogFileStream.write.apply(myLogFileStream, arguments as any);
+ }
+
+ process.stdout.write = writeStdout as any;
+ process.stderr.write = process.stdout.write;
+}
+
+export const UI_START_SERVER_COMMAND = '__ui:start-server' as const;
+
+export const uiStartServer = new Command('__ui:start-server')
+ .argument('', 'Port to serve on')
+ .argument('', 'Log file path')
+ .action(async (port: string, logFile: string) => {
+ redirectStdoutToFile(logFile);
+
+ process.on('error', (error): void => {
+ console.log(`Error in tools process: ${error}`);
+ });
+ process.on('uncaughtException', (err, somethingelse) => {
+ console.log(`Uncaught error in tools process: ${err} ${somethingelse}`);
+ });
+ process.on('unhandledRejection', (reason, p) => {
+ console.log(`Unhandled rejection in tools process: ${reason}`);
+ });
+
+ const portNum = Number.parseInt(port) || 4100;
+ const manager = await startManager(true);
+ await startServer(manager, portNum);
+ });
diff --git a/genkit-tools/cli/src/commands/ui-start.ts b/genkit-tools/cli/src/commands/ui-start.ts
index dd270a4410..c99d1dfc55 100644
--- a/genkit-tools/cli/src/commands/ui-start.ts
+++ b/genkit-tools/cli/src/commands/ui-start.ts
@@ -126,14 +126,14 @@ async function startAndWaitUntilHealthy(
serversDir: string
): Promise {
return new Promise((resolve, reject) => {
- const serverPath = path.join(__dirname, '../utils/server-harness.js');
const child = spawn(
- 'node',
- [serverPath, port.toString(), serversDir + '/devui.log'],
+ process.execPath,
+ ['__ui:start-server', port.toString(), serversDir + '/devui.log'],
{
stdio: ['ignore', 'ignore', 'ignore'],
}
);
+
// Only print out logs from the child process to debug output.
child.on('error', (error) => reject(error));
child.on('exit', (code) =>
diff --git a/genkit-tools/cli/src/utils/server-harness.ts b/genkit-tools/cli/src/utils/server-harness.ts
deleted file mode 100644
index 02feeb07c2..0000000000
--- a/genkit-tools/cli/src/utils/server-harness.ts
+++ /dev/null
@@ -1,53 +0,0 @@
-/**
- * Copyright 2024 Google LLC
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-import { startServer } from '@genkit-ai/tools-common/server';
-import fs from 'fs';
-import { startManager } from './manager-utils';
-
-const args = process.argv.slice(2);
-const port = Number.parseInt(args[0]) || 4100;
-redirectStdoutToFile(args[1]);
-
-async function start() {
- const manager = await startManager(true);
- await startServer(manager, port);
-}
-
-function redirectStdoutToFile(logFile: string) {
- var myLogFileStream = fs.createWriteStream(logFile);
-
- var originalStdout = process.stdout.write;
- function writeStdout() {
- originalStdout.apply(process.stdout, arguments as any);
- myLogFileStream.write.apply(myLogFileStream, arguments as any);
- }
-
- process.stdout.write = writeStdout as any;
- process.stderr.write = process.stdout.write;
-}
-
-process.on('error', (error): void => {
- console.log(`Error in tools process: ${error}`);
-});
-process.on('uncaughtException', (err, somethingelse) => {
- console.log(`Uncaught error in tools process: ${err} ${somethingelse}`);
-});
-process.on('unhandledRejection', (reason, p) => {
- console.log(`Unhandled rejection in tools process: ${reason}`);
-});
-
-start();
diff --git a/genkit-tools/cli/tsconfig.json b/genkit-tools/cli/tsconfig.json
index 4496203d6b..3673086655 100644
--- a/genkit-tools/cli/tsconfig.json
+++ b/genkit-tools/cli/tsconfig.json
@@ -2,7 +2,9 @@
"extends": "../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
- "outDir": "dist"
+ "outDir": "dist",
+ "types": ["bun-types"],
+ "resolveJsonModule": true
},
"include": ["src"]
}
diff --git a/genkit-tools/common/package.json b/genkit-tools/common/package.json
index b5fa05cd09..edb532e97a 100644
--- a/genkit-tools/common/package.json
+++ b/genkit-tools/common/package.json
@@ -48,6 +48,7 @@
"@types/json-schema": "^7.0.15",
"@types/node": "^20.11.19",
"@types/uuid": "^9.0.8",
+ "bun-types": "^1.2.16",
"genversion": "^3.2.0",
"jest": "^29.7.0",
"npm-run-all": "^4.1.5",
@@ -63,61 +64,61 @@
},
"author": "genkit",
"license": "Apache-2.0",
- "types": "./lib/types/types/index.d.ts",
+ "types": "./lib/types/src/types/index.d.ts",
"exports": {
".": {
- "require": "./lib/cjs/types/index.js",
- "import": "./lib/esm/types/index.js",
- "types": "./lib/types/types/index.d.ts",
- "default": "./lib/esm/types/index.js"
+ "require": "./lib/cjs/src/types/index.js",
+ "import": "./lib/esm/src/types/index.js",
+ "types": "./lib/types/src/types/index.d.ts",
+ "default": "./lib/esm/src/types/index.js"
},
"./eval": {
- "types": "./lib/types/eval/index.d.ts",
- "require": "./lib/cjs/eval/index.js",
- "import": "./lib/esm/eval/index.js",
- "default": "./lib/esm/eval/index.js"
+ "types": "./lib/types/src/eval/index.d.ts",
+ "require": "./lib/cjs/src/eval/index.js",
+ "import": "./lib/esm/src/eval/index.js",
+ "default": "./lib/esm/src/eval/index.js"
},
"./plugin": {
- "types": "./lib/types/plugin/index.d.ts",
- "require": "./lib/cjs/plugin/index.js",
- "import": "./lib/esm/plugin/index.js",
- "default": "./lib/esm/plugin/index.js"
+ "types": "./lib/types/src/plugin/index.d.ts",
+ "require": "./lib/cjs/src/plugin/index.js",
+ "import": "./lib/esm/src/plugin/index.js",
+ "default": "./lib/esm/src/plugin/index.js"
},
"./manager": {
- "types": "./lib/manager/index.d.ts",
- "require": "./lib/cjs/manager/index.js",
- "import": "./lib/esm/manager/index.js",
- "default": "./lib/esm/manager/index.js"
+ "types": "./lib/types/src/manager/index.d.ts",
+ "require": "./lib/cjs/src/manager/index.js",
+ "import": "./lib/esm/src/manager/index.js",
+ "default": "./lib/esm/src/manager/index.js"
},
"./server": {
- "types": "./lib/server/index.d.ts",
- "require": "./lib/cjs/server/index.js",
- "import": "./lib/esm/server/index.js",
- "default": "./lib/esm/server/index.js"
+ "types": "./lib/types/src/server/index.d.ts",
+ "require": "./lib/cjs/src/server/index.js",
+ "import": "./lib/esm/src/server/index.js",
+ "default": "./lib/esm/src/server/index.js"
},
"./utils": {
- "types": "./lib/utils/index.d.ts",
- "require": "./lib/cjs/utils/index.js",
- "import": "./lib/esm/utils/index.js",
- "default": "./lib/esm/utils/index.js"
+ "types": "./lib/types/src/utils/index.d.ts",
+ "require": "./lib/cjs/src/utils/index.js",
+ "import": "./lib/esm/src/utils/index.js",
+ "default": "./lib/esm/src/utils/index.js"
}
},
"typesVersions": {
"*": {
"eval": [
- "lib/types/eval"
+ "lib/types/src/eval"
],
"plugin": [
- "lib/types/plugin"
+ "lib/types/src/plugin"
],
"manager": [
- "lib/types/manager"
+ "lib/types/src/manager"
],
"server": [
- "lib/types/server"
+ "lib/types/src/server"
],
"utils": [
- "lib/types/utils"
+ "lib/types/src/utils"
]
}
}
diff --git a/genkit-tools/common/src/utils/errors.ts b/genkit-tools/common/src/utils/errors.ts
new file mode 100644
index 0000000000..00e3102d8d
--- /dev/null
+++ b/genkit-tools/common/src/utils/errors.ts
@@ -0,0 +1,155 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Connection error codes for different runtimes
+const CONNECTION_ERROR_CODES = {
+ NODE_ECONNREFUSED: 'ECONNREFUSED',
+ BUN_CONNECTION_REFUSED: 'ConnectionRefused',
+ ECONNRESET: 'ECONNRESET',
+} as const;
+
+const CONNECTION_ERROR_PATTERNS = [
+ 'ECONNREFUSED',
+ 'Connection refused',
+ 'ConnectionRefused',
+ 'connect ECONNREFUSED',
+] as const;
+
+type ErrorWithCode = {
+ code?: string;
+ message?: string;
+ cause?: ErrorWithCode;
+};
+
+/**
+ * Checks if an error is a connection refused error across Node.js and Bun runtimes.
+ *
+ * Node.js structure: error.cause.code === 'ECONNREFUSED'
+ * Bun structure: error.code === 'ConnectionRefused' or error.code === 'ECONNRESET'
+ */
+export function isConnectionRefusedError(error: unknown): boolean {
+ if (!error) {
+ return false;
+ }
+
+ const errorCode = getErrorCode(error);
+ if (errorCode && isConnectionErrorCode(errorCode)) {
+ return true;
+ }
+
+ // Fallback: check error message
+ if (isErrorWithMessage(error)) {
+ return CONNECTION_ERROR_PATTERNS.some((pattern) =>
+ error.message.includes(pattern)
+ );
+ }
+
+ return false;
+}
+
+/**
+ * Helper function to check if a code is a connection error code.
+ */
+function isConnectionErrorCode(code: string): boolean {
+ return Object.values(CONNECTION_ERROR_CODES).includes(
+ code as (typeof CONNECTION_ERROR_CODES)[keyof typeof CONNECTION_ERROR_CODES]
+ );
+}
+
+/**
+ * Type guard to check if an error has a message property.
+ */
+function isErrorWithMessage(error: unknown): error is { message: string } {
+ return (
+ typeof error === 'object' &&
+ error !== null &&
+ 'message' in error &&
+ typeof (error as any).message === 'string'
+ );
+}
+
+/**
+ * Extracts error code from an object, handling nested structures.
+ */
+function extractErrorCode(obj: unknown): string | undefined {
+ if (
+ typeof obj === 'object' &&
+ obj !== null &&
+ 'code' in obj &&
+ typeof (obj as ErrorWithCode).code === 'string'
+ ) {
+ return (obj as ErrorWithCode).code;
+ }
+ return undefined;
+}
+
+/**
+ * Gets the error code from an error object, handling both Node.js and Bun styles.
+ */
+export function getErrorCode(error: unknown): string | undefined {
+ if (!error) {
+ return undefined;
+ }
+
+ // Direct error code
+ const directCode = extractErrorCode(error);
+ if (directCode) {
+ return directCode;
+ }
+
+ // Node.js style with cause
+ if (typeof error === 'object' && error !== null && 'cause' in error) {
+ const causeCode = extractErrorCode((error as ErrorWithCode).cause);
+ if (causeCode) {
+ return causeCode;
+ }
+ }
+
+ return undefined;
+}
+
+/**
+ * Extracts error message from various error formats.
+ */
+function extractErrorMessage(error: unknown): string | undefined {
+ if (error instanceof Error) {
+ return error.message;
+ }
+
+ if (isErrorWithMessage(error)) {
+ return error.message;
+ }
+
+ return undefined;
+}
+
+/**
+ * Safely extracts error details for logging.
+ */
+export function getErrorDetails(error: unknown): string {
+ if (error === null || error === undefined) {
+ return 'Unknown error';
+ }
+
+ const code = getErrorCode(error);
+ const message = extractErrorMessage(error);
+
+ if (message) {
+ return code ? `${message} (${code})` : message;
+ }
+
+ return String(error);
+}
diff --git a/genkit-tools/common/src/utils/package.ts b/genkit-tools/common/src/utils/package.ts
index 7e94aef735..88561673dc 100644
--- a/genkit-tools/common/src/utils/package.ts
+++ b/genkit-tools/common/src/utils/package.ts
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-import { readFileSync } from 'fs';
-import { join } from 'path';
+import toolsPackage from '../../package.json';
-const packagePath = join(__dirname, '../../../package.json');
-export const toolsPackage = JSON.parse(readFileSync(packagePath, 'utf8'));
+export { toolsPackage };
diff --git a/genkit-tools/common/src/utils/utils.ts b/genkit-tools/common/src/utils/utils.ts
index 9368e22a3e..28531fd2af 100644
--- a/genkit-tools/common/src/utils/utils.ts
+++ b/genkit-tools/common/src/utils/utils.ts
@@ -17,6 +17,7 @@
import * as fs from 'fs/promises';
import * as path from 'path';
import type { Runtime } from '../manager/types';
+import { isConnectionRefusedError } from './errors';
import { logger } from './logger';
export interface DevToolsInfo {
@@ -145,10 +146,7 @@ export async function checkServerHealth(url: string): Promise {
const response = await fetch(`${url}/api/__health`);
return response.status === 200;
} catch (error) {
- if (
- error instanceof Error &&
- (error.cause as any).code === 'ECONNREFUSED'
- ) {
+ if (isConnectionRefusedError(error)) {
return false;
}
}
@@ -189,10 +187,7 @@ export async function waitUntilUnresponsive(
try {
const health = await fetch(`${url}/api/__health`);
} catch (error) {
- if (
- error instanceof Error &&
- (error.cause as any).code === 'ECONNREFUSED'
- ) {
+ if (isConnectionRefusedError(error)) {
return true;
}
}
diff --git a/genkit-tools/common/tests/utils/errors_test.ts b/genkit-tools/common/tests/utils/errors_test.ts
new file mode 100644
index 0000000000..d1f14b0100
--- /dev/null
+++ b/genkit-tools/common/tests/utils/errors_test.ts
@@ -0,0 +1,172 @@
+/**
+ * Copyright 2025 Google LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { describe, expect, it } from '@jest/globals';
+import {
+ getErrorCode,
+ getErrorDetails,
+ isConnectionRefusedError,
+} from '../../src/utils/errors';
+
+describe('errors.ts', () => {
+ describe('isConnectionRefusedError', () => {
+ it('should return false for null/undefined', () => {
+ expect(isConnectionRefusedError(null)).toBe(false);
+ expect(isConnectionRefusedError(undefined)).toBe(false);
+ });
+
+ it('should detect plain objects with connection error codes', () => {
+ expect(isConnectionRefusedError({ code: 'ECONNREFUSED' })).toBe(true);
+ expect(isConnectionRefusedError({ code: 'ConnectionRefused' })).toBe(
+ true
+ );
+ expect(isConnectionRefusedError({ code: 'ECONNRESET' })).toBe(true);
+ expect(isConnectionRefusedError({ code: 'OTHER_ERROR' })).toBe(false);
+ });
+
+ it('should detect Error instances with direct code', () => {
+ const err = new Error('Connection failed');
+ (err as any).code = 'ECONNREFUSED';
+ expect(isConnectionRefusedError(err)).toBe(true);
+
+ const err2 = new Error('Connection failed');
+ (err2 as any).code = 'ConnectionRefused';
+ expect(isConnectionRefusedError(err2)).toBe(true);
+
+ const err3 = new Error('Connection failed');
+ (err3 as any).code = 'ECONNRESET';
+ expect(isConnectionRefusedError(err3)).toBe(true);
+ });
+
+ it('should detect Node.js style errors with cause', () => {
+ const err = new Error('Fetch failed');
+ (err as any).cause = { code: 'ECONNREFUSED' };
+ expect(isConnectionRefusedError(err)).toBe(true);
+ });
+
+ it('should fallback to checking error messages', () => {
+ expect(
+ isConnectionRefusedError(
+ new Error('connect ECONNREFUSED 127.0.0.1:3000')
+ )
+ ).toBe(true);
+ expect(
+ isConnectionRefusedError(new Error('Connection refused to server'))
+ ).toBe(true);
+ expect(
+ isConnectionRefusedError(
+ new Error('ConnectionRefused: Unable to connect')
+ )
+ ).toBe(true);
+ expect(
+ isConnectionRefusedError(new Error('Something else went wrong'))
+ ).toBe(false);
+ });
+
+ it('should handle complex nested structures', () => {
+ const err = new Error('Outer error');
+ (err as any).cause = new Error('Inner error');
+ (err as any).cause.code = 'ECONNREFUSED';
+ expect(isConnectionRefusedError(err)).toBe(true);
+ });
+ });
+
+ describe('getErrorCode', () => {
+ it('should return undefined for null/undefined', () => {
+ expect(getErrorCode(null)).toBeUndefined();
+ expect(getErrorCode(undefined)).toBeUndefined();
+ });
+
+ it('should extract code from plain objects', () => {
+ expect(getErrorCode({ code: 'ECONNREFUSED' })).toBe('ECONNREFUSED');
+ expect(getErrorCode({ code: 'CUSTOM_ERROR' })).toBe('CUSTOM_ERROR');
+ expect(getErrorCode({ message: 'No code here' })).toBeUndefined();
+ });
+
+ it('should extract code from Error instances', () => {
+ const err = new Error('Test error');
+ (err as any).code = 'TEST_CODE';
+ expect(getErrorCode(err)).toBe('TEST_CODE');
+ });
+
+ it('should extract code from cause property', () => {
+ const err = new Error('Outer error');
+ (err as any).cause = { code: 'INNER_CODE' };
+ expect(getErrorCode(err)).toBe('INNER_CODE');
+ });
+
+ it('should prioritize direct code over cause code', () => {
+ const err = new Error('Test error');
+ (err as any).code = 'DIRECT_CODE';
+ (err as any).cause = { code: 'CAUSE_CODE' };
+ expect(getErrorCode(err)).toBe('DIRECT_CODE');
+ });
+
+ it('should handle non-string code values', () => {
+ expect(getErrorCode({ code: 123 })).toBeUndefined();
+ expect(getErrorCode({ code: null })).toBeUndefined();
+ expect(getErrorCode({ code: {} })).toBeUndefined();
+ });
+ });
+
+ describe('getErrorDetails', () => {
+ it('should return "Unknown error" for null/undefined', () => {
+ expect(getErrorDetails(null)).toBe('Unknown error');
+ expect(getErrorDetails(undefined)).toBe('Unknown error');
+ });
+
+ it('should format Error instances with code', () => {
+ const err = new Error('Connection failed');
+ (err as any).code = 'ECONNREFUSED';
+ expect(getErrorDetails(err)).toBe('Connection failed (ECONNREFUSED)');
+ });
+
+ it('should format Error instances without code', () => {
+ const err = new Error('Simple error');
+ expect(getErrorDetails(err)).toBe('Simple error');
+ });
+
+ it('should format plain objects with message and code', () => {
+ expect(getErrorDetails({ message: 'Failed', code: 'ERR123' })).toBe(
+ 'Failed (ERR123)'
+ );
+ expect(getErrorDetails({ message: 'No code here' })).toBe('No code here');
+ });
+
+ it('should handle string errors', () => {
+ expect(getErrorDetails('String error')).toBe('String error');
+ });
+
+ it('should handle number errors', () => {
+ expect(getErrorDetails(123)).toBe('123');
+ });
+
+ it('should handle boolean errors', () => {
+ expect(getErrorDetails(true)).toBe('true');
+ expect(getErrorDetails(false)).toBe('false');
+ });
+
+ it('should handle objects without message', () => {
+ expect(getErrorDetails({ code: 'ERR_NO_MSG' })).toBe('[object Object]');
+ });
+
+ it('should extract code from cause for formatting', () => {
+ const err = new Error('Outer error');
+ (err as any).cause = { code: 'INNER_CODE' };
+ expect(getErrorDetails(err)).toBe('Outer error (INNER_CODE)');
+ });
+ });
+});
diff --git a/genkit-tools/common/tsconfig.json b/genkit-tools/common/tsconfig.json
index b63e7bcd20..035ea1b991 100644
--- a/genkit-tools/common/tsconfig.json
+++ b/genkit-tools/common/tsconfig.json
@@ -6,7 +6,8 @@
"outDir": "lib/esm",
"esModuleInterop": true,
"typeRoots": ["./node_modules/@types"],
- "rootDirs": ["src"]
+ "rootDirs": ["src"],
+ "resolveJsonModule": true
},
"include": ["src"]
}
diff --git a/genkit-tools/pnpm-lock.yaml b/genkit-tools/pnpm-lock.yaml
index 7fa94f66f2..ee44519383 100644
--- a/genkit-tools/pnpm-lock.yaml
+++ b/genkit-tools/pnpm-lock.yaml
@@ -75,6 +75,9 @@ importers:
'@types/node':
specifier: ^20.11.19
version: 20.19.1
+ bun-types:
+ specifier: ^1.2.16
+ version: 1.2.16
genversion:
specifier: ^3.2.0
version: 3.2.0
@@ -84,6 +87,9 @@ importers:
ts-jest:
specifier: ^29.1.2
version: 29.4.0(@babel/core@7.24.5)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.5))(jest-util@29.7.0)(jest@29.7.0(@types/node@20.19.1)(ts-node@10.9.2(@types/node@20.19.1)(typescript@5.8.3)))(typescript@5.8.3)
+ ts-node:
+ specifier: ^10.9.2
+ version: 10.9.2(@types/node@20.19.1)(typescript@5.8.3)
typescript:
specifier: ^5.3.3
version: 5.8.3
@@ -202,6 +208,9 @@ importers:
'@types/uuid':
specifier: ^9.0.8
version: 9.0.8
+ bun-types:
+ specifier: ^1.2.16
+ version: 1.2.16
genversion:
specifier: ^3.2.0
version: 3.2.0
@@ -1247,6 +1256,9 @@ packages:
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
+ bun-types@1.2.16:
+ resolution: {integrity: sha512-ciXLrHV4PXax9vHvUrkvun9VPVGOVwbbbBF/Ev1cXz12lyEZMoJpIJABOfPcN9gDJRaiKF9MVbSygLg4NXu3/A==}
+
bytes@3.1.2:
resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==}
engines: {node: '>= 0.8'}
@@ -4368,6 +4380,10 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
+ bun-types@1.2.16:
+ dependencies:
+ '@types/node': 20.19.1
+
bytes@3.1.2: {}
call-bind-apply-helpers@1.0.2: