Building VS Code Extensions with Bun and Mise

I wanted to build a VS Code extension. I also wanted to try out Bun for a better developer experience and Mise to manage my project tools. This post is a quick log of what I did and a small macOS problem I ran into.

Here’s the package.json I used. Bun builds the extension code and puts a CommonJS bundle into the out folder.

JSON
{
    "name": "audit-scope",
    "displayName": "Audit Scope",
    "module": "src/extension.ts",
    "type": "module",
    "private": true,
    "devDependencies": {
        "@types/bun": "latest",
        "@types/vscode": "latest"
    },
    "engines": {
        "vscode": "^1.105.0",
        "bun": "1.3.1"
    },
    "peerDependencies": {
        "typescript": "^5"
    },
    "scripts": {
        "build": "bun build ./src/extension.ts --outdir ./out --target=node --format=cjs --external=* --sourcemap=linked",
        "watch": "bun build ./src/extension.ts --outdir ./out --target=node --format=cjs --external=* --sourcemap=inline --watch"
    }
}

I set up VS Code tasks to run these scripts. This is what my tasks.json looks like.

JSON
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "bun: watch",
            "type": "shell",
            "command": "bun",
            "args": ["run", "watch"],
            "isBackground": true,
            "problemMatcher": "$tsc-watch",
            "presentation": {
                "reveal": "never"
            },
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
        {
            "label": "bun: compile",
            "type": "shell",
            "command": "bun",
            "args": ["run", "build"],
            "problemMatcher": "$tsc",
            "group": "build"
        }
    ]
}

My launch.json file tells the debugger where to find the built files.

JSON
{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Run Extension",
            "type": "extensionHost",
            "request": "launch",
            "args": ["--extensionDevelopmentPath=${workspaceFolder}"],
            "outFiles": ["${workspaceFolder}/out/**/*.js"],
            "preLaunchTask": "${defaultBuildTask}"
        }
    ]
}

The macOS problem and how I fixed it

When I tried to run the extension, the watch task failed right away with this error.

Plain Text
 *  Executing task: bun run watch

zsh:1: command not found: bun

 *  The terminal process "/bin/zsh '-l', '-c', 'bun run watch'" failed to launch (exit code: 127).

Bun is installed on my machine. It works fine when I run it in a normal terminal. The problem was that VS Code was starting the automation terminal in a shell that did not load the same profile files. On macOS, shells that are not interactive or not login shells can skip your usual profile where the PATH gets updated.

The simple fix I use is to make the automation terminal a login interactive zsh. Add this to your user or workspace settings:

JSON
{
    "terminal.integrated.automationProfile.osx": {
        "path": "/bin/zsh",
        "args": ["--login", "--interactive"]
    }
}

With this change, VS Code runs tasks in a shell that reads your ~/.zprofile or ~/.zshrc. Then bun run watch runs the same way it does in a normal terminal.

Why this matters: If your tools fail in tasks with “command not found”, but work when you run them yourself, check how the shell is started. Making the automation terminal a login and interactive shell is a good way to make sure task shells act like your normal shell.