Build API
The Build API provides functionality for managing build processes, build configurations, and build automation in Trae.
Overview
The Build API enables you to:
- Configure and manage build processes
- Execute build tasks and scripts
- Monitor build progress and status
- Handle build artifacts and outputs
- Integrate with various build tools
- Manage build environments and dependencies
- Automate build pipelines
- Handle build notifications and events
Basic Usage
Build Manager
typescript
import { TraeAPI } from '@trae/api';
import * as path from 'path';
import * as fs from 'fs';
// Build manager
class BuildManager {
private builds: Map<string, BuildProcess> = new Map();
private buildConfigs: Map<string, BuildConfiguration> = new Map();
private eventEmitter = new TraeAPI.EventEmitter<BuildEvent>();
private disposables: TraeAPI.Disposable[] = [];
private buildQueue: BuildTask[] = [];
private activeBuild: BuildProcess | undefined;
private buildHistory: BuildResult[] = [];
private maxHistorySize = 100;
constructor() {
this.setupDefaultConfigurations();
this.setupEventHandlers();
}
// Build configuration management
registerBuildConfiguration(name: string, config: BuildConfiguration): void {
this.buildConfigs.set(name, config);
this.eventEmitter.fire({ type: 'configurationRegistered', name, config });
}
getBuildConfiguration(name: string): BuildConfiguration | undefined {
return this.buildConfigs.get(name);
}
getAllBuildConfigurations(): Map<string, BuildConfiguration> {
return new Map(this.buildConfigs);
}
// Build execution
async startBuild(
configName: string,
options: BuildOptions = {}
): Promise<BuildResult> {
const config = this.buildConfigs.get(configName);
if (!config) {
throw new Error(`Build configuration '${configName}' not found`);
}
const buildId = this.generateBuildId();
const buildProcess = new BuildProcess(buildId, config, options);
this.builds.set(buildId, buildProcess);
this.activeBuild = buildProcess;
try {
this.eventEmitter.fire({ type: 'buildStarted', buildId, config });
const result = await this.executeBuild(buildProcess);
this.addToHistory(result);
this.eventEmitter.fire({ type: 'buildCompleted', buildId, result });
return result;
} catch (error) {
const errorResult: BuildResult = {
buildId,
configName,
status: 'failed',
startTime: buildProcess.startTime,
endTime: new Date(),
duration: Date.now() - buildProcess.startTime.getTime(),
error: error.message,
artifacts: [],
logs: buildProcess.logs
};
this.addToHistory(errorResult);
this.eventEmitter.fire({ type: 'buildFailed', buildId, error, result: errorResult });
throw error;
} finally {
this.builds.delete(buildId);
if (this.activeBuild === buildProcess) {
this.activeBuild = undefined;
}
}
}
async stopBuild(buildId: string): Promise<void> {
const buildProcess = this.builds.get(buildId);
if (!buildProcess) {
throw new Error(`Build process '${buildId}' not found`);
}
await buildProcess.stop();
this.builds.delete(buildId);
if (this.activeBuild === buildProcess) {
this.activeBuild = undefined;
}
this.eventEmitter.fire({ type: 'buildStopped', buildId });
}
// Build queue management
queueBuild(configName: string, options: BuildOptions = {}): string {
const taskId = this.generateTaskId();
const task: BuildTask = {
id: taskId,
configName,
options,
status: 'queued',
queuedAt: new Date()
};
this.buildQueue.push(task);
this.eventEmitter.fire({ type: 'buildQueued', task });
// Process queue if no active build
if (!this.activeBuild) {
this.processQueue();
}
return taskId;
}
private async processQueue(): Promise<void> {
while (this.buildQueue.length > 0 && !this.activeBuild) {
const task = this.buildQueue.shift()!;
try {
task.status = 'running';
task.startedAt = new Date();
this.eventEmitter.fire({ type: 'buildTaskStarted', task });
await this.startBuild(task.configName, task.options);
task.status = 'completed';
task.completedAt = new Date();
this.eventEmitter.fire({ type: 'buildTaskCompleted', task });
} catch (error) {
task.status = 'failed';
task.error = error.message;
task.completedAt = new Date();
this.eventEmitter.fire({ type: 'buildTaskFailed', task, error });
}
}
}
// Build execution implementation
private async executeBuild(buildProcess: BuildProcess): Promise<BuildResult> {
const { config, options } = buildProcess;
const workspaceRoot = TraeAPI.workspace.workspaceFolders?.[0]?.uri.fsPath;
if (!workspaceRoot) {
throw new Error('No workspace folder found');
}
// Pre-build steps
if (config.preBuild) {
await this.executeSteps(config.preBuild, buildProcess, workspaceRoot);
}
// Main build steps
await this.executeSteps(config.build, buildProcess, workspaceRoot);
// Post-build steps
if (config.postBuild) {
await this.executeSteps(config.postBuild, buildProcess, workspaceRoot);
}
// Collect artifacts
const artifacts = await this.collectArtifacts(config, workspaceRoot);
const result: BuildResult = {
buildId: buildProcess.id,
configName: config.name,
status: 'success',
startTime: buildProcess.startTime,
endTime: new Date(),
duration: Date.now() - buildProcess.startTime.getTime(),
artifacts,
logs: buildProcess.logs
};
return result;
}
private async executeSteps(
steps: BuildStep[],
buildProcess: BuildProcess,
workspaceRoot: string
): Promise<void> {
for (const step of steps) {
if (buildProcess.isStopped) {
throw new Error('Build was stopped');
}
this.eventEmitter.fire({ type: 'buildStepStarted', buildId: buildProcess.id, step });
try {
await this.executeStep(step, buildProcess, workspaceRoot);
this.eventEmitter.fire({ type: 'buildStepCompleted', buildId: buildProcess.id, step });
} catch (error) {
this.eventEmitter.fire({ type: 'buildStepFailed', buildId: buildProcess.id, step, error });
throw error;
}
}
}
private async executeStep(
step: BuildStep,
buildProcess: BuildProcess,
workspaceRoot: string
): Promise<void> {
const cwd = step.workingDirectory ? path.resolve(workspaceRoot, step.workingDirectory) : workspaceRoot;
switch (step.type) {
case 'command':
await this.executeCommand(step as CommandStep, buildProcess, cwd);
break;
case 'script':
await this.executeScript(step as ScriptStep, buildProcess, cwd);
break;
case 'copy':
await this.executeCopy(step as CopyStep, buildProcess, workspaceRoot);
break;
case 'delete':
await this.executeDelete(step as DeleteStep, buildProcess, workspaceRoot);
break;
case 'mkdir':
await this.executeMkdir(step as MkdirStep, buildProcess, workspaceRoot);
break;
default:
throw new Error(`Unknown build step type: ${(step as any).type}`);
}
}
private async executeCommand(
step: CommandStep,
buildProcess: BuildProcess,
cwd: string
): Promise<void> {
const { spawn } = require('child_process');
return new Promise((resolve, reject) => {
const [command, ...args] = step.command.split(' ');
const child = spawn(command, args, {
cwd,
env: { ...process.env, ...step.env },
stdio: 'pipe'
});
let stdout = '';
let stderr = '';
child.stdout?.on('data', (data: Buffer) => {
const text = data.toString();
stdout += text;
buildProcess.addLog('stdout', text);
this.eventEmitter.fire({ type: 'buildOutput', buildId: buildProcess.id, output: text, stream: 'stdout' });
});
child.stderr?.on('data', (data: Buffer) => {
const text = data.toString();
stderr += text;
buildProcess.addLog('stderr', text);
this.eventEmitter.fire({ type: 'buildOutput', buildId: buildProcess.id, output: text, stream: 'stderr' });
});
child.on('close', (code) => {
if (code === 0) {
resolve();
} else {
reject(new Error(`Command failed with exit code ${code}: ${stderr}`));
}
});
child.on('error', (error) => {
reject(error);
});
// Handle build stop
buildProcess.onStop(() => {
child.kill();
reject(new Error('Build was stopped'));
});
});
}
private async executeScript(
step: ScriptStep,
buildProcess: BuildProcess,
cwd: string
): Promise<void> {
// Execute inline script or script file
if (step.inline) {
// Execute inline script
const tempFile = path.join(cwd, `temp-script-${Date.now()}.js`);
fs.writeFileSync(tempFile, step.inline);
try {
await this.executeCommand({
type: 'command',
name: `Execute inline script`,
command: `node ${tempFile}`,
env: step.env
}, buildProcess, cwd);
} finally {
if (fs.existsSync(tempFile)) {
fs.unlinkSync(tempFile);
}
}
} else if (step.file) {
// Execute script file
const scriptPath = path.resolve(cwd, step.file);
if (!fs.existsSync(scriptPath)) {
throw new Error(`Script file not found: ${scriptPath}`);
}
await this.executeCommand({
type: 'command',
name: `Execute script: ${step.file}`,
command: `node ${scriptPath}`,
env: step.env
}, buildProcess, cwd);
} else {
throw new Error('Script step must specify either inline code or file path');
}
}
private async executeCopy(
step: CopyStep,
buildProcess: BuildProcess,
workspaceRoot: string
): Promise<void> {
const sourcePath = path.resolve(workspaceRoot, step.source);
const destPath = path.resolve(workspaceRoot, step.destination);
buildProcess.addLog('info', `Copying ${sourcePath} to ${destPath}`);
if (!fs.existsSync(sourcePath)) {
throw new Error(`Source path does not exist: ${sourcePath}`);
}
// Ensure destination directory exists
const destDir = path.dirname(destPath);
if (!fs.existsSync(destDir)) {
fs.mkdirSync(destDir, { recursive: true });
}
// Copy file or directory
const stats = fs.statSync(sourcePath);
if (stats.isDirectory()) {
await this.copyDirectory(sourcePath, destPath);
} else {
fs.copyFileSync(sourcePath, destPath);
}
}
private async copyDirectory(source: string, dest: string): Promise<void> {
if (!fs.existsSync(dest)) {
fs.mkdirSync(dest, { recursive: true });
}
const entries = fs.readdirSync(source, { withFileTypes: true });
for (const entry of entries) {
const sourcePath = path.join(source, entry.name);
const destPath = path.join(dest, entry.name);
if (entry.isDirectory()) {
await this.copyDirectory(sourcePath, destPath);
} else {
fs.copyFileSync(sourcePath, destPath);
}
}
}
private async executeDelete(
step: DeleteStep,
buildProcess: BuildProcess,
workspaceRoot: string
): Promise<void> {
const targetPath = path.resolve(workspaceRoot, step.path);
buildProcess.addLog('info', `Deleting ${targetPath}`);
if (fs.existsSync(targetPath)) {
const stats = fs.statSync(targetPath);
if (stats.isDirectory()) {
fs.rmSync(targetPath, { recursive: true, force: true });
} else {
fs.unlinkSync(targetPath);
}
}
}
private async executeMkdir(
step: MkdirStep,
buildProcess: BuildProcess,
workspaceRoot: string
): Promise<void> {
const targetPath = path.resolve(workspaceRoot, step.path);
buildProcess.addLog('info', `Creating directory ${targetPath}`);
if (!fs.existsSync(targetPath)) {
fs.mkdirSync(targetPath, { recursive: true });
}
}
private async collectArtifacts(
config: BuildConfiguration,
workspaceRoot: string
): Promise<BuildArtifact[]> {
const artifacts: BuildArtifact[] = [];
if (!config.artifacts) {
return artifacts;
}
for (const artifactConfig of config.artifacts) {
const artifactPath = path.resolve(workspaceRoot, artifactConfig.path);
if (fs.existsSync(artifactPath)) {
const stats = fs.statSync(artifactPath);
artifacts.push({
name: artifactConfig.name || path.basename(artifactPath),
path: artifactPath,
size: stats.size,
type: artifactConfig.type || this.getArtifactType(artifactPath),
createdAt: stats.mtime
});
}
}
return artifacts;
}
private getArtifactType(filePath: string): string {
const ext = path.extname(filePath).toLowerCase();
switch (ext) {
case '.js':
case '.mjs':
return 'javascript';
case '.ts':
return 'typescript';
case '.css':
return 'stylesheet';
case '.html':
return 'html';
case '.json':
return 'json';
case '.zip':
case '.tar':
case '.gz':
return 'archive';
case '.exe':
case '.app':
case '.deb':
case '.rpm':
return 'executable';
default:
return 'file';
}
}
// Build status and monitoring
getBuildStatus(buildId: string): BuildStatus | undefined {
const buildProcess = this.builds.get(buildId);
if (!buildProcess) {
return undefined;
}
return {
buildId,
status: buildProcess.isStopped ? 'stopped' : 'running',
startTime: buildProcess.startTime,
duration: Date.now() - buildProcess.startTime.getTime(),
currentStep: buildProcess.currentStep
};
}
getAllBuildStatuses(): BuildStatus[] {
return Array.from(this.builds.values()).map(process => ({
buildId: process.id,
status: process.isStopped ? 'stopped' : 'running',
startTime: process.startTime,
duration: Date.now() - process.startTime.getTime(),
currentStep: process.currentStep
}));
}
getBuildHistory(): BuildResult[] {
return [...this.buildHistory];
}
private addToHistory(result: BuildResult): void {
this.buildHistory.unshift(result);
if (this.buildHistory.length > this.maxHistorySize) {
this.buildHistory.pop();
}
}
// Default configurations
private setupDefaultConfigurations(): void {
// Node.js build configuration
this.registerBuildConfiguration('nodejs', {
name: 'nodejs',
description: 'Node.js application build',
build: [
{
type: 'command',
name: 'Install dependencies',
command: 'npm install'
},
{
type: 'command',
name: 'Build application',
command: 'npm run build'
}
],
artifacts: [
{ path: 'dist', name: 'Distribution', type: 'directory' },
{ path: 'package.json', name: 'Package manifest', type: 'json' }
]
});
// TypeScript build configuration
this.registerBuildConfiguration('typescript', {
name: 'typescript',
description: 'TypeScript project build',
build: [
{
type: 'command',
name: 'Install dependencies',
command: 'npm install'
},
{
type: 'command',
name: 'Type check',
command: 'npx tsc --noEmit'
},
{
type: 'command',
name: 'Build TypeScript',
command: 'npx tsc'
}
],
artifacts: [
{ path: 'dist', name: 'Compiled JavaScript', type: 'directory' },
{ path: 'dist/**/*.d.ts', name: 'Type definitions', type: 'typescript' }
]
});
// React build configuration
this.registerBuildConfiguration('react', {
name: 'react',
description: 'React application build',
build: [
{
type: 'command',
name: 'Install dependencies',
command: 'npm install'
},
{
type: 'command',
name: 'Build React app',
command: 'npm run build'
}
],
artifacts: [
{ path: 'build', name: 'React build', type: 'directory' },
{ path: 'build/static', name: 'Static assets', type: 'directory' }
]
});
}
private setupEventHandlers(): void {
// Setup build event handlers
}
private generateBuildId(): string {
return `build-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
private generateTaskId(): string {
return `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
}
// Event handling
onBuildStarted(listener: (event: BuildStartedEvent) => void): TraeAPI.Disposable {
return this.eventEmitter.event(event => {
if (event.type === 'buildStarted') {
listener(event as BuildStartedEvent);
}
});
}
onBuildCompleted(listener: (event: BuildCompletedEvent) => void): TraeAPI.Disposable {
return this.eventEmitter.event(event => {
if (event.type === 'buildCompleted') {
listener(event as BuildCompletedEvent);
}
});
}
onBuildFailed(listener: (event: BuildFailedEvent) => void): TraeAPI.Disposable {
return this.eventEmitter.event(event => {
if (event.type === 'buildFailed') {
listener(event as BuildFailedEvent);
}
});
}
onBuildOutput(listener: (event: BuildOutputEvent) => void): TraeAPI.Disposable {
return this.eventEmitter.event(event => {
if (event.type === 'buildOutput') {
listener(event as BuildOutputEvent);
}
});
}
// Dispose
dispose(): void {
// Stop all builds
for (const buildProcess of this.builds.values()) {
buildProcess.stop().catch(console.error);
}
this.builds.clear();
// Clear configurations
this.buildConfigs.clear();
// Clear queue
this.buildQueue = [];
// Clear history
this.buildHistory = [];
// Dispose other resources
this.disposables.forEach(d => d.dispose());
this.disposables = [];
this.eventEmitter.dispose();
}
}
// Build process implementation
class BuildProcess {
private _isStopped = false;
private _logs: BuildLog[] = [];
private _currentStep: string | undefined;
private _stopCallbacks: (() => void)[] = [];
constructor(
public readonly id: string,
public readonly config: BuildConfiguration,
public readonly options: BuildOptions,
public readonly startTime: Date = new Date()
) {}
get isStopped(): boolean {
return this._isStopped;
}
get logs(): BuildLog[] {
return [...this._logs];
}
get currentStep(): string | undefined {
return this._currentStep;
}
addLog(level: 'info' | 'warn' | 'error' | 'stdout' | 'stderr', message: string): void {
this._logs.push({
level,
message,
timestamp: new Date()
});
}
setCurrentStep(step: string): void {
this._currentStep = step;
}
async stop(): Promise<void> {
this._isStopped = true;
// Notify all stop callbacks
for (const callback of this._stopCallbacks) {
try {
callback();
} catch (error) {
console.error('Error in stop callback:', error);
}
}
this._stopCallbacks = [];
}
onStop(callback: () => void): void {
this._stopCallbacks.push(callback);
}
}
// Initialize build manager
const buildManager = new BuildManager();Interface Definitions
typescript
// Build configuration
interface BuildConfiguration {
name: string;
description?: string;
preBuild?: BuildStep[];
build: BuildStep[];
postBuild?: BuildStep[];
artifacts?: ArtifactConfiguration[];
environment?: Record<string, string>;
timeout?: number;
}
// Build step types
type BuildStep = CommandStep | ScriptStep | CopyStep | DeleteStep | MkdirStep;
interface BaseStep {
type: string;
name: string;
workingDirectory?: string;
condition?: string;
}
interface CommandStep extends BaseStep {
type: 'command';
command: string;
env?: Record<string, string>;
}
interface ScriptStep extends BaseStep {
type: 'script';
inline?: string;
file?: string;
env?: Record<string, string>;
}
interface CopyStep extends BaseStep {
type: 'copy';
source: string;
destination: string;
}
interface DeleteStep extends BaseStep {
type: 'delete';
path: string;
}
interface MkdirStep extends BaseStep {
type: 'mkdir';
path: string;
}
// Build options
interface BuildOptions {
clean?: boolean;
verbose?: boolean;
environment?: Record<string, string>;
timeout?: number;
}
// Build result
interface BuildResult {
buildId: string;
configName: string;
status: 'success' | 'failed' | 'stopped';
startTime: Date;
endTime: Date;
duration: number;
error?: string;
artifacts: BuildArtifact[];
logs: BuildLog[];
}
// Build artifact
interface BuildArtifact {
name: string;
path: string;
size: number;
type: string;
createdAt: Date;
}
// Artifact configuration
interface ArtifactConfiguration {
path: string;
name?: string;
type?: string;
}
// Build log
interface BuildLog {
level: 'info' | 'warn' | 'error' | 'stdout' | 'stderr';
message: string;
timestamp: Date;
}
// Build status
interface BuildStatus {
buildId: string;
status: 'running' | 'stopped';
startTime: Date;
duration: number;
currentStep?: string;
}
// Build task
interface BuildTask {
id: string;
configName: string;
options: BuildOptions;
status: 'queued' | 'running' | 'completed' | 'failed';
queuedAt: Date;
startedAt?: Date;
completedAt?: Date;
error?: string;
}
// Build events
type BuildEvent = BuildStartedEvent | BuildCompletedEvent | BuildFailedEvent | BuildStoppedEvent |
BuildOutputEvent | BuildStepStartedEvent | BuildStepCompletedEvent | BuildStepFailedEvent |
BuildQueuedEvent | BuildTaskStartedEvent | BuildTaskCompletedEvent | BuildTaskFailedEvent |
ConfigurationRegisteredEvent;
interface BuildStartedEvent {
type: 'buildStarted';
buildId: string;
config: BuildConfiguration;
}
interface BuildCompletedEvent {
type: 'buildCompleted';
buildId: string;
result: BuildResult;
}
interface BuildFailedEvent {
type: 'buildFailed';
buildId: string;
error: any;
result: BuildResult;
}
interface BuildStoppedEvent {
type: 'buildStopped';
buildId: string;
}
interface BuildOutputEvent {
type: 'buildOutput';
buildId: string;
output: string;
stream: 'stdout' | 'stderr';
}
interface BuildStepStartedEvent {
type: 'buildStepStarted';
buildId: string;
step: BuildStep;
}
interface BuildStepCompletedEvent {
type: 'buildStepCompleted';
buildId: string;
step: BuildStep;
}
interface BuildStepFailedEvent {
type: 'buildStepFailed';
buildId: string;
step: BuildStep;
error: any;
}
interface BuildQueuedEvent {
type: 'buildQueued';
task: BuildTask;
}
interface BuildTaskStartedEvent {
type: 'buildTaskStarted';
task: BuildTask;
}
interface BuildTaskCompletedEvent {
type: 'buildTaskCompleted';
task: BuildTask;
}
interface BuildTaskFailedEvent {
type: 'buildTaskFailed';
task: BuildTask;
error: any;
}
interface ConfigurationRegisteredEvent {
type: 'configurationRegistered';
name: string;
config: BuildConfiguration;
}API Reference
BuildManager
Methods
registerBuildConfiguration(name, config): void- Register build configurationgetBuildConfiguration(name): BuildConfiguration | undefined- Get build configurationgetAllBuildConfigurations(): Map<string, BuildConfiguration>- Get all configurationsstartBuild(configName, options?): Promise<BuildResult>- Start build processstopBuild(buildId): Promise<void>- Stop build processqueueBuild(configName, options?): string- Queue build for executiongetBuildStatus(buildId): BuildStatus | undefined- Get build statusgetAllBuildStatuses(): BuildStatus[]- Get all build statusesgetBuildHistory(): BuildResult[]- Get build history
Events
onBuildStarted(listener): Disposable- Build startedonBuildCompleted(listener): Disposable- Build completedonBuildFailed(listener): Disposable- Build failedonBuildOutput(listener): Disposable- Build output
Best Practices
- Configuration: Use descriptive names and clear step descriptions
- Error Handling: Implement proper error handling and logging
- Performance: Optimize build steps and use caching when possible
- Security: Validate build configurations and sanitize inputs
- Monitoring: Provide real-time build progress and status updates
- Artifacts: Properly collect and manage build artifacts
- Environment: Use environment variables for configuration
- Cleanup: Clean up temporary files and resources
- Parallelization: Consider parallel execution for independent steps
- Documentation: Document build configurations and requirements
Related APIs
- Tasks API - Task execution integration
- Terminal API - Terminal command execution
- Files API - File system operations
- Workspace API - Workspace configuration
- Settings API - Build settings management
- Extensions API - Build extension development
- Testing API - Test integration with builds
- Git API - Version control integration