Best Boilerplates for Building Developer Tools 2026
·StarterPick Team
developer-toolsclivscode-extensionnpm-packagetypescript2026
TL;DR
There's no single "developer tools boilerplate" — you pick the right starter for your tool type. CLI tools: use create-typescript-app or oclif. VS Code extensions: the official Yeoman generator or generator-code. npm packages: tsup-starter or pkgroll setup. Language servers: vscode-languageserver-node. Each category has established patterns in 2026 with TypeScript as the non-negotiable baseline.
Key Takeaways
- CLI tools:
oclif(Heroku, complex CLIs) orcommander/meow(lightweight) - VS Code extensions: Official
yo codegenerator, then customize - npm packages:
tsup+changesets+ GitHub Actions release workflow - Language servers: LSP protocol via
vscode-languageserver-nodeor@volar/language-server - Browser DevTools: Chrome DevTools extension template
- TypeScript: Non-negotiable baseline for all categories in 2026
CLI Tools: Two Approaches
Lightweight CLI (commander/meow pattern)
npm create typescript-app@latest my-cli
# Or from scratch:
mkdir my-cli && cd my-cli
npm init -y
npm install typescript commander chalk ora
npm install -D @types/node tsup
// src/index.ts — production CLI pattern:
#!/usr/bin/env node
import { program } from 'commander';
import chalk from 'chalk';
import ora from 'ora';
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
const packageJson = JSON.parse(
await readFile(new URL('../package.json', import.meta.url), 'utf-8')
);
program
.name('my-cli')
.description('My developer tool CLI')
.version(packageJson.version);
program
.command('analyze <path>')
.description('Analyze files in a directory')
.option('-r, --recursive', 'Analyze recursively', false)
.option('--format <type>', 'Output format (json|table)', 'table')
.action(async (path, options) => {
const spinner = ora('Analyzing...').start();
try {
const result = await analyze(resolve(path), options);
spinner.succeed(chalk.green('Analysis complete!'));
if (options.format === 'json') {
console.log(JSON.stringify(result, null, 2));
} else {
printTable(result);
}
} catch (err) {
spinner.fail(chalk.red(`Failed: ${err.message}`));
process.exit(1);
}
});
program.parse();
// package.json for CLI:
{
"name": "my-cli",
"version": "1.0.0",
"bin": {
"my-cli": "./dist/index.js"
},
"type": "module",
"main": "./dist/index.js",
"scripts": {
"build": "tsup src/index.ts --format esm --target node18",
"dev": "tsx src/index.ts",
"test": "vitest"
},
"publishConfig": {
"access": "public"
}
}
oclif: Full-Featured CLI Framework
npx oclif generate my-cli
cd my-cli
// src/commands/analyze.ts — oclif command:
import { Command, Flags, Args } from '@oclif/core';
import chalk from 'chalk';
export default class Analyze extends Command {
static override description = 'Analyze files in a directory';
static override args = {
path: Args.string({ description: 'Path to analyze', required: true }),
};
static override flags = {
recursive: Flags.boolean({ char: 'r', description: 'Analyze recursively' }),
format: Flags.string({ char: 'f', default: 'table', options: ['json', 'table'] }),
output: Flags.file({ char: 'o', description: 'Write output to file' }),
};
async run(): Promise<void> {
const { args, flags } = await this.parse(Analyze);
this.log(chalk.blue(`Analyzing ${args.path}...`));
const result = await analyze(args.path, flags);
if (flags.format === 'json') {
this.log(JSON.stringify(result, null, 2));
} else {
this.table(result.files, {
name: { header: 'File' },
size: { header: 'Size' },
issues: { header: 'Issues' },
});
}
}
}
oclif gives you:
→ Auto-generated help (--help on every command)
→ Plugin system (users can extend your CLI)
→ Auto-update mechanism
→ Multi-command CLIs with subcommands
→ Tab completion
Used by: Heroku CLI, Salesforce CLI, Twilio CLI
VS Code Extension Boilerplate
# Official generator:
npm install -g yo generator-code
yo code
# Choose:
# - Extension (TypeScript/JavaScript)
# - Color Theme
# - Language Support
# - Snippet pack
Generated structure:
my-extension/
src/
extension.ts ← Main entry (activate/deactivate)
test/
suite/
package.json ← Contributes section = extension manifest
.vscode/
launch.json ← Debug configuration
tsconfig.json
// src/extension.ts — VS Code extension pattern:
import * as vscode from 'vscode';
let statusBarItem: vscode.StatusBarItem;
export function activate(context: vscode.ExtensionContext) {
// Register a command:
const analyzeCommand = vscode.commands.registerCommand(
'myExtension.analyze',
async () => {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('No active editor');
return;
}
const document = editor.document;
const text = document.getText();
await vscode.window.withProgress(
{
location: vscode.ProgressLocation.Notification,
title: 'Analyzing...',
cancellable: false,
},
async (progress) => {
progress.report({ increment: 0 });
const result = await analyze(text);
progress.report({ increment: 100 });
vscode.window.showInformationMessage(
`Analysis complete: ${result.issues} issues found`
);
}
);
}
);
// Status bar item:
statusBarItem = vscode.window.createStatusBarItem(
vscode.StatusBarAlignment.Right,
100
);
statusBarItem.command = 'myExtension.analyze';
statusBarItem.text = '$(search) Analyze';
statusBarItem.show();
// Register a code action provider:
const codeActionProvider = vscode.languages.registerCodeActionsProvider(
['typescript', 'javascript'],
new MyCodeActionProvider(),
{ providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }
);
context.subscriptions.push(analyzeCommand, statusBarItem, codeActionProvider);
}
export function deactivate() {
statusBarItem?.dispose();
}
class MyCodeActionProvider implements vscode.CodeActionProvider {
provideCodeActions(
document: vscode.TextDocument,
range: vscode.Range,
context: vscode.CodeActionContext
): vscode.CodeAction[] {
return context.diagnostics
.filter(d => d.code === 'MY_RULE')
.map(diagnostic => {
const fix = new vscode.CodeAction('Fix: auto-fix', vscode.CodeActionKind.QuickFix);
fix.edit = new vscode.WorkspaceEdit();
fix.edit.replace(document.uri, diagnostic.range, 'corrected-value');
fix.diagnostics = [diagnostic];
fix.isPreferred = true;
return fix;
});
}
}
// package.json contributes section:
{
"contributes": {
"commands": [
{
"command": "myExtension.analyze",
"title": "Analyze Current File",
"category": "My Extension"
}
],
"menus": {
"editor/context": [
{
"command": "myExtension.analyze",
"when": "editorHasSelection",
"group": "myExtension"
}
]
},
"configuration": {
"title": "My Extension",
"properties": {
"myExtension.autoAnalyze": {
"type": "boolean",
"default": false,
"description": "Auto-analyze on save"
}
}
},
"keybindings": [
{
"command": "myExtension.analyze",
"key": "ctrl+shift+a",
"mac": "cmd+shift+a",
"when": "editorTextFocus"
}
]
}
}
npm Package: Release Automation
# Modern npm package starter:
npm create typescript-app@latest my-package
# Or manual setup:
mkdir my-package && cd my-package
npm init -y
npm install -D typescript tsup vitest @changesets/cli
// package.json — dual CJS/ESM exports:
{
"name": "my-package",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"files": ["dist"],
"scripts": {
"build": "tsup",
"test": "vitest run",
"release": "changeset publish"
}
}
// tsup.config.ts:
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
splitting: false,
sourcemap: true,
clean: true,
treeshake: true,
});
# .github/workflows/release.yml — automated releases:
name: Release
on:
push:
branches: [main]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
- run: pnpm install --frozen-lockfile
- run: pnpm test
- run: pnpm build
- name: Create release PR or publish
uses: changesets/action@v1
with:
publish: pnpm release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Decision Guide
Building a CLI tool:
→ Simple single-command: commander + tsup
→ Multi-command with plugins: oclif
→ Maximum speed (Bun-based): oclif on Bun or commander + Bun
Building a VS Code extension:
→ Start with yo code (official generator)
→ Add language server if syntax highlighting/intellisense needed
→ Use vscode-test for integration testing
Building an npm package:
→ tsup + changesets is the 2026 standard
→ Dual CJS/ESM exports via exports field
→ Automated releases via changesets/action GitHub Action
Building a language server:
→ vscode-languageserver-node for VS Code integration
→ Volar (@volar/language-server) for Vue-ecosystem tools
→ Both implement the Language Server Protocol (LSP)
Find developer tool boilerplates at StarterPick.