Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PlopJS 3 #293

Merged
merged 15 commits into from
Nov 27, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions .eslintrc.js → .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ module.exports = {
es2021: true,
node: true,
},
parserOptions: {
sourceType: "module",
"allowImportExportEverywhere": true
},
extends: ["plugin:prettier/recommended"],
rules: {
// https://github.com/plopjs/plop/issues/288
Expand Down
2 changes: 1 addition & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
npx --no-install lint-staged
97 changes: 50 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ $ npm install -g plop
```
### 3. Create a plopfile.js at the root of your project
``` javascript
module.exports = function (plop) {
export default function (plop) {
// create your generators here
plop.setGenerator('basics', {
description: 'this is a skeleton plopfile',
Expand All @@ -42,18 +42,18 @@ module.exports = function (plop) {
```

## Your First Plopfile
A plopfile starts its life as a lowly node module that exports a function which accepts the `plop` object as its first parameter.
A plopfile starts its life as a node module that exports a function which accepts the `plop` object as its first parameter.

``` javascript
module.exports = function (plop) {};
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.
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.

``` javascript
module.exports = function (plop) {
export default function (plop) {
// controller generator
plop.setGenerator('controller', {
description: 'application controller logic',
Expand Down Expand Up @@ -127,7 +127,7 @@ Because saving your team (or yourself) 5-15 minutes when creating every route, c

Because [context switching is expensive](https://www.petrikainulainen.net/software-development/processes/the-cost-of-context-switching/) and saving time is not the only [benefit to automating workflows](https://kentcdodds.com/blog/automation)

# Plopfile Api
# 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`](#setgenerator) but this section documents the other methods that you may also find useful in your plopfile.

## TypeScript Declarations
Expand All @@ -145,7 +145,7 @@ export default function (plop: NodePlopAPI) {

```javascript
// plopfile.js
module.exports = function (
export default function (
/** @type {import('plop').NodePlopAPI} */
plop
) {
Expand All @@ -158,7 +158,7 @@ These are the methods you will commonly use when creating a plopfile. Other meth

Method | Parameters | Returns | Description
------ | ---------- | ------- | -----------
[**setGenerator**](#setgenerator) | *String, [GeneratorConfig](#interface-generatorconfig)* | *[GeneratorConfig](#interface-generatorconfig)* | setup a generator
[**setGenerator**](#setgenerator) | *String, [GeneratorConfig](#interface-generatorconfig)* | *[PlopGenerator](#interface-plopgenerator)* | setup a generator
[**setHelper**](#sethelper) | *String, Function* | | setup handlebars helper
[**setPartial**](#setpartial) | *String, String* | | setup a handlebars partial
[**setActionType**](#setactiontype) | *String, [CustomAction](#functionsignature-custom-action)* | | register a custom action type
Expand All @@ -169,7 +169,7 @@ Method | Parameters | Returns | Description
`setHelper` directly corresponds to the handlebars method `registerHelper`. So if you are familiar with [handlebars helpers](https://handlebarsjs.com/guide/expressions.html#helpers), then you already know how this works.

``` javascript
module.exports = function (plop) {
export default function (plop) {
plop.setHelper('upperCase', function (text) {
return text.toUpperCase();
});
Expand All @@ -183,7 +183,7 @@ module.exports = function (plop) {
`setPartial` directly corresponds to the handlebars method `registerPartial`. So if you are familiar with [handlebars partials](https://handlebarsjs.com/guide/partials.html), then you already know how this works.

``` javascript
module.exports = function (plop) {
export default function (plop) {
plop.setPartial('myTitlePartial', '<h1>{{titleCase name}}</h1>');
// used in template as {{> myTitlePartial }}
};
Expand All @@ -200,8 +200,7 @@ Parameters | Type | Description
**plop** | *[PlopfileApi](#plopfile-api)* | The plop api for the plopfile where this action is being run

``` javascript
module.exports = function (plop) {

export default function (plop) {
plop.setActionType('doTheThing', function (answers, config, plop) {
// do something
doSomething(config.configProp);
Expand Down Expand Up @@ -241,12 +240,12 @@ module.exports = function (plop) {
[Inquirer](https://github.com/SBoudrias/Inquirer.js) 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](https://github.com/SBoudrias/Inquirer.js#inquirerregisterpromptname-prompt). Also check out the [plop community driven list of custom prompts](https://github.com/plopjs/plop/blob/master/inquirer-prompts.md).

``` javascript
const promptDirectory = require('inquirer-directory');
module.exports = function (plop) {
plop.setPrompt('directory', promptDirectory);
import autocompletePrompt from 'inquirer-autocomplete-prompt';
export default function (plop) {
plop.setPrompt('autocomplete', autocompletePrompt);
plop.setGenerator('test', {
prompts: [{
type: 'directory',
type: 'autocomplete',
...
}]
});
Expand All @@ -265,6 +264,14 @@ Property | Type | Default | Description

> If your list of actions needs to be dynamic, take a look at [using a dynamic actions array.](#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-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](#built-in-actions).

Expand Down Expand Up @@ -292,7 +299,7 @@ Method | Parameters | Returns | Description
**getActionType** | *String* | *[CustomAction](#functionsignature-custom-action)* | 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](#interface-generatorconfig)* | get the [GeneratorConfig](#interface-generatorconfig) by name
**getGenerator** | *String* | *[GeneratorConfig](#interface-generatorconfig)* | get the [PlopGenerator](#interface-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
Expand Down Expand Up @@ -407,7 +414,7 @@ Alternatively, the `actions` property of the [GeneratorConfig](#interface-genera
This allows you to adapt the actions array based on provided answers:

``` javascript
module.exports = function (plop) {
export default function (plop) {
plop.setGenerator('test', {
prompts: [{
type: 'confirm',
Expand Down Expand Up @@ -442,7 +449,7 @@ If you have written an inquirer prompt plugin and want to support plop's bypass

``` javascript
// My confirmation inquirer plugin
module.exports = MyConfirmPluginConstructor;
export default MyConfirmPluginConstructor;
function MyConfirmPluginConstructor() {
// ...your main plugin code
this.bypass = (rawValue, promptConfig) => {
Expand Down Expand Up @@ -470,32 +477,26 @@ Your `index.js` file should look like the following:

```javascript
#!/usr/bin/env node
const path = require('path');
import path from "node:path";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should narrow your engines support to node versions that support the node protocol if not changing this on build.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe I already have narrowed this properly, but will make sure to double-check :)

import minimist from "minimist";
import { Plop, run } from "plop";

const args = process.argv.slice(2);
const {Plop, run} = require('plop');
const argv = require('minimist')(args);
const argv = minimist(args);

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

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

Plop.prepare({
cwd: argv.cwd,
// In order for `plop` to always pick up the `plopfile.js` despite the CWD, you must use `__dirname`
configPath: path.join(__dirname, 'plopfile.js'),
require: argv.require,
preload: argv.preload || [],
completion: argv.completion
// This will merge the `plop` argv and the generator argv.
// This means that you don't need to use `--` anymore
}, env => run(env, undefined, true));
}, env => Plop.execute(env, run));
```

> Be aware that if you choose to use the `env => run(env, undefined, true))`, you may run into command merging issues
> when using generator arg passing.
>
> If you'd like to opt-out of this behavior and act like plop does (requiring `--` before passing named arguments to generators)
> simply replace the `env =>` arrow function with `run`:
>
>```javascript
>Plop.launch({}, run);
>```

And your `package.json` should look like the following:

```json
Expand All @@ -511,7 +512,7 @@ And your `package.json` should look like the following:
},
"preferGlobal": true,
"dependencies": {
"plop": "^2.6.0"
"plop": "^3.0.0"
}
}
```
Expand All @@ -521,15 +522,17 @@ And your `package.json` should look like the following:
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:

```javascript
Plop.launch({
Plop.prepare({
// config like above
}, 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)
})
}, 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
Expand Down
13 changes: 8 additions & 5 deletions bin/plop.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
#!/usr/bin/env node
const args = process.argv.slice(2);
const { Plop, run } = require("../src/plop");
const argv = require("minimist")(args);
import { Plop, run } from "../src/plop.js";
import minimist from "minimist";
const argv = minimist(args);

Plop.launch(
Plop.prepare(
{
cwd: argv.cwd,
preload: argv.preload || [],
configPath: argv.plopfile,
require: argv.require,
completion: argv.completion,
},
run
function (env) {
Plop.execute(env, run);
}
);
7 changes: 5 additions & 2 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
module.exports = {
import { resolve } from "node:path";

export default {
coverageThreshold: {
global: {
branches: 40,
Expand All @@ -7,5 +9,6 @@ module.exports = {
statements: 50,
},
},
projects: [require.resolve("./tests/config/jest.config.js")],
projects: [resolve("./tests/config/jest.config.js")],
transform: {},
};