diff --git a/README.md b/README.md index de6f9492..b7b7fd9b 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,7 @@ Fastify command line interface, available commands are: * generate-swagger generate Swagger/OpenAPI schema for a project using @fastify/swagger * readme generate a README.md for the plugin * print-routes prints the representation of the internal radix tree used by the router, useful for debugging. + * print-plugins prints the representation of the internal plugin tree used by avvio, useful for debugging. * version the current fastify-cli version * help help about commands diff --git a/help/help.txt b/help/help.txt index c22ab8bb..fc106f18 100644 --- a/help/help.txt +++ b/help/help.txt @@ -8,6 +8,7 @@ Fastify command line interface available commands are: * generate-swagger generate Swagger/OpenAPI schema for a project using @fastify/swagger * readme generate a README.md for the plugin * print-routes prints the representation of the internal radix tree used by the router, useful for debugging. + * print-plugins prints the representation of the internal plugin tree used by avvio, useful for debugging. * version the current fastify-cli version * help help about commands diff --git a/help/print-plugins.txt b/help/print-plugins.txt new file mode 100644 index 00000000..22ff3162 --- /dev/null +++ b/help/print-plugins.txt @@ -0,0 +1 @@ +Usage: fastify print-plugins diff --git a/print-plugins.js b/print-plugins.js new file mode 100644 index 00000000..30ae0d26 --- /dev/null +++ b/print-plugins.js @@ -0,0 +1,82 @@ +#! /usr/bin/env node + +'use strict' + +const parseArgs = require('./args') +const log = require('./log') +const { + exit, + requireFastifyForModule, + requireServerPluginFromPath, + showHelpForCommand +} = require('./util') + +let Fastify = null + +function loadModules (opts) { + try { + Fastify = requireFastifyForModule(opts._[0]).module + } catch (e) { + module.exports.stop(e) + } +} + +function printPlugins (args) { + const opts = parseArgs(args) + if (opts.help) { + return showHelpForCommand('print-plugins') + } + + if (opts._.length !== 1) { + console.error('Missing the required file parameter\n') + return showHelpForCommand('print-plugins') + } + + // we start crashing on unhandledRejection + require('make-promises-safe') + + loadModules(opts) + + return runFastify(opts) +} + +async function runFastify (opts) { + require('dotenv').config() + + let file = null + + try { + file = await requireServerPluginFromPath(opts._[0]) + } catch (e) { + return module.exports.stop(e) + } + + const fastify = Fastify(opts.options) + + const pluginOptions = {} + if (opts.prefix) { + pluginOptions.prefix = opts.prefix + } + + await fastify.register(file, pluginOptions) + await fastify.ready() + log('debug', fastify.printPlugins()) + + return fastify +} + +function stop (message) { + exit(message) +} + +function cli (args) { + return printPlugins(args).then(fastify => { + if (fastify) return fastify.close() + }) +} + +module.exports = { cli, stop, printPlugins } + +if (require.main === module) { + cli(process.argv.slice(2)) +} diff --git a/test/print-plugins.test.js b/test/print-plugins.test.js new file mode 100644 index 00000000..0e9abaf1 --- /dev/null +++ b/test/print-plugins.test.js @@ -0,0 +1,112 @@ +'use strict' + +const proxyquire = require('proxyquire') +const tap = require('tap') +const sinon = require('sinon') +const { execSync } = require('child_process') + +const printPlugins = require('../print-plugins') + +const test = tap.test + +test('should print plugins', async t => { + t.plan(3) + + const spy = sinon.spy() + const command = proxyquire('../print-plugins', { + './log': spy + }) + const fastify = await command.printPlugins(['./examples/plugin.js']) + + await fastify.close() + t.ok(spy.called) + t.same(spy.args[0][0], 'debug') + t.match(spy.args[0][1], /bound root \d+ ms\n├── bound _after \d+ ms\n├─┬ function \(fastify, options, next\) { -- fastify\.decorate\('test', true\) \d+ ms\n│ ├── bound _after \d+ ms\n│ ├── bound _after \d+ ms\n│ └── bound _after \d+ ms\n└── bound _after \d+ ms\n/) +}) + +test('should plugins routes via cli', async t => { + t.plan(1) + const output = execSync('node cli.js print-plugins ./examples/plugin.js', { encoding: 'utf-8' }) + t.match( + output, + /bound root \d+ ms\n├── bound _after \d+ ms\n├─┬ function \(fastify, options, next\) { -- fastify\.decorate\('test', true\) \d+ ms\n│ ├── bound _after \d+ ms\n│ ├── bound _after \d+ ms\n│ └── bound _after \d+ ms\n└── bound _after \d+ ms\n\n/ + ) +}) + +test('should warn on file not found', t => { + t.plan(1) + + const oldStop = printPlugins.stop + t.teardown(() => { printPlugins.stop = oldStop }) + printPlugins.stop = function (message) { + t.ok(/not-found.js doesn't exist within/.test(message), message) + } + + const argv = ['./data/not-found.js'] + printPlugins.printPlugins(argv) +}) + +test('should throw on package not found', t => { + t.plan(1) + + const oldStop = printPlugins.stop + t.teardown(() => { printPlugins.stop = oldStop }) + printPlugins.stop = function (err) { + t.ok(/Cannot find module 'unknown-package'/.test(err.message), err.message) + } + + const argv = ['./test/data/package-not-found.js'] + printPlugins.printPlugins(argv) +}) + +test('should throw on parsing error', t => { + t.plan(1) + + const oldStop = printPlugins.stop + t.teardown(() => { printPlugins.stop = oldStop }) + printPlugins.stop = function (err) { + t.equal(err.constructor, SyntaxError) + } + + const argv = ['./test/data/parsing-error.js'] + printPlugins.printPlugins(argv) +}) + +test('should exit without error on help', t => { + const exit = process.exit + process.exit = sinon.spy() + + t.teardown(() => { + process.exit = exit + }) + + const argv = ['-h', 'true'] + printPlugins.printPlugins(argv) + + t.ok(process.exit.called) + t.equal(process.exit.lastCall.args[0], undefined) + + t.end() +}) + +test('should print plugins of server with an async/await plugin', async t => { + const nodeMajorVersion = process.versions.node.split('.').map(x => parseInt(x, 10))[0] + if (nodeMajorVersion < 7) { + t.pass('Skip because Node version < 7') + return t.end() + } + + t.plan(3) + + const spy = sinon.spy() + const command = proxyquire('../print-plugins', { + './log': spy + }) + const argv = ['./examples/async-await-plugin.js'] + const fastify = await command.printPlugins(argv) + + await fastify.close() + t.ok(spy.called) + t.same(spy.args[0][0], 'debug') + t.match(spy.args[0][1], /bound root \d+ ms\n├── bound _after \d+ ms\n├─┬ async function \(fastify, options\) { -- fastify\.get\('\/', async function \(req, reply\) { \d+ ms\n│ ├── bound _after \d+ ms\n│ └── bound _after \d+ ms\n└── bound _after \d+ ms\n/) +})