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
'swhen
.
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
- Default:
- 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
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
Executed when all actions are completed.
module.exports = {
async completed() {
this.gitInit()
await this.npmInstall()
this.showCompleteTips()
}
}