Rails-inspired generator system that provides scaffolding for your apps
Package.json manipulation and dependency resolution with version management for npm packages and project configuration.
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'
]);
}
}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
});
}
}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'
}
});
}
}
}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']);
}
}
}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 });
}
}
}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;
}
}
}// 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