Skip to content

Build API

Build APIは、Traeでビルドプロセス、ビルド設定、ビルド自動化を管理するための機能を提供します。

概要

Build APIでは以下のことができます:

  • ビルドプロセスの設定と管理
  • ビルドタスクとスクリプトの実行
  • ビルドの進行状況とステータスの監視
  • ビルド成果物と出力の処理
  • 様々なビルドツールとの統合
  • ビルド環境と依存関係の管理
  • ビルドパイプラインの自動化
  • ビルド通知とイベントの処理

基本的な使用方法

ビルドマネージャー

typescript
import { TraeAPI } from '@trae/api';
import * as path from 'path';
import * as fs from 'fs';

// ビルドマネージャー
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();
  }

  // ビルド設定管理
  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);
  }

  // ビルド実行
  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 configuration
  • getBuildConfiguration(name): BuildConfiguration | undefined - Get build configuration
  • getAllBuildConfigurations(): Map<string, BuildConfiguration> - Get all configurations
  • startBuild(configName, options?): Promise<BuildResult> - Start build process
  • stopBuild(buildId): Promise<void> - Stop build process
  • queueBuild(configName, options?): string - Queue build for execution
  • getBuildStatus(buildId): BuildStatus | undefined - Get build status
  • getAllBuildStatuses(): BuildStatus[] - Get all build statuses
  • getBuildHistory(): BuildResult[] - Get build history

Events

  • onBuildStarted(listener): Disposable - Build started
  • onBuildCompleted(listener): Disposable - Build completed
  • onBuildFailed(listener): Disposable - Build failed
  • onBuildOutput(listener): Disposable - Build output

Best Practices

  1. Configuration: Use descriptive names and clear step descriptions
  2. Error Handling: Implement proper error handling and logging
  3. Performance: Optimize build steps and use caching when possible
  4. Security: Validate build configurations and sanitize inputs
  5. Monitoring: Provide real-time build progress and status updates
  6. Artifacts: Properly collect and manage build artifacts
  7. Environment: Use environment variables for configuration
  8. Cleanup: Clean up temporary files and resources
  9. Parallelization: Consider parallel execution for independent steps
  10. Documentation: Document build configurations and requirements

究極の AI 駆動 IDE 学習ガイド