anvil.scaffold
is a generic scaffolding utility for the anvil.js Node.js build engine. anvil.scaffold
exposes a method that allows other plugins to configure scaffold definitions using a conventional format to generate templated files and directories.
--
In order to use anvil.scaffold
you must first have Anvil installed, then run the following from the command line:
anvil install anvil.scaffold
In order for your plugin or task to consume Anvil's scaffolding, it must also be a required plugin within your project's package.json
:
{
"name": "anvil.exampleplugin",
"version": "0.1.0",
"description": "Your sample plugin",
"main": "lib/index.js",
"requiredPlugins": [
"anvil.scaffold"
]
}
Once anvil.scaffold
is installed, you can start consuming it with your own Anvil plugins. As a simple demo, here is an anvil plugin that will define how additional Anvil plugins can be scaffolded:
module.exports = function (_, anvil) {
return anvil.plugin({
name: 'anvil.scaffold.plugin',
configure: function (config, command, done) {
anvil.scaffold({
type: 'plugin',
description: 'Creates a new anvil plugin',
prompt: [{
name: 'name',
description: 'Please choose a name for this plugin:',
required: true
}],
output: {
lib: {},
src: {
'index.js': anvil.scaffold.file( __dirname + '/plugin.template.js' )
}
}
});
done();
}
});
};
The contents of the plugin.template.js
is:
module.exports = function (_, anvil) {
return anvil.plugin({
name: '{{name}}',
configure: function (config, command, done) {
done();
},
run: function (done) {
done()
}
});
};
Once this scaffold is executed, it will output lib
and src
directories and an index.js
file with src
that has the name populated from metadata supplied by the user at the command line.
anvil.scaffold( definition );
Having anvil.scaffold
installed exposes a new method called anvil.scaffold
which plugins can use to create scaffold definitions. These scaffold definitions must currently exist within a plugin's configure
method. anvil.scaffold
accepts a single object for defining the scaffold:
module.exports = function (_, anvil) {
return anvil.plugin({
name: 'anvil.scaffold.plugin',
configure: function (config, command, done) {
anvil.scaffold({
...
});
done();
}
});
};
The following is a list of acceptable properties that can be set on the scaffold definition object.
--
type
The unique identifier to reference this scaffold. This type will be used on the command line to specify which scaffold to invoke.
Examples
type: 'plugin'
type: 'jquery-plugin'
type: 'backbone:model'
--
description
A short description that will be shown on the command line when when a user runs anvil scaffold list
Examples
description: 'Create an empty Backbone project'
description: 'Create a new Anvil plugin'
--
prompt
This is a value used to solicit additional metadata from the user. This metadata will be passed as a model to all files and directories for templating. prompt
accepts input in any schema defined by the prompt package.
Examples
// will ask for "name" at command line,
// creating a "name" property for all template models
prompt: ['name']
// will require description at command line,
// creating a "name" property for all template models
prompt: [{
name: 'name',
description: 'Please choose a name for this plugin:',
required: true
}]
// will require 2 descriptions at command line,
// creating a "username" and "password" property for all template models,
// hiding the password entered
prompt: [{
name: 'username',
description: 'Enter your username:',
required: true
}, {
name: 'password',
description: 'Enter your password:',
required: true,
hidden: true
}]
Please see prompt for more detailed usage of the prompt
API.
--
output
Take note that at this time, using output from scaffolding will overwrite any files and directories specified. Please use caution when creating your output format.
Define the structure of directories and files to write to the file system. This accepts a map of properties to file and directory names and contents. If a property is an object, a directory will be generated. If a property is a string, a file will be generated. Take the following example:
output: {
destination: {}
}
Since destination
is an object, a directory with that name will be created. It should also be possible to generate directories using a more complex path style:
output: {
'src/scripts/external': {}
}
This output would create an external
directory within src/scripts
, additionally generating those directories as well if they did not already exist. Since the value of the object of this destination is empty, the directory itself will also remain empty. Nesting any structures within the object will create a nested directory tree:
output: {
src: {
scripts: {
external: {}
}
}
}
In order to generate files, the property's value must be a string. Continuing with our previous example:
output: {
src: {
scripts: {
external: {
'file.js': '(function () {})();'
}
}
}
}
This would output the same directory structure as before, except now a file.js
will be created in the external
directory. The contents to use for the file are the string value of the property, or (function () {})();
. You may also choose to store the content of the file in another file and read it as a string, possibly making maintenance of these files easier.
In addition, all keys and string values are passed through Handlebars for templating. This allows you to dynamically generate file contents or even file and directory names themselves:
output: {
src: {
scripts: {
'{{dirType}}': {
'{{scriptName}}.js': '(function ({{lib}}) {})({{lib}});'
}
}
}
}
The data which is used as a model for the templates comes from data optionally supplied in the scaffold or from a user's inputs at the command prompts. This data is automatically merged together for you. The type
will be passed to the view template automatically.
Finally, you can supply a function for any one of the values as long as your function passes a either a string (the contents of a file), or an object for a new directory level. The final merged user input and data
from your scaffold will be passed into the method as its only argument. These functions are evaluated asynchronously and therefore must invoke their done
callback to continue, passing the value into the callback.
This example would generate a different file based on theoretical user input:
output: function ( data, done ) {
if ( data.short ) {
done( { "short.js": fileContentsShort } );
} else {
done( { "normal.js": fileContentsNormal } );
}
}
You can (and should) use this to load files just when you need them instead of loading them every time your plugin is fired up:
output: {
"yourfile.js": function ( data, done ) {
anvil.fs.read( "./templates/yourfile.js", done );
}
}
In fact, having a file loaded and templated when the scaffold is invoked is such a common use case that there is also a helper method for handing this:
output: {
"yourfile.js": anvil.scaffold.file( __dirname + "/templates/yourfile.js" )
}
Using this helper will return a function that will automatically load the file asynchronously and pass the contents back through the done
method.
--
data
An object containing additional data to pass to scaffold templates (e.g. file contents and file and directory names). This data will be automatically merged with other data including properties provided by anvil.scaffold
and from user input prompt responses. Please note that any properties provided in data
that match names provided in prompt
will have their values overwritten by user input.
Examples
data: {
author: 'appendTo',
status: 'l33t'
},
// Now usable within scaffold templates:
output: {
scripts: {
'{{author}}.js': 'console.log("This app is {{status}}");'
}
}
The following is a list of methods that have default functionality, but you can override to provide a greater level of control to your scaffolds:
--
render
All keys and values on your output
object are passed through this method. The default render
method looks like this:
render: function ( data ) {
var template = Handlebars.compile( data.template );
return template( data.data );
}
The data
argument contains relevant properties necessary for rendering a string for file output:
mode
: Will either be"name"
or"file"
based on if it's rendering a file/directory name, or the contents of a filetemplate
: The contents of either the file/directory name, or the contents of a filefilename
: If in"file"
mode this will be just the name and extension of the file, otherwisenull
To disable templating entirely, pass false
as the value for render
:
render: false
--
processData
You can override this method to manipulate the data being passed to any templates right before templating occurs, e.g. right after user input has been retrieved. It receives the data as its only parameter, and only what you return will be used as the new template data.
By default it simply returns the data that is passed in.
Example
In this example, the user supplied name is cleaned and prepared to be used as a directory name and key:
prompt: [{
name: 'name',
description: 'Please choose a name for your theme:',
required: true
}],
processData: function ( data ) {
data.key = data.name
.toLowerCase()
.trim()
.replace( /[^a-z0-9_-]/g, '-' )
.replace( /-+/g, '-' );
return data;
}
--
Invoking a scaffold from the command line:
anvil scaffold list
anvil scaffold <type>
where type
is the name of a scaffold definition. scaffold
is also aliased to generate
or gen
:
anvil generate backbone:model
anvil gen jquery-plugin
If you wanted to list available scaffolds, and you had the plugin example from earlier ready:
anvil scaffold list
The expected output (after default Anvil output) would be:
Currently available scaffolds:
* plugin Creates a new anvil plugin
If you wanted to scaffold a plugin, for example, using our previous definition:
anvil scaffold plugin
Since there was prompt information defined in the scaffold, this would be the expected output to the command line:
$ anvil scaffold plugin
checking for 0 build dependencies
loading plugins
loading tasks from /Users/me/git/some-plugin/tasks
plugin configuration complete
starting activity, scaffold
running plugin: 'anvil.scaffold'
Please choose a name for this plugin: awesome
Creating directory: lib
Creating directory: src
Creating file: index.js