CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-yeoman-generator

Rails-inspired generator system that provides scaffolding for your apps

Overview
Eval results
Files

package-management.mddocs/

Package Management

Package.json manipulation and dependency resolution with version management for npm packages and project configuration.

Capabilities

Dependency Management

Add and resolve dependencies to package.json with automatic version resolution.

/**
 * Add dependencies to the destination package.json
 * Environment watches for package.json changes and triggers package manager install
 * @param dependencies - Dependencies to add (string, array, or object)
 * @returns Promise resolving to resolved dependency versions
 */
async addDependencies(dependencies: string | string[] | Record<string, string>): Promise<Record<string, string>>;

/**
 * Add development dependencies to the destination package.json  
 * Environment watches for package.json changes and triggers package manager install
 * @param devDependencies - Dev dependencies to add (string, array, or object)
 * @returns Promise resolving to resolved dependency versions
 */
async addDevDependencies(devDependencies: string | string[] | Record<string, string>): Promise<Record<string, string>>;

Usage Examples:

export default class MyGenerator extends Generator {
  async configuring() {
    // Add single dependency (latest version)
    await this.addDependencies('express');
    
    // Add multiple dependencies
    await this.addDependencies([
      'express',
      'body-parser', 
      'cors'
    ]);
    
    // Add dependencies with specific versions
    await this.addDependencies({
      'express': '^4.18.0',
      'lodash': '~4.17.21',
      'moment': '2.29.4'
    });
    
    // Add mixed format (package@version)
    await this.addDependencies([
      'react@18.2.0',
      'react-dom@18.2.0',
      'typescript' // Latest version
    ]);
  }
  
  async writing() {
    // Add dev dependencies conditionally
    if (this.answers.includeTests) {
      await this.addDevDependencies([
        'jest',
        '@types/jest',
        'supertest'
      ]);
    }
    
    if (this.answers.useTypeScript) {
      await this.addDevDependencies({
        'typescript': '^5.0.0',
        '@types/node': '^18.0.0',
        'ts-node': '^10.9.0'
      });
    }
    
    // Add build tools
    await this.addDevDependencies([
      'webpack@5.75.0',
      'webpack-cli',  
      'babel-loader'
    ]);
  }
}

Version Resolution

Automatic resolution of package versions to latest available versions.

/**
 * Resolve dependencies to their latest versions
 * Internal method used by addDependencies and addDevDependencies
 * @param dependencies - Dependencies to resolve
 * @returns Promise resolving to dependency name/version map
 */
async _resolvePackageJsonDependencies(
  dependencies: string | string[] | Record<string, string>
): Promise<Record<string, string>>;

Usage Example:

export default class MyGenerator extends Generator {
  async configuring() {
    // Manual version resolution (usually not needed)
    const resolved = await this._resolvePackageJsonDependencies([
      'express',
      'lodash@4.17.20', // Specific version kept
      'moment'          // Will resolve to latest
    ]);
    
    this.log('Resolved versions:', resolved);
    // Output: { express: '4.18.2', lodash: '4.17.20', moment: '2.29.4' }
    
    // Use resolved versions
    this.packageJson.merge({
      dependencies: resolved
    });
  }
}

Package.json Integration

Direct manipulation of package.json through the Storage interface.

/**
 * Package.json Storage resolved to this.destinationPath('package.json')
 * Environment watches for package.json changes at this.env.cwd and triggers install
 * If package.json is at different folder, propagate to Environment: this.env.cwd = this.destinationPath()
 */
readonly packageJson: Storage;

Usage Examples:

export default class MyGenerator extends Generator {
  configuring() {
    // Basic package.json setup
    this.packageJson.merge({
      name: this.answers.name,
      version: '1.0.0',
      description: this.answers.description,
      main: 'index.js',
      license: this.answers.license
    });
    
    // Add scripts
    this.packageJson.merge({
      scripts: {
        start: 'node index.js',
        test: 'npm run test:unit',
        'test:unit': 'jest',
        build: 'webpack --mode production',
        dev: 'webpack --mode development --watch'
      }
    });
    
    // Add repository information
    if (this.answers.repository) {
      this.packageJson.merge({
        repository: {
          type: 'git',
          url: this.answers.repository
        }
      });
    }
    
    // Add keywords
    if (this.answers.keywords) {
      this.packageJson.merge({
        keywords: this.answers.keywords.split(',').map(k => k.trim())
      });
    }
  }
  
  async writing() {
    // Conditional package.json modifications
    if (this.answers.useTypeScript) {
      this.packageJson.merge({
        main: 'dist/index.js',
        types: 'dist/index.d.ts',
        scripts: {
          build: 'tsc',
          'build:watch': 'tsc --watch'
        }
      });
    }
    
    // Add peer dependencies for library projects
    if (this.answers.projectType === 'library') {
      this.packageJson.merge({
        peerDependencies: {
          'react': '>=16.8.0',
          'react-dom': '>=16.8.0'
        }
      });
    }
  }
}

Environment Integration

Automatic package manager integration through yeoman-environment.

Usage Examples:

export default class MyGenerator extends Generator {
  configuring() {
    // Change package.json location (if not in destination root)
    if (this.answers.monorepo) {
      // Tell environment where to watch for package.json changes
      this.env.cwd = this.destinationPath('packages', this.answers.name);
      
      // Create package.json in subdirectory
      const subPackageJson = this.createStorage(
        this.destinationPath('packages', this.answers.name, 'package.json')
      );
      
      subPackageJson.merge({
        name: `@${this.answers.org}/${this.answers.name}`,
        version: '1.0.0',
        private: true
      });
    }
  }
  
  async install() {
    // Package manager install is triggered automatically by environment
    // when package.json changes are committed to disk
    
    // You can still run manual installs
    if (this.options.yarn) {
      await this.spawnCommand('yarn', ['install']);
    } else {
      await this.spawnCommand('npm', ['install']);
    }
  }
}

Advanced Package Management

Complex dependency management scenarios.

Usage Examples:

export default class MyGenerator extends Generator {
  async configuring() {
    // Framework-specific dependencies
    const frameworkDeps = {
      react: {
        deps: ['react', 'react-dom'],
        devDeps: ['@types/react', '@types/react-dom']
      },
      vue: {
        deps: ['vue'],
        devDeps: ['@vue/cli-service']
      },
      angular: {
        deps: ['@angular/core', '@angular/common'],
        devDeps: ['@angular/cli']
      }
    };
    
    const framework = frameworkDeps[this.answers.framework];
    if (framework) {
      await this.addDependencies(framework.deps);
      await this.addDevDependencies(framework.devDeps);
    }
    
    // Conditional tool dependencies
    const toolDeps = [];
    const toolDevDeps = [];
    
    if (this.answers.useLinting) {
      toolDevDeps.push('eslint', '@eslint/js');
      if (this.answers.useTypeScript) {
        toolDevDeps.push('@typescript-eslint/parser', '@typescript-eslint/eslint-plugin');
      }
    }
    
    if (this.answers.useFormatting) {
      toolDevDeps.push('prettier');
    }
    
    if (this.answers.useTesting) {
      toolDevDeps.push('jest', '@types/jest');
      if (this.answers.framework === 'react') {
        toolDevDeps.push('@testing-library/react', '@testing-library/jest-dom');
      }
    }
    
    if (toolDeps.length) await this.addDependencies(toolDeps);
    if (toolDevDeps.length) await this.addDevDependencies(toolDevDeps);
  }
  
  writing() {
    // Update package.json based on added dependencies
    const currentPkg = this.packageJson.getAll();
    
    // Add scripts based on dependencies
    const scripts = {};
    
    if (currentPkg.devDependencies?.eslint) {
      scripts.lint = 'eslint src/**/*.{js,ts}';
      scripts['lint:fix'] = 'eslint src/**/*.{js,ts} --fix';
    }
    
    if (currentPkg.devDependencies?.prettier) {
      scripts.format = 'prettier --write src/**/*.{js,ts}';
    }
    
    if (currentPkg.devDependencies?.jest) {
      scripts.test = 'jest';
      scripts['test:watch'] = 'jest --watch';
      scripts['test:coverage'] = 'jest --coverage';
    }
    
    if (Object.keys(scripts).length) {
      this.packageJson.merge({ scripts });
    }
  }
}

Package Manager Selection

Support for different package managers.

Usage Examples:

export default class MyGenerator extends Generator {
  constructor(args, opts) {
    super(args, opts);
    
    this.option('package-manager', {
      type: String,
      alias: 'pm',
      description: 'Package manager to use',
      choices: ['npm', 'yarn', 'pnpm'],
      default: 'npm'
    });
  }
  
  async configuring() {
    // Add dependencies using selected package manager format
    await this.addDependencies(['express', 'lodash']);
    
    // Package manager specific configurations
    if (this.options.packageManager === 'yarn') {
      // Add yarn-specific configurations
      this.packageJson.merge({
        engines: {
          yarn: '>=1.22.0'
        }
      });
      
      // Create .yarnrc if needed
      this.writeDestination('.yarnrc', 'save-prefix "^"\n');
    }
    
    if (this.options.packageManager === 'pnpm') {
      // Add pnpm-specific configurations
      this.packageJson.merge({
        engines: {
          pnpm: '>=7.0.0'
        }
      });
      
      // Create .npmrc for pnpm
      this.writeDestination('.npmrc', 'package-manager=pnpm\n');
    }
  }
  
  async install() {
    // Install using selected package manager
    const pm = this.options.packageManager;
    
    switch (pm) {
      case 'yarn':
        await this.spawnCommand('yarn', ['install']);
        break;
      case 'pnpm': 
        await this.spawnCommand('pnpm', ['install']);
        break;
      default:
        await this.spawnCommand('npm', ['install']);
        break;
    }
  }
}

Types

// Dependency input formats
type DependencyInput = string | string[] | Record<string, string>;

// Resolved dependency map
type ResolvedDependencies = Record<string, string>;

Install with Tessl CLI

npx tessl i tessl/npm-yeoman-generator

docs

command-execution.md

command-line.md

configuration.md

core-generator.md

file-system.md

git-integration.md

index.md

package-management.md

task-lifecycle.md

user-interaction.md

tile.json