Skip to content

Scripts

Scripts let you run arbitrary Node.js code on the IDE host machine. They live in a scripts/ folder in your project, are copied into the extension output during unextension sync, and are called from the webview via runScript.

Scripts run with full Node.js access — filesystem, network, child processes, installed packages — with no sandbox.


Add scriptsDir to your unextension.config.ts:

import { defineConfig } from '@unextension/cli'
export default defineConfig({
name: 'my-extension',
// ...
scriptsDir: './dist/scripts', // compiled output, not source
})

Section titled “Approach 1 — TypeScript with esbuild (recommended)”

Write scripts in TypeScript and bundle them into self-contained files using esbuild. Each script is bundled with all its imports inlined, so no node_modules is needed at runtime inside the extension.

my-extension/
scripts/
build.mjs ← esbuild bundler
hello.ts ← your script source
dist/
scripts/
hello.js ← bundled output (copied to extension)
Terminal window
pnpm add -D esbuild @types/node
import { build } from 'esbuild'
import { readdirSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const scriptsDir = dirname(fileURLToPath(import.meta.url))
const outDir = join(scriptsDir, '../dist/scripts')
const entries = readdirSync(scriptsDir)
.filter((f) => f.endsWith('.ts') && f !== 'build.mjs')
.map((f) => join(scriptsDir, f))
if (entries.length > 0) {
await build({
entryPoints: entries,
outdir: outDir,
bundle: true, // inline all imports — no node_modules needed at runtime
platform: 'node',
target: 'node18',
format: 'cjs',
external: [],
})
}
{
"scripts": {
"build": "vite build && node scripts/build.mjs && unextension sync"
}
}

Use createScript from @unextension/bridge/script to handle the payload/result boilerplate:

scripts/hello.ts
import { createScript } from '@unextension/bridge/script'
createScript(async (payload: { name: string }) => {
return {
greeting: `Hello, ${payload.name}!`,
nodeVersion: process.version,
cwd: process.cwd(),
}
})

Call it from the webview:

import { runScript } from '@unextension/bridge'
const result = await runScript('hello', { name: 'World' })
console.log(result.result) // { greeting: 'Hello, World!', nodeVersion: 'v20.x.x', ... }

If you don’t need TypeScript, write scripts as plain .js files and point scriptsDir directly at your scripts folder:

unextension.config.ts
export default defineConfig({
scriptsDir: './scripts',
})

Scripts communicate via:

  • Inputprocess.env.UNEXTENSION_PAYLOAD — a JSON string of the payload passed to runScript
  • Output — write a JSON string to process.stdout — this becomes result.result
  • Errors — write to process.stderr and/or exit with a non-zero code
scripts/hello.js
const payload = JSON.parse(process.env.UNEXTENSION_PAYLOAD ?? 'null')
const result = {
greeting: `Hello, ${payload?.name ?? 'World'}!`,
nodeVersion: process.version,
}
process.stdout.write(JSON.stringify(result))

You can also use createScript in plain JS:

scripts/hello.js
const { createScript } = require('@unextension/bridge/script')
createScript(async (payload) => {
return {
greeting: `Hello, ${payload?.name ?? 'World'}!`,
nodeVersion: process.version,
}
})

import { createScript } from '@unextension/bridge/script'
createScript<TPayload, TResult>(
handler: (payload: TPayload) => TResult | Promise<TResult>
): Promise<void>
ParameterTypeDescription
handler(payload: TPayload) => TResult | Promise<TResult>Your script logic. Return any JSON-serializable value.

createScript handles:

  • Parsing UNEXTENSION_PAYLOAD from the environment
  • Calling your handler with the typed payload
  • Writing the result as JSON to stdout
  • Catching errors, writing to stderr, and exiting with code 1