Giter Club home page Giter Club logo

plop's Introduction

Plop

Micro-generator framework that makes it easy for an entire team to create files with a level of uniformity. plop demo

Documentation also available on plopjs.com

Getting Started

npm ย  npm ย  plop on slack

What is Plop?

Plop is what I like to call a "micro-generator framework." Now, I call it that because it is a small tool that gives you a simple way to generate code or any other type of flat text files in a consistent way. You see, we all create structures and patterns in our code (routes, controllers, components, helpers, etc). These patterns change and improve over time so when you need to create a NEW insert-name-of-pattern-here, it's not always easy to locate the files in your codebase that represent the current "best practice." That's where plop saves you. With plop, you have your "best practice" method of creating any given pattern in CODE. Code that can easily be run from the terminal by typing plop. Not only does this save you from hunting around in your codebase for the right files to copy, but it also turns "the right way" into "the easiest way" to make new files.

If you boil plop down to its core, it is basically glue code between inquirer prompts and handlebar templates.

This documentation is a work in progress. If you have great ideas, I'd love to hear them.

Installation

1. Add plop to your project

$ npm install --save-dev plop

2. Install plop globally (optional, but recommended for easy access)

$ npm install -g plop

3. Create a plopfile.js at the root of your project

export default function (plop) {
	// create your generators here
	plop.setGenerator('basics', {
		description: 'this is a skeleton plopfile',
		prompts: [], // array of inquirer prompts
		actions: []  // array of actions
	});
};

export default is only allowed in NodeJS inside "ESM" supported files. To use this syntax, your plopfile must be either:

  • An ESM .js file with type: "module" in package.json
  • An ESM .mjs file with any type declared in package.json

Alternatively, you can have a plopfile with module.exports = function (plop) instead. For this syntax, your plopfile must be either:

  • A CommonJS .js file with type: "commonjs" in package.json
  • A CommonJS .cjs file with any type declared in package.json

Your First Plopfile

A plopfile starts its life as a node module that exports a function which accepts the plop object as its first parameter.

export default function (plop) {};

The plop object exposes the plop API object which contains the setGenerator(name, config) function. This is the function that you use to (wait for it) create a generator for this plopfile. When plop is run from the terminal in this directory (or any sub-directory), a list of these generators will be displayed.

Let's try setting up a basic generator to see how that looks.

export default function (plop) {
	// controller generator
	plop.setGenerator('controller', {
		description: 'application controller logic',
		prompts: [{
			type: 'input',
			name: 'name',
			message: 'controller name please'
		}],
		actions: [{
			type: 'add',
			path: 'src/{{name}}.js',
			templateFile: 'plop-templates/controller.hbs'
		}]
	});
};

The controller generator we created above will ask us 1 question, and create 1 file. This can be expanded to ask as many questions as needed, and create as many files as needed. There are also additional actions that can be used to alter our codebase in different ways.

Using Prompts

Plop uses the inquirer.js library to gather user data. A list of prompt types can be found on the inquirer official website.

CLI Usage

Once plop is installed, and you have created a generator, you are ready to run plop from the terminal. Running plop with no parameters will present you with a list of generators to pick from. You can also run plop [generatorName] to trigger a generator directly. If you did not install plop globally, you will need to setup an npm script to run plop for you.

// package.json
{
    ...,
    "scripts": {
        "plop": "plop"
    },
    ...
}

Bypassing Prompts

Once you get to know a project (and its generators) well, you may want to provide answers to the prompts when you run the generator. If I have (for instance) a component generator that has one prompt (name), I can run that generator using plop component "some component name" and it will immediately execute as though I had typed "some component name" into the prompt. If that same generator had a second prompt, the same input would have resulted in the user being prompted for the second value.

Prompts like confirm and list try to make sense of your input as best they can. For instance entering "y", "yes", "t", or "true" for a confirm prompt will result in a boolean true value. You can select items from a list using their value, index, key, or name. Checkbox prompts can accept a comma separated list of values in order to select multiples.

plop bypass demo

If you want to provide bypass input for the second prompt but not the first, you can use an underscore "_" to skip the bypass (ie plop component _ "input for second prompt").

Plop comes with bypass logic built-in for standard inquirer prompts, but there are also ways to provide custom logic for how to handle user input for a specific prompt.

If you have published a 3rd party inquirer prompt plugin and would like to support bypass functionality for plop users out of the box, that is covered in another section of this documentation.

Bypassing Prompts (by Name)

You can also bypass prompts by name using -- and then providing arguments for each prompt that you'd like to bypass. Examples below.

Bypass Examples

## Bypassing both prompt 1 and 2
$ plop component "my component" react
$ plop component -- --name "my component" --type react

## Bypassing only prompt 2 (will be prompted for name)
$ plop component _ react
$ plop component -- --type react

Running a Generator Forcefully

By default Plop actions keep your files safe by failing when things look fishy. The most obvious example of this is not allowing an add action to overwrite a file that already exists. Plop actions individually support the force property but you can also use the --force flag when running Plop from the terminal. Using the --force flag will tell every action to run forcefully. With great power...๐Ÿ•ท

Using TypeScript plopfiles

Plop bundles TypeScript declarations and supports TypeScript plopfiles via tsx loaders, a feature of NodeJS command line imports.

First, make a TypesScript plopfile using plop --init-ts or by hand:

// plopfile.ts
import {NodePlopAPI} from 'plop';

export default function (plop: NodePlopAPI) {
  // plop generator code
};

Next, install tsx and optionally cross-env:

npm i -D tsx cross-env

Finally, use NODE_OPTIONS to activate the tsx loader. Now Plop can import your plopfile.ts:

Node.js v20.6 and above

// package.json
"scripts": {
  "cross-env NODE_OPTIONS='--import tsx' plop --plopfile=plopfile.ts"
}

Node.js v20.5.1 and below

// package.json
"scripts": {
  "cross-env NODE_OPTIONS='--loader tsx' plop --plopfile=plopfile.ts"
}

Why Generators?

Because when you create your boilerplate separate from your code, you naturally put more time and thought into it.

Because saving your team (or yourself) 5-15 minutes when creating every route, component, controller, helper, test, view, etc... really adds up.

Because context switching is expensive and saving time is not the only benefit to automating workflows

Plopfile API

The plopfile api is the collection of methods that are exposed by the plop object. Most of the work is done by setGenerator but this section documents the other methods that you may also find useful in your plopfile.

TypeScript Support

Plop bundles TypeScript declarations. See using TypeScript plopfiles for more details.

JSDoc Support

Whether or not you write your plopfile in TypeScript, many editors will offer code assistance via JSDoc declarations.

// plopfile.js
export default function (
	/** @type {import('plop').NodePlopAPI} */
	plop
) {
	// plop generator code
};

Main Methods

These are the methods you will commonly use when creating a plopfile. Other methods that are mostly for internal use are list in the other methods section.

Method Parameters Returns Description
setGenerator String, GeneratorConfig PlopGenerator setup a generator
setHelper String, Function setup handlebars helper
setPartial String, String setup a handlebars partial
setActionType String, CustomAction register a custom action type
setPrompt String, InquirerPrompt registers a custom prompt type with inquirer
load Array[String], Object, Object loads generators, helpers and/or partials from another plopfile or npm module

setHelper

setHelper directly corresponds to the handlebars method registerHelper. So if you are familiar with handlebars helpers, then you already know how this works.

export default function (plop) {
	plop.setHelper('upperCase', function (text) {
		return text.toUpperCase();
	});

	// or in es6/es2015
	plop.setHelper('upperCase', (txt) => txt.toUpperCase());
};

setPartial

setPartial directly corresponds to the handlebars method registerPartial. So if you are familiar with handlebars partials, then you already know how this works.

export default function (plop) {
	plop.setPartial('myTitlePartial', '<h1>{{titleCase name}}</h1>');
	// used in template as {{> myTitlePartial }}
};

setActionType

setActionType allows you to create your own actions (similar to add or modify) that can be used in your plopfiles. These are basically highly reusable custom action functions.

FunctionSignature Custom Action

Parameters Type Description
answers Object Answers to the generator prompts
config ActionConfig The object in the "actions" array for the generator
plop PlopfileApi The plop api for the plopfile where this action is being run
export default function (plop) {
	plop.setActionType('doTheThing', function (answers, config, plop) {
		// do something
		doSomething(config.configProp);
		// if something went wrong
		throw 'error message';
		// otherwise
		return 'success status message';
	});

	// or do async things inside of an action
	plop.setActionType('doTheAsyncThing', function (answers, config, plop) {
		// do something
		return new Promise((resolve, reject) => {
			if (success) {
				resolve('success status message');
			} else {
				reject('error message');
			}
		});
	});

	// use the custom action
	plop.setGenerator('test', {
		prompts: [],
		actions: [{
			type: 'doTheThing',
			configProp: 'available from the config param'
		}, {
			type: 'doTheAsyncThing',
			speed: 'slow'
		}]
	});
};

setPrompt

Inquirer provides many types of prompts out of the box, but it also allows developers to build prompt plugins. If you'd like to use a prompt plugin, you can register it with setPrompt. For more details see the Inquirer documentation for registering prompts. Also check out the plop community driven list of custom prompts.

import autocompletePrompt from 'inquirer-autocomplete-prompt';
export default function (plop) {
	plop.setPrompt('autocomplete', autocompletePrompt);
	plop.setGenerator('test', {
		prompts: [{
			type: 'autocomplete',
			...
		}]
	});
};

setGenerator

The config object needs to include prompts and actions (description is optional). The prompts array is passed to inquirer. The actions array is a list of actions to take (described in greater detail below)

Interface GeneratorConfig

Property Type Default Description
description [String] short description of what this generator does
prompts Array[InquirerQuestion] questions to ask the user
actions Array[ActionConfig] actions to perform

If your list of actions needs to be dynamic, take a look at using a dynamic actions array.

Interface PlopGenerator

Property Type Default Description
runPrompts Function a function to run the prompts within a generator
runActions Function a function to run the actions within a generator

This interface also contains all properties from GeneratorConfig

Interface ActionConfig

The following properties are the standard properties that plop handles internally. Other properties will be required depending on the type of action. Also take a look at the built-in actions.

Property Type Default Description
type String the type of action (add, modify, addMany, etc)
force Boolean false performs the action forcefully (means different things depending on the action)
data Object / Function {} specifies data that should be mixed with user prompt answers when running this action
abortOnFail Boolean true if this action fails for any reason abort all future actions
skip Function an optional function that specifies if the action should run

The data property on any ActionConfig can also be a Function that returns an Object or a Function that returns a Promise that resolves with an Object.

The skip function on any ActionConfig is optional and should return a string if the action should be skipped. The return value is the reason to skip the action.

Instead of an Action Object, a function can also be used

Other Methods

Method Parameters Returns Description
getHelper String Function get the helper function
getHelperList Array[String] get a list of helper names
getPartial String String get a handlebars partial by name
getPartialList Array[String] get a list of partial names
getActionType String CustomAction get an actionType by name
getActionTypeList Array[String] get a list of actionType names
setWelcomeMessage String Customizes the displayed message that asks you to choose a generator when you run plop.
getGenerator String GeneratorConfig get the PlopGenerator by name
getGeneratorList Array[Object] gets an array of generator names and descriptions
setPlopfilePath String set the plopfilePath value which is used internally to locate resources like template files
getPlopfilePath String returns the absolute path to the plopfile in use
getDestBasePath String returns the base path that is used when creating files
setDefaultInclude Object Object sets the default config that will be used for this plopfile if it is consumed by another plopfile using plop.load()
getDefaultInclude String Object gets the default config that will be used for this plopfile if it is consumed by another plopfile using plop.load()
renderString String, Object String Runs the first parameter (String) through the handlebars template renderer using the second parameter (Object) as the data. Returns the rendered template.

Built-In Actions

There are several types of built-in actions you can use in your GeneratorConfig. You specify which type of action (all paths are based on the location of the plopfile), and a template to use.

The Add, AddMany and Modify actions have an optional transform method that can be used to transform the template result before it is written to disk. The transform function receives the template result or file contents as a string and the action data as arguments. It must return a string or a Promise that resolves to a string.

Add

The add action is used to (you guessed it) add a file to your project. The path property is a handlebars template that will be used to create the file by name. The file contents will be determined by the template or templateFile property.

Property Type Default Description
path String a handlebars template that (when rendered) is the path of the new file
template String a handlebars template that should be used to build the new file
templateFile String a path a file containing the template
skipIfExists Boolean false skips a file if it already exists (instead of failing)
transform Function an optional function that can be used to transform the template result before writing the file to disk
skip Function inherited from ActionConfig
force Boolean false inherited from ActionConfig (overwrites files if they exist)
data Object {} inherited from ActionConfig
abortOnFail Boolean true inherited from ActionConfig

AddMany

The addMany action can be used to add multiple files to your project with a single action. The destination property is a handlebars template that will be used to identify the folder that the generated files should go into. The base property can be used to alter what section of the template paths should be omitted when creating files. The paths located by the templateFiles glob can use handlebars syntax in their file/folder names if you'd like the added file names to be unique (example: {{ dashCase name }}.spec.js).

Property Type Default Description
destination String a handlebars template that (when rendered) is the destination folder for the new files
base String the section of the path that should be excluded when adding files to the destination folder
templateFiles Glob glob pattern that matches multiple template files to be added
stripExtensions [String] ['hbs'] file extensions that should be stripped from templateFiles files names while being added to the destination
globOptions Object glob options that change how to match to the template files to be added
verbose Boolean true print each successfully added file path
transform Function an optional function that can be used to transform the template result before writing each file to disk
skip Function inherited from ActionConfig
skipIfExists Boolean false inherited from Add (skips a file if it already exists)
force Boolean false inherited from ActionConfig (overwrites files if they exist)
data Object {} inherited from ActionConfig
abortOnFail Boolean true inherited from ActionConfig

Modify

The modify action can be used two ways. You can use a pattern property to find/replace text in the file located at the path specified, or you can use a transform function to transform the file contents. Both pattern and transform can be used at the same time (transform will happen last). More details on modify can be found in the example folder.

Property Type Default Description
path String handlebars template that (when rendered) is the path of the file to be modified
pattern RegExp endโ€‘ofโ€‘file regular expression used to match text that should be replaced
template String handlebars template that should replace what was matched by the pattern. capture groups are available as $1, $2, etc
templateFile String path a file containing the template
transform Function an optional function that can be used to transform the file before writing it to disk
skip Function inherited from ActionConfig
data Object {} inherited from ActionConfig
abortOnFail Boolean true inherited from ActionConfig

Append

The append action is a commonly used subset of modify. It is used to append data in a file at a particular location.

Property Type Default Description
path String handlebars template that (when rendered) is the path of the file to be modified
pattern RegExp, String regular expression used to match text where the append should happen
unique Boolean true whether identical entries should be removed
separator String new line the value that separates entries
template String handlebars template to be used for the entry
templateFile String path a file containing the template
data Object {} inherited from ActionConfig
abortOnFail Boolean true inherited from ActionConfig

Custom (Action Function)

The Add and Modify actions will take care of almost every case that plop is designed to handle. However, plop does offer custom action functions for the node/js guru. A custom action function is a function that is provided in the actions array.

  • Custom action functions are executed by plop with the same CustomAction function signature.
  • Plop will wait for the custom action to complete before executing the next action.
  • The function must let plop known whatโ€™s happening through the return value. If you return a Promise, we wonโ€™t start other actions until the promise resolves. If you return a message (String), we know that the action is done and weโ€™ll report the message in the status of the action.
  • A custom action fails if the promise is rejected, or the function throws an Exception

See the example plopfile for a sample synchronous custom action.

Comments

Comment lines can be added to the actions array by adding a string in place of an action config object. Comments are printed to the screen when plop comes to them and have no functionality of their own.

Built-In Helpers

There are a few helpers that I have found useful enough to include with plop. They are mostly case modifiers, but here is the complete list.

Case Modifiers

  • camelCase: changeFormatToThis
  • snakeCase: change_format_to_this
  • dashCase/kebabCase: change-format-to-this
  • dotCase: change.format.to.this
  • pathCase: change/format/to/this
  • properCase/pascalCase: ChangeFormatToThis
  • lowerCase: change format to this
  • sentenceCase: Change format to this,
  • constantCase: CHANGE_FORMAT_TO_THIS
  • titleCase: Change Format To This

Other Helpers

  • pkg: look up a property from a package.json file in the same folder as the plopfile.

Taking it Further

There is not a lot needed to get up and running on some basic generators. However, if you want to take your plop-fu further, read on young padawan.

Using a Dynamic Actions Array

Alternatively, the actions property of the GeneratorConfig can itself be a function that takes the answers data as a parameter and returns the actions array.

This allows you to adapt the actions array based on provided answers:

export default function (plop) {
	plop.setGenerator('test', {
		prompts: [{
			type: 'confirm',
			name: 'wantTacos',
			message: 'Do you want tacos?'
		}],
		actions: function(data) {
			var actions = [];

			if(data.wantTacos) {
				actions.push({
					type: 'add',
					path: 'folder/{{dashCase name}}.txt',
					templateFile: 'templates/tacos.txt'
				});
			} else {
				actions.push({
					type: 'add',
					path: 'folder/{{dashCase name}}.txt',
					templateFile: 'templates/burritos.txt'
				});
			}

			return actions;
		}
	});
};

3rd Party Prompt Bypass

If you have written an inquirer prompt plugin and want to support plop's bypass functionality, the process is pretty simple. The plugin object that your prompt exports should have a bypass function. This bypass function will be run by plop with the user's input as the first parameter and the prompt config object as the second parameter. The value that this function returns will be added to the answer data object for that prompt.

// My confirmation inquirer plugin
export default MyConfirmPluginConstructor;
function MyConfirmPluginConstructor() {
	// ...your main plugin code
	this.bypass = (rawValue, promptConfig) => {
		const lowerVal = rawValue.toString().toLowerCase();
		const trueValues = ['t', 'true', 'y', 'yes'];
		const falseValues = ['f', 'false', 'n', 'no'];
		if (trueValues.includes(lowerVal)) return true;
		if (falseValues.includes(lowerVal)) return false;
		throw Error(`"${rawValue}" is not a valid ${promptConfig.type} value`);
	};
	return this;
}

For the above example, the bypass function takes the user's text input and turns it into a Boolean value that will be used as the prompt answer data.

Adding Bypass Support to Your Plopfile

If the 3rd party prompt plugin you are using does not support bypass by default, you can add the bypass function above to your prompt's config object and plop will use it for handling bypass data for that prompt.

Wrapping Plop

Plop provides a lot of powerful functionality "for free". This utility is so powerful, in fact, that you can even wrap plop into your own CLI project. To do so, you only need a plopfile.js, a package.json, and a template to reference.

Your index.js file should look like the following:

#!/usr/bin/env node
import path from "node:path";
import minimist from "minimist";
import { Plop, run } from "plop";

const args = process.argv.slice(2);
const argv = minimist(args);

import { dirname } from "node:path";
import { fileURLToPath } from "node:url";

const __dirname = dirname(fileURLToPath(import.meta.url));

Plop.prepare({
  cwd: argv.cwd,
  configPath: path.join(__dirname, 'plopfile.js'),
  preload: argv.preload || [],
  completion: argv.completion
}, env => Plop.execute(env, run));

And your package.json should look like the following:

{
  "name": "create-your-name-app",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "start": "plop",
  },
  "bin": {
    "create-your-name-app": "./index.js"
  },
  "preferGlobal": true,
  "dependencies": {
    "plop": "^3.0.0"
  }
}

Setting the base destination path for the wrapper

When wrapping plop, you might want to have the destination path to be based on the cwd when running the wrapper. You can configure the dest base path like this:

Plop.prepare({
	// config like above
}, env => 
    Plop.execute(env, (env) => {
        const options = {
            ...env,
            dest: process.cwd() // this will make the destination path to be based on the cwd when calling the wrapper
        }
        return run(options, undefined, true)
    })
)

Adding General CLI Actions

Many CLI utilities handle some actions for you, such as running git init or npm install once the template is generated.

While we'd like to provide these actions, we also want to keep the core actions limited in scope. As such, we maintain a collection of libraries built to add these actions to Plop in our Awesome Plop list. There, you'll be able to find options for those actions, or even build your own and add it to the list!

Further Customization

While plop provides a great level of customization for CLI utility wrappers, there may be usecases where you simply want more control over the CLI experience while also utilizing the template generation code.

Luckily, node-plop may be for you! It's what the plop CLI itself is built upon and can be easily extended for other usage in the CLI. However, be warned, documentation is not quite as fleshed out for integration with node-plop. That is to say Thar be dragons.

We note lackluster documentation on node-plop integration not as a point of pride, but rather a word of warning. If you'd like to contribute documentation to the project, please do so! We always welcome and encourage contributions!

plop's People

Contributors

0xflotus avatar alchar avatar aleclarson avatar amwmedia avatar armand1m avatar benallfree avatar bradgarropy avatar calcaide avatar crutchcorn avatar cspotcode avatar dabstractor avatar dependabot[bot] avatar greenkeeper[bot] avatar jacksteamdev avatar jednano avatar jquense avatar justmaier avatar knikolov-nuvolo avatar macrozone avatar nicoespeon avatar panstav avatar pike avatar robinknipe avatar shubhamzanwar avatar thibodeaujf avatar tkvw avatar vinicius0026 avatar williamdclt avatar zhouhaoyiu avatar zonzujiro avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

plop's Issues

Built-in pluralize helper support

I just discovered Plop and was amazed by how easy it is to use! Kudos to you sir!

I would like to ask if there are plans to add support for a built-in pluralize helper so that I can emulate what Rails does when I run the rails generate scaffold command, to generate certain file/folder names contain the plural version of the input I pass into my plop generator.

If you are open to that idea I can make a PR for it ๐Ÿ˜„

Is there a way to read the users input in validate

I have a select that purpose a folder list.

In the next step, I need to validated to ask him to type a folder name.

I will have to read the value of the folder list choice in order to write the validate function.

Is that possible ? to read the user input ? I've looked everywhere in the documentation it's like missing.

Run plop from Node

I'm on a team that uses a somewhat complex plopfile and I'd like to have a quick way to verify that it is working the way we want. I would love to be able to require('plop') and automatically generate some components with defaults.

The interface for my script might look something like this:

var plop = require('plop');
var path = require('path');

var exampleDir = path.resolve(__dirname, 'example');

plop.runGenerator('widget', {
  // Use the same variables as the prompts require
  path: path.resolve(exampleDir, 'widgets'),
  name: 'my-widget',
  animal: 'dog'
});

plop.runGenerator('complexWidget', {
  path: path.resolve(exampleDir, 'complex', 'widgets'),
  name: 'my-complex-widget',
  animal: 'hippo'
});

@kentcdodds this might be a good way to verify your plopfile settings, as an alternative to #7.

Call generator within another generator

Let's say I have the following generators:

  • module
  • model
  • view
  • controller

I want the module generator to internally call model, view and controller generators.

Is there an API method to achieve this?

Cheers.

Dynamic templateFile

In the prompts, I have

{
  type: 'list',
  name: 'type',
  message: 'Type of the saga',
  choices: [{
    name: 'non-blockingly watcher',
    value: 'nonBlockinglyWatcher'
  }]
}

and in the actions

{
  type: 'add',
  path: 'common/sagas/assets/index.js',
  templateFile: 'generators/sagas/{{ type }}/index.js'
}

But when I run the generator, I get an ENOENT:

? [SAGA] Type of the saga non-blockingly watcher
[FAILED] add PROJECT/common/sagas/assets/index.js ENOENT:
  no such file or directory, open 'PROJECT/generators/sagas/{{ type }}/index.js'

i.e. {{ type }} will not be replaced.

Question

I was wondering: what terminal emulator is running in your screenshot gif in the readme?

Thanks

Customize the starting prompt

The starting prompt says: "[PLOP] Please choose a generator. (Use arrow keys)" each time when I run plop, is it possible to customize it to some other message?

Generate multiple files using * (glob)

This is a feature request, to generate multiple files in one action. Something like generate src/modules/{{lowerCase name}}/*.js from templates in plop-templates/modules/*.js using the asterisk / glob (*).

e.g plop-templates folder

plop-templates/modules/index.js
plop-templates/modules/parser.js
plop-templates/modules/utils.js
plop-templates/modules/helpers.js
plop-templates/modules/others.js
plop-templates/modules/anothers.js

And then in plopfile.js:

...
prompts: [
      {
        type: 'input',
        name: 'name',
        message: 'What is the module name?',
      },
    ],
    actions: [
      {
        type: 'add',
        path: 'src/modules/{{lowerCase name}}/*.js', // <--- here are the globs
        templateFile: 'plop-templates/modules/*.js', // <--- globs
      }
]
...

and command plop module foo will generate:

src/modules/foo/index.js
src/modules/foo/parser.js
src/modules/foo/utils.js
src/modules/foo/helpers.js
src/modules/foo/others.js
src/modules/foo/anothers.js

Your great works are much appreciated. Cheers โ˜•

Parsing a react.js template, and it gives errors on double quotes.

render() {
    return (
      <div style={{ height: '100%', width: '100%' }} />
    );
}

It gives error: Expecting 'CLOSE_RAW_BLOCK', 'CLOSE', 'CLOSE_UNESCAPED', 'STRING', 'NUMBER', 'BOOLEAN', 'OPEN_SEXPR', ' CLOSE_SEXPR', 'ID', 'DATA', got 'INVALID'

In general, this is a piece of my template which is react.js file, so that double quotes are useful to react. In this case, how would I parse that?

Totes amaze!

Just a quick line to say thank you for creating plop. Why haven't I heard about it before?

I'm using it to create assets for a HAPI server, and although it took a couple of hours to get it in place, it's going to save so much time.

Windows 10 can't navigate

Hi, I'm having a trouble to use arrow keys to navigate. I've tried cmd and git bash, as well as the webstorm terminal.

I'm testing it as a part of the react-boilerplate. This is the pre-anniversary win10.

Inquirer plugin crashes with plop 1.6.0

Looks like there is some mismatch in plop, inquirer, around the use of promises:

Offending code:
plop/node_modules/inquirer/lib/ui/prompt.js:79:50

PromptUI.prototype.fetchAnswer = function (question) {
  var Prompt = this.prompts[question.type];
  var prompt = new Prompt(question, this.rl, this.answers);
  return rx.Observable.defer(function () {
// This next lines fails as prompt.run() doesn't seem to return a promise
    return rx.Observable.fromPromise(prompt.run().then(function (answer) {
      return {name: question.name, answer: answer};
    }));
  });
};

Log:

(Use "/" key to search this directory)[ERROR] Cannot read property 'then' of undefined TypeError: Cannot read property 'then' of undefined
    at /Users/ppodil/Projects/react-mobx-seed/node_modules/plop/node_modules/inquirer/lib/ui/prompt.js:79:50
    at tryCatcher (/Users/ppodil/Projects/react-mobx-seed/node_modules/rx/dist/rx.js:63:31)
    at Defer.subscribeCore (/Users/ppodil/Projects/react-mobx-seed/node_modules/rx/dist/rx.js:2536:37)
    at Defer.tryCatcher (/Users/ppodil/Projects/react-mobx-seed/node_modules/rx/dist/rx.js:63:31)
    at setDisposable (/Users/ppodil/Projects/react-mobx-seed/node_modules/rx/dist/rx.js:2082:46)
    at Defer.Rx.ObservableBase.ObservableBase._subscribe (/Users/ppodil/Projects/react-mobx-seed/node_modules/rx/dist/rx.js:2097:9)
    at Defer.Rx.Observable.observableProto.subscribe.observableProto.forEach (/Users/ppodil/Projects/react-mobx-seed/node_modules/rx/dist/rx.js:2034:19)
    at MergeObserver.handleSubscribe (/Users/ppodil/Projects/react-mobx-seed/node_modules/rx/dist/rx.js:3525:28)
    at MergeObserver.next (/Users/ppodil/Projects/react-mobx-seed/node_modules/rx/dist/rx.js:3531:14)
    at MergeObserver.Rx.internals.AbstractObserver.AbstractObserver.onNext (/Users/ppodil/Projects/react-mobx-seed/node_modules/rx/dist/rx.js:1762:31)


May be create a more "shareable" ecosystem for plop?

I just met plop. And I am in love with it.

Why I choose Plop over Yeoman?
Because it is composable, I can set a project by calling some third party plops (#20) and also add my owns, right on this way:

module.exports = function (plop) {
  require('plop-generator-react')(plop);
  require('plop-generator-redux')(plop);

  // Custom generators here...
};

So my proposal is: why not creating a website for Plop? And may be list the community created Plops?

Cheers :).

inquirer-recursive doesn't work with [email protected] installed by plop

Hey, I just tried plop with inquirer-recursive and each time plop comes to prompt with recursive question it fails with error given below.

I resolved the issue by updating [email protected] to [email protected]. At the moment inquirer-recursive works properly and nothing have broke so it's not an issue for me, but some other people may be misguided by https://github.com/amwmedia/plop/blob/master/inquirer-prompts.md.
I will suggest to:

  • place proper information in documentation that inquirer-recursive may need to upgrade inquirer to newer version
  • or consider upgrading plop's inquirer dependency
  • or maybe there is an issue with node 6.5.0 compatibility (this will be an issue for inquirer of course)

plop: 1.7.3
OS: Windows 10
Node: 6.5.0

Receivied error:

[ERROR] inquirer.prompt(...).then is not a function TypeError: inquirer.prompt(...).then is not a function
at Prompt.askForLoop (@project_folder@\node_modules\inquirer-recursive\index.js:27:8)
at Prompt._run (@project_folder@\node_modules\inquirer-recursive\index.js:46:10)
at Prompt.run (@project_folder@\node_modules\inquirer\lib\prompts\base.js:55:8)
at @Roaming_folder@\nvm\v6.5.0\node_modules\plop\node_modules\inquirer\lib\ui\prompt.js:79:45
at tryCatcher (@Roaming_folder@\nvm\v6.5.0\node_modules\plop\node_modules\rx\dist\rx.js:63:31)
at Defer.subscribeCore (@Roaming_folder@\nvm\v6.5.0\node_modules\plop\node_modules\rx\dist\rx.js:2536:37)
at Defer.tryCatcher (@Roaming_folder@\nvm\v6.5.0\node_modules\plop\node_modules\rx\dist\rx.js:63:31)
at setDisposable (@Roaming_folder@\nvm\v6.5.0\node_modules\plop\node_modules\rx\dist\rx.js:2082:46)
at Defer.Rx.ObservableBase.ObservableBase._subscribe (@Roaming_folder@\nvm\v6.5.0\node_modules\plop\node_modules\rx\dist\rx.js:2097:9)
at Defer.Rx.Observable.observableProto.subscribe.observableProto.forEach (@Roaming_folder@\nvm\v6.5.0\node_modules\plop\node_modules\rx\dist\rx.js:2034:19)

Extract template language and data gathering

I think that it'd make this utility more powerful if both inquirer and handlebars integration were extracted to plugins to make this utility pluggable. That would allow me to provide my own template compiler and data gatherer. It would also allow for customizing these things without having to make plop expose APIs for these things explicitly. Thoughts?

file generated relative to current directory

I'm new to plop.

I followed the advice of putting the plopfile.js file at the root of my project.

When I ran plop the first time, I ran it from a subdirectory of the project. I expected it to create the files relative to the current subdirectory. It didn't. It created them at the root of the project.

Is there an easy way to get plop to generate the files in the current subdirectory without asking for it? I finally used plop.addHelper('cwd', (p) => process.cwd()); and then prefixed all of my path strings with {{cwd}}/.

Add PWD option for `path`

Maybe this is possible but after looking at the docs and some of the code I don't think so. I want to be able to navigate to a folder and generate some files there rather than having the path be relative to the plopfile.js file, I'd rather have the path be relative to where the command is executed. Right now, I can accomplish this by generating the path like this:

function getPath(subPath) {
  const plopPath = process.cwd().substring(plop.getPlopfilePath().length + 1)
  return path.join(plopPath, subPath)
}

But I'd much rather not have to do that and do this instead:

{
  type: 'add',
  path: '{{name}}.js',
  usePWDForPath: true,
  // ...etc
}

An important note here is that the templateFile should remain as it is and should not be relative to the PWD.

What do you think?

Pass custom json to actions

Is there an easy way to pass custom json objects to actions?
The scenario: I'd like to define a metadata json structure and pass it to generators to populate files (and templates) accordingly.
I'd also need to let data flow (specifically, array items) from one generator to another.

Testing example?

While building these things, I'm finding that it would be really nice to be able to run some tests rather than having to test in manually. Do you think we could develop a good way to do that? Even if it's just some examples that would be handy. But being able to assert files were created and their contents without actually creating them would be really helpful.

Add `addPartial` method

I have a use case for this capability. I've tried requiring handlebars myself and adding the partial that way but that doesn't appear to register it with the instance being used by plop somehow because I get an error that my partial could not be found.

Skip prompt if name argument supplied

ie ( 'plop component myComp' ), given that I have a generator named 'component', I would like to skip the prompt for name if the their is a name argument passed in. I am able to access the argument via this.process.argv[3], but it seems I can't use 'prompts' property as a function, so I am unable to add any conditional logic here as I can with actions. Is there an easier way to do this?

Modify action for json files

My proposition is to add a modify action for JSON files, where you will provide callback instead of pattern and template. In callback you would get JSON.parsed object and you should also return POJsO which will be JSON.stringifyd and written to file.

Adding actions conditionally depending on previous answers

I haven't found it in the example file or in the readme so I'm not sure if that is already possible. If not, I would like to file a feature request here ;)

I am using plop to create a json configuration which can contain a selector property with an array as value which is freely expandable and can hold 1-n items. In my case it's a css selector. I have now created an action to add a new css selector:

{
    type: 'prompt',
    name: 'selector',
    message: 'Enter a css selector to add:',
}

What I am now looking for is to jump back to prompts[0] in case prompts[1] is answered with yes:

{
    type: 'confirm',
    name: 'addMore',
    message: 'Add another css selector?',
}

So what I would like to have is something like this:

[PLOP] Please choose a generator. cssconfig
[TEST] Enter a css selector to add: .foo
[TEST] Add another css selector? Yes
[TEST] Enter a css selector to add: .bar
[TEST] Add another css selector? Yes
[TEST] Enter a css selector to add: .foobar
[TEST] Add another css selector? No
[SUCCESS] add W:\test.js

The generated file should then contain something like:

{
  "selector": [".foo", ".bar", ".foobar"]
  [...]
}

Sure, in my example I could just allow to write several selectors comma separated but I can think of other usecases where you might want to have further actions based on previous selections.

So is it possible? If not, can it be (more or less) easily added as feature? (Probably it's more a inquirer issue than plop, isn't it?)

Is it possible to modify "data"?

Is it possible to modify the data or answers object after prompts?

Here is the general idea:

  • A generator asks the user to select one of the Mongoose Schema models from the directory - this part works.
  • I require the schema, then parse out the schema paths - this also works.
  • I'd like to modify the data object that is passed to my jade templates so that I can do something like: for each schema type, add an input field to the view.

global help menu

Really digging this repo @amwmedia The final paragraph of the README is spot on. Some thoughts:

Since I'm installing this globally, there are a few things that could be really handy...

  • plop help or plop --help
  • plog init to generate a simple plopfile.js in a directory. I'd envision this with some boilerplate like the addHelper and setGenerator.

If you have any opinions on how that would be implemented, I'd be happy to submit a pull request. Really excited to start using this though. Cheers!

"message" does not appear to be supporting 'typeof Function', contrary to docs.

According to the Inquirer docs, message can take either a String or a Function:

message: (String|Function) The question to print. If defined as a function,
the first parameter will be the current inquirer session answers.

So, I would expect the following question object to print the previous answers in the prompt:

{
      type: 'confirm',
      name: 'confirmCreateComponent',
      message: function (answers) {
        return (
          `Are you sure you would like to create the following component?
          name: <${answers.ComponentName} />
          type: ${answers.componentType}
          location: ${path.join(__dirname, answers.dir)}
          `
        );
      },

Instead, it prints the function body in the prompt:

? [COMPONENT] function (answers) {
    return (
      `Are you sure you would like to create the following component?
      name: <${answers.ComponentName} />
      type: ${answers.componentType}
      location: ${path.join(__dirname, answers.dir)}
      `
    );
  } (Y/n)

Am I misunderstanding the docs?

Document validate of setGenerator Prompts

Hello!

Partially, there is no documentation for the fields a prompt or action can have.
I only found that information by reading the example plop file.

Please document the validate property of prompts.

Forcing the creation of files

I'd like to be able to run a plopfile add action and overwrite the existing files. I've tried setting abortOnFail to false without any luck. I imagine I can delete the existing files in my custom action, but am curious if there is already a built-in way to do this. I couldn't find docs for abortOnFail.

Edit: this is specifically for use with the add function.

Support for one time configuration

If I want to give the user, for example, to choose which css preprocessor he preferred sass/less/stylus ( for the file extension ) and I need to save this somewhere for not asking him again every time, There is a way to do that?

Plop throwing SyntaxError from node-plop

Alright, so first off, I'm pretty stoked to be taking Plop for a spin. It seems super promising and can't wait to roll it into my workflow.

It seem as though node-plop is breaking the CLI tool at the moment. Plop is installed both as a global and a dev dependency for my project. However, Plop fails when I try invoking it. I've tested my own plopfile as well as pulled the one from the example dir and also found another in a gist which was tried. All of them yield the same issue.

Here is the stack trace for the issue:

~\AppData\Roaming\npm\node_modules\plop\node_modules\node-plop\lib\modules\node-plop.js:29
function nodePlop(plopfilePath = '', plopCfg = {}) {
                               ^

SyntaxError: Unexpected token =
    at exports.runInThisContext (vm.js:53:16)
    at Module._compile (module.js:373:25)
    at Object.Module._extensions..js (module.js:416:10)
    at Module.load (module.js:343:32)
    at Function.Module._load (module.js:300:12)
    at Module.require (module.js:353:17)
    at require (internal/module.js:12:17)
    at Object.<anonymous> (~\AppData\Roaming\npm\node_modules\plop\node_modules\node-plop\lib\index.js:5:17)
    at Module._compile (module.js:409:26)
    at Object.Module._extensions..js (module.js:416:10)

Add a when property for actions

Add the ability to pass in a 'when' into the action object so that it only triggers if the condition passes similar to the 'when' in prompts

Replace existing files

Is it possible to force the replacement of existing files?
That's required when running the generator after changes (example: template updated)

Is your company using plop?

Hey there ploppers (ok, that's terrible), I'd like to add a list of companies that are using plop to the plopjs.com website. So if your company uses plop and would like to be included on the list, please leave a comment below (or email) with your company's name and website address.

Thanks all!

Autorunning generators

I am using plop kind of a time now, it saves a lot of times, so thanks for it!

Now I have tried to autoload generators, when scaffolding a project, but I couldnt figure out how to do it. Is it possible, did I missed something? I want to run it on my postinstall, so a command like plop page --name=index would make sense.

global plopfile?

how would you suggest creating a global plopfile that can be used across projects?

Enhance error message returns

Something that came up when I was plopping files:

It being so late, haha, I was trying to reference the pascalCase helper within handlebars with a literal PascalCase.

Instead of throwing the error Missing helper "PascalCase" from the message prop within the error object instead the only error output printed in the console was [FAILED] undefined undefined undefined

Maybe we include the error message in the fail by default and use a falsy to check for a line.type to determine if we should output the additional props?

plopping projects - request for input

I'm going to be soon introducing the ability to plop an entire project codebase and I want to throw out the approach I'm currently considering for feedback. My plan is to enable a syntax something like the following.

$ plop --project http://github.com/some/repo --destination ./some-repo
or shorthand
$ plop -p http://github.com/some/repo -d ./some-repo

this command would trigger the following...

  • inquirer prompts to request the destination directory (if --destination was not provided)
  • git clone of the repo
  • locate a file by the name of plopinit.js. This would use standard plopfile syntax to setup helpers, partials, and generators to be used for initialization
  • plop would be executed against this file. If only 1 generator is found, that generator would be executed (prompts and actions)
  • before any of the actions execute, all files in the codebase would be parsed for double curly braces {{ and each instance found would be replaced with an escaped version \{{. This replacement would also search for another set of tokens (thinking {{# #}} or {{% %}}). These would be replaced with standard curly braces.
  • plop would then run every file in the codebase through handlebars for processing
  • the escaped curly braces would then be reverted back to their previous state
  • the actions array would execute
  • the plopinit.js file would be removed

Benefits to This Approach

  • uses all of the same plop concepts, very low additional learning curve
  • allows the possibility of flexibility by defining multiple generators (more than one way to initialize the project) without requiring it (if there's only 1 generator, it's executed by default)
  • allows close to standard handlebar templates to be used for initialization while keeping any use of curly braces within the codebase safe from accidental replacement during initialization.

/cc @nicoespeon @kentcdodds @mxstbr @calcaide @Kevin-Wolf

Looking for questions or input

Be able to pass arguments to plop commands.

Hi !

I need to allow arguments to be passed to a plop command (to make it easier to automate certain tasks).
For a generator like this:

  plop.setGenerator('CreateUser', {
    description: 'create a json user file',
    prompts: [
      {
        type: 'input',
        name: 'name',
        message: 'Please enter a name'
      },
      {
        type: 'input',
        name: 'phoneNumber',
        message: 'Please enter a name'
      }
    ],
    actions: []
  });

It would be cool if we could execute this command plop CreateUser --name="PAG" --phoneNumber="0836656565"

I did a test to implement this behavior and it works great.
Are you guys interested in this feature?

Let `pkg` helper resolve property paths out of the box

It may be useful to reach deeper than the 1st level of the package object.

I suggest plop should come bundled with pkg anyone can override like this:

const get = require('lodash.get');
const pkg = require('./package.json');

plop.addHelper('pkg', propertyPath => get(pkg, propertyPath));

It may be taken right from lodash like I suggested above or it can be replicated in this project if you don't want more dependencies.

Is plop using node-plop?

@amwmedia As you said in issue #20 (if I understood well) plop is using node-plop internally, right?
Supossing that, I can see that plop is not requesting node-plop, in fact, the files are duplicated, please correct me if I am worng. That confuses me a bit, actually, that's why I didn't realise that plop is using node-plop.

In case that my assumption is correct, what do you think about using node-plop as a module?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.