hapi pal CLI
Lead Maintainer - Devin Ivy
hpal
was designed to help you,
- ✨ create new hapi projects from the pal boilerplate
- 💐 generate files for routes, extensions, models, services, etc. via
haute-couture
- 📚 search the hapi docs from the command line– plus many others such as joi and toys
- 🐝 run custom commands defined by your server's hapi plugins
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.
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: 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
hpal new <new-project-directory> e.g. hpal new ~/node-projects/new-pal-project
Clones the pal boilerplate, helps you fill-in initial details with npm init
, pulls down the pal flavors, and leaves you prepared to make the first commit to your new project.
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 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
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
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 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[:<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 for the relevant section or configuration item then prints it formatted to the console.
💫 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
or hpal docs:joi any.strip
.
<docs-section>
can be,
- the name of any haute-couture item (e.g.
route
,plugins
,auth/default
) when in a haute-couture project - the name of any server, request, toolkit, etc. method
- a substring of any heading from the docs
- e.g.
hpal docs router
- e.g.
- an anchor seen anywhere in the docs
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 [--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.
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 then you should already be all set!
Here is a very basic example,
// 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);
}
Any hapi plugin can create commands that are runnable with hpal run
! Commands are exposed to hpal using hapi's 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 commandhpal run my-plugin
. Camel-cased command names are converted to kebab-case, so if the key issomeCommand
then it is run usinghpal run my-plugin:some-command
. - whose values are either objects
{ command, description }
or functionscommand
where,command
- a function with the signatureasync function(server, args, root, ctx)
.server
- the initialized hapi server.args
- an array of all the command's CLI arguments. For example, runninghpal run my-plugin --custom-flag value
will result inargs
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)
, andcolors.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 aDisplayError
will output the error'smessage
and exit the process with code1
, but not display a stack trace as would happen with an unexpected error.
description
- a string description of the command displayed byhpal run --list
. May alternatively be a function with signaturefunction (ctx)
that receivesctx
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,
// 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}`);
});
}
});
}
};