API
Pinion has a small API with only a few tasks necessary to build powerful generators and CLI tools. This page has an overview of all available tasks, helpers and types.
Tasks
Tasks perform actions and possibly update the context with information that can be used in the next step. Most tasks take their arguments in two forms. Either as plain values like renderTemplate('This is the template string')
or functions (or asynchronous functions) that get called with the current context
and return the value, e.g.
renderTemplate<Context>(
(context) => `This is a dynamic template for ${context.name}`
)
prompt
prompt(options|context => options)
takes a list of questions using inquirer.js and updates the context with the answers. A context callback can be used to only ask prompts conditionally (e.g. skipping when they have already been provided from the command line).
import { PinionContext, prompt } from '@featherscloud/pinion'
// The main types of your generator
export interface Context extends PinionContext {
// Add the types from prompts and command line arguments here
name: string
}
export const generate = (init: Context) =>
Promise.resolve(init).then(
prompt((context) => [
{
type: 'input',
name: 'name',
message: 'What is the name of your app?',
// Only show prompt if there is no name
when: !context.name
}
])
)
commander
commander(program|context => program)
parses the generator command line arguments using a commander program and adds them to the context.
import { PinionContext, commander, Command } from '../../src/index.js'
interface Context extends PinionContext {
name: string
}
const program = new Command()
.description('My awesome generator')
.option('-n, --name <name>', 'Name of your project')
export const generate = (init: Context) =>
Promise.resolve(init)
.then(commander(program))
.then((context) => {
console.log(context.name)
})
renderTemplate
renderTemplate(text|context => text, toFile, writeOptions)
renders a string to a target file. writeOptions
can be { force: true }
to skip prompting if an an existing file should be overwritten.
To put together file names dynamically, the toFile
helper can be used:
import {
PinionContext,
prompt,
renderTemplate,
toFile
} from '@featherscloud/pinion'
// The main types of your generator
export interface Context extends PinionContext {
// Add the types from prompts and command line arguments here
name: string
}
export const template = ({ name }: Context) =>
`# ${name}
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (init: Context) =>
Promise.resolve(init)
.then(
prompt((context) => [
{
type: 'input',
name: 'name',
message: 'What is the name of your app?',
// Only show prompt if there is no --name CLI argument
when: !context.name
}
])
)
.then(renderTemplate(template, toFile('readme.md')))
when
when(boolean|context => boolean, operation)
evaluates a condition and runs the task if it returns true.
import { PinionContext, prompt } from '@featherscloud/pinion'
// The main types of your generator
export interface Context extends PinionContext {
// Add the types from prompts and command line arguments here
name: string
}
export const generate = (init: Context) =>
Promise.resolve(init)
.then(
prompt((context) => [
{
type: 'input',
name: 'name',
message: 'What is the name of your app?',
// Only show prompt if there is no name
when: !context.name
}
])
)
.then(
when<Context>(
({ name }) => name === 'David',
renderTemplate("I'm afraid I can't do that Dave", toFile('greeting.md'))
)
)
inject
inject(text|context => text, location, toFile)
injects a template at a specific location into an existing file. The location functions can be used as follows:
before(text|context => text)
inject at the line beforetext
after(text|context => text)
inject at the line aftertext
prepend()
inject at the beginning of the fileappend()
inject at the end of the file
import {
PinionContext,
inject,
before,
prepend,
toFile
} from '@featherscloud/pinion'
export const generate = (init: PinionContext) =>
Promise.resolve(init)
.then(
inject(
'Injected before copyright notice',
before('Copyright (c)'),
toFile('readme.md')
)
)
.then(inject('Appended hello world', append(), toFile('readme.md')))
copyFiles
copyFiles(fromFile, toFile, options)
recursively copies all files from a location to a destination. It will prompt to overwrite if a file already exists. options
can be { force: true }
to skip prompting if an an existing file should be overwritten.
import {
PinionContext,
copyFiles,
fromFile,
toFile
} from '@featherscloud/pinion'
export const generate = (init: PinionContext) =>
Promise.resolve(init).then(
copyFiles(fromFile(__dirname, 'static'), toFile('.'))
)
writeJSON
writeJSON(data|context => data, toFile, writeOptions)
write JSON data to a file. writeOptions
can be { force: true }
to skip prompting if an an existing file should be overwritten.
import { PinionContext, writeJSON, toFile } from '@featherscloud/pinion'
export const generate = (init: PinionContext) =>
Promise.resolve(context).then(
writeJSON({ description: 'Something' }, toFile('package.json'))
)
mergeJSON
mergeJSON(data|context => data, toFile, writeOptions)
merges new data into an existing file.
readJSON
readJSON(fromFile, converter, fallback?)
loads a JSON file, parses it and extends the context
with the data returned by converter
.
import { PackageJson } from 'type-fest'
import {
PinionContext,
writeJSON,
readJSON,
fromFile,
toFile
} from '@featherscloud/pinion'
// The main types of your generator
export interface Context extends PinionContext {
package: PackageJson
}
export const generate = (init: Context) =>
Promise.resolve(init)
// Load package.json, fall back to an empty object if it doesn't exist
.then(readJSON(fromFile('package.json'), (package) => ({ package }), {}))
// Merge existing package.json and write an updated description
.then(
writeJSON<Context>(
({ package }) => ({
...package,
description: 'Overwritten description'
}),
toFile('package.json')
)
)
exec
exec(command|context => command, args|context => args)
runs a command with command line arguments in the current working directory. It will error if the command returns an error exit code.
import { PinionContext, install } from '@featherscloud/pinion'
export const generate = (init: Context) =>
generator(init).then(exec('npm', ['install', '@feathersjs/feathers']))
runGenerators
runGenerators((filePart|context => filePart)[])
will run all *.tpl.ts
or *.tpl.js
generators in the given path alphabetically in sequence, passing the current context.
import { PinionContext, runGenerators } from '@featherscloud/pinion'
export const generate = (init: Context) =>
Promise.resolve(init).then(runGenerators(__dirname, 'templates'))
Helpers
file
file(...fileParts[]|context => fileParts[])
points to a filename, usually within the current working directory.
toFile
toFile(...fileParts[]|context => fileParts[])
works like file but will create the file and all folders that don't exist yet.
fromFile
fromFile(...fileParts[]|context => fileParts[])
works like file but makes sure that the file already exists.