CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-yeoman-generator

Rails-inspired generator system that provides scaffolding for your apps

Overview
Eval results
Files

git-integration.mddocs/

Git Integration

Git utilities for user information and repository operations through simple-git integration with support for configuration access and GitHub username resolution.

Capabilities

Git User Information

Access git user configuration for author information and defaults.

/**
 * Git utilities interface
 */
interface GitUtil {
  /**
   * Retrieves user's name from Git in the global scope or project scope
   * Takes what Git will use in the current context
   * @returns Promise resolving to configured git name or undefined
   */
  name(): Promise<string | undefined>;
  
  /**
   * Retrieves user's email from Git in the global scope or project scope  
   * Takes what Git will use in the current context
   * @returns Promise resolving to configured git email or undefined
   */
  email(): Promise<string | undefined>;
}

/**
 * Git utilities instance
 */
readonly git: GitUtil;

Usage Examples:

export default class MyGenerator extends Generator {
  async prompting() {
    // Get git user info for prompt defaults
    const gitName = await this.git.name();
    const gitEmail = await this.git.email();
    
    this.answers = await this.prompt([
      {
        name: 'authorName',
        message: 'Author name:',
        default: gitName || 'Anonymous',
        store: true
      },
      {
        name: 'authorEmail', 
        message: 'Author email:',
        default: gitEmail || '',
        store: true
      }
    ]);
  }
  
  async configuring() {
    // Use git info in package.json
    const author = {
      name: this.answers.authorName || await this.git.name(),
      email: this.answers.authorEmail || await this.git.email()
    };
    
    this.packageJson.merge({ author });
  }
  
  async initializing() {
    // Validate git configuration
    const gitName = await this.git.name();
    const gitEmail = await this.git.email();
    
    if (!gitName || !gitEmail) {
      this.log('Warning: Git user.name and user.email should be configured');
      this.log('Run: git config --global user.name "Your Name"');
      this.log('Run: git config --global user.email "your.email@example.com"');
    }
  }
}

Simple Git Integration

Direct access to simple-git instance for advanced git operations.

/**
 * Simple-git instance for advanced git operations
 * Configured with current destination root and locale settings
 */
readonly simpleGit: SimpleGit;

Usage Examples:

import { SimpleGit } from 'simple-git';

export default class MyGenerator extends Generator {
  async writing() {
    // Initialize git repository
    if (this.answers.initGit && !await this.isGitRepo()) {
      await this.simpleGit.init();
      this.log('Initialized git repository');
    }
    
    // Set up git configuration for this project
    if (this.answers.gitConfig) {
      await this.simpleGit.addConfig('user.name', this.answers.authorName);
      await this.simpleGit.addConfig('user.email', this.answers.authorEmail);
    }
    
    // Add gitignore
    this.renderTemplate('gitignore.ejs', '.gitignore', {
      nodeModules: true,
      buildDirs: ['dist/', 'build/'],
      logFiles: ['*.log', 'logs/']
    });
  }
  
  async install() {
    // Create initial commit after installation
    if (this.answers.initialCommit) {
      await this.simpleGit.add('.');
      await this.simpleGit.commit('Initial commit\n\n🤖 Generated with Yeoman');
      this.log('Created initial commit');
    }
  }
  
  async end() {
    // Set up remote repository
    if (this.answers.remoteUrl) {
      await this.simpleGit.addRemote('origin', this.answers.remoteUrl);
      
      if (this.answers.pushToRemote) {
        await this.simpleGit.push('origin', 'main');
        this.log(`Pushed to remote: ${this.answers.remoteUrl}`);
      }
    }
  }
  
  // Helper method to check if directory is a git repo
  async isGitRepo(): Promise<boolean> {
    try {
      await this.simpleGit.status();
      return true;
    } catch (error) {
      return false;
    }
  }
}

GitHub Integration (Deprecated)

GitHub utilities for username resolution (deprecated, will be removed in v8).

/**
 * @deprecated Will be removed in version 8
 * GitHub utilities
 */
readonly github: {
  /**
   * @deprecated Will be removed in version 8. Use 'github-username' package with await this.git.email() result instead
   * Retrieves GitHub's username from the GitHub API using email
   * @returns Promise resolving to GitHub username or undefined
   */
  username(): Promise<string | undefined>;
};

Usage Example:

export default class MyGenerator extends Generator {
  async prompting() {
    // Get GitHub username (deprecated - use alternative approach)
    let githubUsername;
    
    try {
      // Deprecated approach
      githubUsername = await this.github.username();
    } catch (error) {
      // Alternative approach using email
      const email = await this.git.email();
      if (email) {
        // Manual username derivation or use github-username package
        githubUsername = email.split('@')[0];
      }
    }
    
    this.answers = await this.prompt([
      {
        name: 'githubUsername',
        message: 'GitHub username:',
        default: githubUsername,
        when: this.answers.publishToGithub
      }
    ]);
  }
}

Advanced Git Operations

Complex git workflows and repository management.

Usage Examples:

export default class MyGenerator extends Generator {
  async initializing() {
    // Check git status and working directory
    if (await this.isGitRepo()) {
      const status = await this.simpleGit.status();
      
      if (!status.isClean()) {
        this.log('Warning: Working directory is not clean');
        this.log('Uncommitted changes may be overwritten');
        
        const proceed = await this.prompt({
          type: 'confirm',
          name: 'continueWithDirtyRepo',
          message: 'Continue anyway?',
          default: false
        });
        
        if (!proceed.continueWithDirtyRepo) {
          throw new Error('Aborted due to uncommitted changes');
        }
      }
    }
  }
  
  async writing() {
    // Create branch for generated code
    if (this.answers.createBranch) {
      const branchName = `generator/${this.answers.name}`;
      
      try {
        await this.simpleGit.checkoutLocalBranch(branchName);
        this.log(`Created and switched to branch: ${branchName}`);
      } catch (error) {
        this.log(`Branch ${branchName} may already exist, switching to it`);
        await this.simpleGit.checkout(branchName);
      }
    }
    
    // Set up git hooks
    if (this.answers.setupHooks) {
      this.renderTemplate('pre-commit.ejs', '.git/hooks/pre-commit', {
        runLint: this.answers.useLinting,
        runTests: this.answers.useTesting
      });
      
      // Make hook executable
      await this.spawn('chmod', ['+x', '.git/hooks/pre-commit']);
    }
  }
  
  async end() {
    // Tag the generated version
    if (this.answers.createTag) {
      const tagName = `v${this.packageJson.get('version')}`;
      
      await this.simpleGit.addTag(tagName);
      this.log(`Created tag: ${tagName}`);
    }
    
    // Generate changelog from commits
    if (this.answers.generateChangelog) {
      const log = await this.simpleGit.log(['--oneline', '--decorate']);
      
      const changelog = log.all.map(commit => 
        `- ${commit.message} (${commit.hash.substring(0, 7)})`
      ).join('\n');
      
      this.writeDestination('CHANGELOG.md', `# Changelog\n\n${changelog}\n`);
    }
  }
}

Git Configuration and Validation

Validate git setup and configure repository settings.

Usage Examples:

export default class MyGenerator extends Generator {
  async initializing() {
    // Comprehensive git validation
    await this.validateGitSetup();
  }
  
  async validateGitSetup() {
    // Check if git is available
    try {
      await this.spawnCommand('git --version', { stdio: 'pipe' });
    } catch (error) {
      throw new Error('Git is not installed or not available in PATH');
    }
    
    // Check git configuration
    const gitName = await this.git.name();
    const gitEmail = await this.git.email();
    
    if (!gitName) {
      this.log('⚠️  Git user.name is not configured');
      this.log('Run: git config --global user.name "Your Name"');
    }
    
    if (!gitEmail) {
      this.log('⚠️  Git user.email is not configured');
      this.log('Run: git config --global user.email "your.email@example.com"');
    }
    
    // Check if we're in a git repository
    const isRepo = await this.isGitRepo();
    if (!isRepo && this.answers.requireGitRepo) {
      throw new Error('This generator requires a git repository. Run "git init" first.');
    }
    
    return { gitName, gitEmail, isRepo };
  }
  
  async configuring() {
    // Configure repository-specific settings
    if (this.answers.configureGit) {
      // Set repository-specific configuration
      await this.simpleGit.addConfig('core.autocrlf', 'input');
      await this.simpleGit.addConfig('pull.rebase', 'false');
      
      // Configure commit template
      if (this.answers.useCommitTemplate) {
        this.writeDestination('.gitmessage', `
# Title: Summary, imperative, start upper case, don't end with a period
# No more than 50 chars. #### 50 chars is here:  #

# Remember blank line between title and body.

# Body: Explain *what* and *why* (not *how*). Include task ID (Jira issue).
# Wrap at 72 chars. ################################## which is here:  #


# At the end: Include Co-authored-by for all contributors. 
# Include at least one empty line before it. Format: 
# Co-authored-by: name <user@users.noreply.github.com>
#
# How to Write a Git Commit Message:
# https://chris.beams.io/posts/git-commit/
#
# 1. Separate subject from body with a blank line
# 2. Limit the subject line to 50 characters
# 3. Capitalize the subject line
# 4. Do not end the subject line with a period
# 5. Use the imperative mood in the subject line
# 6. Wrap the body at 72 characters
# 7. Use the body to explain what and why vs. how
        `.trim());
        
        await this.simpleGit.addConfig('commit.template', '.gitmessage');
      }
    }
  }
  
  async isGitRepo(): Promise<boolean> {
    try {
      await this.simpleGit.status();
      return true;
    } catch (error) {
      return false;
    }
  }
}

Types

interface GitUtil {
  name(): Promise<string | undefined>;
  email(): Promise<string | undefined>;
}

// Simple-git types (from simple-git package)
interface SimpleGit {
  init(): Promise<void>;
  status(): Promise<StatusResult>;
  add(files: string | string[]): Promise<void>;
  commit(message: string): Promise<CommitResult>;
  push(remote?: string, branch?: string): Promise<void>;
  addRemote(remoteName: string, remoteUrl: string): Promise<void>;
  addConfig(key: string, value: string): Promise<void>;
  getConfig(key: string): Promise<ConfigGetResult>;
  addTag(tagName: string): Promise<void>;
  log(options?: string[]): Promise<LogResult>;
  checkout(branch: string): Promise<void>;
  checkoutLocalBranch(branchName: string): Promise<void>;
}

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