Skip to content

User Input

This section shows how to handle user input through prompts and CLI arguments.

Prompting

As we have seen before, the prompt task allows to ask questions from the command line using the Inquirer library. It gets passed a list of Inquirer questions that can also be put together based on the current context. This can be used to e.g. ask questions conditionally or skip them if the value is already passed (e.g. in an automated test):

ts
import {
  PinionContext,
  prompt,
  renderTemplate,
  toFile
} from '@featherscloud/pinion'

// Setup the Context to receive user input
interface Context extends PinionContext {
  name: string
  description: string
}

// The template uses Context variables.
function readme({ name, description }: Context) {
  return `# ${name}

> ${description}

This is a readme generated by Pinion

Copyright (c) ${new Date().getFullYear()}
`
}

export function generate(init: Context) {
  return Promise.resolve(init)
    // Ask prompts (using Inquirer)
    .then(
      prompt((context) => {
        // Only ask question if `name` or `description` are not passed
        return {
          name: {
            type: 'input',
            message: 'What is the name of your app?',
            when: !context.name
          },
          description: {
            type: 'input',
            message: 'Write a short description',
            when: !context.description
          }
        }
      })
    )
    // Render the template
    .then(renderTemplate(readme, toFile('readme.md')))
}
sh
npx pinion generators/readme.tpl.ts

CLI arguments

The CLI arguments passed to a generator are available as context.argv and include any arguments after the generator filename. For example when running something like

sh
npx pinion generators/readme.ts --description hello something

context.argv would be ['--description', 'hello', 'something']. You can parse the list yourself and Pinion also ships with a commander task that uses the commander module to create command-line interfaces. The following example adds --name and --description command line arguments and skips prompting the user if it is passed.

ts
import {
  Command,
  PinionContext,
  commander,
  prompt,
  renderTemplate,
  toFile
} from '@featherscloud/pinion'

const program = new Command()
  .description('A readme generator')
  .option('-n, --name <name>', 'Name of your app')
  .option('-d, --description <description>', 'The description for your app')

// Setup the Context to receive user input
interface Context extends PinionContext {
  name: string
  description: string
}

// The template uses Context variables.
function readme({ name, description }: Context) {
  return `# ${name}

> ${description}

This is a readme generated by Pinion

Copyright (c) ${new Date().getFullYear()}
`
}

export function generate(init: Context) {
  return Promise.resolve(init)
    // Parse command line arguments
    .then(commander(program))
    // Ask prompts (using Inquirer)
    .then(
      prompt((context) => {
        // Only ask question if `name` or `description` are not passed
        return {
          name: {
            type: 'input',
            message: 'What is the name of your app?',
            when: !context.name
          },
          description: {
            type: 'input',
            message: 'Write a short description',
            when: !context.description
          }
        }
      })
    )
    // Render the template
    .then(renderTemplate(readme, toFile('readme.md')))
}
sh
npx pinion generators/readme.tpl.ts --name Test --description "The description from the command line"

What's next

This are all the steps necessary to build a generator. Head over to the API documentation for a full list of all available tasks. The composability chapter described how to call other generators, how to write tests and how they can be embedded in your own tools.