npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@manuth/extended-yo-generator

v14.0.2

Published

An extended version of the Yeoman-Generator

Downloads

125

Readme

ExtendedYoGenerator

An extended version of the Yeoman-Generator

status-badge

Installing ExtendedYoGenerator

ExtendedYoGenerator can be installed using the npm-cli:

npm install --save @manuth/extended-yo-generator

Using ExtendedYoGenerator

You can use the extended yo-generator by inheriting the Generator-class provided by the module.

All class members are documented using js-doc in order to provide the best possible user-experience.

Example

First you might want to create an interface describing the settings of your generator where the prompting-answers are stored.

./src/IMySettings.ts

export interface IMySettings extends IGeneratorSettings
{
    /**
     * The name of the project to generate.
     */
    name: string;

    /**
     * The license-type of the project to generate.
     */
    licenseType: "apache" | "gpl";

    /**
     * The path to write the project to.
     */
    destination: string;
}

Now you're ready to create the actual generator-class:

./src/index.ts

import { Generator, GeneratorOptions, IGeneratorSettings, Question, ResolveValue } from "@manuth/extended-yo-generator";
import { IMySettings } from "./IMySettings";

export = class MyGenerator extends Generator<IMySettings, GeneratorOptions>
{
    protected get TemplateRoot(): string
    {
        return "app";
    }

    protected get Questions(): Array<Question<IMySettings>>
    {
        return [
            {
                name: "name",
                message: "What's the name of your project?",
                default: "example"
            }
        ];
    }

    protected get FileMappings(): Array<IFileMapping<IMySettings, GeneratorOptions>>
    {
        return [
            {
                Source: "README.md",
                Destination: "README.md",
                Context: () => ({
                    Name: this.Settings.name
                })
            }
        ];
    }

    public async prompting(): Promise<void>
    {
        this.log("Welcome to my generator!");
        return super.prompting();
    }

    public async writing(): Promise<void>
    {
        return super.writing();
    }

    public async install(): Promise<void>
    {
        await super.install();
        this.npmInstall();
    }

    public async end(): Promise<void>
    {
        await super.end();
        this.log("Finished!");
    }
}

Features

Separate Template-Folders

Generally all templates are loaded from ./templates. The TemplateRoot-member of the Generator-class allows you to load template-files from separate sub-folders of ./templates.

Example

export = class MyGenerator extends Generator
{
    protected get TemplateRoot(): string
    {
        return "app";
    }
}

This causes this.templatePath(...path) to create paths relative to ./templates/app rather than ./templates.
The this.commonTemplatePath(...path) method always creates paths relative to ./templates.

FileMappings

The Generator.FileMappings-property allows you to provide a set of files which are being copied when invoking Generator.writing().

Example

export = class MyGenerator extends Generator<IMySettings, GeneratorOptions>
{
    protected get FileMappings(): Array<IFileMapping<IMySettings, GeneratorOptions>>
    {
        return [
            {
                Source: "README.md",
                Destination: "README.md",
                Context: () => ({
                    ProjectName: "Example"
                })
            },
            {
                Source: ".gitignore",
                Destination: ".gitignore"
            }
        ];
    }

    public async writing(): Promise<void>
    {
        return super.writing();
    }
}

this.templatePath("README.md") will be copied to this.destinationPath("README.md") using ejs, because a function for creating a Context-object is provided. this.templatePath(".gitignore"), on the other hand, will be copied to this.destinationPath(".gitignore") without the use of ejs, because the Context property is not present.

Components

You can provide components the user can choose to install.
Each component can provide any number of file-mappings and additional questions.

Example

export = class MyGenerator extends Generator<IMySettings, GeneratorOptions>
{
    protected get Components(): IComponentCollection<IMySettings, GeneratorOptions>
    {
        return {
            Question: "What should be included in your project?",
            Categories: [
                {
                    DisplayName: "General",
                    Components: [
                        {
                            ID: "Readme",
                            DisplayName: "README-File",
                            FileMappings: [
                                {
                                    Source: this.templatePath("README.md"),
                                    Destination: "README.md",
                                    Context: (fileMapping, generator) =>
                                    {
                                        return {
                                            Name: generator.Settings.name
                                        };
                                    }
                                }
                            ]
                        },
                        {
                            ID: "License",
                            DisplayName: "License-File",
                            Questions: [
                                {
                                    type: "list",
                                    name: "licenseType",
                                    message: "What kind of license do you want to use?",
                                    default: "gpl",
                                    choices: [
                                        {
                                            value: "gpl",
                                            name: "GNU GPL"
                                        },
                                        {
                                            value: "apache",
                                            name: "Apache-2.0"
                                        }
                                    ]
                                }
                            ],
                            FileMappings: [
                                {
                                    Source: () =>
                                    {
                                        return this.Settings.licenseType === "gpl" ? "GPL.txt" : "Apache.txt");
                                    },
                                    Destination: "LICENSE"
                                }
                            ]
                        }
                    ]
                }
            ]
        };
    }
}

The generator asks what components to install.
If there are any questions declared for the component the generator will automatically ask them if the component has been checked.

The IDs of the components the user has chosen are then written to the Generator.Settings[GeneratorSetting.Components] member.

Questions

Specify questions which are asked when invoking Generator.prompting().
All answers are stored in the Generator.Settings-property.

Example

export = class MyGenerator extends Generator<IMySettings, GeneratorOptions>
{
    protected get Questions(): Array<Question<IMySettings>>
    {
        return [
            {
                name: "destination",
                message: "Where do you want to store the project?",
                default: "./"
            },
            {
                name: "name",
                message: "What's the name of your project?"
            }
        ];
    }
}

Manipulating Questions

You might want to ask questions after the user has chosen components. You can reach this goal by manipulating the QuestionCollection-property like this:

export = class MyGenerator extends Generator<IMySettings, GeneratorOptions>
{
    protected get QuestionCollection(): Array<Question<IMySettings>>
    {
        let result = super.QuestionCollection;
        result.push(
            {
                name: "destination",
                message: "Where do you want to store the project?",
                default: "./"
            });
    }
}

Settings

The Generator.Settings-property contains all answers to the prompts.

ModulePath

Using the Generator.modulePath(...path)-method you can create paths relative to the root of your generator-module.

This may be useful for instance if you want to copy your tslint-rules to the generated workspace:

export class = MyGenerator extends Generator
{
    public async writing(): Promise<void>
    {
        super.writing();
        this.fs.copy(this.modulePath("tslint.json"), this.destinationPath("tslint.json"));
    }
}

Prompting

The Generator.prompting()-method asks all questions specified using Generator.Questions and Generator.ProvidedComponents.

Example

export = class MyGenerator extends Generator
{
    public async prompting(): Promise<void>
    {
        this.log("Welcome to my generator!");
        return super.prompting();
    }
}

Writing

The Generator.writing()-method copies all FileMappings of the components.

Example

export = class MyGenerator extends Generator
{
    public async writing(): Promise<void>
    {
        await super.writing();
        this.fs.copy(this.templatePath("package.json"), this.destinationPath("package.json"));
    }
}

Yo-Generator Methods

Naturally the default yo-generator methods remain which are…

  • initializing():
    Initializes the generator, allowing to inherit other generators using Generator.composeWith
  • prompting():
    Asks all Questions and additionally all questions related to the Components
  • writing():
    Generates the project, automatically creating all specified file-mappings of the Components
  • install():
    This method can be used for installing the generated project
  • end():
    This method is invoked after the generator finished running

Extending Generators

You might want to not only inherit some methods from a generator but also its file-mappings and components.
As file-mappings usually make use of the Generator.modulePath, Generator.commonTemplatePath or Generator.templatePath method. When inheriting a generator-class normally, its Generator.modulePath along with the other named methods will create paths relative to your module rather than the module of the generator you're inheriting.

The static Generator.ComposeWith is designed to solve this problem. Generator.ComposeWith takes two parameters: The class you want to extend and a path to a file inside the module its Generator.modulePath should resolve to.

You can use Generator.ComposeWith like this:

import { Generator } from "@manuth/extended-yo-generator";
import TSPluginGenerator from "@my/generator-ts-plugin";

export = class MyOwnGenerator extends Generator.ComposeWith(TSPluginGenerator, require.resolve("@my/generator-ts-plugin"))
{ }

An instance of the extending class will have a Base-property returning an independent instance of the TSPluginGenerator class and BaseFileMappings and BaseComponent properties for manipulating its file-mappings and components.

This way your MyOwnGenerator will safely inherit the FileMappings and Components correctly bound to the module-path of your base-generator.

console.log(this.modulePath()); // Logs the path to your module
console.log(this.Base.modulePath()); // Logs "./node_modules/@my/generator-ts-plugin"