Spec for custom commands + deployment strategies in Sails.js
Currently Sails.js does not have the ability to create custom commands on the sails command line tool. Hereâs how we can do it.
json { "commands": { "deploy": "sails-deploy" }, "deploy": { "module": "sails-deploy-azure" } }
When the CLI is run as sails deploy, since this is an unrecognized command, Sails should load base config using the rc module (see bin/sails-generate.js for an example). This will load any relevant config from the .sailsrc file(s) which apply to the current app.
Assuming we have a .sailsrc like the one above, there will be new commands and deploy namespaces in the appâs configuration object. So weâd be able to access:
js configObjectReturnedByRC.deploy.module // => 'sails-deploy-azure' configObjectReturnedByRC.commands.deploy // => 'sails-deploy'
Sails core should then check to see if commands exist, and if so, loop through each one and see if there is a command matching what the user is trying to do (e.g. âdeployâ). If no match is found, cli does exactly what it does today (show usage instructions). But if a match is found, then the CLI requires the configured module (âsails-deployâ) from the context of the current sails projectâs âappPathâ (configObjectReturnedByRC.appPath). For example, something like this (warning: I did not run or double-check this code):
â`js var configObjectReturnedByRC = require(ârcâ)('sailsâ); var matchingCommand = _.find(configObjectReturnedByRC.commands || [], function (val, key){}); if (!matchingCommand) { /*⌠handle this case exactly as happens currently, then return early */ return; } var pathToRequire = require('pathâ).resolve(configObjectReturnedByRC.appPath||process.cwd(), 'node_modulesâ, matchingCommand); var customCmd; try { customCmd = require(pathToRequire); } catch (e){ // check if this is a MODULE_NOT_FOUND error (look at e.type or e.code, canât remember) // if not, log an error message explaining that the custom command module referenced in one of the userâs sailsrc files has an issue (then dump the original error stack and bail out/return early) // if so, log a nice error message explaining the command the user typed was recognized as a custom command defined in one of their sailsrc files, but it could not be required from the specified path. Then bail out (return early) }
// Finally, if that all worked, run the custom command. customCmd({ config: configObjectReturnedByRC }, function (err){ if (err) { // log msg explaining deployment failed, then log the original error stack for details. return; } // log msg explaining deployment was successful. }); â`
Note that this approach means users will need to npm install sails-noop --save in their project in order to run sails noop on the command line.
Custom sails commands are npm packages that export a function w/ two arguments- options and cb (following standard node conventions).
For example, hereâs a very simple no-op command as an example (sails-noop). Aside from its package.json , it has only one file: index.js.
â`js /** * Module dependences */ // âŚ
/** * Usage: * $ sails noop */ module.exports = function noopCmd (options, cb) { // options.config is provided with the raw config that Sails core gathered by running rc. return cb(); }; â`
====================================================
sails deploy can be implemented as a custom command as specced out above.
The job of the sails-deploy package is to require() the configured strategy, then run it. Eventually it can end up taking responsibility for generic shared logic between deployment strategies (e.g. talking to github, pulling/pushing/tagging, etc.)- but we wonât know the right things to generalize until later when we have a few different strategies to look at. For now, weâll just run the strategy.
This will be pretty similar to what we just did (which is also more or less how generators, adapters, and hooks work). In the deploy object in the sailsrc file, the module property can be specified as any of the following: 1. an absolute path (if it starts with /) 2. a relative path from the sailsrc file (if it starts with ./ or ../), or otherwise 3. an NPM package name (in which sails will attempt to require() the deployment strategy module from the node_modules folder within the sails projectâs appPath).
Note that this approach means a user will need to npm install sails-deploy-noop --save in her project and configure "deploy": {"module": "sails-deploy-noop"} in her sailsrc file. Then she can run sails deploy on the command line and sails-deploy-noop will be used automatically.
sails-deploy-* strategies
The simplest no-op deploy strategy (e.g. sails-deploy-noop) is simply an npm package with a package.json and an index.js file:
â`js /** * Module dependences */ // âŚ
module.exports = function sailsDeployNoop(options, cb) { // options.config is provided with the raw config that Sails core gathered by running rc. return cb(); }; â`
Again, we can get fancier here eventually as far as what happens in sails-deploy itself, but for now, a simple function is the right approach.
If anyone else is interested in getting involved on this effort, please let me know here (or on twitter @mikermcneil.) Thanks!
(this was originally proposed in this PR)