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

Adding Typescript support #431

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,10 @@ Because [context switching is expensive](https://www.petrikainulainen.net/softwa
# 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
## TypeScript Support

`plop` bundles TypeScript declarations. Whether or not you write your plopfile in TypeScript, many editors will offer code assistance via these declarations.
`plop` bundles TypeScript declarations and supports `plopfile.{c|m}ts` out of the box, run `plop --init-ts` to create a `plopfile.ts` file.
Whether or not you write your plopfile in TypeScript, many editors will offer code assistance via these declarations.

```javascript
// plopfile.ts
Expand Down
1 change: 1 addition & 0 deletions packages/node-plop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"handlebars": "^4.7.8",
"inquirer": "^9.2.12",
"isbinaryfile": "^5.0.0",
"jiti": "^1.21.0",
"lodash.get": "^4.4.2",
"mkdirp": "^3.0.1",
"resolve": "^1.22.8",
Expand Down
9 changes: 3 additions & 6 deletions packages/node-plop/src/node-plop.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import resolve from "resolve";

import bakedInHelpers from "./baked-in-helpers.js";
import generatorRunner from "./generator-runner.js";
import tryRequire from "./try-require.js";

import { createRequire } from "node:module";
import { pathToFileURL } from "url";
const require = createRequire(import.meta.url);

async function nodePlop(plopfilePath = "", plopCfg = {}) {
Expand Down Expand Up @@ -262,11 +262,8 @@ async function nodePlop(plopfilePath = "", plopCfg = {}) {
loadPackageJson();

const joinedPath = path.join(plopfilePath, plopFileName);
const plopFileExport = await import(pathToFileURL(joinedPath).href);
const plop =
prisis marked this conversation as resolved.
Show resolved Hide resolved
typeof plopFileExport === "function"
? plopFileExport
: plopFileExport.default;

const plop = tryRequire(joinedPath, plopfilePath);

await plop(plopfileApi, plopCfg);
} else {
Expand Down
21 changes: 21 additions & 0 deletions packages/node-plop/src/try-require.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import jiti from "jiti";

/**
* Try to require a module, if it fails, return the errorReturn value
*
* @param {string} id - The module id to require
* @param {string} rootDirectory - The root directory to require the module from
* @param {any} errorReturn - The value to return if the require fails
*
* @returns {any} The required module or the errorReturn value
*/
const tryRequire = (id, rootDirectory, errorReturn) => {
const _require = jiti(rootDirectory, {
esmResolve: true,
interopDefault: true,
});

return _require(id);
};

export default tryRequire;
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import nodePlop from "../../src/index.js";
import { setupMockPath } from "../helpers/path.js";
const { clean, mockPath } = setupMockPath(import.meta.url);

const plopfilePath = path.join(mockPath, "plopfile.js");

describe("load-assets-from-plopfile", function () {
afterEach(clean);

const plopfilePath = path.join(mockPath, "plopfile.js");

/////
// test the various ways to import all or part of a plopfile
//
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
add test
name: {{name}}
upperCase: {{constantCase name}}
{{> salutation}}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
the modify option in the test plop should add lines below for each run.
Use modify for things like adding script references to an HTML file.

-- APPEND ITEMS HERE --

+++++++++++++++++++++++++++++++++++++++

-- PREPEND ITEMS HERE --
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
this is prepended! ## replace name here ##: {{age}}
$1
115 changes: 115 additions & 0 deletions packages/node-plop/tests/typescript-plopfile/plopfile.cts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import fs from "fs";
import { join } from "path";

import { PlopCfg, NodePlopAPI } from "../../types";

export default function (plop: NodePlopAPI) {
///////
// helpers are passed through to handlebars and made
// available for use in the generator templates
//

// adds 4 dashes around some text (yes es6/es2015 is supported)
plop.setHelper("dashAround", function (text) {
return "---- " + text + " ----";
});
// plop.setHelper('dashAround', (text) => '---- ' + text + ' ----');

// formats an array of options like you would write
// it if you were speaking (one, two, and three)
plop.setHelper("wordJoin", function (words) {
return words.join(", ").replace(/(:?.*),/, "$1, and");
});

// greet the user using a partial
plop.setPartial(
"salutation",
"my name is {{ properCase name }} and I am {{ age }}.",
);

// setGenerator creates a generator that can be run with "plop generatorName"
plop.setGenerator("basic-add", {
description: "adds a file using a template",
prompts: [
{
type: "input",
name: "name",
message: "What is your name?",
validate: function (value) {
if (/.+/.test(value)) {
return true;
}
return "name is required";
},
},
{
type: "input",
name: "age",
message: "How old are you?",
validate: function (value) {
let digitsOnly = /\d+/;
if (digitsOnly.test(value)) {
return true;
}
return "Invalid age! Must be a number genius!";
},
},
],
actions: [
{
type: "add",
path: "src/{{dashCase name}}.txt",
templateFile: "plop-templates/add.txt",
abortOnFail: true,
},
{
type: "add",
path: "src/_{{constantCase name}}.txt",
template:
'test: {{pkg "name"}}\npropertyPathTest: {{pkg "config.nested[1]"}}\ninline template: {{name}}',
abortOnFail: true,
},
function customAction(answers) {
// move the current working directory to the plop file path
// this allows this action to work even when the generator is
// executed from inside a subdirectory

let plopFilePath = plop.getPlopfilePath();

// custom function can be synchronous or async (by returning a promise)
let copiedMsg = "hey {{name}}, I copied change-me.txt for you",
changeFile = "change-me.txt",
toPath = join(plopFilePath, "src", changeFile),
fromPath = join(plopFilePath, "plop-templates", changeFile);

// you can use plop.renderString to render templates
copiedMsg = plop.renderString(copiedMsg, answers);

if (fs.existsSync(toPath)) {
fs.unlinkSync(toPath);
}

fs.writeFileSync(toPath, fs.readFileSync(fromPath));
return copiedMsg;
},
{
type: "modify",
path: "src/change-me.txt",
pattern: /(-- APPEND ITEMS HERE --)/gi,
template: "$1\r\n{{name}}: {{age}}",
},
{
type: "modify",
path: "src/change-me.txt",
pattern: /(-- PREPEND ITEMS HERE --)/gi,
templateFile: "plop-templates/part.txt",
},
{
type: "modify",
path: "src/change-me.txt",
pattern: /## replace name here ##/gi,
template: "replaced => {{dashCase name}}",
},
],
});
}
115 changes: 115 additions & 0 deletions packages/node-plop/tests/typescript-plopfile/plopfile.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import fs from "fs";
import { join } from "path";

import { PlopCfg, NodePlopAPI } from "../../types";

export default function (plop: NodePlopAPI, config: Partial<PlopCfg> = {}) {
///////
// helpers are passed through to handlebars and made
// available for use in the generator templates
//

// adds 4 dashes around some text (yes es6/es2015 is supported)
plop.setHelper("dashAround", function (text) {
return "---- " + text + " ----";
});
// plop.setHelper('dashAround', (text) => '---- ' + text + ' ----');

// formats an array of options like you would write
// it if you were speaking (one, two, and three)
plop.setHelper("wordJoin", function (words) {
return words.join(", ").replace(/(:?.*),/, "$1, and");
});

// greet the user using a partial
plop.setPartial(
"salutation",
"my name is {{ properCase name }} and I am {{ age }}.",
);

// setGenerator creates a generator that can be run with "plop generatorName"
plop.setGenerator("basic-add", {
description: "adds a file using a template",
prompts: [
{
type: "input",
name: "name",
message: "What is your name?",
validate: function (value) {
if (/.+/.test(value)) {
return true;
}
return "name is required";
},
},
{
type: "input",
name: "age",
message: "How old are you?",
validate: function (value) {
let digitsOnly = /\d+/;
if (digitsOnly.test(value)) {
return true;
}
return "Invalid age! Must be a number genius!";
},
},
],
actions: [
{
type: "add",
path: "src/{{dashCase name}}.txt",
templateFile: "plop-templates/add.txt",
abortOnFail: true,
},
{
type: "add",
path: "src/_{{constantCase name}}.txt",
template:
'test: {{pkg "name"}}\npropertyPathTest: {{pkg "config.nested[1]"}}\ninline template: {{name}}',
abortOnFail: true,
},
function customAction(answers) {
// move the current working directory to the plop file path
// this allows this action to work even when the generator is
// executed from inside a subdirectory

let plopFilePath = plop.getPlopfilePath();

// custom function can be synchronous or async (by returning a promise)
let copiedMsg = "hey {{name}}, I copied change-me.txt for you",
changeFile = "change-me.txt",
toPath = join(plopFilePath, "src", changeFile),
fromPath = join(plopFilePath, "plop-templates", changeFile);

// you can use plop.renderString to render templates
copiedMsg = plop.renderString(copiedMsg, answers);

if (fs.existsSync(toPath)) {
fs.unlinkSync(toPath);
}

fs.writeFileSync(toPath, fs.readFileSync(fromPath));
return copiedMsg;
},
{
type: "modify",
path: "src/change-me.txt",
pattern: /(-- APPEND ITEMS HERE --)/gi,
template: "$1\r\n{{name}}: {{age}}",
},
{
type: "modify",
path: "src/change-me.txt",
pattern: /(-- PREPEND ITEMS HERE --)/gi,
templateFile: "plop-templates/part.txt",
},
{
type: "modify",
path: "src/change-me.txt",
pattern: /## replace name here ##/gi,
template: "replaced => {{dashCase name}}",
},
],
});
}
Loading