or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

commands.mdconfiguration.mddependency-management.mdindex.mdtask-execution.mdutilities.md
tile.json

dependency-management.mddocs/

Dependency Management

ember-try uses a dependency adapter system to support multiple package managers and handle dependency changes across test scenarios. The system provides abstraction over npm, pnpm, yarn, and workspace configurations.

Capabilities

ScenarioManager Class

Central coordinator for managing dependency changes across scenarios.

/**
 * Manages dependency changes across test scenarios
 */
class ScenarioManager {
  /**
   * Create a new ScenarioManager instance
   * @param options - Manager configuration options
   */
  constructor(options: {
    /** Array of dependency manager adapters to use */
    dependencyManagerAdapters: DependencyManagerAdapter[];
  });

  /**
   * Setup all dependency manager adapters
   * @returns Promise that resolves when setup is complete
   */
  async setup(): Promise<void>;

  /**
   * Change dependencies to match a specific scenario
   * @param scenario - Scenario with dependency specifications
   * @returns Promise resolving to array of dependency state changes
   */
  async changeTo(scenario: Scenario): Promise<any[]>;

  /**
   * Cleanup and restore original dependency state
   * @returns Promise that resolves when cleanup is complete
   */
  async cleanup(): Promise<void>;
}

Usage Examples:

// Create scenario manager with adapters
const scenarioManager = new ScenarioManager({
  dependencyManagerAdapters: [npmAdapter, yarnAdapter]
});

// Setup and execute scenario
await scenarioManager.setup();
const changes = await scenarioManager.changeTo(scenario);
// ... run tests ...
await scenarioManager.cleanup();

DependencyManagerAdapter Interface

Base interface that all package manager adapters must implement.

/**
 * Interface for package manager adapters
 */
interface DependencyManagerAdapter {
  /** Configuration key for this adapter (e.g., 'npm', 'yarn') */
  configKey: string;
  /** Default installation options for this package manager */
  defaultInstallOptions: string[];
  /** Name of lockfile for this package manager */
  lockfile: string;
  /** Display name of package manager */
  name: string;
  /** Key for overrides in package.json (npm/pnpm only) */
  overridesKey: string;

  /**
   * Setup the adapter and create backups
   * @returns Promise that resolves when setup is complete
   */
  setup(): Promise<void>;

  /**
   * Change to a specific dependency set
   * @param dependencies - Dependency configuration
   * @returns Promise resolving to array of changes made
   */
  changeToDependencySet(dependencies: any): Promise<any[]>;

  /**
   * Cleanup and restore original state
   * @returns Promise that resolves when cleanup is complete
   */
  cleanup(): Promise<void>;

  /**
   * Install dependencies
   * @param managerOptions - Options to pass to package manager
   * @returns Promise that resolves when installation is complete
   */
  install(managerOptions?: string[]): Promise<void>;

  /**
   * Add dependencies to package.json
   * @param dependencies - Dependencies to add
   * @returns Promise resolving to changes made
   */
  addDependencies(dependencies: Record<string, string>): Promise<any[]>;

  /**
   * Remove dependencies from package.json
   * @param dependencies - Dependencies to remove
   * @returns Promise resolving to changes made
   */
  removeDependencies(dependencies: string[]): Promise<any[]>;
}

BaseAdapter Class

Abstract base class providing common functionality for dependency adapters.

/**
 * Base class for dependency manager adapters
 */
class BaseAdapter implements DependencyManagerAdapter {
  /** Configuration key for this adapter */
  configKey: string = 'npm';
  /** Default installation options */
  defaultInstallOptions: string[] = [];
  /** Lockfile name */
  lockfile: string = '';
  /** Package manager name */
  name: string = '';
  /** Overrides key in package.json */
  overridesKey: string = '';
  /** Backup utility instance */
  backup: Backup = null;
  /** Debug function instance */
  debugFunction: Function = null;

  /**
   * Create a new adapter instance
   * @param options - Adapter configuration options
   */
  constructor(options: {
    /** Current working directory */
    cwd: string;
    /** Package manager options */
    managerOptions?: string[];
    /** Custom build manager options function */
    buildManagerOptions?: (scenario: Scenario) => string[];
    /** Custom run function for command execution */
    run?: (command: string, args: string[], options: any) => Promise<void>;
  });

  /**
   * Debug logging function
   * @param args - Arguments to log
   */
  debug(...args: any[]): void;

  /**
   * Setup the adapter - backs up package.json and lockfile
   * @returns Promise that resolves when setup is complete
   */
  async setup(): Promise<void>;

  /**
   * Apply dependency set and install dependencies
   * @param dependencySet - Dependency configuration
   * @returns Promise resolving to current dependency state
   */
  async changeToDependencySet(dependencySet: any): Promise<any[]>;

  /**
   * Apply dependency changes to package.json
   * @param dependencySet - Dependency configuration
   * @returns Promise that resolves when changes are applied
   */
  async applyDependencySet(dependencySet: any): Promise<void>;

  /**
   * Cleanup and restore original files
   * @returns Promise that resolves when cleanup is complete
   */
  async cleanup(): Promise<void>;

  /**
   * Find current version of an installed package
   * @param name - Package name
   * @returns Current version or null if not found
   */
  _findCurrentVersionOf(name: string): string | null;

  /**
   * Install dependencies with package manager
   * @param dependencySet - Dependency configuration for build options
   * @returns Promise that resolves when installation is complete
   */
  async _install(dependencySet?: any): Promise<void>;

  /**
   * Generate package.json with dependency changes applied
   * @param packageJSON - Original package.json object
   * @param dependencySet - Dependency changes to apply
   * @returns Modified package.json object
   */
  _packageJSONForDependencySet(packageJSON: any, dependencySet: any): any;
}

Package Manager Adapters

Specific implementations for different package managers.

/**
 * NPM package manager adapter
 */
class NpmAdapter extends BaseAdapter {
  configKey: 'npm';
  name: 'npm';
  lockfile: 'package-lock.json';
  overridesKey: 'overrides';
  defaultInstallOptions: string[];
}

/**
 * PNPM package manager adapter
 */
class PnpmAdapter extends BaseAdapter {
  configKey: 'npm';
  name: 'pnpm';
  lockfile: 'pnpm-lock.yaml';
  overridesKey: 'pnpm.overrides';
  defaultInstallOptions: string[];
}

/**
 * Yarn package manager adapter
 */
class YarnAdapter extends BaseAdapter {
  configKey: 'npm';
  name: 'yarn';
  lockfile: 'yarn.lock';
  overridesKey: 'resolutions';
  defaultInstallOptions: string[];
}

/**
 * Workspace-aware adapter for monorepos
 */
class WorkspaceAdapter extends BaseAdapter {
  configKey: 'npm';
  name: string; // Varies based on underlying package manager
  lockfile: string; // Varies based on underlying package manager
  overridesKey: string; // Varies based on underlying package manager
  defaultInstallOptions: string[];
}

DependencyManagerAdapterFactory

Factory for creating appropriate adapters based on configuration.

/**
 * Factory for generating dependency manager adapters
 */
interface DependencyManagerAdapterFactory {
  /**
   * Generate adapters from ember-try configuration
   * @param config - ember-try configuration
   * @param cwd - Current working directory
   * @returns Array of configured adapters
   */
  generateFromConfig(
    config: EmberTryConfig,
    cwd: string
  ): DependencyManagerAdapter[];
}

Factory Logic:

The factory creates adapters based on configuration:

  1. If useWorkspaces: true → WorkspaceAdapter
  2. If packageManager: 'pnpm' → PnpmAdapter
  3. If packageManager: 'yarn' → YarnAdapter
  4. If scenarios use npm key → NpmAdapter
  5. Otherwise → No adapters

Usage Examples:

// Generate adapters from config
const adapters = DependencyManagerAdapterFactory.generateFromConfig(config, cwd);

// Use adapters in scenario manager
const scenarioManager = new ScenarioManager({
  dependencyManagerAdapters: adapters
});

Dependency Change Operations

Adapters perform various dependency modification operations.

/**
 * Dependency change operations
 */
interface DependencyOperations {
  /**
   * Add or update dependencies
   * @param deps - Dependencies to add/update
   * @param section - Package.json section ('dependencies' or 'devDependencies')
   * @returns Changes made
   */
  addDependencies(
    deps: Record<string, string>,
    section: 'dependencies' | 'devDependencies'
  ): Promise<any[]>;

  /**
   * Remove dependencies
   * @param deps - Dependency names to remove
   * @param section - Package.json section
   * @returns Changes made
   */
  removeDependencies(
    deps: string[],
    section: 'dependencies' | 'devDependencies'
  ): Promise<any[]>;

  /**
   * Set overrides/resolutions
   * @param overrides - Override/resolution mappings
   * @returns Changes made
   */
  setOverrides(overrides: Record<string, string>): Promise<any[]>;

  /**
   * Update ember configuration
   * @param emberConfig - Ember configuration overrides
   * @returns Changes made
   */
  updateEmberConfig(emberConfig: Record<string, any>): Promise<any[]>;
}

Backup and Restore System

Adapters use a backup system to restore original state after testing.

/**
 * Backup utility for managing file backups
 */
class Backup {
  /**
   * Create a new backup instance
   * @param options - Backup configuration
   */
  constructor(options: { cwd: string });

  /**
   * Create backup of files
   * @param files - Array of file paths to backup
   * @returns Promise that resolves when backup is complete
   */
  backup(files: string[]): Promise<void>;

  /**
   * Restore files from backup
   * @returns Promise that resolves when restore is complete
   */
  restore(): Promise<void>;

  /**
   * Clean up backup files
   * @returns Promise that resolves when cleanup is complete
   */
  cleanup(): Promise<void>;
}

Package Manager Constants

Constants used across the dependency management system.

/**
 * Package manager file constants
 */
const PACKAGE_MANAGERS = {
  PACKAGE_JSON: 'package.json',
  LOCKFILE: {
    npm: 'package-lock.json',
    pnpm: 'pnpm-lock.yaml', 
    yarn: 'yarn.lock'
  }
};

Dependency Resolution

Different package managers handle dependency resolution differently.

/**
 * Package manager specific resolution strategies
 */
interface ResolutionStrategies {
  /** NPM uses overrides in package.json */
  npm: {
    overridesKey: 'overrides';
    lockfile: 'package-lock.json';
    installOptions: ['--no-package-lock', '--no-shrinkwrap'];
  };

  /** PNPM uses pnpm.overrides in package.json */
  pnpm: {
    overridesKey: 'pnpm.overrides';
    lockfile: 'pnpm-lock.yaml';
    installOptions: ['--no-lockfile'];
  };

  /** Yarn uses resolutions in package.json */
  yarn: {
    overridesKey: 'resolutions';
    lockfile: 'yarn.lock';
    installOptions: ['--no-lockfile', '--ignore-engines'];
  };
}

Error Handling

Dependency management includes comprehensive error handling:

  • Package installation failures
  • Backup/restore failures
  • File system permission errors
  • Network connectivity issues
  • Invalid dependency specifications

Adapters provide detailed error messages and attempt graceful recovery where possible.