SAO File

TIP

Make sure you have read the guide for creating generators first!

SAO file, i.e. saofile.js lies in the root directory of a generator, it's used to create a generator instance which defines how to generate a new project.

Prompts

Type: Prompt[] | (this: Generator) => Prompt[] | Promise<Prompt[]>

prompts is a list of questions you want the user to answer.

All prompt types in Inquirer.js are supported here. There are a few differences in the prompt options though:

when

The when property in each prompt can also be a string which will be evaluated in the context of answers.

For example:










 



prompts: [
  {
    name: 'useBundler',
    message: 'Do you want a bundler'
  },
  {
    name: 'bundler',
    type: 'list',
    choices: ['webpack', 'parcel', 'poi'],
    when: 'useBundler'
  }
]

Basically it's equivalent to when: answers => answers.useBundler.

store

  • Type: boolean
  • Default: false

This is a property only for SAO, it is used to store user inputs so that SAO can use stored value as default value the next time. Note that different version of a generator stores the inputs in different places.

default

When default is a string, you can use {propName} to access properties on Generator Instance. e.g. Use default: '{gitUser.name}' to set the default value to the name of the git user. If you want to disable interpolations here, use double back-slash: \\{gitUser.name}.

Actions

Type: Action[] | (this: Generator) => Action[] | Promise<Action[]>

actions is used to manipulate files. There're 4 kinds of actions which share following options:

  • type: Action type
  • when: Similar to prompts's when.

type: 'add'

Add files from template directory to target directory.

actions: [
  {
    type: 'add',
    files: '**',
    filters: {
      'foo.js': '!someAnswer'
    }
  }
]
  • files: One or more glob patterns, files are resolved relative to templateDir.
  • transform: Enable/Disable transformer.
    • Default: true
  • transformInclude: One or more glob patterns, transform specific files with the transformer.
  • transformExclude: One or more glob patterns, DON'T transform specific files with the transformer.
  • filters: Exclude some files from being added. It's an object whose key is a glob pattern and the value should be either a boolean or a string which will be evaluated in the context of answers.
  • templateData: See templateData but for this action only.
  • templateDir: See templateDir but for this action only.

type: 'modify'

Modify files in target directory.

actions: [
  {
    type: 'modify',
    files: 'package.json',
    handler(data, filepath) {
      data.main = './foo.js'
      return data
    }
  }
]
  • files: One or more glob patterns.
  • handler: The function we use to get new file contents. For .json we automatically parse and stringify it. Otherwise you will receive raw string.

type: 'move'

Move files in target directory.

actions: [
  {
    type: 'move',
    patterns: {
      'index-*.js': 'index.js'
    }
  }
]
  • patterns: Each entry can be a glob pattern which is supposed to matches zero or one file in target directory.

If you need a dynamic file path based on user answers, you can achieve it by passing a function to actions

actions: function() {
  return [
    {
      type: 'move',
      patterns: {
        'module.ts': `${this.answers.name}.module.ts`
      }
    }
  ]
}

type: 'remove'

Remove files in target directory.

actions: [
  {
    type: 'remove',
    files: '**/*.ts',
    when: '!useTypescript'
  }
]
  • files: One or more glob patterns to match the files that should be removed.

templateDir

  • Type: string
  • Default: template

The working directory for file action: add.

templateData

The files added via file action add will be interpolated using EJS syntax. By default the data you can access is:

  • answers: Directly access answers, e.g. <%= description %> to access the answer of project description.
  • context: The generator instance. e.g. <%= context.npmClient %>

You can provide more data with the templateData option:

module.exports = {
  templateData: {
    date: new Date()
  }
}

Then you can access it via <%= date %> in your files.

templateData can also be a function which can access generator context via this:

module.exports = {
  templateData() {
    return {
      link: `https://github.com/${this.gitUser.name}`
    }
  }
}

Sub-Generators

You can use the subGenerators option to register a list of sub generators:

module.exports = {
  subGenerators: [
    {
      name: 'foo',
      // A path to the generator, relative to the saofile
      generator: './generators/foo'
    },
    {
      name: 'bar',
      // Or use a package, like `sao-bar` here
      // It's also resolved relative to the saofile
      generator: 'sao-bar'
    }
  ]
}

Then you can call these sub-generators like this:

sao sample:foo
sao sample:bar

Hooks

prepare

Type: (this: Generator) => Prompt | void`

Executed before prompts and actions, you can throw an error here to exit the process:

module.exports = {
  // A generator that requires package.json in output directory
  async prepare() {
    const hasPkg = await this.fs.pathExists(this.resolve('package.json'))
    if (!hasPkg) {
      throw this.createError('Missing package.json')
      // You can also throw new Error('...') directly
      // While `this.createError` will only display error message without stack trace.
    }
  }
}

completed

Type: (this: Generator) => Prompt | void`

Executed when all actions are completed.

module.exports = {
  async completed() {
    this.gitInit()
    await this.npmInstall()
    this.showCompleteTips()
  }
}