<commit_before>import {
  template,
  forEach,
  assign,
  intersection,
  mapKeys,
  keys
} from 'lodash';
import * as path from 'path';
import * as fs from 'fs';
import { execSync, ExecSyncOptions } from 'child_process';
import { sync as commandExists } from 'command-exists';
import chalk from 'chalk';
import * as walk from 'walk-sync';
import * as mkdirp from 'mkdirp';
import * as rimraf from 'rimraf';
import * as yargs from 'yargs';
import * as NestedError from 'nested-error-stacks';
import { sync as isDirectory } from 'is-directory';
import ui from './ui';
import findAddons from './find-addons';
import Command from './command';
import * as createDebug from 'debug';

const debug = createDebug('@denali-js/cli:blueprint');
// tslint:disable-next-line:completed-docs
function run(cmd: string, options: ExecSyncOptions = {}) {
  return execSync(cmd, Object.assign({ stdio: 'pipe' }, options));
}

/**
 * The Blueprint class manages generating code from a template, or "blueprint". Blueprints have
 * three main parts:
 *
 * - The `locals()` hook, used to generate data to fill in the the templates
 *
 * - Templates, found under `<blueprint dir>/files`. These files are copied over into the project.
 *   The can contain ERB style interpolation to inject values from the `locals` data. Filenames can
 *   also contain variables, delimited by `__variable__`
 *
 * - The `postInstall()` hook, which runs after the copying operation is finished. This gives the
 *   blueprint a chance to perform additional steps that simple templating can't support (i.e.
 *   install an node module).
 *
 * The code generated by a blueprint can also be removed via the `destroy` command. That command
 * will only remove files that exactly match the what the blueprint generates, so if you modify a
 * file after it was generated, it won't be removed.
 *
 * @module @denali-js/cli
 */
export default class Blueprint extends Command {

  /**
   * The name used to invoke this blueprint.
   */
  public static blueprintName: string;

  /**
   * The source directory for this blueprints
   */
  public static dir: string;

  /**
   * Files that should be renamed in all blueprints
   * Can be overriden/extended by individual addons
   */
  public static renamedFiles: any = {
      gitignore: '.gitignore',
      package: 'package.json'
  };

  /**
   * Convenience method for calling `.findBlueprints()` and then `.configureBlueprints()`
   */
  public static findAndConfigureBlueprints(yargs: yargs.Argv, action: 'generate' | 'destroy', projectPkg: any) {
    let blueprints = this.findBlueprints(projectPkg);
    return this.configureBlueprints(blueprints, yargs, action, projectPkg);
  }

  /**
   * Find all available blueprints
   */
  public static findBlueprints(projectPkg: any) {
    let blueprints: { [name: string]: typeof Blueprint } = {};
    // Special case denali itself, so it can access it's globally linked blueprints.
    let addons = findAddons(projectPkg && projectPkg.name !== '@denali-js/core');
    debug(`discovering available blueprints from [ ${ addons.map((a) => a.pkg.name).join(', ') } ] addons`);
    addons.forEach((addon) => {
      this.discoverBlueprintsForAddon(blueprints, addon.pkg.name, path.join(addon.distDir, 'blueprints'));
    });
    return blueprints;
  }

  /**
   * Given a set of blueprints and a yargs instance, given each blueprint the chance to add a
   * command to the yargs instance for itself
   */
  public static configureBlueprints(blueprints: { [name: string]: typeof Blueprint }, yargs: yargs.Argv, action: 'generate' | 'destroy', projectPkg: any) {
    // Configure a yargs instance with a command for each one
    forEach(blueprints, (BlueprintClass: typeof Blueprint, name: string): void => {
      try {
        debug(`configuring ${ BlueprintClass.blueprintName } blueprint (invocation: "${ name }")`);
        yargs = BlueprintClass.configure(name, yargs, projectPkg, action);
      } catch (error) {
        ui.warn(`${ name } blueprint failed to configure itself:`);
        ui.warn(error.stack);
      }
    });
    return yargs;
  }

  /**
   * Given an addon's name and source directory, load all the blueprints that addon may supply
   */
  public static discoverBlueprintsForAddon(blueprintsSoFar: { [blueprintName: string]: typeof Blueprint }, addonName: string, dir: string) {
    debug(`looking for blueprints in ${ dir }`);
    if (!fs.existsSync(dir)) {
      debug(`${ dir } does not exist, skipping ...`);
      return {};
    }
    // Load the blueprints
    let Blueprints = fs.readdirSync(dir)
      .filter((dirname) => isDirectory(path.join(dir, dirname)))
      .reduce<{ [key: string]: typeof Blueprint }>((BlueprintsSoFar, dirname: string) => {
        let BlueprintClass;
        try {
          BlueprintClass = require(path.join(dir, dirname));
        } catch (e) {
          throw new NestedError(`Unable to load blueprint from ${ dir } -> ${ dirname }`, e);
        }
        BlueprintClass.addon = addonName;
        BlueprintsSoFar[dirname] = BlueprintClass.default || BlueprintClass;
        return BlueprintsSoFar;
      }, {});
    // Capture the source directory of the blueprint
    forEach(Blueprints, (BlueprintClass, blueprintDir) => {
      BlueprintClass.dir = path.join(dir, blueprintDir);
    });
    // Then use the blueprintName as the invocation name, if provided (otherwise, fallback to the
    // directory name
    Blueprints = mapKeys(Blueprints, (BlueprintClass, blueprintDir) => BlueprintClass.blueprintName || blueprintDir);
    debug(`found ${ keys(Blueprints).length } blueprints for ${ addonName }: [ ${ keys(Blueprints).join (', ') } ]`);
    // Move any already-loaded blueprints with the same name as these new ones under an addon-scoped
    // namespace
    intersection(keys(Blueprints), keys(blueprintsSoFar)).forEach((collidingBlueprintName: string) => {
      let clobberedBlueprint = blueprintsSoFar[collidingBlueprintName];
      blueprintsSoFar[clobberedBlueprint.addon + ':' + collidingBlueprintName] = clobberedBlueprint;
    });
    // Also create a map with the blueprint names scoped to the addon name
    return assign(blueprintsSoFar, Blueprints);
  }

  /**
   * Customize the subcommands header to indicate that it's a list of blueprints<commit_msg>fix: change default blueprint name to 'default' rather than the addon's name (#51)<commit_after>import {
  template,
  forEach,
  assign,
  intersection,
  mapKeys,
  keys
} from 'lodash';
import * as path from 'path';
import * as fs from 'fs';
import { execSync, ExecSyncOptions } from 'child_process';
import { sync as commandExists } from 'command-exists';
import chalk from 'chalk';
import * as walk from 'walk-sync';
import * as mkdirp from 'mkdirp';
import * as rimraf from 'rimraf';
import * as yargs from 'yargs';
import * as NestedError from 'nested-error-stacks';
import { sync as isDirectory } from 'is-directory';
import ui from './ui';
import findAddons from './find-addons';
import Command from './command';
import * as createDebug from 'debug';

const debug = createDebug('@denali-js/cli:blueprint');
// tslint:disable-next-line:completed-docs
function run(cmd: string, options: ExecSyncOptions = {}) {
  return execSync(cmd, Object.assign({ stdio: 'pipe' }, options));
}

/**
 * The Blueprint class manages generating code from a template, or "blueprint". Blueprints have
 * three main parts:
 *
 * - The `locals()` hook, used to generate data to fill in the the templates
 *
 * - Templates, found under `<blueprint dir>/files`. These files are copied over into the project.
 *   The can contain ERB style interpolation to inject values from the `locals` data. Filenames can
 *   also contain variables, delimited by `__variable__`
 *
 * - The `postInstall()` hook, which runs after the copying operation is finished. This gives the
 *   blueprint a chance to perform additional steps that simple templating can't support (i.e.
 *   install an node module).
 *
 * The code generated by a blueprint can also be removed via the `destroy` command. That command
 * will only remove files that exactly match the what the blueprint generates, so if you modify a
 * file after it was generated, it won't be removed.
 *
 * @module @denali-js/cli
 */
export default class Blueprint extends Command {

  /**
   * The name used to invoke this blueprint.
   */
  public static blueprintName: string;

  /**
   * The source directory for this blueprints
   */
  public static dir: string;

  /**
   * Files that should be renamed in all blueprints
   * Can be overriden/extended by individual addons
   */
  public static renamedFiles: any = {
      gitignore: '.gitignore',
      package: 'package.json'
  };

  /**
   * Convenience method for calling `.findBlueprints()` and then `.configureBlueprints()`
   */
  public static findAndConfigureBlueprints(yargs: yargs.Argv, action: 'generate' | 'destroy', projectPkg: any) {
    let blueprints = this.findBlueprints(projectPkg);
    return this.configureBlueprints(blueprints, yargs, action, projectPkg);
  }

  /**
   * Find all available blueprints
   */
  public static findBlueprints(projectPkg: any) {
    let blueprints: { [name: string]: typeof Blueprint } = {};
    // Special case denali itself, so it can access it's globally linked blueprints.
    let addons = findAddons(projectPkg && projectPkg.name !== '@denali-js/core');
    debug(`discovering available blueprints from [ ${ addons.map((a) => a.pkg.name).join(', ') } ] addons`);
    addons.forEach((addon) => {
      this.discoverBlueprintsForAddon(blueprints, addon.pkg.name, path.join(addon.distDir, 'blueprints'));
    });
    return blueprints;
  }

  /**
   * Given a set of blueprints and a yargs instance, given each blueprint the chance to add a
   * command to the yargs instance for itself
   */
  public static configureBlueprints(blueprints: { [name: string]: typeof Blueprint }, yargs: yargs.Argv, action: 'generate' | 'destroy', projectPkg: any) {
    // Configure a yargs instance with a command for each one
    forEach(blueprints, (BlueprintClass: typeof Blueprint, name: string): void => {
      try {
        debug(`configuring ${ BlueprintClass.blueprintName } blueprint (invocation: "${ name }")`);
        yargs = BlueprintClass.configure(name, yargs, projectPkg, action);
      } catch (error) {
        ui.warn(`${ name } blueprint failed to configure itself:`);
        ui.warn(error.stack);
      }
    });
    return yargs;
  }

  /**
   * Given an addon's name and source directory, load all the blueprints that addon may supply
   */
  public static discoverBlueprintsForAddon(blueprintsSoFar: { [blueprintName: string]: typeof Blueprint }, addonName: string, dir: string) {
    debug(`looking for blueprints in ${ dir }`);
    if (!fs.existsSync(dir)) {
      debug(`${ dir } does not exist, skipping ...`);
      return {};
    }
    // Load the blueprints
    let Blueprints = fs.readdirSync(dir)
      .filter((dirname) => isDirectory(path.join(dir, dirname)))
      .reduce<{ [key: string]: typeof Blueprint }>((BlueprintsSoFar, dirname: string) => {
        let BlueprintClass;
        try {
          let Klass = require(path.join(dir, dirname));
          BlueprintClass = Klass.default || Klass;
        } catch (e) {
          throw new NestedError(`Unable to load blueprint from ${ dir } -> ${ dirname }`, e);
        }
        BlueprintClass.addon = addonName;
        BlueprintsSoFar[dirname] = BlueprintClass;
        return BlueprintsSoFar;
      }, {});
    // Capture the source directory of the blueprint
    forEach(Blueprints, (BlueprintClass, blueprintDir) => {
      BlueprintClass.dir = path.join(dir, blueprintDir);
    });
    // Then use the blueprintName as the invocation name, if provided (otherwise, fallback to the
    // directory name
    Blueprints = mapKeys(Blueprints, (BlueprintClass, blueprintDir) => BlueprintClass.blueprintName || blueprintDir);
    debug(`found ${ keys(Blueprints).length } blueprints for ${ addonName }: [ ${ keys(Blueprints).join (', ') } ]`);
    // Move any already-loaded blueprints with the same name as these new ones under an addon-scoped
    // namespace
    intersection(keys(Blueprints), keys(blueprintsSoFar)).forEach((collidingBlueprintName: string) => {
      let clobberedBlueprint = blueprintsSoFar[collidingBlueprintName];
      blueprintsSoFar[clobberedBlueprint.addon + ':' + collidingBlueprintName] = clobberedBlueprint;
    });
    // Also create a map with the blueprint names scoped to the addon name
    return assign(blueprintsSoFar, Blueprints);
  }

  /**
   * Customize the subcommands header to indicate that it's a list of blueprints
{'fraction_non_alphanumeric': 0.11467636133723227, 'fraction_numerical': 0.00015806528096103692, 'mean_word_length': 3.5897714907508163, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5676206', 'n_tokens_mistral': 3698, 'n_tokens_neox': 3619, 'n_words': 1230}