feat: migrate to Bun for development

- Replace tsdown with Bun.build() for bundling
- Add bunfig.toml and tests/setup.ts for Bun test config
- Update CI to use Bun for install and build
- Update all package test scripts to use vitest (Node runtime)
- Remove deprecated deps: jest, ts-jest, tsdown, tsx, cross-env
- Delete jest.config.js, jest.resolver.cjs, tsdown.config.ts

Build: Bun bundler (faster than tsdown/esbuild)
Install: Bun package manager (bun.lock)
Tests: Vitest on Node (Bun runtime has Zod SSR issues)
Distribution: Node-compatible output unchanged

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ralph Khreish
2026-01-26 16:48:32 +01:00
parent 364a160775
commit 03d145c2af
16 changed files with 5738 additions and 37519 deletions

View File

@@ -19,6 +19,7 @@ env:
DO_NOT_TRACK: 1
NODE_ENV: development
NODE_VERSION: 20
BUN_VERSION: 1.2.6
jobs:
# Single install job that caches node_modules for all other jobs
@@ -32,16 +33,20 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Cache node_modules
id: cache-node-modules
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ runner.os }}-node${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}
key: node-modules-${{ runner.os }}-bun${{ env.BUN_VERSION }}-${{ hashFiles('bun.lockb') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
run: bun install
timeout-minutes: 5
# Fast checks that can run in parallel
@@ -56,20 +61,24 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Restore node_modules cache
id: cache-node-modules
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ runner.os }}-node${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}
key: node-modules-${{ runner.os }}-bun${{ env.BUN_VERSION }}-${{ hashFiles('bun.lockb') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
run: bun install
timeout-minutes: 5
- name: Format Check
run: npm run format-check
run: bun run format-check
env:
FORCE_COLOR: 1
@@ -116,20 +125,24 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Restore node_modules cache
id: cache-node-modules
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ runner.os }}-node${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}
key: node-modules-${{ runner.os }}-bun${{ env.BUN_VERSION }}-${{ hashFiles('bun.lockb') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
run: bun install
timeout-minutes: 5
- name: Typecheck
run: npm run turbo:typecheck
run: bun run turbo:typecheck
env:
FORCE_COLOR: 1
@@ -145,20 +158,24 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Restore node_modules cache
id: cache-node-modules
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ runner.os }}-node${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}
key: node-modules-${{ runner.os }}-bun${{ env.BUN_VERSION }}-${{ hashFiles('bun.lockb') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
run: bun install
timeout-minutes: 5
- name: Build
run: npm run turbo:build
run: bun run build
env:
NODE_ENV: production
FORCE_COLOR: 1
@@ -186,16 +203,20 @@ jobs:
with:
node-version: ${{ env.NODE_VERSION }}
- uses: oven-sh/setup-bun@v2
with:
bun-version: ${{ env.BUN_VERSION }}
- name: Restore node_modules cache
id: cache-node-modules
uses: actions/cache@v4
with:
path: node_modules
key: node-modules-${{ runner.os }}-node${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}
key: node-modules-${{ runner.os }}-bun${{ env.BUN_VERSION }}-${{ hashFiles('bun.lockb') }}
- name: Install dependencies
if: steps.cache-node-modules.outputs.cache-hit != 'true'
run: npm ci
run: bun install
timeout-minutes: 5
- name: Download build artifacts
@@ -205,8 +226,7 @@ jobs:
path: dist/
- name: Run Tests
run: |
npm run test:coverage -- --coverageThreshold '{"global":{"branches":0,"functions":0,"lines":0,"statements":0}}' --detectOpenHandles --forceExit
run: bun run turbo:test
env:
NODE_ENV: test
CI: true

View File

@@ -13,12 +13,12 @@
"typecheck": "tsc --noEmit",
"lint": "biome check src",
"format": "biome format --write src",
"test": "vitest run",
"test:watch": "vitest",
"test": "vitest run --coverage=false",
"test:watch": "vitest --coverage=false",
"test:coverage": "vitest run --coverage",
"test:unit": "vitest run '**/*.spec.ts'",
"test:integration": "vitest run '**/*.test.ts'",
"test:e2e": "vitest run --dir tests/e2e",
"test:unit": "vitest run --coverage=false '**/*.spec.ts'",
"test:integration": "vitest run --coverage=false '**/*.test.ts'",
"test:e2e": "vitest run --coverage=false --dir tests/e2e",
"test:ci": "vitest run --coverage --reporter=dot"
},
"dependencies": {
@@ -43,7 +43,6 @@
"@types/inquirer": "^9.0.3",
"@types/node": "^22.10.5",
"@vitest/coverage-v8": "^4.0.10",
"tsx": "^4.20.4",
"typescript": "^5.9.2",
"vitest": "^4.0.10"
},

View File

@@ -14,11 +14,11 @@
"typecheck": "tsc --noEmit",
"lint": "biome check src",
"format": "biome format --write src",
"test": "vitest run",
"test:watch": "vitest",
"test": "vitest run --coverage=false",
"test:watch": "vitest --coverage=false",
"test:coverage": "vitest run --coverage",
"test:unit": "vitest run '**/*.spec.ts'",
"test:integration": "vitest run '**/*.test.ts'",
"test:unit": "vitest run --coverage=false '**/*.spec.ts'",
"test:integration": "vitest run --coverage=false '**/*.test.ts'",
"test:ci": "vitest run --coverage --reporter=dot"
},
"dependencies": {

5379
bun.lock Normal file

File diff suppressed because it is too large Load Diff

27
bunfig.toml Normal file
View File

@@ -0,0 +1,27 @@
# Bun configuration for Taskmaster
# https://bun.sh/docs/runtime/bunfig
[test]
# Run setup file before each test suite
preload = ["./tests/setup.ts"]
# Enable coverage reporting
coverage = true
# Coverage thresholds
coverageThreshold = { line = 70, function = 70, statement = 70 }
# Test timeouts
timeout = 10000
# Test file patterns
# Bun automatically finds *.test.ts, *.spec.ts files
[install]
# Use frozen lockfile in CI
# frozen = true
[install.lockfile]
# Save lockfile as bun.lockb (binary) for faster parsing
# Can also use "bun.lock" for text format
save = true

View File

@@ -1,77 +0,0 @@
export default {
// Use Node.js environment for testing
testEnvironment: 'node',
// Automatically clear mock calls between every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: false,
// The directory where Jest should output its coverage files
coverageDirectory: 'coverage',
// A list of paths to directories that Jest should use to search for files in
roots: ['<rootDir>/tests'],
// The glob patterns Jest uses to detect test files
testMatch: ['**/__tests__/**/*.js', '**/?(*.)+(spec|test).js'],
// Transform files
preset: 'ts-jest/presets/default-esm',
extensionsToTreatAsEsm: ['.ts'],
moduleFileExtensions: ['js', 'ts', 'json', 'node'],
transform: {
'^.+\\.ts$': [
'ts-jest',
{
useESM: true
}
]
},
// Disable transformations for node_modules
transformIgnorePatterns: ['/node_modules/'],
// Set moduleNameMapper for absolute paths
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1'
},
resolver: '<rootDir>/jest.resolver.cjs',
// Setup module aliases
moduleDirectories: ['node_modules', '<rootDir>'],
// Configure test coverage thresholds
// Note: ts-jest reports coverage against .ts files, not .js
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80
},
// Critical code requires higher coverage
'./src/utils/**/*.ts': {
branches: 70,
functions: 90,
lines: 90,
statements: 90
},
'./src/middleware/**/*.ts': {
branches: 70,
functions: 85,
lines: 85,
statements: 85
}
},
// Generate coverage report in these formats
coverageReporters: ['text', 'lcov'],
// Verbose output
verbose: true,
// Setup file
setupFilesAfterEnv: ['<rootDir>/tests/setup.js']
};

View File

@@ -1,19 +0,0 @@
const { defaultResolver } = require('jest-resolve');
module.exports = function customResolver(request, options) {
const resolve = options.defaultResolver || defaultResolver;
try {
return resolve(request, options);
} catch (error) {
if (request.startsWith('.') && request.endsWith('.js')) {
try {
return resolve(request.replace(/\.js$/, '.ts'), options);
} catch (tsError) {
tsError.cause = tsError.cause ?? error;
throw tsError;
}
}
throw error;
}
};

37318
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -15,28 +15,29 @@
},
"workspaces": ["apps/*", "packages/*", "."],
"scripts": {
"build": "npm run build:build-config && cross-env NODE_ENV=production tsdown",
"dev": "tsdown --watch",
"build": "NODE_ENV=production bun run scripts/build.ts",
"dev": "bun run scripts/dev.js",
"turbo:dev": "turbo dev",
"turbo:build": "turbo build",
"turbo:typecheck": "turbo typecheck",
"turbo:test": "turbo test",
"turbo:test:unit": "turbo test:unit",
"turbo:test:integration": "turbo test:integration",
"build:build-config": "npm run build -w @tm/build-config",
"test": "cross-env NODE_ENV=test node --experimental-vm-modules node_modules/.bin/jest",
"test:unit": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=unit",
"test:integration": "node --experimental-vm-modules node_modules/.bin/jest --testPathPattern=integration",
"test:fails": "node --experimental-vm-modules node_modules/.bin/jest --onlyFailures",
"test:watch": "node --experimental-vm-modules node_modules/.bin/jest --watch",
"test:coverage": "node --experimental-vm-modules node_modules/.bin/jest --coverage",
"test:ci": "node --experimental-vm-modules node_modules/.bin/jest --coverage --ci",
"build:build-config": "bun run build -w @tm/build-config",
"test": "turbo test",
"test:unit": "turbo test:unit",
"test:integration": "turbo test:integration",
"test:watch": "turbo test:watch",
"test:coverage": "turbo test:coverage",
"test:ci": "turbo test:ci",
"test:root": "bun test tests/",
"test:root:watch": "bun test tests/ --watch",
"test:e2e": "./tests/e2e/run_e2e.sh",
"test:e2e-report": "./tests/e2e/run_e2e.sh --analyze-log",
"postpack": "chmod +x dist/task-master.js dist/mcp-server.js",
"changeset": "changeset",
"changeset:validate": "node .github/scripts/validate-changesets.mjs",
"version": "changeset version && node ./.github/scripts/sync-manifest-version.mjs && npm i --package-lock-only",
"version": "changeset version && node ./.github/scripts/sync-manifest-version.mjs && bun install --frozen-lockfile || true",
"release": "node ./.github/scripts/release.mjs",
"publish-packages": "turbo run build lint test && changeset version && changeset publish",
"inspector": "npx @modelcontextprotocol/inspector node dist/mcp-server.js",
@@ -130,7 +131,7 @@
"engines": {
"node": ">=20.0.0"
},
"packageManager": "npm@10.9.2",
"packageManager": "bun@1.2.6",
"repository": {
"type": "git",
"url": "git+https://github.com/eyaltoledano/claude-task-master.git"
@@ -158,22 +159,17 @@
"@manypkg/cli": "^0.25.1",
"@tm/ai-sdk-provider-grok-cli": "*",
"@tm/cli": "*",
"@types/bun": "^1.2.6",
"@types/fs-extra": "^11.0.4",
"@types/jest": "^29.5.14",
"@types/marked-terminal": "^6.1.1",
"@vitest/coverage-v8": "^4.0.10",
"concurrently": "^9.2.1",
"cross-env": "^10.0.0",
"execa": "^8.0.1",
"jest": "^29.7.0",
"jest-environment-node": "^29.7.0",
"mock-fs": "^5.5.0",
"prettier": "^3.5.3",
"supertest": "^7.1.0",
"ts-jest": "^29.4.2",
"tsdown": "^0.15.2",
"tsx": "^4.20.4",
"turbo": "2.5.6",
"typescript": "^5.9.2"
"typescript": "^5.9.2",
"vitest": "^4.0.10"
}
}

View File

@@ -18,13 +18,10 @@
"scripts": {
"build": "tsc",
"typecheck": "tsc --noEmit",
"fix-null-versions": "npx tsx scripts/fix-null-versions.ts"
"fix-null-versions": "bun run scripts/fix-null-versions.ts"
},
"devDependencies": {
"typescript": "^5.9.2"
},
"dependencies": {
"tsup": "^8.5.0"
},
"version": ""
}

View File

@@ -9,8 +9,8 @@
".": "./src/index.ts"
},
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test": "vitest run --coverage=false --passWithNoTests",
"test:watch": "vitest --coverage=false",
"lint": "biome check --write",
"lint:check": "biome check",
"typecheck": "tsc --noEmit"

View File

@@ -20,10 +20,10 @@
"./utils": "./src/utils/index.ts"
},
"scripts": {
"test": "vitest run",
"test:unit": "vitest run '**/*.spec.ts'",
"test:integration": "vitest run '**/*.test.ts'",
"test:watch": "vitest",
"test": "vitest run --coverage=false",
"test:unit": "vitest run --coverage=false '**/*.spec.ts'",
"test:integration": "vitest run --coverage=false '**/*.test.ts'",
"test:watch": "vitest --coverage=false",
"test:coverage": "vitest run --coverage",
"lint": "biome check --write",
"lint:check": "biome check",

View File

@@ -9,10 +9,10 @@
".": "./src/index.ts"
},
"scripts": {
"test": "vitest run",
"test:unit": "vitest run '**/*.spec.ts'",
"test:integration": "vitest run '**/*.test.ts'",
"test:watch": "vitest",
"test": "vitest run --coverage=false",
"test:unit": "vitest run --coverage=false '**/*.spec.ts'",
"test:integration": "vitest run --coverage=false '**/*.test.ts'",
"test:watch": "vitest --coverage=false",
"test:coverage": "vitest run --coverage",
"lint": "biome check --write",
"lint:check": "biome check",

186
scripts/build.ts Normal file
View File

@@ -0,0 +1,186 @@
#!/usr/bin/env bun
/**
* Bun build script for Task Master monorepo
* Replaces tsdown configuration for building the CLI and MCP server
*/
import { $ } from 'bun';
import {
readFileSync,
copyFileSync,
mkdirSync,
existsSync,
readdirSync,
statSync
} from 'node:fs';
import { join, resolve } from 'node:path';
const isProduction = process.env.NODE_ENV === 'production';
const projectRoot = resolve(import.meta.dirname, '..');
console.log(
`Building Task Master (${isProduction ? 'production' : 'development'})...`
);
// Import fs/promises once for use throughout
const fsPromises = await import('node:fs/promises');
// Clean dist directory (but preserve assets if they exist from a previous build)
const distDir = join(projectRoot, 'dist');
if (existsSync(distDir)) {
const entries = readdirSync(distDir);
for (const entry of entries) {
if (entry !== 'assets') {
const entryPath = join(distDir, entry);
const stat = statSync(entryPath);
if (stat.isDirectory()) {
await fsPromises.rm(entryPath, { recursive: true });
} else {
await fsPromises.unlink(entryPath);
}
}
}
}
/**
* Get build-time environment variables
*/
function getBuildTimeEnvs(): Record<string, string> {
const envs: Record<string, string> = {};
// Inject package.json version at build time
try {
const packageJson = JSON.parse(
readFileSync(join(projectRoot, 'package.json'), 'utf8')
);
envs['TM_PUBLIC_VERSION'] = packageJson.version || 'unknown';
} catch (error) {
console.warn('Could not read package.json version during build:', error);
envs['TM_PUBLIC_VERSION'] = 'unknown';
}
// Include all TM_PUBLIC_* env variables
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith('TM_PUBLIC_')) {
envs[key] = value || '';
}
}
return envs;
}
/**
* Recursively copy a directory
*/
function copyDir(src: string, dest: string): void {
mkdirSync(dest, { recursive: true });
const entries = readdirSync(src);
for (const entry of entries) {
const srcPath = join(src, entry);
const destPath = join(dest, entry);
const stat = statSync(srcPath);
if (stat.isDirectory()) {
copyDir(srcPath, destPath);
} else {
copyFileSync(srcPath, destPath);
}
}
}
// Get external dependencies from package.json (exclude @tm/* workspace packages)
function getExternalDependencies(): string[] {
try {
const pkg = JSON.parse(
readFileSync(join(projectRoot, 'package.json'), 'utf8')
);
const allDeps = [
...Object.keys(pkg.dependencies || {}),
...Object.keys(pkg.devDependencies || {}),
...Object.keys(pkg.optionalDependencies || {})
];
// Keep npm packages external, but bundle @tm/* workspace packages
return allDeps.filter((dep) => !dep.startsWith('@tm/'));
} catch {
return [];
}
}
// Build main entry points
const result = await Bun.build({
entrypoints: [
join(projectRoot, 'scripts/dev.js'),
join(projectRoot, 'mcp-server/server.js')
],
outdir: join(projectRoot, 'dist'),
target: 'node',
format: 'esm',
splitting: false,
// Keep npm packages external, bundle @tm/* workspace packages and local code
external: getExternalDependencies(),
minify: isProduction,
sourcemap: isProduction ? 'none' : 'linked',
naming: {
entry: '[name].js'
},
define: Object.fromEntries(
Object.entries(getBuildTimeEnvs()).map(([key, value]) => [
`process.env.${key}`,
JSON.stringify(value)
])
)
});
if (!result.success) {
console.error('Build failed:');
for (const log of result.logs) {
console.error(log);
}
process.exit(1);
}
// Rename output files to match expected names
const devOutputPath = join(distDir, 'dev.js');
const taskMasterPath = join(distDir, 'task-master.js');
/**
* Ensure file has exactly one shebang at the top
*/
function ensureShebang(content: string): string {
const shebang = '#!/usr/bin/env node\n';
// Remove any existing shebangs (there might be multiple from bundling)
let cleaned = content.replace(/^(#!.*\n)+/gm, '');
// Also handle shebangs that appear after the first line
cleaned = cleaned.replace(/\n#!\/usr\/bin\/env node\n/g, '\n');
return shebang + cleaned;
}
if (existsSync(devOutputPath)) {
const content = readFileSync(devOutputPath, 'utf8');
await fsPromises.writeFile(taskMasterPath, ensureShebang(content));
await fsPromises.unlink(devOutputPath);
await fsPromises.chmod(taskMasterPath, 0o755);
}
// Rename and fix shebang for mcp-server.js
const mcpServerPath = join(distDir, 'server.js');
const mcpFinalPath = join(distDir, 'mcp-server.js');
if (existsSync(mcpServerPath)) {
const content = readFileSync(mcpServerPath, 'utf8');
await fsPromises.writeFile(mcpFinalPath, ensureShebang(content));
await fsPromises.unlink(mcpServerPath);
await fsPromises.chmod(mcpFinalPath, 0o755);
}
// Copy assets directory
const assetsDir = join(projectRoot, 'assets');
const distAssetsDir = join(distDir, 'assets');
if (existsSync(assetsDir)) {
copyDir(assetsDir, distAssetsDir);
console.log('Copied assets to dist/');
}
console.log(
'Build complete:',
result.outputs.map((o) => o.path.replace(projectRoot + '/', ''))
);

75
tests/setup.ts Normal file
View File

@@ -0,0 +1,75 @@
/**
* Bun test setup file
*
* This file is run before each test suite to set up the test environment.
* Used for root-level tests (tests/) that use Bun's test runner.
* Package-level tests (tm-core, cli, mcp) use Vitest for advanced mocking.
*/
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { afterAll } from 'bun:test';
// Capture the actual original working directory before any changes
const originalWorkingDirectory = process.cwd();
// Store original working directory and project root
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, '..');
// Ensure we're always starting from the project root
if (process.cwd() !== projectRoot) {
process.chdir(projectRoot);
}
// Mock environment variables
process.env.MODEL = 'sonar-pro';
process.env.MAX_TOKENS = '64000';
process.env.TEMPERATURE = '0.2';
process.env.DEBUG = 'false';
process.env.TASKMASTER_LOG_LEVEL = 'error'; // Set to error to reduce noise in tests
process.env.DEFAULT_SUBTASKS = '5';
process.env.DEFAULT_PRIORITY = 'medium';
process.env.PROJECT_NAME = 'Test Project';
process.env.PROJECT_VERSION = '1.0.0';
// Ensure tests don't make real API calls by setting mock API keys
process.env.ANTHROPIC_API_KEY = 'test-mock-api-key-for-tests';
process.env.PERPLEXITY_API_KEY = 'test-mock-perplexity-key-for-tests';
// Add global test helpers if needed
declare global {
var wait: (ms: number) => Promise<void>;
var originalWorkingDirectory: string;
var projectRoot: string;
}
globalThis.wait = (ms: number) =>
new Promise((resolve) => setTimeout(resolve, ms));
// Store original working directory for tests that need it
globalThis.originalWorkingDirectory = originalWorkingDirectory;
globalThis.projectRoot = projectRoot;
// If needed, silence console during tests
if (process.env.SILENCE_CONSOLE === 'true') {
globalThis.console = {
...console,
log: () => {},
info: () => {},
warn: () => {},
error: () => {}
};
}
// Clean up signal-exit listeners after all tests to prevent open handle warnings
// This is needed because packages like proper-lockfile register signal handlers
afterAll(async () => {
// Give any pending async operations time to complete
await new Promise((resolve) => setImmediate(resolve));
// Clean up any registered signal handlers from signal-exit
const listeners = ['SIGINT', 'SIGTERM', 'SIGHUP'] as const;
for (const signal of listeners) {
process.removeAllListeners(signal);
}
});

View File

@@ -1,46 +0,0 @@
import { resolve } from 'path';
import { baseConfig, mergeConfig } from '@tm/build-config';
import { config } from 'dotenv';
import { defineConfig } from 'tsdown';
// Load .env file explicitly with absolute path
config({ path: resolve(process.cwd(), '.env') });
// Get all TM_PUBLIC_* env variables for build-time injection
const getBuildTimeEnvs = () => {
const envs: Record<string, string> = {};
// Inject package.json version at build time
try {
const packageJson = JSON.parse(
require('fs').readFileSync('package.json', 'utf8')
);
envs['TM_PUBLIC_VERSION'] = packageJson.version || 'unknown';
} catch (error) {
console.warn('Could not read package.json version during build:', error);
envs['TM_PUBLIC_VERSION'] = 'unknown';
}
for (const [key, value] of Object.entries(process.env)) {
if (key.startsWith('TM_PUBLIC_')) {
envs[key] = value || '';
}
}
return envs;
};
export default defineConfig(
mergeConfig(baseConfig, {
entry: {
'task-master': 'scripts/dev.js',
'mcp-server': 'mcp-server/server.js'
},
outDir: 'dist',
copy: ['assets'],
ignoreWatch: ['node_modules', 'dist', 'tests', 'apps/extension'],
// Bundle only our workspace packages, keep npm dependencies external
noExternal: [/^@tm\//],
env: getBuildTimeEnvs()
})
);