# template-kit

[![NPM Version][npm-image]][npm-url]
[![NPM Downloads][downloads-image]][downloads-url]
[![Deps][david-image]][david-url]
[![Dev Deps][david-dev-image]][david-dev-url]

Library for creating projects from templates.

## Features

 * Project generation using local template as a directory, archive file, or globally installed npm package
 * Download templates from git, npm, or a URL to a archive file
 * Support for `.zip`, `.gz`, `.bz2`, `.tar`, `tar.gz`, `tar.bz2`, `.tgz`, and `.tbz2` archives
 * Run text files through [`ejs`][ejs] during file copy
 * JavaScript lifecycle hooks
 * User-defined copy file inclusion/exclusion filters
 * Data-driven destination directory and filenames
 * npm dependency installation
 * git repository initialization

## Installation

    npm install template-kit --save

## Example

```javascript
import TemplateEngine from 'template-kit';

(async () => {
    const engine = new TemplateEngine();

    engine.on('create', async (state, next) => {
        console.log('Creating the project from the template');
        await next();
        console.log('Project created!');
    });

    await engine.run({
        src: 'git@github.com:appcelerator/template-kit-test.git',
        dest: '/path/to/new/project'
    });

    console.log('Project created successfully!');
})();
```

## Template Specification

### Structure

A template can be an empty directory or a massive collection of files and subdirectories. All of
the files in the template can be compressed in a single archive file (.zip, .tgz, etc) for
distribution.

Templates do _not_ need to be npm packages and they do _not_ need to have a `package.json`. If they
do, template-kit will happily call `npm install` after generation.

Any file that is _not_ binary will be treated as an [ejs][ejs] template regardless of extension.
ejs templates have access to any variable or function in the `data` object including the `opts.data`
passed into the `TemplateEngine` constructor, the `data` export from the `meta.js`, or any
data that has be injected via a hook.

Directories and filenames may contain a `{{variable}}` sequence which will be replaced with the
corresponding value in the `data` object.

By default, template-kit will treat the root of the template directory as the actual template.
However, the template may contain several project templates or place all of the template files in
a single subdirectory. The relative project template path is defined by setting the `template`
property in the `meta.js` file. If there are multiple project templates, then you can present them
by using the prompts mechanism.

### Metadata

template-kit will load the template's metadata from a `meta.js` file or the package's "main" file.
The metadata can be an object or an async function that returns an object. The object contains the
following properties:

| Property | Type             | Description |
| -------- | ---------------- | ----------- |
| complete | `Function`       | A function to call after the project generation is complete. Useful for displaying post create steps. |
| data     | `Object`         | Arbitrary data to mix in with the `run()` data and pass into `ejs` when copying a text file. |
| filters  | `Array.<String>` | An array of file patterns to copy. The patterns follow the gitignore rules. |
| prompts  | `Object`         | An array of prompt descriptors that is used to select the template and template data. |
| template | `String`         | Relative path to the template files. Defaults to `'.'` |

## API

### `TemplateEngine`

Resolves a template source, installs the template, and manages the install lifecycle.

 * [`new TemplateEngine(opts)`](#TemplateEngine+constructor)
   * _methods_
     * [`.run(opts)`](#TemplateEngine+run)
   * _inherited from [`HookEmitter`](https://www.npmjs.com/package/hook-emitter)_ ➚
     * [`.on(event, listener)`](https://www.npmjs.com/package/hook-emitter#onevent-listener) ➚
     * [`.once(event, listener)`](https://www.npmjs.com/package/hook-emitter#onceevent-listener) ➚
     * [`.off(event, listener)`](https://www.npmjs.com/package/hook-emitter#offevent-listener) ➚
   * _events_
     * [`#init`](#TemplateEngine+event+init)
     * [`#git-clone`](#TemplateEngine+event+git-clone)
     * [`#download`](#TemplateEngine+event+download)
     * [`#extract`](#TemplateEngine+event+extract)
     * [`#extract-file`](#TemplateEngine+event+extract-file)
     * [`#extract-progress`](#TemplateEngine+event+extract-progress)
     * [`#npm-download`](#TemplateEngine+event+npm-download)
     * [`#create`](#TemplateEngine+event+create)
     * [`#load-meta`](#TemplateEngine+event+load-meta)
     * [`#prompt`](#TemplateEngine+event+prompt)
     * [`#copy`](#TemplateEngine+event+copy)
     * [`#copy-file`](#TemplateEngine+event+copy-file)
     * [`#npm-install`](#TemplateEngine+event+npm-install)
     * [`#git-init`](#TemplateEngine+event+git-init)
     * [`#cleanup`](#TemplateEngine+event+cleanup)

### Constructor

<a name="TemplateEngine+constructor"></a>

#### `new TemplateEngine(opts)`

Initializes the template engine.

| Param                           | Type      | Description                                                  |
| ------------------------------- | --------- | ------------------------------------------------------------ |
| [opts]                          | `Object`  | Various options.                                             |
| [opts.requestOptions]           | `Object`  | `got` HTTP client options and proxy/security settings below. |
| [opts.requestOptions.caFile]    | `String`  | A path to a PEM-formatted certificate authority bundle.      |
| [opts.requestOptions.certFile]  | `String`  | A path to a client cert file used for authentication.        |
| [opts.requestOptions.keyFile]   | `String`  | A path to a private key file used for authentication.        |
| [opts.requestOptions.proxy]     | `String`  | A proxy server URL. Can be `http` or `https`.                |
| [opts.requestOptions.strictSSL] | `Boolean` | When falsey, disables TLS/SSL certificate validation for both `https` requests and `https` proxy servers. |

### Methods

<a name="TemplateEngine+run"></a>

#### `.run(opts)` ⇒ `Promise`

Builds a project based on the specified template and options.

| Param               | Type                      | Description     |
| ------------------- | ------------------------- | --------------- |
| opts                | `Object`                  | Various options |
| [opts.data]         | `Object`                  | A data object that is passed into `ejs` when copying template files. |
| opts.dest           | `String`                  | The destination directory to create the project in. |
| [opts.filters]      | `Set` \| `Array.<String>` | A list of file patterns to pass into `micromatch` when copying files. |
| [opts.force]        | `Boolean`                 | When `true`, overrides the destination if it already exists. |
| [opts.git=true]     | `Boolean`                 | When `true` and `git` executable is found, after the the project is generated, initialize a git repo in the project directory. |
| [opts.npmArgs]      | `Array.<String>`          | An array of additional parameters to pass into npm. Useful if you need to add extra arguments for things such as skipping shrinkwrap or production only. |
| opts.src            | `String`                  | The path to a directory, archive file, globally installed npm package, archive URL, npm package name, or git repo. |
| [opts.template='.'] | `String`                  | Relative path to the directory containing the template files. This value overrides the default template from the `meta.js`, but not the `template` value returned from prompting. |

##### Run State

Every time `run()` is invoked, a new `state` object is created and passed through the various
stages. The contents of the `state` depends on the Source Type.

| Property    | Type                      | Description                                                  |
| ----------- | ------------------------- | ------------------------------------------------------------ |
| dest        | `String`                  | The destination directory to create the project in.          |
| destFile    | `String`                  | When copying files, this is the destination file path.       |
| disposables | `Array.<String>`          | A list of temp directories to cleanup.                       |
| extractDest | `String`                  | The temporary directory where the archive was extracted to.  |
| filters     | `Set` \| `Array.<String>` | A list of file patterns to copy.                             |
| force       | `Boolean`                 | When `true`, overrides the destination if it already exists. |
| git         | `Boolean`                 | When `true` and `git` executable is found, after the the project is generated, initialize a git repo in the project directory. |
| gitInfo     | `Object`                  | The parsed git repo information.                             |
| npmArgs     | `Array.<String>`          | An array of additional parameters to pass into npm. Useful if you need to add extra arguments for things such as skipping shrinkwrap. |
| npmManifest | `Object`                  | The npm package information                                  |
| prompts     | `Array.<Object>`          | A list of prompt descriptors.                                |
| src         | `String`                  | The path to a directory, archive file, globally installed npm package, archive URL, npm package name, or git repo. |
| srcFile     | `String`                  | When copying files, this is the source file path.            |
| template    | `String`                  | Relative path to the directory containing the template files. Defaults to `'.'` |

### Events

The `TemplateEngine` emits several events. It uses the [hook-emitter][hook-emitter] package which
supports async event listeners.

Some events are only emitted depending on the source type (e.g. the `src` passed into `run()`).

```
Event Flow                   ┌───────┐
                             │ run() │
                             └───┬───┘ ┌───────┐
                                 ├─────┤ #init │
                                 │     └───────┘
      ┌──────────────┬───────────┼────────┬──────────┬──────────┐
     git            URL        Local    Local      Global      npm
      │        ┌─────┴─────┐   File   Directory  npm Package    │
      │        │ #download │     │        │          │          │
      │        └─────┬─────┘     │        │          │          │
┌─────┴──────┐       └─────┬─────┘        │          │  ┌───────┴───────┐
│ #git-clone │   ┌─────────┴─────────┐    │          │  │ #npm-download │
└─────┬──────┘   │ #extract          │    │          │  └───────┬───────┘
      │          │ #extract-file     │    │          │          │
      │          │ #extract-progress │    │          │          │
      │          └─────────┬─────────┘    │          │          │
      └────────────────────┴───────┬──────┴──────────┴──────────┘
                             ┌─────┴──────┐
                             │ #load-meta │
                             └─────┬──────┘ ┌─────────┐
                                   ├────────┤ #prompt │
                              ┌────┴────┐   └─────────┘
                              │ #create │
                              └────┬────┘   ┌───────┐
                                   ├────────┤ #copy │
                                   │        └───┬───┘    ┌────────────┐
                                   │            └────────┤ #copy-file │
                                   │    ┌──────────────┐ └────────────┘
                                   ├────┤ #npm-install │
                                   │    └──────────────┘
                                   │    ┌───────────┐
                                   ├────┤ #git-init │
                                   │    └───────────┘
                              ┌────┴─────┐
                              │ #cleanup │
                              └──────────┘
```

<a name="TemplateEngine+event+init"></a>

#### `#init`

Initialize the run state with the options passed into `run()`.

**Source Type:** Local file, local directory, global npm package, npm, git, URL

| Param              | Type       | Description                      |
| ------------------ | ---------- | -------------------------------- |
| opts               | `Object`   | The options passed into `run()`. |
| [async next(opts)] | `Function` | Continue to next hook.           |

```javascript
engine.on('init', async opts => {
    // before the run state has been initialized
});
```

or

```javascript
engine.on('init', async (opts, next) => {
    // before the run state has been initialized
    await next();
    // after initialization
});
```

<a name="TemplateEngine+event+git-clone"></a>

#### `#git-clone`

Emitted when `git clone` is called.

**Source Type:** git

| Param              | Type             | Description                                  |
| ------------------ | ---------------- | -------------------------------------------- |
| state              | `Object`         | The run state.                               |
| args               | `Array.<String>` | The arguments passed into the `git` command. |
| opts               | `Object`         | `spawn()` options.                           |
| [async next(opts)] | `Function`       | Continue to next hook.                       |

```javascript
engine.on('git-clone', async (state, args, opts, next) => {
    // before the git clone call
    await next();
    // after the clone
});
```

<a name="TemplateEngine+event+download"></a>

#### `#download`

Emitted when downloading a file based on a http/https URL.

**Source Type:** URL

| Param              | Type       | Description            |
| ------------------ | ---------- | ---------------------- |
| state              | `Object`   | The run state.         |
| [async next(opts)] | `Function` | Continue to next hook. |

```javascript
engine.on('download', async (state, next) => {
    // before the download begins
    await next();
    // after the clone
});
```

<a name="TemplateEngine+event+extract"></a>

#### `#extract`

Emitted when extracting the downloaded or local archive file.

**Source Type:** Local directory, URL

| Param              | Type       | Description            |
| ------------------ | ---------- | ---------------------- |
| state              | `Object`   | The run state.         |
| [async next(opts)] | `Function` | Continue to next hook. |

```javascript
engine.on('extract', async (state, next) => {
    // before the archive is extracted
    await next();
    // after extraction
});
```

<a name="TemplateEngine+event+extract-file"></a>

#### `#extract-file`

Emits the current file being extracted from the archive.

**Source Type:** Local directory, URL

| Param              | Type       | Description                           |
| ------------------ | ---------- | ------------------------------------- |
| state              | `Object`   | The run state.                        |
| file               | `String`   | The name of the file being extracted. |

```javascript
engine.on('extract-file', async (state, file) => {
    console.log(`Extracting ${file}`);
});
```

<a name="TemplateEngine+event+extract-progress"></a>

#### `#extract-progress`

Emits the current progress of the file extraction from `0` to `100`.

**Source Type:** Local directory, URL

| Param              | Type       | Description              |
| ------------------ | ---------- | ------------------------ |
| state              | `Object`   | The run state.           |
| percent            | `Number`   | The percentage complete. |

```javascript
engine.on('extract-progress', async (state, percent) => {
    console.log(`Extracted ${percent}%`);
});
```

<a name="TemplateEngine+event+npm-download"></a>

#### `#npm-download`

Emitted when downloading and extracting an npm package.

**Source Type:** npm

| Param              | Type       | Description            |
| ------------------ | ---------- | ---------------------- |
| state              | `Object`   | The run state.         |
| [async next(opts)] | `Function` | Continue to next hook. |

```javascript
engine.on('npm-download', async (state, next) => {
    // before downloading and extracting the npm package
    await next();
    // after extraction
});
```

<a name="TemplateEngine+event+create"></a>

#### `#create`

Emitted when about to populate the destination directory.

**Source Type:** All sources.

| Param              | Type       | Description            |
| ------------------ | ---------- | ---------------------- |
| state              | `Object`   | The run state.         |
| [async next(opts)] | `Function` | Continue to next hook. |

```javascript
engine.on('create', async (state, next) => {
    // before the project is generated
    await next();
    // after project is generated
});
```

<a name="TemplateEngine+event+load-meta"></a>

#### `#load-meta`

Emitted when attempting to load the template's `meta.js` or "main" file.

**Source Type:** Any source with a meta script.

| Param              | Type             | Description              |
| ------------------ | ---------------- | ------------------------ |
| state              | `Object`         | The run state.           |
| [async next(opts)] | `Function`       | Continue to next hook.   |

```javascript
engine.on('load-meta', async (state, next) => {
    // before npm dependencies have been installed
    await next();
    // after installation
});
```

<a name="TemplateEngine+event+prompt"></a>

#### `#prompt`

Allows application opportunity to prompt for missing data, then populate state's `data` property.

**Source Type:** Any source with a meta script.

| Param              | Type       | Description                            |
| ------------------ | ---------- | -------------------------------------- |
| state              | `Object`   | The run state.                         |

```javascript
engine.on('prompt', async state => {
    // prompt for `state.prompts` and store results in `state.data`
});
```

<a name="TemplateEngine+event+copy"></a>

#### `#copy`

Emitted when copying the template files to the destination.

**Source Type:** All sources.

| Param              | Type       | Description                          |
| ------------------ | ---------- | ------------------------------------ |
| state              | `Object`   | The run state.                       |
| [async next(opts)] | `Function` | Continue to next hook.               |

```javascript
engine.on('copy', async (state, next) => {
    // before copying has begun
    await next();
    // after files have been copied
});
```

<a name="TemplateEngine+event+copy-file"></a>

#### `#copy-file`

Emitted for each file copied.

**Source Type:** All sources.

| Param              | Type       | Description                         |
| ------------------ | ---------- | ----------------------------------- |
| state              | `Object`   | The run state.                      |
| [async next(opts)] | `Function` | Continue to next hook.               |

```javascript
engine.on('copy-file', async (state, next) => {
    // before a specific file is to be copied
    await next();
    // file has been copied
});
```

<a name="TemplateEngine+event+npm-install"></a>

#### `#npm-install`

Emitted when installing npm dependencies in the destination directory.

**Source Type:** Any source with a `package.json`.

| Param              | Type             | Description              |
| ------------------ | ---------------- | ------------------------ |
| state              | `Object`         | The run state.           |
| cmd                | `String`         | The path to npm command. |
| args               | `Array.<String>` | The npm arguments.       |
| opts               | `Object`         | `spawn()` options.       |
| [async next(opts)] | `Function`       | Continue to next hook.   |

```javascript
engine.on('npm-install', async (state, cmd, args, opts, next) => {
    // before npm dependencies have been installed
    await next();
    // after installation
});
```

<a name="TemplateEngine+event+git-init"></a>

#### `#git-init`

Emitted when a git repository is being initialized in the project directory.

**Source Type:** Any source.

| Param              | Type             | Description            |
| ------------------ | ---------------- | ---------------------- |
| state              | `Object`         | The run state.         |
| args               | `Array.<String>` | `git` arguments.       |
| opts               | `Object`         | `spawn()` options.     |
| [async next(opts)] | `Function`       | Continue to next hook. |

```javascript
engine.on('git-init`', async (state, args, opts, next) => {
    // before the git repo has been initialized
    await next();
    // after initialization
});
```

<a name="TemplateEngine+event+cleanup"></a>

#### `#cleanup`

Emitted after the project has been created and the temp directories are to be deleted.

**Source Type:** Any source.

| Param              | Type       | Description            |
| ------------------ | ---------- | ---------------------- |
| state              | `Object`   | The run state.         |
| [async next(opts)] | `Function` | Continue to next hook. |

```javascript
engine.on('cleanup`', async (state, next) => {
    // before temp directories have been deleted
    await next();
    // after cleanup
});
```

## Legal

This project is open source under the [Apache Public License v2][1] and is developed by
[Axway, Inc](http://www.axway.com/) and the community. Please read the [`LICENSE`][1] file included
in this distribution for more information.

[1]: https://github.com/appcelerator/template-kit/blob/master/LICENSE
[npm-image]: https://img.shields.io/npm/v/template-kit.svg
[npm-url]: https://npmjs.org/package/template-kit
[downloads-image]: https://img.shields.io/npm/dm/template-kit.svg
[downloads-url]: https://npmjs.org/package/template-kit
[david-image]: https://img.shields.io/david/appcelerator/template-kit.svg
[david-url]: https://david-dm.org/appcelerator/template-kit
[david-dev-image]: https://img.shields.io/david/dev/appcelerator/template-kit.svg
[david-dev-url]: https://david-dm.org/appcelerator/template-kit#info=devDependencies
[ejs]: https://www.npmjs.com/package/ejs
[hook-emitter]: https://www.npmjs.com/package/hook-emitter
