mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2026-01-30 06:12:05 +00:00
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:
50
.github/workflows/ci.yml
vendored
50
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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": {
|
||||
|
||||
27
bunfig.toml
Normal file
27
bunfig.toml
Normal 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
|
||||
@@ -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']
|
||||
};
|
||||
@@ -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
37318
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
186
scripts/build.ts
Normal 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
75
tests/setup.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
@@ -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()
|
||||
})
|
||||
);
|
||||
Reference in New Issue
Block a user