Saltar a contenido

Yeoman Cheatsheet

יh1⁄4] Yeoman - La herramienta de andamio de la Web "Clase de inscripción" Yeoman es un sistema de andamiaje genérico que permite la creación de cualquier tipo de aplicación. Permite comenzar rápidamente en nuevos proyectos y simplifica el mantenimiento de los proyectos existentes. Yeoman es agnóstico del lenguaje y puede generar proyectos en cualquier idioma (Web, Java, Python, C#, etc.). ▪/p] ■/div titulada

########################################################################################################################################################################################################################################################## Copiar todos los comandos
########################################################################################################################################################################################################################################################## Generar PDF seleccionado/button

■/div titulada ■/div titulada

Cuadro de contenidos

Instalación

Instalación global

# Install Yeoman globally
npm install -g yo

# Verify installation
yo --version

# Check Yeoman help
yo --help

Requisitos del sistema

# Yeoman requires Node.js and npm
node --version  # Should be >= 10.0.0
npm --version   # Should be >= 6.0.0

# Git is recommended for many generators
git --version

Instalación de generadores

# Install popular generators
npm install -g generator-webapp
npm install -g generator-angular
npm install -g generator-react
npm install -g generator-node
npm install -g generator-express

# Search for generators
npm search yeoman-generator

# Install specific generator
npm install -g generator-fountain-webapp

Configuración de proyectos

# Create project directory
mkdir my-yeoman-project
cd my-yeoman-project

# Run a generator
yo webapp

# Or run with options
yo webapp --skip-install

Comienzo

Uso básico

# List installed generators
yo

# Run a specific generator
yo webapp

# Get help for a generator
yo webapp --help

# Run generator with options
yo webapp --skip-install --coffee

# Run sub-generator
yo webapp:route myroute

Primer proyecto

# Create a new web app
mkdir my-webapp
cd my-webapp

# Initialize with webapp generator
yo webapp

# Follow the prompts:
# ? What more would you like? (Press <space> to select, <a> to toggle all, <i> to invert selection)
# ❯◉ Sass
#  ◉ Bootstrap
#  ◉ Modernizr

# Install dependencies
npm install

# Start development server
npm start

Estructura del proyecto

my-webapp/
├── app/
│   ├── index.html
│   ├── scripts/
│   │   └── main.js
│   ├── styles/
│   │   └── main.scss
│   └── images/
├── test/
├── bower.json
├── package.json
├── gulpfile.js
├── .bowerrc
├── .gitignore
└── README.md

Comandos comunes

# List all available generators
yo

# Update generators
npm update -g generator-webapp

# Uninstall generator
npm uninstall -g generator-webapp

# Clear Yeoman cache
yo --clear-cache

# Get version information
yo --version

Generadores populares

Aplicaciones de la Web

# Modern web app with build tools
npm install -g generator-webapp
yo webapp

# Angular applications
npm install -g generator-angular
yo angular

# React applications
npm install -g generator-react-webpack
yo react-webpack

# Vue.js applications
npm install -g generator-vue
yo vue

# Progressive Web App
npm install -g generator-pwa
yo pwa

Backend & APIs

# Node.js applications
npm install -g generator-node
yo node

# Express.js applications
npm install -g generator-express
yo express

# Koa.js applications
npm install -g generator-koa
yo koa

# REST API
npm install -g generator-rest
yo rest

# GraphQL API
npm install -g generator-graphql
yo graphql

Mobile Development

# Ionic applications
npm install -g generator-ionic
yo ionic

# React Native
npm install -g generator-rn-toolbox
yo rn-toolbox

# Cordova/PhoneGap
npm install -g generator-cordova
yo cordova

Generadores de sitios estáticos

# Jekyll sites
npm install -g generator-jekyllrb
yo jekyllrb

# Hugo sites
npm install -g generator-hugo
yo hugo

# Hexo blogs
npm install -g generator-hexo
yo hexo

Bibliotecas de componentes

# Storybook
npm install -g generator-storybook
yo storybook

# Web Components
npm install -g generator-polymer
yo polymer

# UI Library
npm install -g generator-ui-library
yo ui-library

Usando Generadores

Modo interactivo

# Run generator interactively
yo webapp

# Example prompts:
# ? What more would you like?
#   ◉ Sass
#   ◉ Bootstrap  
#   ◉ Modernizr
#   ◯ Babel
#   ◯ jQuery

# ? Which version of Bootstrap?
#   Bootstrap v4
#   Bootstrap v3

# ? Would you like to include jQuery?
#   Yes
#   No

Opciones de línea de mando

# Skip installation of dependencies
yo webapp --skip-install

# Skip cache
yo webapp --skip-cache

# Force overwrite files
yo webapp --force

# Use specific options
yo angular --coffee --compass

# Quiet mode
yo webapp --quiet

# Get help
yo webapp --help

Archivos de configuración

# Yeoman stores configuration in .yo-rc.json
cat .yo-rc.json

# Example .yo-rc.json
{
  "generator-webapp": {
    "includeBootstrap": true,
    "includeModernizr": true,
    "includeSass": true
  }
}

Subgeneradores

# List available sub-generators
yo webapp --help

# Common sub-generators:
yo angular:controller myController
yo angular:service myService
yo angular:directive myDirective
yo angular:filter myFilter
yo angular:route myRoute

# Express sub-generators:
yo express:route users
yo express:model User
yo express:controller users

Creación de generadores personalizados

Estructura del generador

# Create generator directory
mkdir generator-myapp
cd generator-myapp

# Initialize package.json
npm init

# Install yeoman-generator
npm install --save yeoman-generator

# Create generator structure
mkdir -p generators/app
touch generators/app/index.js

Generador básico

// generators/app/index.js
const Generator = require('yeoman-generator');

module.exports = class extends Generator {
  // Constructor
  constructor(args, opts) {
    super(args, opts);

    // Add option
    this.option('babel', {
      desc: 'Use Babel',
      type: Boolean,
      defaults: false
    });
  }

  // Prompt user for input
  async prompting() {
    this.answers = await this.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Your project name',
        default: this.appname
      },
      {
        type: 'confirm',
        name: 'cool',
        message: 'Would you like to enable the Cool feature?'
      }
    ]);
  }

  // Write files
  writing() {
    // Copy template files
    this.fs.copy(
      this.templatePath('index.html'),
      this.destinationPath('public/index.html')
    );

    // Copy with template processing
    this.fs.copyTpl(
      this.templatePath('_package.json'),
      this.destinationPath('package.json'),
      {
        title: this.answers.name,
        babel: this.options.babel
      }
    );
  }

  // Install dependencies
  install() {
    this.npmInstall();
  }
};

Paquete.json para Generator

{
  "name": "generator-myapp",
  "version": "1.0.0",
  "description": "My custom Yeoman generator",
  "main": "generators/app/index.js",
  "keywords": [
    "yeoman-generator"
  ],
  "dependencies": {
    "yeoman-generator": "^4.0.0"
  },
  "files": [
    "generators"
  ]
}

Plantillas Directorio

# Create templates directory
mkdir -p generators/app/templates

# Add template files
touch generators/app/templates/index.html
touch generators/app/templates/_package.json
touch generators/app/templates/README.md

Archivos de plantilla

<!-- generators/app/templates/index.html -->
<!DOCTYPE html>
<html>
<head>
    <title><%= title %></title>
</head>
<body>
    <h1>Welcome to <%= title %>!</h1>
    <% if (cool) { %>
    <p>Cool feature is enabled!</p>
    <% } %>
</body>
</html>
// generators/app/templates/_package.json
{
  "name": "<%= title %>",
  "version": "1.0.0",
  "description": "Generated with my custom generator",
  "scripts": {
    "start": "node server.js"<% if (babel) { %>,
    "build": "babel src -d lib"<% } %>
  },
  "dependencies": {<% if (babel) { %>
    "@babel/core": "^7.0.0",
    "@babel/cli": "^7.0.0"<% } %>
  }
}

Generator Development

Generator Lifecycle

// generators/app/index.js
const Generator = require('yeoman-generator');

module.exports = class extends Generator {
  // 1. Initialization
  initializing() {
    this.log('Initializing generator...');
  }

  // 2. Prompting
  async prompting() {
    this.answers = await this.prompt([
      {
        type: 'input',
        name: 'name',
        message: 'Project name:'
      }
    ]);
  }

  // 3. Configuring
  configuring() {
    this.config.set('name', this.answers.name);
    this.config.save();
  }

  // 4. Default (custom methods)
  default() {
    this.log('Running default tasks...');
  }

  // 5. Writing
  writing() {
    this.fs.copy(
      this.templatePath('**/*'),
      this.destinationPath()
    );
  }

  // 6. Conflicts (handled automatically)

  // 7. Install
  install() {
    this.npmInstall();
  }

  // 8. End
  end() {
    this.log('Generator finished successfully!');
  }
};

Prompting avanzado

async prompting() {
  const prompts = [
    {
      type: 'input',
      name: 'name',
      message: 'Project name:',
      default: this.appname,
      validate: (input) => {
        if (input.length === 0) {
          return 'Project name cannot be empty';
        }
        return true;
      }
    },
    {
      type: 'list',
      name: 'framework',
      message: 'Choose a framework:',
      choices: [
        'React',
        'Angular',
        'Vue',
        'Vanilla JS'
      ],
      default: 'React'
    },
    {
      type: 'checkbox',
      name: 'features',
      message: 'Select features:',
      choices: [
        {
          name: 'Sass',
          value: 'sass',
          checked: true
        },
        {
          name: 'TypeScript',
          value: 'typescript',
          checked: false
        },
        {
          name: 'Testing',
          value: 'testing',
          checked: true
        }
      ]
    },
    {
      type: 'confirm',
      name: 'installDeps',
      message: 'Install dependencies now?',
      default: true
    }
  ];

  this.answers = await this.prompt(prompts);
}

Operaciones del sistema de archivos

writing() {
  // Copy single file
  this.fs.copy(
    this.templatePath('index.html'),
    this.destinationPath('public/index.html')
  );

  // Copy with template processing
  this.fs.copyTpl(
    this.templatePath('_package.json'),
    this.destinationPath('package.json'),
    this.answers
  );

  // Copy entire directory
  this.fs.copy(
    this.templatePath('src/**/*'),
    this.destinationPath('src/')
  );

  // Write file from string
  this.fs.write(
    this.destinationPath('README.md'),
    `# ${this.answers.name}\n\nGenerated with Yeoman`
  );

  // Extend JSON file
  this.fs.extendJSON(
    this.destinationPath('package.json'),
    {
      scripts: {
        test: 'jest'
      }
    }
  );

  // Delete file
  this.fs.delete(this.destinationPath('unwanted-file.js'));
}

Generación de archivos condicional

writing() {
  // Base files
  this.fs.copy(
    this.templatePath('base/**/*'),
    this.destinationPath()
  );

  // Framework-specific files
  if (this.answers.framework === 'React') {
    this.fs.copy(
      this.templatePath('react/**/*'),
      this.destinationPath()
    );
  } else if (this.answers.framework === 'Angular') {
    this.fs.copy(
      this.templatePath('angular/**/*'),
      this.destinationPath()
    );
  }

  // Feature-specific files
  if (this.answers.features.includes('typescript')) {
    this.fs.copy(
      this.templatePath('tsconfig.json'),
      this.destinationPath('tsconfig.json')
    );
  }

  if (this.answers.features.includes('testing')) {
    this.fs.copy(
      this.templatePath('test/**/*'),
      this.destinationPath('test/')
    );
  }
}

Subgeneradores

Creación de subgeneradores

# Create sub-generator directory
mkdir -p generators/component
touch generators/component/index.js
mkdir -p generators/component/templates

Ejecución del subgenerador

// generators/component/index.js
const Generator = require('yeoman-generator');

module.exports = class extends Generator {
  constructor(args, opts) {
    super(args, opts);

    // Accept component name as argument
    this.argument('name', {
      type: String,
      required: true,
      desc: 'Component name'
    });
  }

  async prompting() {
    this.answers = await this.prompt([
      {
        type: 'confirm',
        name: 'withStyles',
        message: 'Include CSS file?',
        default: true
      },
      {
        type: 'confirm',
        name: 'withTest',
        message: 'Include test file?',
        default: true
      }
    ]);
  }

  writing() {
    const componentName = this.options.name;

    // Create component file
    this.fs.copyTpl(
      this.templatePath('component.js'),
      this.destinationPath(`src/components/${componentName}.js`),
      {
        name: componentName,
        className: componentName.charAt(0).toUpperCase() + componentName.slice(1)
      }
    );

    // Create styles if requested
    if (this.answers.withStyles) {
      this.fs.copyTpl(
        this.templatePath('component.css'),
        this.destinationPath(`src/components/${componentName}.css`),
        { name: componentName }
      );
    }

    // Create test if requested
    if (this.answers.withTest) {
      this.fs.copyTpl(
        this.templatePath('component.test.js'),
        this.destinationPath(`src/components/${componentName}.test.js`),
        { name: componentName }
      );
    }
  }
};

Plantillas subgeneradoras

// generators/component/templates/component.js
import React from 'react';<% if (withStyles) { %>
import './<%= name %>.css';<% } %>

const <%= className %> = () => {
  return (
    <div className="<%= name %>">
      <h2><%= className %> Component</h2>
    </div>
  );
};

export default <%= className %>;

Utilizando subgeneradores

# Run sub-generator
yo myapp:component MyButton

# With options
yo myapp:component MyButton --with-styles --with-test

# List available sub-generators
yo myapp --help

Configuración

Configuración del generador

// Store configuration
configuring() {
  this.config.set({
    framework: this.answers.framework,
    features: this.answers.features,
    version: '1.0.0'
  });
  this.config.save();
}

// Read configuration
default() {
  const config = this.config.getAll();
  this.log('Current config:', config);

  const framework = this.config.get('framework');
  this.log('Framework:', framework);
}

Configuración global

# Set global configuration
yo --global-config

# Configuration file location
# ~/.yo-rc-global.json

Medio ambiente

// Use environment variables
writing() {
  const isDev = process.env.NODE_ENV === 'development';

  this.fs.copyTpl(
    this.templatePath('config.js'),
    this.destinationPath('config.js'),
    {
      apiUrl: isDev ? 'http://localhost:3000' : 'https://api.example.com'
    }
  );
}

Opciones y argumentos

constructor(args, opts) {
  super(args, opts);

  // Add option
  this.option('skip-install', {
    desc: 'Skip installation of dependencies',
    type: Boolean,
    defaults: false
  });

  // Add argument
  this.argument('name', {
    type: String,
    required: false,
    desc: 'Project name'
  });
}

// Use options and arguments
writing() {
  const projectName = this.options.name || 'my-project';
  const skipInstall = this.options['skip-install'];

  // Use in templates
  this.fs.copyTpl(
    this.templatePath('_package.json'),
    this.destinationPath('package.json'),
    { name: projectName }
  );
}

install() {
  if (!this.options['skip-install']) {
    this.npmInstall();
  }
}

Plantillas

Sintaxis de plantilla

<!-- EJS template syntax -->
<!DOCTYPE html>
<html>
<head>
    <title><%= title %></title>
    <% if (includeBootstrap) { %>
    <link rel="stylesheet" href="bootstrap.css">
    <% } %>
</head>
<body>
    <h1>Welcome to <%= appName %>!</h1>

    <% features.forEach(function(feature) { %>
    <p>Feature: <%= feature %></p>
    <% }); %>

    <% if (author) { %>
    <footer>Created by <%= author %></footer>
    <% } %>
</body>
</html>

Ayudantes de plantilla

// Custom template helpers
writing() {
  const templateData = {
    title: this.answers.title,
    features: this.answers.features,

    // Helper functions
    capitalize: (str) => str.charAt(0).toUpperCase() + str.slice(1),
    kebabCase: (str) => str.toLowerCase().replace(/\s+/g, '-'),
    camelCase: (str) => str.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : '')
  };

  this.fs.copyTpl(
    this.templatePath('component.js'),
    this.destinationPath('src/Component.js'),
    templateData
  );
}

Plantillas condicionales

// generators/app/templates/webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'dist'),
    filename: 'bundle.js'
  },
  module: {
    rules: [<% if (babel) { %>
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: 'babel-loader'
      },<% } %><% if (sass) { %>
      {
        test: /\.scss$/,
        use: ['style-loader', 'css-loader', 'sass-loader']
      },<% } %>
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  }<% if (devServer) { %>,
  devServer: {
    contentBase: './dist',
    hot: true
  }<% } %>
};

Sistema de archivos

Operaciones de archivo

writing() {
  // Check if file exists
  if (this.fs.exists(this.destinationPath('package.json'))) {
    this.log('Package.json already exists');
  }

  // Read file
  const content = this.fs.read(this.templatePath('template.txt'));

  // Write file
  this.fs.write(
    this.destinationPath('output.txt'),
    content.replace('{{name}}', this.answers.name)
  );

  // Append to file
  this.fs.append(
    this.destinationPath('README.md'),
    '\n## Generated with Yeoman'
  );

  // Copy with transformation
  this.fs.copy(
    this.templatePath('src/**/*.js'),
    this.destinationPath('src/'),
    {
      process: (content) => {
        return content.toString().replace(/OLD_NAME/g, this.answers.name);
      }
    }
  );
}

Manipulación de JSON

writing() {
  // Read and modify package.json
  const pkg = this.fs.readJSON(this.destinationPath('package.json'), {});

  // Add dependencies
  pkg.dependencies = pkg.dependencies || {};
  pkg.dependencies.express = '^4.17.1';

  if (this.answers.database === 'mongodb') {
    pkg.dependencies.mongoose = '^5.12.0';
  }

  // Add scripts
  pkg.scripts = pkg.scripts || {};
  pkg.scripts.start = 'node server.js';
  pkg.scripts.dev = 'nodemon server.js';

  // Write back to file
  this.fs.writeJSON(this.destinationPath('package.json'), pkg);

  // Or use extendJSON for simpler cases
  this.fs.extendJSON(this.destinationPath('package.json'), {
    keywords: ['yeoman', 'generator'],
    author: this.answers.author
  });
}

Operaciones de directorio

writing() {
  // Create directory
  this.fs.copy(
    this.templatePath('empty-dir/.gitkeep'),
    this.destinationPath('logs/.gitkeep')
  );

  // Copy entire directory structure
  this.fs.copy(
    this.templatePath('project-template/**/*'),
    this.destinationPath(),
    {
      globOptions: {
        dot: true // Include hidden files
      }
    }
  );

  // Selective copying
  const features = this.answers.features;

  if (features.includes('auth')) {
    this.fs.copy(
      this.templatePath('auth/**/*'),
      this.destinationPath('src/auth/')
    );
  }

  if (features.includes('database')) {
    this.fs.copy(
      this.templatePath('models/**/*'),
      this.destinationPath('src/models/')
    );
  }
}

Interacción del usuario

Prompts avanzados

async prompting() {
  const prompts = [
    {
      type: 'input',
      name: 'name',
      message: 'Project name:',
      default: this.appname,
      filter: (input) => input.toLowerCase().replace(/\s+/g, '-'),
      validate: (input) => {
        if (!/^[a-z0-9-]+$/.test(input)) {
          return 'Project name must contain only lowercase letters, numbers, and hyphens';
        }
        return true;
      }
    },
    {
      type: 'list',
      name: 'framework',
      message: 'Choose a framework:',
      choices: [
        { name: 'React', value: 'react' },
        { name: 'Angular', value: 'angular' },
        { name: 'Vue.js', value: 'vue' },
        { name: 'Vanilla JavaScript', value: 'vanilla' }
      ],
      default: 'react'
    },
    {
      type: 'checkbox',
      name: 'features',
      message: 'Select additional features:',
      choices: [
        { name: 'TypeScript', value: 'typescript', checked: true },
        { name: 'Sass/SCSS', value: 'sass', checked: true },
        { name: 'ESLint', value: 'eslint', checked: true },
        { name: 'Prettier', value: 'prettier', checked: true },
        { name: 'Jest Testing', value: 'jest', checked: false },
        { name: 'Storybook', value: 'storybook', checked: false }
      ]
    },
    {
      type: 'confirm',
      name: 'installDeps',
      message: 'Install dependencies automatically?',
      default: true
    }
  ];

  // Conditional prompts
  const answers = await this.prompt(prompts);

  if (answers.framework === 'react') {
    const reactPrompts = await this.prompt([
      {
        type: 'list',
        name: 'reactVersion',
        message: 'React version:',
        choices: ['17', '18'],
        default: '18'
      },
      {
        type: 'confirm',
        name: 'useHooks',
        message: 'Use React Hooks?',
        default: true
      }
    ]);

    Object.assign(answers, reactPrompts);
  }

  this.answers = answers;
}

Prompts dinámicos

async prompting() {
  // First set of prompts
  const basicAnswers = await this.prompt([
    {
      type: 'list',
      name: 'projectType',
      message: 'What type of project?',
      choices: ['web-app', 'api', 'library', 'cli-tool']
    }
  ]);

  let additionalPrompts = [];

  // Dynamic prompts based on project type
  switch (basicAnswers.projectType) {
    case 'web-app':
      additionalPrompts = [
        {
          type: 'list',
          name: 'frontend',
          message: 'Frontend framework:',
          choices: ['React', 'Vue', 'Angular', 'Svelte']
        },
        {
          type: 'confirm',
          name: 'pwa',
          message: 'Make it a Progressive Web App?',
          default: false
        }
      ];
      break;

    case 'api':
      additionalPrompts = [
        {
          type: 'list',
          name: 'backend',
          message: 'Backend framework:',
          choices: ['Express', 'Koa', 'Fastify', 'NestJS']
        },
        {
          type: 'list',
          name: 'database',
          message: 'Database:',
          choices: ['MongoDB', 'PostgreSQL', 'MySQL', 'SQLite']
        }
      ];
      break;

    case 'library':
      additionalPrompts = [
        {
          type: 'checkbox',
          name: 'targets',
          message: 'Build targets:',
          choices: ['CommonJS', 'ES Modules', 'UMD', 'Browser']
        }
      ];
      break;
  }

  const additionalAnswers = await this.prompt(additionalPrompts);
  this.answers = { ...basicAnswers, ...additionalAnswers };
}
```_

### Indicación de progreso
```javascript
writing() {
  this.log('Generating project files...');

  // Show progress
  const files = [
    'package.json',
    'README.md',
    'src/index.js',
    'src/components/App.js',
    'public/index.html'
  ];

  files.forEach((file, index) => {
    this.log(`[${index + 1}/${files.length}] Creating ${file}`);

    // Simulate file creation
    this.fs.copyTpl(
      this.templatePath(file),
      this.destinationPath(file),
      this.answers
    );
  });

  this.log('✓ All files generated successfully!');
}

Generadores de ensayo

Configuración de pruebas

# Install testing dependencies
npm install --save-dev yeoman-test yeoman-assert mocha

# Create test directory
mkdir test
touch test/app.js

Pruebas básicas

// test/app.js
const path = require('path');
const assert = require('yeoman-assert');
const helpers = require('yeoman-test');

describe('generator-myapp:app', () => {
  beforeEach(() => {
    return helpers
      .run(path.join(__dirname, '../generators/app'))
      .withPrompts({
        name: 'test-project',
        framework: 'react',
        features: ['typescript', 'sass']
      });
  });

  it('creates files', () => {
    assert.file([
      'package.json',
      'README.md',
      'src/index.js',
      'public/index.html'
    ]);
  });

  it('fills package.json with correct information', () => {
    assert.fileContent('package.json', '"name": "test-project"');
    assert.jsonFileContent('package.json', {
      dependencies: {
        react: expect.any(String)
      }
    });
  });

  it('creates TypeScript config when selected', () => {
    assert.file('tsconfig.json');
    assert.fileContent('tsconfig.json', '"compilerOptions"');
  });

  it('creates Sass files when selected', () => {
    assert.file('src/styles/main.scss');
  });
});

Pruebas avanzadas

// test/sub-generator.js
describe('generator-myapp:component', () => {
  it('creates component files', () => {
    return helpers
      .run(path.join(__dirname, '../generators/component'))
      .withArguments(['MyButton'])
      .withPrompts({
        withStyles: true,
        withTest: true
      })
      .then(() => {
        assert.file([
          'src/components/MyButton.js',
          'src/components/MyButton.css',
          'src/components/MyButton.test.js'
        ]);

        assert.fileContent(
          'src/components/MyButton.js',
          'const MyButton = () => {'
        );
      });
  });

  it('skips optional files when not requested', () => {
    return helpers
      .run(path.join(__dirname, '../generators/component'))
      .withArguments(['SimpleButton'])
      .withPrompts({
        withStyles: false,
        withTest: false
      })
      .then(() => {
        assert.file('src/components/SimpleButton.js');
        assert.noFile([
          'src/components/SimpleButton.css',
          'src/components/SimpleButton.test.js'
        ]);
      });
  });
});

Pruebas con Opciones

describe('generator options', () => {
  it('skips installation when --skip-install is passed', () => {
    return helpers
      .run(path.join(__dirname, '../generators/app'))
      .withOptions({ 'skip-install': true })
      .withPrompts({ name: 'test-app' })
      .then(() => {
        // Verify that npm install was not called
        // This would require mocking or checking for specific behavior
        assert.file('package.json');
      });
  });

  it('uses Babel when --babel option is passed', () => {
    return helpers
      .run(path.join(__dirname, '../generators/app'))
      .withOptions({ babel: true })
      .withPrompts({ name: 'babel-app' })
      .then(() => {
        assert.file('.babelrc');
        assert.jsonFileContent('package.json', {
          devDependencies: {
            '@babel/core': expect.any(String)
          }
        });
      });
  });
});
```_

### Test Utilities
```javascript
// test/helpers.js
const helpers = require('yeoman-test');
const path = require('path');

// Helper to run generator with common setup
exports.runGenerator = (prompts = {}, options = {}) => {
  return helpers
    .run(path.join(__dirname, '../generators/app'))
    .withPrompts({
      name: 'test-project',
      framework: 'react',
      ...prompts
    })
    .withOptions(options);
};

// Helper to check package.json dependencies
exports.assertDependency = (dep, version) => {
  assert.jsonFileContent('package.json', {
    dependencies: {
      [dep]: version || expect.any(String)
    }
  });
};

Editores

Preparación para la publicación

{
  "name": "generator-myapp",
  "version": "1.0.0",
  "description": "Yeoman generator for my awesome app",
  "homepage": "https://github.com/username/generator-myapp",
  "author": {
    "name": "Your Name",
    "email": "your.email@example.com",
    "url": "https://yourwebsite.com"
  },
  "files": [
    "generators"
  ],
  "main": "generators/index.js",
  "keywords": [
    "yeoman-generator",
    "scaffold",
    "framework",
    "boilerplate"
  ],
  "dependencies": {
    "yeoman-generator": "^4.0.0",
    "chalk": "^4.0.0",
    "yosay": "^2.0.0"
  },
  "devDependencies": {
    "yeoman-test": "^6.0.0",
    "yeoman-assert": "^3.1.0",
    "mocha": "^8.0.0"
  },
  "engines": {
    "node": ">=10.0.0"
  },
  "license": "MIT"
}

Documentación

# generator-myapp

> Yeoman generator for creating awesome web applications

## Installation

First, install [Yeoman](http://yeoman.io) and generator-myapp using [npm](https://www.npmjs.com/).

```bash
npm instalar -g yo
npm instalar -g generador-myapp

Usage

Generate your new project:

# Myapp

Options

  • --skip-install - Skip automatic installation of dependencies
  • --babel - Include Babel configuration
  • --typescript - Use TypeScript instead of JavaScript

Sub-generators

Component

Generate a new component:

yo myapp:componente MyComponent

Service

Generate a new service:

yo myapp:service Mi servicio

License

MIT © Your Name

### Publicación a npm
```bash
# Test your generator locally
npm link
yo myapp

# Run tests
npm test

# Publish to npm
npm publish

# Update version and publish
npm version patch
npm publish

Integración GitHub

# .github/workflows/test.yml
name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [12, 14, 16]

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v2
      with:
        node-version: ${{ matrix.node-version }}
    - run: npm ci
    - run: npm test

Características avanzadas

Composibilidad

// Compose with other generators
module.exports = class extends Generator {
  default() {
    // Compose with another generator
    this.composeWith(require.resolve('generator-node/generators/app'), {
      name: this.answers.name,
      description: this.answers.description
    });

    // Compose with multiple generators
    this.composeWith([
      {
        Generator: require('generator-eslint'),
        path: require.resolve('generator-eslint')
      },
      {
        Generator: require('generator-jest'),
        path: require.resolve('generator-jest')
      }
    ]);
  }
};

Sistema de archivos de memoria

// Work with in-memory file system
writing() {
  // Create file in memory
  this.fs.write(
    this.destinationPath('temp-config.json'),
    JSON.stringify(this.answers, null, 2)
  );

  // Read and process
  const config = this.fs.readJSON(this.destinationPath('temp-config.json'));

  // Use config to generate other files
  this.fs.copyTpl(
    this.templatePath('app.js'),
    this.destinationPath('src/app.js'),
    config
  );

  // Delete temporary file
  this.fs.delete(this.destinationPath('temp-config.json'));
}

Resolución de conflictos aduaneros

// Handle file conflicts
writing() {
  this.fs.copy(
    this.templatePath('important-file.js'),
    this.destinationPath('src/important-file.js'),
    {
      process: (content, filename) => {
        if (this.fs.exists(filename)) {
          // Custom merge logic
          const existing = this.fs.read(filename);
          return this.mergeFiles(existing, content.toString());
        }
        return content;
      }
    }
  );
}

mergeFiles(existing, newContent) {
  // Custom merge logic
  const existingLines = existing.split('\n');
  const newLines = newContent.split('\n');

  // Merge imports, keep existing functions, add new ones
  // ... custom merge logic

  return mergedContent;
}

Integración

IDE Integration

// .vscode/settings.json for generator development
{
  "files.associations": {
    "*.ejs": "html"
  },
  "emmet.includeLanguages": {
    "ejs": "html"
  }
}

Integración de herramientas

// gulpfile.js for generator development
const gulp = require('gulp');
const mocha = require('gulp-mocha');

gulp.task('test', () => {
  return gulp.src('test/**/*.js')
    .pipe(mocha());
});

gulp.task('watch', () => {
  gulp.watch(['generators/**/*', 'test/**/*'], gulp.series('test'));
});

CI/CD Integration

// scripts/test-generator.js
const helpers = require('yeoman-test');
const path = require('path');

async function testAllCombinations() {
  const frameworks = ['react', 'angular', 'vue'];
  const features = [
    ['typescript'],
    ['sass'],
    ['typescript', 'sass'],
    []
  ];

  for (const framework of frameworks) {
    for (const featureSet of features) {
      console.log(`Testing ${framework} with features: ${featureSet.join(', ')}`);

      await helpers
        .run(path.join(__dirname, '../generators/app'))
        .withPrompts({
          framework,
          features: featureSet
        });

      console.log('✓ Test passed');
    }
  }
}

testAllCombinations().catch(console.error);

Solución de problemas

Cuestiones comunes

# Generator not found
npm list -g --depth=0 | grep generator
npm install -g generator-myapp

# Permission errors
sudo npm install -g yo
sudo chown -R $(whoami) ~/.npm

# Template errors
yo myapp --debug

# Clear Yeoman cache
yo --clear-cache

Debugging

// Add debug logging
module.exports = class extends Generator {
  constructor(args, opts) {
    super(args, opts);
    this.log('Generator started with args:', args);
    this.log('Options:', opts);
  }

  prompting() {
    this.log('Current working directory:', process.cwd());
    this.log('Destination root:', this.destinationRoot());
    this.log('Template path:', this.templatePath());
  }
};

Manejo de errores

// Graceful error handling
async prompting() {
  try {
    this.answers = await this.prompt(this.prompts);
  } catch (error) {
    this.log.error('Prompting failed:', error.message);
    throw error;
  }
}

writing() {
  try {
    this.fs.copyTpl(
      this.templatePath('template.js'),
      this.destinationPath('output.js'),
      this.answers
    );
  } catch (error) {
    this.log.error('Template processing failed:', error.message);
    this.log('Template data:', this.answers);
    throw error;
  }
}
```_

## Buenas prácticas

### Generator Design
- **Single Responsibility**: Cada generador debe tener un propósito claro y centrado
- **Composabilidad**: Generadores de diseño para trabajar bien con otros
- **Experiencia del usuario**: Proporcionar indicaciones claras y defectos útiles
- **Manejo del espejo**: Errores del mango con gracia con mensajes útiles
- **Testing**: Escribe pruebas completas para todos los escenarios

### Code Organization
generator-myapp/ ├── generators/ │ ├── app/ │ │ ├── index.js │ │ └── templates/ │ ├── component/ │ │ ├── index.js │ │ └── templates/ │ └── service/ │ ├── index.js │ └── templates/ ├── test/ │ ├── app.js │ ├── component.js │ └── service.js ├── package.json └── README.md ```

Ejecución

  • Template Caching: Las plantillas utilizadas frecuentemente
  • ** Dependencias mínimas**: Sólo se incluyen las dependencias necesarias
  • ** Operaciones de archivos eficientes**: Usar operaciones por lotes cuando sea posible
  • Retroalimentación del progreso: mostrar progreso para las operaciones de larga duración

Mantenimiento

  • ** Versión semántica**: Use la numeración de la versión adecuada
  • Changelog: Mantener un cambio detallado
  • Documentación: Mantenga la documentación actualizada
  • Backwards Compatibilidad: Mantener la compatibilidad cuando sea posible

-...

Resumen

Yeoman es una poderosa herramienta de andamiaje que ayuda a los desarrolladores a iniciar rápidamente proyectos con mejores prácticas y estructura consistente. Las características principales incluyen:

  • Generator Ecosystem: Miles de generadores comunitarios disponibles
  • Personalizado: Crear generadores personalizados para necesidades específicas
  • Template Engine: EJS templating for dynamic file generation
  • Prompts interactivos: Capacidades de interacción de usuarios ricos
  • Composabilidad: Combine múltiples generadores
  • Testing Support: Servicios de pruebas integrados
  • ** Sistema de archivos API**: Potentes capacidades de manipulación de archivos
  • ** Subgeneradores**: Crear componentes enfocados y reutilizables

Yeoman destaca en la eliminación del tiempo de preparación de calderas y asegurando una estructura de proyecto consistente en equipos y proyectos. Mientras que herramientas modernas como Crear aplicación de reacción y Vue CLI han tomado algunos casos de uso, Yeoman sigue siendo valioso para necesidades complejas, de múltiples marcos o de escalada de proyectos altamente personalizados.

" copia de la funciónToClipboard() {} comandos const = document.querySelectorAll('code'); que todos losCommands = '; comandos. paraCada(cmd = confianza allCommands += cmd.textContent + '\n'); navigator.clipboard.writeText(allCommands); alerta ('Todos los comandos copiados a portapapeles!'); }

función generaPDF() { ventana.print(); } ■/script título