# hpal

hapi pal CLI

[![Build Status](https://travis-ci.org/hapipal/hpal.svg?branch=master)](https://travis-ci.org/hapipal/hpal) [![Coverage Status](https://coveralls.io/repos/hapipal/hpal/badge.svg?branch=master&service=github)](https://coveralls.io/github/hapipal/hpal?branch=master)

Lead Maintainer - [Devin Ivy](https://github.com/devinivy)

`hpal` was designed to help you,
  - :sparkles: create new hapi projects from the [pal boilerplate](https://github.com/hapipal/boilerplate)
  - :bouquet: generate files for routes, extensions, [models](https://github.com/hapipal/schwifty), [services](https://github.com/hapipal/schmervice), etc. via [`haute-couture`](https://github.com/hapipal/haute-couture)
  - :books: search the [hapi docs](https://github.com/hapijs/hapi/blob/master/API.md) from the command line– plus many others such as [joi](https://github.com/hapijs/joi/blob/master/API.md) and [toys](https://github.com/hapipal/toys/blob/master/API.md)
  - :honeybee: run custom commands defined by your server's hapi plugins

## Installation
> Note, the hpal CLI is designed for use with **hapi v17+** and **nodejs v8+**.

We recommend installing the hpal CLI as a dev dependency within your project, then invoking it using [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b).
```
npm install --save-dev hpal
npx hpal --help
```

If you want to try the hpal CLI right now, just copy and paste this right into your terminal!
```
npx hpal docs --ver 18.1.0 h.response
```

## Usage
```
Usage: hpal <command> <options>

Commands:

  hpal new <new-project-directory>
    e.g. hpal new ~/node-projects/new-pal-project

  hpal make [--asDir|--asFile] <haute-couture-item> [<item-name>]
    e.g. hpal make route create-user

  hpal docs[:<package-name>] [--ver x.y.z|ref] <docs-section> [<config-item>]
    e.g. hpal docs --ver 17.2.0 h.continue

  hpal run [--list] <cmd> [<cmd-options>]
    e.g. hpal run plugin-name:command-name


Options:

  -h, --help       show usage options
  -v, --version    show version information
  -d, --asDir      [make] creates new haute-couture item in a directory index file
  -f, --asFile     [make] creates new haute-couture item in a file
  -V, --ver        [docs] specifies the version/ref of the API docs to search for the given package
  -l, --list       [run] lists all available commands on your server
```

### Commands
#### `hpal new`
> ```
> hpal new <new-project-directory>
>   e.g. hpal new ~/node-projects/new-pal-project
> ```

Clones the [pal boilerplate](https://github.com/hapipal/boilerplate), helps you fill-in initial details with [`npm init`](https://docs.npmjs.com/cli/init), pulls down the [pal flavors](https://github.com/hapipal/boilerplate#flavors), and leaves you prepared to make the first commit to your new project.

#### `hpal make`
> ```
> hpal make [--asDir|--asFile] <haute-couture-item> [<item-name>]
>   e.g. hpal make route create-user
> ```

Creates a new file for a [`haute-couture` item](https://github.com/hapipal/haute-couture/blob/master/API.md#files-and-directories) with details ready to be filled-in.  This is the best way to add a route, plugin, model, service, etc. to any project that uses haute-couture.

Relies on the presence of a [`.hc.js`](https://github.com/hapipal/haute-couture/blob/master/API.md#specifying-amendments-with-hcjs) file in the project, even if it's empty, in order to determine the base directory of the plugin in which to write the file.  If `.hc.js` contains amendments then those will be respected– in this way you can customize the behavior of `hpal make` per project.  Projects created with [`hpal new`](#hpal-new) are already configured to work with `hpal make`.

The `--asDir` and `--asFile` flags can be used to determine where the file is written.  For a list item like `routes`, specifying `--asFile` (`hpal make route --asFile`) will create `routes.js` rather than `routes/index.js`.  For a single item like `auth/default`, specifying `--asDir` (`hpal make auth/default --asDir`) will create `auth/default/index.js` rather than `auth/default.js`.  When an optional `<item-name>` is specified then that will always place a file in the relevant directory with the name `<item-name>.js`.  For example, `hpal make route create-user` will write the file `routes/create-user.js`.

In order to omit the statement to enable [strict mode](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode) to the top of the generated files (per the hapi style guide), you may specify `exampleUseStrict` as `false` inside the `meta` property of the relevant haute-couture manifest items.

#### `hpal docs`
> ```
> hpal docs[:<package-name>] [--ver x.y.z|ref] <docs-section> [<config-item>]
>   e.g. hpal docs --ver 17.2.0 h.continue
> ```

Searches the [hapi API reference](https://github.com/hapijs/hapi/blob/master/API.md) for the relevant section or configuration item then prints it formatted to the console.

:dizzy: This command can also search the API reference for any package within the pal and hapijs ecosystems by specifying `<package-name>`, e.g. [`hpal docs:toys noop`](https://github.com/hapipal/toys/blob/master/API.md#toysnoop) or [`hpal docs:joi any.strip`](https://github.com/hapijs/joi/blob/master/API.md#anystrip).

`<docs-section>` can be,
 - the name of any haute-couture item (e.g. `route`, `plugins`, `auth/default`) when in a haute-couture project
   - e.g. [`hpal docs auth/scheme`](https://github.com/hapijs/hapi/blob/master/API.md#server.auth.scheme())
 - the name of any server, request, toolkit, etc. method
   - e.g. [`hpal docs request.setUrl`](https://github.com/hapijs/hapi/blob/master/API.md#request.setUrl())
 - a substring of any heading from the docs
   - e.g. [`hpal docs router`](https://github.com/hapijs/hapi/blob/master/API.md#server.options.router)
 - an anchor seen anywhere in the docs
   - e.g. [`hpal docs '#catch-all-route'`](https://github.com/hapijs/hapi/blob/master/API.md#catch-all-route)

When `<config-item>` is also specified, the first list item within the matched `<docs-section>` that matches text from `<config-item>` will be displayed on its own.  For example, `hpal docs request.setUrl` is a long section of the docs but `hpal docs request.setUrl stripTrailingSlash` contains only information relevant to the `stripTrailingSlash` argument.

All searches are case-insensitive.

When `--ver` is specified as a semver version or a git ref (branch, tag, or commit), then that version of the docs will be searched.  Otherwise, when inside a project the docs for the currently installed version of the given package will be searched.  When not in a project and `--ver` is not specified, the master branch of the package's docs will be searched.

#### `hpal run`
> ```
> hpal run [--list] <cmd> [<cmd-options>]
>   e.g. hpal run plugin-name:command-name
> ```

Runs the command `<cmd>` defined by some plugin on your hapi server.  If the plugin `my-plugin` defines a command `do-the-thing`, then that command can be run with `hpal run my-plugin:do-the-thing`.  If the plugin's name is prefixed with `hpal-`, then `hpal-` may be omitted when running the command.  Plugins may also have a "default" command that can be run as `hpal run my-plugin`.

A list of commands available on the server and their descriptions may be viewed with `hpal run --list`.

Upon running a command hpal will initialize the server if it is not already initialized, then stop the server when the command exits successfully.

##### Requirements

In order to use `hpal run`, hpal must be able to find your hapi server.  It will look in `server.js` and `server/index.js` relative to the root of your project.  That file should export a property `deployment` which contains a function that returns a hapi server, or a promise for a hapi server (for example, an `async` function).

If you're using the [pal boilerplate](https://github.com/hapipal/boilerplate) then you should already be all set!

Here is a very basic example,
```js
// server/index.js
const Hapi = require('hapi');
const AppPlugin = require('../app');

exports.deployment = async (start) => {

    const server = Hapi.server();

    await server.register(AppPlugin);

    if (start) {
        await server.start();
    }

    return server;
};

// Start the server only when this file is
// run directly from the CLI, i.e. "node ./server"

if (!module.parent) {
    exports.deployment(true);
}
```

##### Creating your own commands

Any hapi plugin can create commands that are runnable with `hpal run`!  Commands are exposed to hpal using hapi's [`server.expose()`](https://github.com/hapijs/hapi/blob/master/API.md#server.expose()).  Inside your plugin `my-plugin` simply call `server.expose('commands', commands)`, where `commands` is an object,
 - whose keys are command names.  The name `default` is reserved for the command `hpal run my-plugin`.  Camel-cased command names are converted to kebab-case, so if the key is `someCommand` then it is run using `hpal run my-plugin:some-command`.
 - whose values are either objects `{ command, description }` or functions `command` where,
   - `command` - a function with the signature `async function(server, args, root, ctx)`.
     - `server` - the initialized hapi server.
     - `args` - an array of all the command's CLI arguments.  For example, running `hpal run my-plugin --custom-flag value` will result in `args` being `['--custom-flag', 'value']`.
     - `root` - an absolute path to the project's root directory.
     - `ctx` - a context object containing some hpal internals that may be useful during testing, plus some public helpers.  The following are public:
       - `colors` - an object with functions for basic formatting of CLI output with colors and styles: `colors.green(str)`, `colors.yellow(str)`, `colors.red(str)`, `colors.grey(str)`, and `colors.bold(str)`.  When the CLI does not support color, these functions take no effect.
       - `DisplayError` - a class that can be used to indicate a "safe" failure to hpal.  Throwing a `DisplayError` will output the error's `message` and exit the process with code `1`, but not display a stack trace as would happen with an unexpected error.
   - `description` - a string description of the command displayed by `hpal run --list`.  May alternatively be a function with signature `function (ctx)` that receives `ctx` as described above and returns a string description.

For example, here is a plugin that creates a command to display the server's route table,
```js
// hpal run route-table:show

module.exports = {
    name: 'hpal-route-table', // The hpal- prefix is ignored when running the command
    register(server) {

        server.expose('commands', {
            show(srv) {

                console.log('Route table:');

                srv.table().forEach(({ method, path }) => {

                    console.log(`  ${method} ${path}`);
                });
            }
        });
    }
};
```
