1 | <p align="center">
|
2 | <img src="http://i.imgur.com/NyusmRJ.png" alt="vantage.js" />
|
3 | </p>
|
4 | <p align="center">
|
5 | <img src="https://travis-ci.org/dthree/vantage.svg" alt="Build Status" />
|
6 | <a href="https://gitter.im/dthree/vantage?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge">
|
7 | <img src="https://img.shields.io/badge/gitter-join%20chat-brightgreen.svg" alt="Gitter" />
|
8 | </a><br>
|
9 | <a href="https://www.npmjs.com/package/vantage">
|
10 | <img src="https://img.shields.io/npm/v/vantage.svg" alt="NPM Version" />
|
11 | </a>
|
12 | <a href="https://www.npmjs.com/package/vantage">
|
13 | <img src="https://img.shields.io/npm/dm/vantage.svg" alt="NPM Downloads" />
|
14 | </a>
|
15 |
|
16 | </p>
|
17 |
|
18 | <p align="center">
|
19 | <i>
|
20 | Vantage is now production ready.
|
21 | <br>
|
22 | <a href="https://github.com/dthree/vantage/releases">New features & updates.</a>
|
23 | </i>
|
24 | </p>
|
25 |
|
26 | <br>
|
27 |
|
28 | `Vantage.js` = `CLI` + `SSH` + `REPL` for your live node app. In one line:
|
29 |
|
30 | `require("vantage")().listen(4000);`
|
31 |
|
32 | <br>
|
33 |
|
34 | <p align="center">
|
35 | <img src="http://i.imgur.com/ZwAxqv4.gif" alt="vantage.js demo" />
|
36 | </p>
|
37 |
|
38 |
|
39 | * [What just happened?](#er-that-gif-im-so-confused)
|
40 | * [That's voodoo magic: show me the code](https://github.com/dthree/vantage/tree/master/examples/spiffy-gif/)
|
41 | * [Tell me more](#contents)
|
42 |
|
43 | ## Contents
|
44 |
|
45 | * [Introduction](#introduction)
|
46 | * [Getting Started](#getting-started)
|
47 | - [Tutorial](#tutorial)
|
48 | - [Examples](#examples)
|
49 | - [Community](#community)
|
50 | - [Quick Start](#quick-start)
|
51 | * [Methods](#methods)
|
52 | - [.command](#commandcommand-description)
|
53 | - [.mode](#modecommand-description)
|
54 | - [.delimiter](#delimiterstring)
|
55 | - [.banner](#bannerstring)
|
56 | - [.show](#show)
|
57 | - [.listen](#listenapp-options)
|
58 | * [Events](#events)
|
59 | * [Automation](#automation)
|
60 | * [Firewall](#firewall)
|
61 | * [Authentication](#authentication)
|
62 | * [Extensions](#extensions)
|
63 | - [Creating an Extension](#creating-an-extension)
|
64 | * [License](#license)
|
65 | * [Footnotes](#footnotes)
|
66 |
|
67 | ## Introduction
|
68 |
|
69 | Vantage gives you a new perspective into your live node application not previously available.
|
70 |
|
71 | Inspired by and based on [commander.js](https://www.npmjs.com/package/commander), Vantage turns your live Node app into a CLI with an interactive prompt provided by [inquirer.js](https://www.npmjs.com/package/inquirer). Accessible locally or remotely, Vantage lets you build your own API and import community extensions, introducing the possibility of live activity and diagnostics for your `dev` and `prod` environments.
|
72 |
|
73 | - Node now has a first-class CLI: tab completion, history, you name it.
|
74 | - Build your own API with the familiar syntax of `commander.js`.
|
75 | - Build and use community extensions for suites of commands: coded or in realtime.
|
76 | - Production-ready, with authentication middleware and a basic firewall.
|
77 | - Built-in REPL.
|
78 |
|
79 | Unlike other REPL or CLI modules, Vantage allows you to remotely connect to your live app and access the CLI transparently, exactly as you would in an SSH session. Vantage can connect through an unlimited number of live Node instances across multiple machines, piping commands and information to and from your local terminal.
|
80 |
|
81 | ## Getting Started
|
82 |
|
83 | ##### Tour
|
84 |
|
85 | [This Vantage tour](https://github.com/dthree/vantage/tree/master/examples/tour) will give you a live walk-through of vantage's features.
|
86 |
|
87 | ```bash
|
88 | $ npm install -g vantage
|
89 | $ vantage tour
|
90 | ```
|
91 |
|
92 | ##### Examples
|
93 |
|
94 | - [Standalone Vantage Server](https://github.com/dthree/vantage/tree/master/examples/server)
|
95 | - [Koa.js with Vantage](https://github.com/dthree/vantage/tree/master/examples/koa)
|
96 | - [Express.js with Vantage](https://github.com/dthree/vantage/tree/master/examples/express)
|
97 | - [Hapi.js with Vantage](https://github.com/dthree/vantage/tree/master/examples/hapi)
|
98 | - [Using the "mode" command](https://github.com/dthree/vantage/tree/master/examples/mode)
|
99 | - [Using the Firewall](https://github.com/dthree/vantage/tree/master/examples/firewall)
|
100 |
|
101 | ##### Community
|
102 |
|
103 | - [Q&A? Join Gitter Chat](https://gitter.im/dthree/vantage?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
|
104 | - [List of Vantage Extensions](https://github.com/vantagejs/awesome-vantagejs)
|
105 |
|
106 | ##### Quick Start
|
107 |
|
108 | First, install `vantage` globally:
|
109 |
|
110 | ```bash
|
111 | $ npm install -g vantage
|
112 | ```
|
113 |
|
114 | Now, add the following to a file named `server.js`.
|
115 |
|
116 | ```js
|
117 | // Create a new instance of vantage.
|
118 | var vantage = require("vantage")();
|
119 |
|
120 | // Add the command "foo", which logs "bar".
|
121 | vantage
|
122 | .command("foo")
|
123 | .description("Outputs 'bar'.")
|
124 | .action(function(args, callback) {
|
125 | this.log("bar");
|
126 | callback();
|
127 | });
|
128 |
|
129 | // Name your prompt delimiter
|
130 | // "websvr~$", listen on port 80
|
131 | // and show the Vantage prompt.
|
132 | vantage
|
133 | .delimiter("websvr~$")
|
134 | .listen(80)
|
135 | .show();
|
136 | ```
|
137 | Run `server.js`. You Node app has become a CLI.
|
138 |
|
139 | ```bash
|
140 | $ node server.js
|
141 | websvr~$
|
142 | ```
|
143 |
|
144 | Open another terminal. Because Vantage is listening on port 80, you can remotely connect to it:
|
145 |
|
146 | ```bash
|
147 | $ vantage 80
|
148 | $ Connecting to 127.0.0.1:80 using http...
|
149 | websvr~$
|
150 | ```
|
151 |
|
152 | Try out your "foo" command.
|
153 |
|
154 | ```bash
|
155 | websvr~$ foo
|
156 | bar
|
157 | websvr~$
|
158 | ```
|
159 |
|
160 | Now type "help" to see Vantage's built in commands in addition to "foo":
|
161 |
|
162 | ```bash
|
163 | websvr~$ help
|
164 |
|
165 | Commands
|
166 |
|
167 | help [command] Provides help for a given command.
|
168 | exit [options] Exits instance of Vantage.
|
169 | use <module> Installs a vantage extension in realtime.
|
170 | vantage [server] Connects to another application running vantage.
|
171 | foo Outputs "bar".
|
172 |
|
173 | websvr~$
|
174 | ```
|
175 |
|
176 | That's the basic idea. Once you get the hang of it, read on to learn some of the fancier things Vantage can do.
|
177 |
|
178 | ## Methods
|
179 |
|
180 | ### .command(command, [description])
|
181 |
|
182 | Adds a new command to your command line API. Returns a `Command` object, with the following chainable functions:
|
183 |
|
184 | * [`.description(string)`](#commanddescriptionstring): Used in automated help for your command.
|
185 | * [`.hidden()`](#commandhidden): Removes command from help menus.
|
186 | * [`.option(string, [description])`](#commandoptionstring-description): Provides command options, as in `-f` or `--force`.
|
187 | * [`.action(function)`](#commandactionfunction): Function to execute when command is executed.
|
188 |
|
189 | ```js
|
190 | vantage
|
191 | .command("foo")
|
192 | .description("Outputs 'bar'.")
|
193 | .action(function(args, callback) {
|
194 | this.log("bar");
|
195 | callback();
|
196 | });
|
197 | ```
|
198 | The syntax is similar to `commander.js` with the exception of allowing nested sub-commands for grouping large APIs into manageable chunks.
|
199 |
|
200 | ```js
|
201 | // Simple command with no arguments.
|
202 | vantage.command("foo", "Description of foo.");
|
203 |
|
204 | // Optional argument.
|
205 | vantage.command("foo [bar]");
|
206 |
|
207 | // Required argument.
|
208 | vantage.command("foo <bar>");
|
209 |
|
210 | // Examples of nested subcommands:
|
211 | vantage.command("farm animals");
|
212 | vantage.command("farm tools");
|
213 | vantage.command("farm feed [animal]");
|
214 | vantage.command("farm with farmer brown and reflect on <subject>");
|
215 | ```
|
216 | Descriptions can optionally be passed in as the second parameter, which are used to build the automated help.
|
217 |
|
218 | ##### Sub-commands
|
219 |
|
220 | When displaying the help menu, sub-commands will be grouped separately:
|
221 |
|
222 | ```bash
|
223 | webapp~$ help
|
224 |
|
225 | Commands: ( ... )
|
226 |
|
227 | Command Groups:
|
228 |
|
229 | farm * 4 sub-commands.
|
230 |
|
231 | ```
|
232 | Entering "farm" or "farm --help" would then drill down on the commands:
|
233 |
|
234 | ```bash
|
235 | webapp~$ farm
|
236 |
|
237 | Commands:
|
238 |
|
239 | farm animals Lists all animals in the farm.
|
240 | farm tools Lists all tools in the farm.
|
241 | farm feed [animal] Feeds a given animal.
|
242 |
|
243 | Command Groups:
|
244 |
|
245 | farm with * 1 sub-command.
|
246 |
|
247 | ```
|
248 |
|
249 | #### .command.description(string)
|
250 |
|
251 | If you don't pass a description into `vantage.command(...)` above, you can use the `description` function as an alternative.
|
252 |
|
253 | ```js
|
254 | vantage
|
255 | .command("foo")
|
256 | .description("outputs bar")
|
257 | // ...
|
258 | ```
|
259 |
|
260 | #### .command.hidden()
|
261 |
|
262 | Makes the command invisible, though executable. Removes from all automated help menus.
|
263 |
|
264 | #### .command.option(string, [description])
|
265 |
|
266 | You can provide both short and long versions of an option. Examples:
|
267 |
|
268 | ```js
|
269 | vantage
|
270 | .command("random", "Does random things.")
|
271 | .option('-f, --force', 'Force file overwrite.')
|
272 | .option('-a, --amount <coffee>', 'Number of cups of coffee.')
|
273 | .option('-v, --verbosity [level]', 'Sets verbosity level.')
|
274 | .option('-A', 'Does amazing things.')
|
275 | .option('--amazing', 'Does amazing things')
|
276 | // ...
|
277 | ```
|
278 |
|
279 | #### .command.autocompletion(text, iteration, callback)
|
280 |
|
281 | Registers a custom tabbed autocompletion for this command.
|
282 |
|
283 | If a user has typed part of a registered command, the default auto-completion will fill in the rest of the command:
|
284 |
|
285 | ```bash
|
286 | node~$ co
|
287 | node~$ cook
|
288 | ```
|
289 |
|
290 | However, after the user has fully typed the command `cook`, you can now implement command-specific auto-completion:
|
291 |
|
292 | ```bash
|
293 | node~$ bake coo # tab is pressed
|
294 | node~$ bake cookies # tab is pressed again
|
295 | cake cookies pie
|
296 | node~$ bake cookies
|
297 | ```
|
298 |
|
299 | This is implemented as follows:
|
300 |
|
301 | ```js
|
302 | vantage
|
303 | .command("bake", "Bakes a meal.")
|
304 | .autocompletion(function(text, iteration, cb) {
|
305 |
|
306 | // The meals are all of the possible actions.
|
307 | var meals = ["cookies", "pie", "cake"];
|
308 |
|
309 | // The iteration is the count of how many times
|
310 | // the `tab` key was pressed in a row. You can
|
311 | // make multiple presses return all of the options
|
312 | // for the user's convenience.
|
313 | if (iteration > 1) {
|
314 |
|
315 | // By returning an array of values, Vantage
|
316 | // will format them in a pretty fashion, as
|
317 | // in the example above.
|
318 | cb(void 0, meals);
|
319 |
|
320 | } else {
|
321 |
|
322 | // `this.match` is a helper function that will
|
323 | // return the closest auto-completion match.
|
324 | // Just makin' your job easier.
|
325 | var match = this.match(text, meals);
|
326 |
|
327 | if (match) {
|
328 |
|
329 | // If there is a good autocomplete, return
|
330 | // it in the callback (first param is reserved
|
331 | // for errors).
|
332 | cb(void 0, meals);
|
333 | } else {
|
334 |
|
335 | // If you don't want to do anything, just
|
336 | // return undefined.
|
337 | cb(void 0, void 0);
|
338 | }
|
339 | }
|
340 | })
|
341 | .action(...);
|
342 | ```
|
343 |
|
344 | #### .command.action(function)
|
345 |
|
346 | This is the action execution function of a given command. It passes in an `arguments` object and `callback`.
|
347 |
|
348 | Actions are executed async and must either call the passed `callback` upon completion or return a `Promise`.
|
349 |
|
350 | ```js
|
351 | // As a callback:
|
352 | command(...).action(function(args, cb){
|
353 | var self = this;
|
354 | doSomethingAsync(function(results){
|
355 | self.log(results);
|
356 | // If this is not called, Vantage will not
|
357 | // return its CLI prompt after command completion.
|
358 | cb();
|
359 | });
|
360 | });
|
361 |
|
362 | // As a newly created Promise:
|
363 | command(...).action(function(args, cb){
|
364 | return new Promise(function(resolve, reject) {
|
365 | if (skiesAlignTonight) {
|
366 | resolve();
|
367 | } else {
|
368 | reject("Better luck next time");
|
369 | }
|
370 | });
|
371 | });
|
372 |
|
373 | // Or as a pre-packaged promise of your app:
|
374 | command(...).action(function(args, cb){
|
375 | return app.promisedAction(args.action);
|
376 | });
|
377 | ```
|
378 |
|
379 | ##### Action Arguments
|
380 |
|
381 | Given the following command:
|
382 |
|
383 | ```js
|
384 | vantage
|
385 | .command('order pizza [type]', 'Orders a type of food.')
|
386 | .option('-s, --size <size>', 'Size of pizza.')
|
387 | .option('-a, --anchovies', 'Include anchovies.')
|
388 | .option('-p, --pineapple', 'Include pineapple.')
|
389 | .option('-o', 'Include olives.')
|
390 | .option('-d, --delivery', 'Pizza should be delivered')
|
391 | .action(function(args, cb){
|
392 | this.log(args);
|
393 | cb();
|
394 | });
|
395 | ```
|
396 | Args would be returned as follows:
|
397 |
|
398 | ```bash
|
399 | $webapp~$ order pizza pepperoni -pod --size "medium" --no-anchovies
|
400 | {
|
401 | "type": "pepperoni",
|
402 | "options": {
|
403 | "pineapple": true,
|
404 | "o": true,
|
405 | "delivery": true,
|
406 | "anchovies": false,
|
407 | "size": "medium",
|
408 | }
|
409 | }
|
410 | ```
|
411 |
|
412 | ##### Action Context (Session)
|
413 |
|
414 | The `this` variable in a `command.action` function is exposed to a special "Session" context. This context has a few functions to make use of:
|
415 |
|
416 | ##### session.log(string)
|
417 |
|
418 | Any and all logging in `command.action` should be done through `this.log`, which behaves exactly like `console.log`. This ensures all output for your given Vantage session is piped back to your original TTY, regardless how many hops into other servers you have made with Vantage.
|
419 |
|
420 | ```js
|
421 | vantage
|
422 | .command("foo", "Outputs 'bar'.")
|
423 | .action(function(args, callback) {
|
424 |
|
425 | // This will pipe back to your terminal.
|
426 | this.log("bar");
|
427 |
|
428 | // This will only log on the remote terminal,
|
429 | // and you will not see it on your local TTY.
|
430 | console.log("bar");
|
431 |
|
432 | callback();
|
433 | });
|
434 | ```
|
435 |
|
436 | ##### session.prompt(object, [callback])
|
437 |
|
438 | Vantage supports mid-command prompting. You can make full use of [inquirer.js](https://www.npmjs.com/package/inquirer)'s `prompt` function, which is exposed through `this.prompt`.
|
439 |
|
440 | Regardless of a direct vantage connection or one proxying your request through ten hops, `vantage.prompt` will send the remote prompt request to your local client and pipe response back to the remote application.
|
441 |
|
442 | ```js
|
443 | vantage.command("destroy database").action(function(args, cb){
|
444 | var self = this;
|
445 | this.prompt({
|
446 | type: "confirm",
|
447 | name: "continue",
|
448 | default: false,
|
449 | message: "That sounds like a really bad idea. Continue?",
|
450 | }, function(result){
|
451 | if (!result.continue) {
|
452 | self.log("Good move.");
|
453 | cb();
|
454 | } else {
|
455 | self.log("Time to dust off that resume.");
|
456 | app.destroyDatabase(cb);
|
457 | }
|
458 | });
|
459 | });
|
460 | ```
|
461 |
|
462 | ```bash
|
463 | dbsvr~$ destroy database
|
464 | ? That sounds like a really bad idea. Continue? y/N: N
|
465 | Good move.
|
466 | dbsvr~$
|
467 | ```
|
468 |
|
469 | ##### session.delimiter(string)
|
470 |
|
471 | You can change the prompt delimiter mid command through `this.delimiter`.
|
472 |
|
473 | ```js
|
474 | vantage
|
475 | .command("delimiter <string>")
|
476 | .action(function(args, cb){
|
477 | this.delimiter(args.string);
|
478 | cb();
|
479 | });
|
480 | ```
|
481 |
|
482 | ```bash
|
483 | websvr~$ delimiter unicornsvr~$
|
484 | unicornsvr~$
|
485 | ```
|
486 |
|
487 | ##### session.user
|
488 |
|
489 | The currently logged on user executing the command is exposed through `this.user`. Defaults to "guest" when there is no authentication enabled.
|
490 |
|
491 | ```js
|
492 | vantage
|
493 | .command("view classified information", "Shows all of our secrets.")
|
494 | .action(function(args, callback) {
|
495 | if (this.user === "president") {
|
496 | this.log(app.classifiedInformation);
|
497 | } else {
|
498 | this.log("Access Denied");
|
499 | }
|
500 | callback(true, "Access Denied");
|
501 | });
|
502 | ```
|
503 |
|
504 | ### .mode(command, [description])
|
505 |
|
506 | Mode is a special type of `command` that brings the user into a given `mode`, wherein regular Vantage commands are ignored and the full command strings are interpreted literally by the `mode.action` function. This will continue until the user exits the mode by typing `exit`.
|
507 |
|
508 | ```js
|
509 | vantage
|
510 | .mode("repl")
|
511 | .description("Enters the user into a REPL session.")
|
512 | .delimiter("repl:")
|
513 | .action(function(command, callback) {
|
514 | this.log(eval(command));
|
515 | });
|
516 | ```
|
517 | ```bash
|
518 | $ node server.js
|
519 | node~$
|
520 | node~$ repl
|
521 | node~$ repl:
|
522 | node~$ repl: 6 * 7
|
523 | 42
|
524 | node~$ repl: Math.random();
|
525 | 0.62392647205
|
526 | node~$ repl: exit
|
527 | node~$
|
528 | ```
|
529 |
|
530 | `mode`'s syntax is a duplicate of `command`'s, with the following additional / altered commands:
|
531 |
|
532 | * [`.delimiter(string)`](#modedelimiterstring): Tacks on an additional prompt delimiter for orientation.
|
533 | * [`.init(function)`](#modeinitfunction): Same as `command`'s `.action`, called once on entering the mode.
|
534 | * [`.action(function)`](#modeactionfunction): Called on each command submission while in the mode.
|
535 |
|
536 | #### .mode.delimiter(string)
|
537 |
|
538 | This will add on an additional delimiter string to one's Vantage prompt upon entering the mode, so the user can differentiate what state he is in.
|
539 |
|
540 | ```js
|
541 | vantage
|
542 | .mode('repl')
|
543 | .delimiter('you are in repl>')
|
544 | .action(function(command, callback) {
|
545 | this.log(eval(command));
|
546 | });
|
547 | ```
|
548 |
|
549 | ```bash
|
550 | node~$
|
551 | node~$ repl
|
552 | node~$ you are in repl>
|
553 | ```
|
554 | #### .mode.init(function)
|
555 |
|
556 | Behaves exactly like `command.action`, where the function passed in is fired once when the user enters the given mode. Passed the same parameters as `command.action`: `args` and `callback`. `init` is helpful when one needs to set up the mode or inform the user of what is happening.
|
557 |
|
558 | ```js
|
559 | vantage
|
560 | .mode('sql')
|
561 | .delimiter('sql:')
|
562 | .init(function(args, callback){
|
563 | this.log('Welcome to SQL mode.\nYou can now directly enter arbitrary SQL commands. To exit, type `exit`.');
|
564 | callback();
|
565 | })
|
566 | .action(function(command, callback) {
|
567 | var self = this;
|
568 | app.query(command, function(res){
|
569 | self.log(res);
|
570 | callback();
|
571 | });
|
572 | });
|
573 | ```
|
574 |
|
575 | ```bash
|
576 | node~$
|
577 | node~$ sql
|
578 | Welcome to SQL mode.
|
579 | You can now directly enter arbitrary SQL commands. To exit, type `exit`.
|
580 | node~$ sql:
|
581 | node~$ sql: select first_name, last_name from persons where first_name = 'George';
|
582 |
|
583 | first_name last_name
|
584 | ---------------- ----------------
|
585 | George Clooney
|
586 | George Smith
|
587 | George Stevens
|
588 |
|
589 | node~$ sql:
|
590 | node~$ sql: exit
|
591 | node~$
|
592 | ```
|
593 |
|
594 | #### .mode.action(function)
|
595 |
|
596 | Similar to `command.action`, `mode.action` differs in that it is repeatedly called on each command the user types until the mode is exited. Instead of `args` passed as the first argument, the full `command` string the user typed is passed and it is expected that `mode.action` appropriately handles the command. Example given above.
|
597 |
|
598 | ### .delimiter(string)
|
599 |
|
600 | Sets the prompt delimiter for the given Vantage server.
|
601 |
|
602 | ```js
|
603 | new Vantage().delimiter('appsvr:3000~$').listen(3000);
|
604 | new Vantage().delimiter('appsvr:3001~$').listen(3001);
|
605 | new Vantage().delimiter('appsvr:3002~$').listen(3002);
|
606 | ```
|
607 |
|
608 | ```bash
|
609 | $ vantage 3000
|
610 | appsvr:3000~$
|
611 | appsvr:3000~$ vantage 3001
|
612 | appsvr:3001~$ vantage 3002
|
613 | appsvr:3002~$ exit
|
614 | appsvr:3001~$ exit
|
615 | appsvr:3000~$ exit -f
|
616 | $
|
617 | ```
|
618 |
|
619 | ### .banner(string)
|
620 |
|
621 | Sets a banner for display when logging into a given Vantage server.
|
622 |
|
623 | ```js
|
624 | var banner =
|
625 | "######################################################################" +
|
626 | "# Welcome to joescrabshack.com #" +
|
627 | "# #" +
|
628 | "# All connections are monitored and recorded #" +
|
629 | "# Disconnect IMMEDIATELY if you are not an authorized user #" +
|
630 | "######################################################################";
|
631 | vantage
|
632 | .delimiter('appsvr:3000~$')
|
633 | .banner(banner)
|
634 | .listen(3000);
|
635 | ```
|
636 |
|
637 | ```bash
|
638 | $ vantage 3000
|
639 | $ Connecting to 127.0.0.1:3000...
|
640 | $ Connected successfully.
|
641 | ######################################################################
|
642 | # Welcome to joescrabshack.com #
|
643 | # #
|
644 | # All connections are monitored and recorded #
|
645 | # Disconnect IMMEDIATELY if you are not an authorized user #
|
646 | ######################################################################
|
647 | ? user:
|
648 | ```
|
649 | *Note: See authentication section for auth details.*
|
650 |
|
651 | ### .show()
|
652 |
|
653 | Attaches the TTY's CLI prompt to that given instance of Vantage. While useless for deployed servers, this is great for testing an application's functions mid-development.
|
654 |
|
655 | ```js
|
656 | // ... (your web server code)
|
657 |
|
658 | vantage
|
659 | .delimiter('websvr~$')
|
660 | .show();
|
661 |
|
662 | vantage
|
663 | .command('build api', 'Builds web server API.')
|
664 | .action(function(args, cb){
|
665 | return app.buildAPI();
|
666 | });
|
667 | ```
|
668 |
|
669 | ```bash
|
670 | node websvr.js
|
671 | Successfully started Web Server.
|
672 | websvr~$
|
673 | websvr~$ build API
|
674 | Building API...
|
675 | ...
|
676 | Successfully built API.
|
677 | websvr~$
|
678 | ```
|
679 | As a note, multiple instances of Vantage can run in the same Node instance. However, only one can be "attached" to your TTY. The last instance given the `show()` command will be attached, and the previously shown instances will detach.
|
680 |
|
681 | ```js
|
682 | var instances = []
|
683 | for (var i = 0; i < 3; ++i) {
|
684 | instances[i] = new Vantage()
|
685 | .delimiter("instance" + i + "~$")
|
686 | .command("switch <instance>", "Switches prompt to another instance.")
|
687 | .action(function(args, cb){
|
688 | instances[args.instance].show();
|
689 | cb();
|
690 | })
|
691 | }
|
692 |
|
693 | instances[0].show();
|
694 | ```
|
695 |
|
696 | ```bash
|
697 | $ node server.js
|
698 | instance0~$ switch 1
|
699 | instance1~$ switch 2
|
700 | instance2~$ switch 0
|
701 | instance0~$
|
702 | ```
|
703 |
|
704 | ### .listen(app, [options or callback], [callback])
|
705 |
|
706 | Starts Vantage as a server.
|
707 |
|
708 | #### Vantage as a standalone web server
|
709 |
|
710 | If you just want it to listen on a port independent of your web application, simply pass in the port and Vantage will spawn a new HTTP server. Every time a client connects to Vantage, the connection callback will be thrown and include the `socket.io` connection object.
|
711 |
|
712 | ```js
|
713 | var vantage = new Vantage();
|
714 | vantage.listen(80, function(socket){
|
715 | this.log("Accepted a connection.")
|
716 | });
|
717 | ```
|
718 |
|
719 | #### Vantage with an existing web server
|
720 |
|
721 | If you want Vantage to listen on the same port as your web application, you can use Vantage's `listen` function in place of your existing web server's `listen` function.
|
722 |
|
723 | This is useful when running clustered instances of your server, such as behind a reverse proxy, where every instance has a separate port that can only be accessed internally. In this way, you can hop into any running instance without having to remember a separate set of ports.
|
724 |
|
725 | ##### With Koa.js
|
726 |
|
727 | ```js
|
728 | var koa = require('koa');
|
729 | var Vantage = require('vantage');
|
730 |
|
731 | var vantage = new Vantage();
|
732 | var app = koa();
|
733 |
|
734 | vantage.listen(app, 80);
|
735 | ```
|
736 |
|
737 | ##### With Express.js
|
738 |
|
739 | ```js
|
740 | var express = require('express');
|
741 | var Vantage = require('vantage');
|
742 |
|
743 | var vantage = new Vantage();
|
744 | var app = express();
|
745 |
|
746 | vantage.listen(app, 80);
|
747 | ```
|
748 |
|
749 | ##### With Hapi.js
|
750 |
|
751 | ```js
|
752 | var Hapi = require('hapi');
|
753 | var Vantage = require('vantage');
|
754 |
|
755 | var vantage = new Vantage();
|
756 | var server = new Hapi.Server();
|
757 |
|
758 | vantage.listen(server, 80);
|
759 |
|
760 | server.start();
|
761 | ```
|
762 |
|
763 | ##### With SSL / advanced options
|
764 |
|
765 | You can pass detailed options to your web server with the second argument in place of the port. These options are the same options you would pass into your web server, with a few exceptions:
|
766 |
|
767 | - `options.port`: Tells vantage what port to listen on.
|
768 | - `options.ssl`: A boolean that tells Vantage whether to spawn an HTTP or HTTPs server.
|
769 | - `options.logActivity`: When true, a TTY acting as a Vantage server that receives a connection will log when clients log in and out of the server. Defaults to `false`.
|
770 |
|
771 | Default HTTPs server example:
|
772 |
|
773 | ```js
|
774 | var vantage = new Vantage();
|
775 | vantage.listen(someMiddleware, {
|
776 | port: 443,
|
777 | ssl: true,
|
778 | key: fs.readFileSync('./../../server.key'),
|
779 | cert: fs.readFileSync('./../../server.crt'),
|
780 | ca: fs.readFileSync('./../../ca.crt'),
|
781 | requestCert: true,
|
782 | rejectUnauthorized: false,
|
783 | });
|
784 | ```
|
785 |
|
786 | ## Events
|
787 |
|
788 | Vantage extends `EventEmitter.prototype`. Simply use `vantage.on('event', fn)` and `vantage.emit('event', data)`. The following events are supported:
|
789 |
|
790 | ##### Socket.io client / server events
|
791 |
|
792 | Vantage uses `socket.io` to handle all communication between instances. The following events map to the default `socket.io` events:
|
793 |
|
794 | - `client_connect`: Maps to `connect` for `socket.io-client`.
|
795 |
|
796 | - `client_connect_error`: Maps to `connect_error` for `socket.io-client`.
|
797 |
|
798 | - `client_error`: Maps to `error` for `socket.io-client`.
|
799 |
|
800 | - `client_disconnect`: Maps to `disconnect` for `socket.io-client`.
|
801 |
|
802 | - `server_connection`: Maps to `connection` for `socket.io`.
|
803 |
|
804 | - `server_disconnect`: Maps to `disconnect` for `socket.io`.
|
805 |
|
806 | ##### Vantage client / server events
|
807 |
|
808 | - `client_keypress`: Fires on keypress on local client terminal.
|
809 |
|
810 | - `client_prompt_submit`: Fires when the CLI prompt has been submitted with a command, including ''.
|
811 |
|
812 | - `client_command_executed`: Fires at the client once the command has been received back as executed.
|
813 |
|
814 | - `client_command_error`: Fires at the client if a command comes back with an error thrown.
|
815 |
|
816 | - `server_command_received`: Fires at the end-server actually executing a command receives the command.
|
817 |
|
818 | - `server_command_executed`: Fires at the end-server once the command has successfully executed.
|
819 |
|
820 | - `server_command_error`: Fires at the end-server if the command has thrown an error.
|
821 |
|
822 | ##### Vantage general events
|
823 |
|
824 | - `command_registered`: Fires when `vantage.command` registers a new command.
|
825 |
|
826 | ## Automation
|
827 |
|
828 | Vantage allows you execute your API commands from javascript synchronously, using either callbacks or promises.
|
829 |
|
830 | ### .connect(server, port, [options or callback], [callback])
|
831 |
|
832 | Connects to another instance of Vantage. Returns callback or promise.
|
833 |
|
834 | ```js
|
835 | // With a promise
|
836 | vantage.connect('127.0.0.1', 8001).then(function(data){
|
837 | // ...
|
838 | }).catch(function(err){
|
839 | console.log('Error connecting: ' + err);
|
840 | });
|
841 |
|
842 | // With a callback
|
843 | vantage.connect('127.0.0.1', 8001, function(err) {
|
844 | if (!err) {
|
845 | // ... connected
|
846 | }
|
847 | });
|
848 | ```
|
849 | ##### Options
|
850 |
|
851 | - `ssl`: Set to true if server you are connecting to uses HTTPS.
|
852 |
|
853 | ### .exec(command, [callback])
|
854 |
|
855 | Executes an API command string. Returns a callback or Promise.
|
856 |
|
857 | ```js
|
858 | // Using Promises:
|
859 | vantage.exec("vantage 8001").then(function(data){
|
860 | return vantage.exec("roll dough");
|
861 | }).then(function(data){
|
862 | return vantage.exec("add cheese");
|
863 | }).then(function(data){
|
864 | return vantage.exec("add pepperoni");
|
865 | }).then(function(data){
|
866 | return vantage.exec("shape crust");
|
867 | }).then(function(data){
|
868 | return vantage.exec("insert into oven");
|
869 | }).then(function(data){
|
870 | return vantage.exec("wait 480000");
|
871 | }).then(function(data){
|
872 | return vantage.exec("remove from oven");
|
873 | }).then(function(data){
|
874 | return vantage.exec("enjoy");
|
875 | }).catch(function(err){
|
876 | console.log("Error baking pizza: " + err);
|
877 | app.orderOut();
|
878 | });
|
879 |
|
880 | // Using callbacks:
|
881 | vantage.exec("vantage 8001", function(err, data) {
|
882 | if (!err) {
|
883 | vantage.exec("bake pizza", function(err, pizza){
|
884 | if (!err) {
|
885 | app.eat(pizza);
|
886 | }
|
887 | });
|
888 | }
|
889 | });
|
890 | ```
|
891 |
|
892 | ### .pipe(function)
|
893 |
|
894 | Captures all session `stdout` piped through Vantage and passes it through a custom function. The string returned from the function is then logged.
|
895 |
|
896 | ```js
|
897 | var onStdout = function(stdout) {
|
898 | app.writeToLog(stdout);
|
899 | return "";
|
900 | }
|
901 |
|
902 | vantage
|
903 | .pipe(onStdout)
|
904 | .connect("127.0.0.1", 80, {});
|
905 | ```
|
906 |
|
907 | ## Firewall
|
908 |
|
909 | If your Vantage server is listening on a public-facing web port such as 80 or 443, your organization's firewall is not going to help you. This is a barebones IP firewall for limiting connections down to your internal subnets. For sensitive applications, this obviously does not replace authentication.
|
910 |
|
911 | ### .firewall.policy(string)
|
912 |
|
913 | Sets the default policy for the firewall to either `ACCEPT` or `REJECT`. Any request that does not match a rule will fall back to this policy. Returns `vantage.firewall`.
|
914 |
|
915 | **Defaults to `ACCEPT`.**
|
916 |
|
917 | ```js
|
918 | // This will reject all remote connections.
|
919 | vantage.firewall.policy("REJECT");
|
920 | ```
|
921 |
|
922 | ### .firewall.accept(address, [subnet])
|
923 |
|
924 | Allows a particular address / subnet to connect to Vantage. Returns `vantage.firewall`. If no arguments are passed, returns the currently-applied policy.
|
925 |
|
926 | ```js
|
927 | vantage.firewall
|
928 | .policy("REJECT")
|
929 | .accept("10.0.0.0/8")
|
930 | .accept("192.168.0.0", 24);
|
931 |
|
932 | console.log(vantage.firewall.policy()) // -> REJECT
|
933 | ```
|
934 |
|
935 | ### .firewall.reject(address, [subnet])
|
936 |
|
937 | Denies access to a particular address / subnet. Returns `vantage.firewall`.
|
938 |
|
939 | ```js
|
940 | vantage.firewall
|
941 | .policy("ACCEPT")
|
942 | .reject("64.0.0.0", 8)
|
943 | .reject("192.168.0.0/16");
|
944 | ```
|
945 | ### .firewall.rules()
|
946 |
|
947 | Returns an array of applied rules.
|
948 |
|
949 | ```js
|
950 | console.log(vantage.firewall.rules());
|
951 | // -> [{ ip: "64.0.0.0", subnet: 8, rule: "REJECT" }]
|
952 | ```
|
953 |
|
954 | ### .firewall.reset()
|
955 |
|
956 | Reverts `vantage.firewall` to an `ACCEPT` policy and erases all rules.
|
957 |
|
958 | ## Authentication
|
959 |
|
960 | Vantage supports authentication strategies as middleware. It comes with a default [Basic Authentication module](https://github.com/vantagejs/vantage-auth-basic).
|
961 |
|
962 | ### vantage.auth(middleware, options)
|
963 |
|
964 | Uses a given authentication strategy. Pass the required middleware into the first variable, and any options / configuration for that middleware as given in that module's documentation into the options parameter.
|
965 |
|
966 | ```js
|
967 | var pam = require("vantage-auth-pam");
|
968 | vantage.auth(pam, options);
|
969 | ```
|
970 |
|
971 | Vantage Basic Auth is built in, and so can be used with the "basic" string instead of requiring a module.
|
972 |
|
973 | ```js
|
974 | var users = [
|
975 | { user: "admin", pass: "4k#842jx!%s" },
|
976 | { user: "user", pass: "Unicorn11" }
|
977 | ];
|
978 |
|
979 | var vantage = require("vantage")();
|
980 |
|
981 | vantage.auth("basic", {
|
982 | "users": users,
|
983 | "retry": 3,
|
984 | "retryTime": 500,
|
985 | "deny": 1,
|
986 | "unlockTime": 3000
|
987 | });
|
988 | ```
|
989 |
|
990 | ##### Security Note
|
991 |
|
992 | If no `vantage.auth` function is declared, your app will not require authentication. As a security measure, if your `NODE_ENV` environment variable is not set to "development" and there is no authentication, Vantage will disallow remote connections. To permit remote connections without authentication, simply set your `NODE_ENV` to "development".
|
993 |
|
994 | ##### Building Authentication Strategies
|
995 |
|
996 | You can publish your own custom authentication strategies for Vantage.js as its own Node module.
|
997 |
|
998 | *I am currently looking to team up with a rocket scientist like you to build a pam-based authentication strategy for Vantage. If you are interested, send me a note!*
|
999 |
|
1000 | The format for publishing a strategy is simple:
|
1001 |
|
1002 | ```js
|
1003 |
|
1004 | module.exports = function(vantage, options) {
|
1005 |
|
1006 | // The Vantage instance is exposed through
|
1007 | // the `vantage` parameter. `options` exposes
|
1008 | // options passed in by the strategy's user, and
|
1009 | // is defined by you.
|
1010 |
|
1011 | // This is where you can persist the log on state of
|
1012 | // the users attempting to log in, etc.
|
1013 |
|
1014 | // You return a function, which executes
|
1015 | // in the same context as a vantage command.
|
1016 | // Every time the user attempts to connect,
|
1017 | // this function runs. In it you can prompt
|
1018 | // the user, etc.
|
1019 | return function(args, callback) {
|
1020 |
|
1021 | /**
|
1022 | * Args exposes several pieces of data
|
1023 | * you can use:
|
1024 | * {
|
1025 | * // If the user pre-passes auth data, it will be
|
1026 | * // available here. Otherwise, prompt him for it.
|
1027 | * user: "admin",
|
1028 | * pass: "Unicorn11",
|
1029 | * // This is based on socket.io's connection handshake,
|
1030 | * // and has a lot more data than this.
|
1031 | * handshake: {
|
1032 | * host: "192.168.0.1",
|
1033 | * port: "800"
|
1034 | * }
|
1035 | * }
|
1036 | */
|
1037 |
|
1038 | // Prompt user / look up credentials, etc.
|
1039 |
|
1040 | // Authentication is determined by your
|
1041 | // callback: `callback(message, authenticated)`.
|
1042 |
|
1043 | // Example of rejected auth.
|
1044 | callback("Invalid credentials.", false);
|
1045 |
|
1046 | // Example of accepted auth.
|
1047 | // callback(void 0, true);
|
1048 | }
|
1049 |
|
1050 | }
|
1051 |
|
1052 | ```
|
1053 |
|
1054 | ## Extensions
|
1055 |
|
1056 | Vantage supports command extensions and this is the primary reason for supporting sub-commands. For example, someone could create a suite of server diagnostic commands under the namespace `system` and publish it as `vantage-system`.
|
1057 |
|
1058 | ##### Programmatic use
|
1059 |
|
1060 | Vantage has a `.use(extension)` function, which expects a Node module extension (exposed as a function). You can also pass in the string of the module as an alternative, and `vantage` will `require` it for you.
|
1061 |
|
1062 | ```js
|
1063 | var system = require('vantage-system');
|
1064 | vantage.use(system);
|
1065 |
|
1066 | /*
|
1067 | Your API would now include a suite of system commands:
|
1068 | system list processes
|
1069 | system status
|
1070 | system ... etc.
|
1071 | */
|
1072 | ```
|
1073 |
|
1074 | ```js
|
1075 | // Does the same thing as above.
|
1076 | vantage.use('vantage-system');
|
1077 | ```
|
1078 |
|
1079 | ##### Realtime use
|
1080 |
|
1081 | Forgot to install a useful extension in development and now you need it live? No problem.
|
1082 |
|
1083 | Vantage has a built-in `use` command, which will automatically import a given NPM module acting as a `vantage` extension, and register the commands contained inside while the app is still live. This import has an in-memory lifecycle and the module is dumped when the thread quits.
|
1084 |
|
1085 | ```bash
|
1086 | node~$
|
1087 | node~$ use vantage-repl
|
1088 | Installing vantage-repl from the NPM registry:
|
1089 | Successfully registered 1 new command.
|
1090 | node~$
|
1091 | node~$ repl
|
1092 | node~$ repl: 6*8
|
1093 | 48
|
1094 | node~$ repl:
|
1095 | ```
|
1096 |
|
1097 | ### Creating an extension
|
1098 |
|
1099 | Creating and publishing a Vantage extension is simple. Simply expose your module as a function which takes two parameters - `vantage` and `options`. When your module is imported by `vantage`, it will pass itself in as the first object, and so you are free to add any commands or configuration that `vantage` supports.
|
1100 |
|
1101 | ```js
|
1102 | module.exports = function(vantage, options) {
|
1103 |
|
1104 | vantage.
|
1105 | .command("foo", "Outputs 'bar'.")
|
1106 | .action(function(args, cb){
|
1107 | this.log("bar");
|
1108 | cb();
|
1109 | });
|
1110 |
|
1111 | // ... more commands!
|
1112 |
|
1113 | }
|
1114 | ```
|
1115 |
|
1116 | The options exist so the user can pass in customizations to your module. In documenting your `vantage` extension, you would lay out your supported options for the user.
|
1117 |
|
1118 | ## Roadmap
|
1119 |
|
1120 | - Suggest something!
|
1121 |
|
1122 | ## License
|
1123 |
|
1124 | MIT
|
1125 |
|
1126 | ## Footnotes
|
1127 |
|
1128 | ##### Er, that GIF... I'm so confused...
|
1129 |
|
1130 | That's okay. Here's what happened:
|
1131 |
|
1132 | 1. In my terminal, I started a local Node web server:
|
1133 |
|
1134 | ```js
|
1135 | $ node websvr.js
|
1136 | ```
|
1137 |
|
1138 | Normally, you would simply see what you logged, and would have no interaction with Node. Instead, Vantage gave us a prompt:
|
1139 |
|
1140 | ```bash
|
1141 | websvr~$
|
1142 | ```
|
1143 |
|
1144 | 2. I typed `help`, which gave me a list of all of Vantage's built-in commands as well as commands I added.
|
1145 |
|
1146 | 3. In my `websvr.js`, I gave Vantage a command that would turn on logging *only for* web requests. By logging domains of activity, this assists productivity in debugging. To run this, I typed `debug web`, and it started logging all web requests.
|
1147 |
|
1148 | 4. I then typed `debug off`, which disabled log output.
|
1149 |
|
1150 | 5. By then entering the `repl` command, I entered a special REPL "mode" where I can access the raw javascript and objects in my application, while it's running. This is the equivalent of running `$ node` in your terminal, except it is in the context of your live application!
|
1151 |
|
1152 | 6. Satisfied with `repl` mode, I exited out of it with the `exit` command.
|
1153 |
|
1154 | 7. So that's nice, you can access the local Node instance in your terminal. But what about remote or daemonized applications? By using the built-in `vantage` command, I remotely connect to my Node database API listening on port `5001`, by running `vantage 127.0.0.1:5001`.
|
1155 |
|
1156 | 8. Just like SSH, I'm now "in" the new instance, and my prompt changed to `dbsvr~$`.
|
1157 |
|
1158 | 9. This server supports another Vantage mode. By typing `sql`, I enter "sql mode". Using this, I typed an arbitrary SQL command and it connected to my database and executed it. When done, I entered `exit`.
|
1159 |
|
1160 | 10. I felt like checking out the latest trend on Hacker News. I typed `help` and was disappointed to find there was no `hacker-news` API command.
|
1161 |
|
1162 | 11. Fortunately, someone made an extension for that - an NPM module called `vantage-hacker-news`. To download it and import the commands into Vantage in realtime, I typed `use vantage-hacker-news`.
|
1163 |
|
1164 | 12. With this command, `vantage` did a temporary `npm install` on the module and loaded it into the application's memory. By typing `help` again, I can see I now have a new Vantage command registered: `hacker-news`!
|
1165 |
|
1166 | 13. I used the command: `hacker-news --length 3`, and this showed me the top 3 items trending on Hacker News. One of them was obviously an article on the Node event loop, because Node is awesome.
|
1167 |
|
1168 | 14. Satisfied, I typed `exit`, which brought me back to my web server.
|
1169 |
|
1170 | 15. I then typed `exit -f` (for `--force`) to actually quit the web server, which was running locally in my terminal.
|
1171 |
|
1172 | * [Ah. Show me the GIF again](#)
|
1173 | * [I get it, I get it. *Tell me more*](#contents)
|
1174 |
|
1175 | <br>
|
1176 |
|
1177 | ---
|
1178 |
|
1179 | <br>
|
1180 |
|
1181 | <p align="center">
|
1182 | <img src="http://i.imgur.com/ajsjp9E.png" alt="vantage.js" />
|
1183 | </p>
|