Operations
Operations perform actions and possibly update the context with information that can be used in the next step. Most operations take their arguments in two forms. Either as plain values like renderTemplate('This is the template string')
or functions that get called with the current context
and return the value, e.g. renderTemplate(context => \
This is a dynamic template for ${context.name}`)`. All functions can also be asynchronous.
prompt
prompt(options|context => options)
takes a list of questions using inquirer.js and updates the context with results. 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, generator, prompt } from '@feathershq/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 = (context: Context) =>
generator(context).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
}
])
)
import { PinionContext, generator, prompt } from '@feathershq/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 = (context: Context) =>
generator(context).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
}
])
)
import { PinionContext, generator, prompt } from '@feathershq/pinion'
export const generate = (context) =>
generator(context).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
}
])
)
import { PinionContext, generator, prompt } from '@feathershq/pinion'
export const generate = (context) =>
generator(context).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
}
])
)
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,
generator,
prompt,
renderTemplate,
toFile
} from '@feathershq/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 = (context: Context) =>
generator(context)
.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')))
import {
PinionContext,
generator,
prompt,
renderTemplate,
toFile
} from '@feathershq/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 = (context: Context) =>
generator(context)
.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')))
import {
PinionContext,
generator,
prompt,
renderTemplate,
toFile
} from '@feathershq/pinion'
export const template = ({ name }) => `# ${name}
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context) =>
generator(context)
.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')))
import {
PinionContext,
generator,
prompt,
renderTemplate,
toFile
} from '@feathershq/pinion'
export const template = ({ name }) => `# ${name}
This is a readme generated by Pinion
Copyright (c) ${new Date().getFullYear()}
`
export const generate = (context) =>
generator(context)
.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 operation when it returns true.
import { PinionContext, generator, prompt } from '@feathershq/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 = (context: Context) =>
generator(context)
.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')
)
)
)
import { PinionContext, generator, prompt } from '@feathershq/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 = (context: Context) =>
generator(context)
.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')
)
)
)
import { PinionContext, generator, prompt } from '@feathershq/pinion'
export const generate = (context) =>
generator(context)
.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(
({ name }) => name === 'David',
renderTemplate(
"I'm afraid I can't do that Dave",
toFile('greeting.md')
)
)
)
import { PinionContext, generator, prompt } from '@feathershq/pinion'
export const generate = (context) =>
generator(context)
.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(
({ 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 ad the beginning of the fileappend()
inject at the end of the file
import {
PinionContext,
inject,
before,
prepend,
toFile
} from '@feathershq/pinion'
export const generate = (context: PinionContext) =>
generator(context)
.then(
inject(
'Injected before copyright notice',
before('Copyright (c)'),
toFile('readme.md')
)
)
.then(inject('Appended hello world', append(), toFile('readme.md')))
import {
PinionContext,
inject,
before,
prepend,
toFile
} from '@feathershq/pinion'
export const generate = (context: PinionContext) =>
generator(context)
.then(
inject(
'Injected before copyright notice',
before('Copyright (c)'),
toFile('readme.md')
)
)
.then(inject('Appended hello world', append(), toFile('readme.md')))
import {
PinionContext,
inject,
before,
prepend,
toFile
} from '@feathershq/pinion'
export const generate = (context) =>
generator(context)
.then(
inject(
'Injected before copyright notice',
before('Copyright (c)'),
toFile('readme.md')
)
)
.then(inject('Appended hello world', append(), toFile('readme.md')))
import {
PinionContext,
inject,
before,
prepend,
toFile
} from '@feathershq/pinion'
export const generate = (context) =>
generator(context)
.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. writeOptions
can be { force: true }
to skip prompting if an an existing file should be overwritten.
import {
PinionContext,
copyFiles,
fromFile,
toFile
} from '@feathershq/pinion'
export const generate = (context: PinionContext) =>
generator(context).then(
copyFiles(fromFile(__dirname, 'static'), toFile('.'))
)
import {
PinionContext,
copyFiles,
fromFile,
toFile
} from '@feathershq/pinion'
export const generate = (context: PinionContext) =>
generator(context).then(
copyFiles(fromFile(__dirname, 'static'), toFile('.'))
)
import {
PinionContext,
copyFiles,
fromFile,
toFile
} from '@feathershq/pinion'
export const generate = (context) =>
generator(context).then(
copyFiles(fromFile(__dirname, 'static'), toFile('.'))
)
import {
PinionContext,
copyFiles,
fromFile,
toFile
} from '@feathershq/pinion'
export const generate = (context) =>
generator(context).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 '@feathershq/pinion'
export const generate = (context: PinionContext) =>
generator(context).then(
writeJSON({ description: 'Something' }, toFile('package.json'))
)
import { PinionContext, writeJSON, toFile } from '@feathershq/pinion'
export const generate = (context: PinionContext) =>
generator(context).then(
writeJSON({ description: 'Something' }, toFile('package.json'))
)
import { PinionContext, writeJSON, toFile } from '@feathershq/pinion'
export const generate = (context) =>
generator(context).then(
writeJSON({ description: 'Something' }, toFile('package.json'))
)
import { PinionContext, writeJSON, toFile } from '@feathershq/pinion'
export const generate = (context) =>
generator(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 '@feathershq/pinion'
// The main types of your generator
export interface Context extends PinionContext {
package: PackageJson
}
export const generate = (context: Context) =>
generator(context)
// 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')
)
)
import { PackageJson } from 'type-fest'
import {
PinionContext,
writeJSON,
readJSON,
fromFile,
toFile
} from '@feathershq/pinion'
// The main types of your generator
export interface Context extends PinionContext {
package: PackageJson
}
export const generate = (context: Context) =>
generator(context)
// 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')
)
)
import { PackageJson } from 'type-fest'
import {
PinionContext,
writeJSON,
readJSON,
fromFile,
toFile
} from '@feathershq/pinion'
export const generate = (context) =>
generator(context)
// 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(
({ package }) => ({
...package,
description: 'Overwritten description'
}),
toFile('package.json')
)
)
import { PackageJson } from 'type-fest'
import {
PinionContext,
writeJSON,
readJSON,
fromFile,
toFile
} from '@feathershq/pinion'
export const generate = (context) =>
generator(context)
// 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(
({ package }) => ({
...package,
description: 'Overwritten description'
}),
toFile('package.json')
)
)
install
install(dependencies[]|context => dependencies[], dev, packageManager = 'npm')
dependencies using the npm
or yarn
package manager.
import { PinionContext, generator, install } from '@feathershq/pinion'
export const generate = (context: Context) =>
generator(context)
.then(install(['@feathersjs/feathers']))
.then(install(['ts-node'], true))
import { PinionContext, generator, install } from '@feathershq/pinion'
export const generate = (context: Context) =>
generator(context)
.then(install(['@feathersjs/feathers']))
.then(install(['ts-node'], true))
import { PinionContext, generator, install } from '@feathershq/pinion'
export const generate = (context) =>
generator(context)
.then(install(['@feathersjs/feathers']))
.then(install(['ts-node'], true))
import { PinionContext, generator, install } from '@feathershq/pinion'
export const generate = (context) =>
generator(context)
.then(install(['@feathersjs/feathers']))
.then(install(['ts-node'], true))
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,
generator,
runGenerators
} from '@feathershq/pinion'
export const generate = (context: Context) =>
generator(context).then(runGenerators(__dirname, 'templates'))
import {
PinionContext,
generator,
runGenerators
} from '@feathershq/pinion'
export const generate = (context: Context) =>
generator(context).then(runGenerators(__dirname, 'templates'))
import {
PinionContext,
generator,
runGenerators
} from '@feathershq/pinion'
export const generate = (context) =>
generator(context).then(runGenerators(__dirname, 'templates'))
import {
PinionContext,
generator,
runGenerators
} from '@feathershq/pinion'
export const generate = (context) =>
generator(context).then(runGenerators(__dirname, 'templates'))