├── .editorconfig ├── .gitignore ├── .jshintrc ├── README.md ├── bin ├── machinepack-about.js ├── machinepack-add.js ├── machinepack-browse.js ├── machinepack-browserify.js ├── machinepack-compare.js ├── machinepack-cp.js ├── machinepack-exec.js ├── machinepack-info.js ├── machinepack-init.js ├── machinepack-ls.js ├── machinepack-mv.js ├── machinepack-rm.js ├── machinepack-scrub.js └── machinepack.js ├── package.json └── templates ├── README.template.md └── travis.template.yml /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ############################ 2 | # npm 3 | ############################ 4 | node_modules 5 | npm-debug.log 6 | 7 | 8 | ############################ 9 | # tmp, editor & OS files 10 | ############################ 11 | .tmp 12 | *.swo 13 | *.swp 14 | *.swn 15 | *.swm 16 | .DS_STORE 17 | *# 18 | *~ 19 | .idea 20 | nbproject 21 | 22 | 23 | ############################ 24 | # Tests 25 | ############################ 26 | testApp 27 | coverage 28 | 29 | 30 | ############################ 31 | # Other 32 | ############################ 33 | .node_history 34 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // ┬┌─┐╦ ╦╦╔╗╔╔╦╗┬─┐┌─┐ 3 | // │└─┐╠═╣║║║║ ║ ├┬┘│ 4 | // o└┘└─┘╩ ╩╩╝╚╝ ╩ ┴└─└─┘ 5 | // 6 | // This file (`.jshintrc`) exists to help with consistency of code 7 | // throughout this package, and throughout Sails and the Node-Machine project. 8 | // 9 | // To review what each of these options mean, see: 10 | // http://jshint.com/docs/options 11 | // 12 | // (or: https://github.com/jshint/jshint/blob/master/examples/.jshintrc) 13 | 14 | 15 | 16 | ////////////////////////////////////////////////////////////////////// 17 | // NOT SUPPORTED IN SOME JSHINT VERSIONS SO LEAVING COMMENTED OUT: 18 | ////////////////////////////////////////////////////////////////////// 19 | // Prevent overwriting prototypes of native classes like `Array`. 20 | // (doing this is _never_ ok in any of our packages that are intended 21 | // to be used as dependencies of other developers' modules and apps) 22 | // "freeze": true, 23 | ////////////////////////////////////////////////////////////////////// 24 | 25 | 26 | ////////////////////////////////////////////////////////////////////// 27 | // EVERYTHING ELSE: 28 | ////////////////////////////////////////////////////////////////////// 29 | 30 | // Allow the use of `eval` and `new Function()` 31 | // (we sometimes actually need to use these things) 32 | "evil": true, 33 | 34 | // Tolerate funny-looking dashes in RegExp literals. 35 | // (see https://github.com/jshint/jshint/issues/159#issue-903547) 36 | "regexdash": true, 37 | 38 | // The potential runtime "Environments" (as defined by jshint) 39 | // that the _style_ of code written in this package should be 40 | // compatible with (not the code itself, of course). 41 | "browser": true, 42 | "node": true, 43 | "wsh": true, 44 | 45 | // Tolerate the use `[]` notation when dot notation would be possible. 46 | // (this is sometimes preferable for readability) 47 | "sub": true, 48 | 49 | // Do NOT suppress warnings about mixed tabs and spaces 50 | // (two spaces always, please; see `.editorconfig`) 51 | "smarttabs": false, 52 | 53 | // Suppress warnings about trailing whitespace 54 | // (this is already enforced by the .editorconfig, so no need to warn as well) 55 | "trailing": false, 56 | 57 | // Suppress warnings about the use of expressions where fn calls or assignments 58 | // are expected, and about using assignments where conditionals are expected. 59 | // (while generally a good idea, without this setting, JSHint needlessly lights up warnings 60 | // in existing, working code that really shouldn't be tampered with. Pandora's box and all.) 61 | "expr": true, 62 | "boss": true, 63 | 64 | // Do NOT suppress warnings about using functions inside loops 65 | // (in the general case, we should be using iteratee functions with `_.each()` 66 | // or `Array.prototype.forEach()` instead of `for` or `while` statements 67 | // anyway. This warning serves as a helpful reminder.) 68 | "loopfunc": false, 69 | 70 | // Suppress warnings about "weird constructions" 71 | // i.e. allow code like: 72 | // ``` 73 | // (new (function OneTimeUsePrototype () { } )) 74 | // ``` 75 | // 76 | // (sometimes order of operations in JavaScript can be scary. There is 77 | // nothing wrong with using an extra set of parantheses when the mood 78 | // strikes or you get "that special feeling".) 79 | "supernew": true, 80 | 81 | // Do NOT allow backwards, node-dependency-style commas. 82 | // (while this code style choice was used by the project in the past, 83 | // we have since standardized these practices to make code easier to 84 | // read, albeit a bit less exciting) 85 | "laxcomma": false, 86 | 87 | // Strictly enforce the consistent use of single quotes. 88 | // (this is a convention that was established primarily to make it easier 89 | // to grep [or FIND+REPLACE in Sublime] particular string literals in 90 | // JavaScript [.js] files. Note that JSON [.json] files are, of course, 91 | // still written exclusively using double quotes around key names and 92 | // around string literals.) 93 | "quotmark": "single", 94 | 95 | // Do NOT suppress warnings about the use of `==null` comparisons. 96 | // (please be explicit-- use Lodash or `require('util')` and call 97 | // either `.isNull()` or `.isUndefined()`) 98 | "eqnull": false, 99 | 100 | // Strictly enforce the use of curly braces with `if`, `else`, and `switch` 101 | // as well as, much less commonly, `for` and `while` statements. 102 | // (this is just so that all of our code is consistent, and to avoid bugs) 103 | "curly": true, 104 | 105 | // Strictly enforce the use of `===` and `!==`. 106 | // (this is always a good idea. Check out "Truth, Equality, and JavaScript" 107 | // by Angus Croll [the author of "If Hemmingway Wrote JavaScript"] for more 108 | // explanation as to why.) 109 | "eqeqeq": true, 110 | 111 | // Allow initializing variables to `undefined`. 112 | // For more information, see: 113 | // • https://jslinterrors.com/it-is-not-necessary-to-initialize-a-to-undefined 114 | // • https://github.com/jshint/jshint/issues/1484 115 | // 116 | // (it is often very helpful to explicitly clarify the initial value of 117 | // a local variable-- especially for folks new to more advanced JavaScript 118 | // and who might not recognize the subtle, yet critically important differences between our seemingly 119 | // between `null` and `undefined`, and the impact on `typeof` checks) 120 | "-W080": true 121 | 122 | } 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | node-machine logo 3 | machinepack (CLI) 4 |

5 | 6 | ### [Docs](http://node-machine.org/implementing)   [Browse other machines](http://node-machine.org/machinepacks)   [FAQ](http://node-machine.org/implementing/FAQ)   [Newsgroup](https://groups.google.com/forum/?hl=en#!forum/node-machine) 7 | 8 | Command-line tool for working with machinepacks and machines. 9 | 10 | ## Installation   [![NPM version](https://badge.fury.io/js/machinepack.svg)](http://badge.fury.io/js/machinepack) 11 | 12 | ```sh 13 | $ npm install -g machinepack 14 | ``` 15 | 16 | ## Usage 17 | 18 | > Note that you'll also want to grab the [Yeoman generator](http://github.com/node-machine/generator-machinepack) 19 | 20 | You should check out [http://node-machine.org/implementing](http://node-machine.org/implementing) for an in-depth tutorial, but here are a few highlights: 21 | 22 | ```bash 23 | # open generated manpage on node-machine.org in your browser of choice 24 | mp browse 25 | 26 | # run a machine 27 | # (theres an interactive prompt- you'll get to choose from a list, then be prompted to provide values for required inputs) 28 | # (supports json entry and validation, re-running using command-line flags, and protects inputs marked as "protected" so they don't show up in your bash history) 29 | mp exec 30 | 31 | # clean everything up: (re)scaffold JSON test files, (re)generate readme using latest metadata, make sure repo url is in package.json, etc. 32 | mp scrub 33 | 34 | # list machines (useful for remembering wtf you're doing) 35 | mp ls 36 | 37 | # add new machine w/ identity="do-some-stuff" and start interactive prompt to get the rest of the necessary info 38 | mp add do-some-stuff 39 | 40 | # copy machine (useful for quickly creating similar machines) 41 | mp cp foo bar 42 | 43 | # rename machine (useful for fixing misspellings) 44 | mp mv initiate-denk-party initiate-dance-party 45 | ``` 46 | 47 | 48 | ## About   [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/node-machine/general?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 49 | 50 | This CLI tool is just sugar on top of the machine specification. That said, it helps _a lot_, and if you're building a pack, I highly recommend checking it out. 51 | 52 | ## License 53 | 54 | MIT © 2015 Mike McNeil 55 | -------------------------------------------------------------------------------- /bin/machinepack-about.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | 7 | var util = require('util'); 8 | var program = require('commander'); 9 | var chalk = require('chalk'); 10 | 11 | 12 | 13 | var VERSION = require('../package.json').version; 14 | 15 | 16 | 17 | program 18 | .usage('[options] ') 19 | .parse(process.argv); 20 | 21 | 22 | var ABOUT = util.format( 23 | ' '+'\n'+ 24 | ' ______ '+'\n'+ 25 | ' / \\ %s'+'\n'+ 26 | ' / %s %s \\ %s'+'\n'+ 27 | ' \\ / '+'\n'+ 28 | ' \\______/ %s '+'\n'+ 29 | ' ', 30 | // Strings 31 | chalk.bold(chalk.cyan('machinepack'))+chalk.reset(' (CLI Tool)'), 32 | chalk.bold('|'),chalk.bold('|'), 33 | chalk.gray('v'+VERSION), 34 | chalk.underline('http://node-machine.org') 35 | ); 36 | 37 | console.log(ABOUT); 38 | 39 | program.outputHelp(); 40 | console.log(); 41 | -------------------------------------------------------------------------------- /bin/machinepack-add.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | 7 | var Path = require('path'); 8 | var program = require('commander'); 9 | var Machinepacks = require('machinepack-localmachinepacks'); 10 | 11 | 12 | program 13 | .usage('[options]') 14 | .parse(process.argv); 15 | 16 | 17 | Machinepacks.promptAboutNewMachine({ 18 | identity: program.args[0] 19 | }).exec({ 20 | error: function (err) { 21 | console.error('Unexpected error occurred:\n', err); 22 | }, 23 | success: function (answers){ 24 | 25 | var newMachineMetadata = { 26 | machinepackRootPath: process.cwd(), 27 | identity: answers.identity, 28 | friendlyName: answers.friendlyName, 29 | description: (function (){ 30 | if (!answers.description) return undefined; 31 | return answers.description; 32 | })(), 33 | extendedDescription: (function (){ 34 | if (!answers.extendedDescription) return undefined; 35 | return answers.extendedDescription; 36 | })(), 37 | inputs: {}, 38 | exits: { 39 | success: { 40 | variableName: 'result', 41 | description: 'Done.' 42 | } 43 | }, 44 | }; 45 | 46 | if (typeof answers.defaultExit !== 'undefined') { 47 | newMachineMetadata.defaultExit = answers.defaultExit; 48 | } 49 | if (typeof answers.idempotent !== 'undefined') { 50 | newMachineMetadata.idempotent = answers.idempotent; 51 | } 52 | if (typeof answers.sync !== 'undefined') { 53 | newMachineMetadata.sync = answers.sync; 54 | } 55 | if (typeof answers.cacheable !== 'undefined') { 56 | newMachineMetadata.cacheable = answers.cacheable; 57 | } 58 | if (typeof answers.moreInfoUrl !== 'undefined') { 59 | newMachineMetadata.moreInfoUrl = answers.moreInfoUrl; 60 | } 61 | 62 | Machinepacks.addMachine(newMachineMetadata).exec({ 63 | error: function (err) { 64 | console.error('Error generating new machine:\n', err); 65 | }, 66 | success: function (){ 67 | // Done! 68 | console.log('New machine (`%s`) successfully added to machinepack.', answers.identity); 69 | } 70 | }); 71 | } 72 | }); 73 | 74 | -------------------------------------------------------------------------------- /bin/machinepack-browse.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 4 | /** 5 | * Module dependencies 6 | */ 7 | 8 | var program = require('commander'); 9 | var chalk = require('chalk'); 10 | var LocalMachinepacks = require('machinepack-localmachinepacks'); 11 | 12 | 13 | 14 | 15 | program 16 | .usage('[toWhat]') 17 | // .command('docs', 'browse usage docs, like a manpage') 18 | // .command('npm', 'browse pack on npmjs.org') 19 | // .command('source', 'browse the changelog / repo') 20 | // .command('tests', 'browse status of automated tests, e.g. on Travis CI') 21 | .parse(process.argv); 22 | 23 | 24 | 25 | LocalMachinepacks.browseToPackUrl({ 26 | dir: process.cwd(), 27 | 28 | // If optional command-line argument was provided, use it as the `toWhat` 29 | toWhat: program.args[0] || '' 30 | 31 | }).exec({ 32 | error: function(err) { 33 | console.error(chalk.red('Unexpected error occurred:\n'), err); 34 | }, 35 | notMachinepack: function() { 36 | console.error('This is ' + chalk.red('not a machinepack') + '.'); 37 | console.error('Be sure and check that the package.json file has a valid `machinepack` property, or run `machinepack init` if you aren\'t sure.'); 38 | }, 39 | success: function(url) { 40 | console.log('Opening %s...',chalk.underline(url)); 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /bin/machinepack-browserify.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('machine-as-script')({ 4 | 5 | friendlyName: 'Bundle machinepack', 6 | 7 | 8 | description: 'Bundle the specified machinepack into a single JavaScript file for use in the browser.', 9 | 10 | 11 | extendedDescription: 'The browserified JavaScript will be exposed within a [umd](https://github.com/forbeslindesay/umd) wrapper.', 12 | 13 | 14 | cacheable: true, 15 | 16 | 17 | inputs: { 18 | 19 | dir: { 20 | description: 'The absolute path to the machinepack directory (if path is relative, will be resolved from pwd). Defaults to current working directory.', 21 | example: '/Users/mikermcneil/machinepack-whatever', 22 | defaultsTo: './' 23 | }, 24 | 25 | exportAs: { 26 | description: 'The variable name under which to expose this machinepack; either on the `window` global, or using the semantics of another detected module system (like AMD/RequireJS).', 27 | extendedDescription: 'If left unspecified, this will be the `friendlyName` of the machinepack.', 28 | example: 'Whatever' 29 | }, 30 | 31 | destination: { 32 | description: 'An optional destination path for the browserified output script. Defaults to "./for-browser.js" in the current directory.', 33 | example: './for-browser.js', 34 | defaultsTo: './for-browser.js' 35 | } 36 | 37 | }, 38 | 39 | 40 | exits: { 41 | 42 | notMachinepack: { 43 | description: 'The specified path is not the root directory of a machinepack' 44 | }, 45 | 46 | success: { 47 | variableName: 'outputPath', 48 | example: 'The output path where the browserified code file was written.' 49 | }, 50 | 51 | }, 52 | 53 | fn: function (inputs, exits) { 54 | 55 | var Path = require('path'); 56 | var MPBrowserify = require('machinepack-browserify'); 57 | var MPMachines = require('machinepack-localmachinepacks'); 58 | var Filesystem = require('machinepack-fs'); 59 | 60 | // Ensure inputs.dir and inputs.destination are absolute paths 61 | // by resolving them from the current working directory. 62 | inputs.dir = Path.resolve(inputs.dir); 63 | inputs.destination = Path.resolve(inputs.destination); 64 | 65 | // Read and parse the package.json file of the local pack in the specified directory. 66 | MPMachines.readPackageJson({ 67 | dir: inputs.dir 68 | }).exec({ 69 | 70 | // An unexpected error occurred. 71 | error: exits.error, 72 | 73 | // The specified path is not the root directory of a machinepack 74 | notMachinepack: exits.notMachinepack, 75 | 76 | // OK. 77 | success: function(packMetadata) { 78 | 79 | // Handle case where `exportAs` input was left unspecified. 80 | if (typeof inputs.exportAs === 'undefined') { 81 | inputs.exportAs = packMetadata.variableName; 82 | } 83 | 84 | // Bundle the machinepack and its dependencies into a single JavaScript file for use on the client. 85 | MPBrowserify.bundle({ 86 | path: inputs.dir, 87 | exportAs: inputs.exportAs 88 | }).exec({ 89 | // An unexpected error occurred. 90 | error: exits.error, 91 | // OK. 92 | success: function (code) { 93 | 94 | // Generate a file on the local filesystem using the specified utf8 string as its contents. 95 | Filesystem.write({ 96 | destination: inputs.destination, 97 | string: code, 98 | force: true 99 | }).exec({ 100 | // An unexpected error occurred. 101 | error: exits.error, 102 | // OK. 103 | success: function() { 104 | return exits.success(inputs.destination); 105 | } 106 | }); 107 | 108 | } 109 | }); 110 | } 111 | }); 112 | } 113 | }).exec({ 114 | // An unexpected error occurred. 115 | error: function(err) { 116 | console.error('An error occurred:\n',err.stack); 117 | }, 118 | 119 | // OK. 120 | success: function (outputPath){ 121 | console.log('New JavaScript file which exposes a browser-ready version of this pack has been created at "%s".',outputPath); 122 | console.log('To load onto page, just include w/ normal '); 124 | console.log(); 125 | console.log('For usage information, see:\nhttp://node-machine.org/machinepack-browserify/bundle-machinepack'); 126 | } 127 | }); 128 | 129 | -------------------------------------------------------------------------------- /bin/machinepack-compare.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('machine-as-script')({ 4 | 5 | 6 | args: ['pathToAbstractPack'], 7 | 8 | 9 | friendlyName: 'Compare machinepack', 10 | 11 | 12 | description: 13 | 'Check if this machinepack satisfies the interface described by abstract pack '+ 14 | 'in another directory.', 15 | 16 | 17 | extendedDescription: 18 | 'The comparison is semantic; i.e. it ignores metadata like `extendedDescription`.', 19 | 20 | 21 | cacheable: true, 22 | 23 | 24 | inputs: { 25 | 26 | dir: { 27 | description: 'The path to the machinepack directory.', 28 | extendedDescription: 'Absolute path recommended. If provided path is relative, will be resolved from pwd. Defaults to current working directory.', 29 | example: '/Users/mikermcneil/code/machinepack-whatever', 30 | defaultsTo: './' 31 | }, 32 | 33 | pathToAbstractPack: { 34 | description: 'The path to the directory of the abstract machinepack.', 35 | extendedDescription: 'Absolute path recommended. If provided path is relative, will be resolved from pwd.', 36 | example: '/Users/mikermcneil/code/waterline-driver-interface', 37 | required: true 38 | }, 39 | 40 | verbose: { 41 | description: 'If set, harmless compatibility notices will also be displayed.', 42 | example: false, 43 | defaultsTo: false 44 | } 45 | 46 | // TODO: build in certain abstract interfaces and allow them to be referenced 47 | // by name as an alternative to specifying a path to an abstract pack; e.g. 48 | // ``` 49 | // mp compare driver 50 | // mp compare db-adapter 51 | // mp compare fs-adapter 52 | // mp compare view-engine 53 | // # etc. 54 | // ``` 55 | 56 | }, 57 | 58 | 59 | exits: { 60 | 61 | notMachinepack: { 62 | description: 'The source path does not resolve to the root directory of a machinepack.' 63 | }, 64 | 65 | notAbstractMachinepack: { 66 | description: 67 | 'The provided path to the abstract pack interface does not resolve to the '+ 68 | 'root directory of an abstract machinepack.' 69 | }, 70 | 71 | success: { 72 | outputVariableName: 'comparison', 73 | example: { 74 | errors: [{}], 75 | warnings: [{}], 76 | notices: [{}], 77 | sourcePackInfo: { npmPackageName: 'machinepack-whatever' }, 78 | abstractPackInfo: { npmPackageName: 'waterline-driver-interface' }, 79 | generatedWithOpts: '===' 80 | } 81 | } 82 | 83 | }, 84 | 85 | fn: function (inputs, exits) { 86 | 87 | var Path = require('path'); 88 | var _ = require('lodash'); 89 | var async = require('async'); 90 | var rttc = require('rttc'); 91 | var Machinepacks = require('machinepack-localmachinepacks'); 92 | 93 | // Ensure inputs.dir and inputs.pathToAbstractPack are absolute paths 94 | // by resolving them from the current working directory. 95 | inputs.dir = Path.resolve(inputs.dir); 96 | inputs.pathToAbstractPack = Path.resolve(inputs.pathToAbstractPack); 97 | 98 | // Load the signatures for the source pack and the abstract pack. 99 | async.auto({ 100 | 101 | source: function loadSourceMachinepack(next){ 102 | Machinepacks.getSignature({ 103 | dir: inputs.dir 104 | }).exec(next); 105 | }, 106 | 107 | abstract: function loadAbstractPackInterface(next) { 108 | Machinepacks.getSignature({ 109 | dir: inputs.pathToAbstractPack 110 | }).exec(next); 111 | } 112 | 113 | }, function afterwards(err, async_data){ 114 | if (err) { return exits(err); } 115 | // console.log(async_data.abstract); 116 | 117 | 118 | // Now build the report which compares the two packs. 119 | var comparisonReport = { 120 | errors: [], 121 | warnings: [], 122 | notices: [], 123 | sourcePackInfo: { npmPackageName: async_data.source.pack.npmPackageName }, 124 | abstractPackInfo: { npmPackageName: async_data.abstract.pack.npmPackageName }, 125 | generatedWithOpts: inputs, 126 | }; 127 | // comparisonReport = FIXTURE_REPRESENTING_EXAMPLE_OF_RESULTS_FROM_COMPARISON; 128 | 129 | 130 | // Here we look for: 131 | // *NOTICES* 132 | // • unrecognized machines 133 | // *WARNINGS* 134 | // • unrecognized inputs 135 | // • unrecognized exits 136 | // *ERRORS* 137 | // • missing machines 138 | // • missing inputs 139 | // • missing exits 140 | // • incompatible machine details (/guarantees) 141 | // • incompatible inputs 142 | // • incompatible exits 143 | 144 | // First look for unrecognized machines. 145 | _.each(async_data.source.machines, function (sourceMachineDef){ 146 | var isRecognized = _.contains(_.pluck(async_data.abstract.machines, 'identity'), sourceMachineDef.identity); 147 | if (!isRecognized) { 148 | comparisonReport.notices.push({ 149 | problem: 'unrecognizedMachine', 150 | machine: sourceMachineDef.identity 151 | }); 152 | } 153 | }); 154 | 155 | 156 | // Then look for missing machines, inputs and exits, as well as 157 | // incompatible machine details, incompatible inputs, and incompatible 158 | // exits AND unrecognized inputs & exits. 159 | _.each(async_data.abstract.machines, function (abstractMachineDef){ 160 | 161 | // Check that the machine exists. 162 | var sourceMachineDef = _.find(async_data.source.machines, { identity: abstractMachineDef.identity }); 163 | if (!sourceMachineDef) { 164 | comparisonReport.errors.push({ 165 | problem: 'missingMachine', 166 | machine: abstractMachineDef.identity 167 | }); 168 | return; 169 | } 170 | 171 | // Check for incompatibilities in machine details. 172 | var incompatibleDetailsFound; 173 | var machineDetailsIncompatError = { 174 | problem: 'incompatibleMachineDetails', 175 | machine: abstractMachineDef.identity, 176 | expecting: {} 177 | }; 178 | var isSourceMachineCacheable = (sourceMachineDef.cacheable === true || sourceMachineDef.sideEffects === 'cacheable'); 179 | var isSourceMachineIdempotent = (sourceMachineDef.idempotent === true || sourceMachineDef.sideEffects === 'idempotent'); 180 | var isAbstractMachineCacheable = (abstractMachineDef.cacheable === true || abstractMachineDef.sideEffects === 'cacheable'); 181 | var isAbstractMachineIdempotent = (abstractMachineDef.idempotent === true || abstractMachineDef.sideEffects === 'idempotent'); 182 | 183 | if (isAbstractMachineCacheable) { 184 | if (!isSourceMachineCacheable) { 185 | incompatibleDetailsFound = true; 186 | machineDetailsIncompatError.expecting.sideEffects = 'cacheable'; 187 | } 188 | } 189 | else if (isAbstractMachineIdempotent) { 190 | // It's ok for the source machine to make a STRONGER guarantee 191 | // (i.e. if abstract machine declares itself "idempotent", then it's still ok if an implementing source machine declares itself "cacheable") 192 | if (!isSourceMachineIdempotent && !isSourceMachineCacheable) { 193 | incompatibleDetailsFound = true; 194 | machineDetailsIncompatError.expecting.sideEffects = 'idempotent'; 195 | } 196 | } 197 | else { 198 | // It's ok for the source machine to make a STRONGER guarantee. 199 | // (i.e. if abstract machine makes no side effects declaration, then it's still ok 200 | // for an implementing source machine to declare itself "cacheable" or "idempotent") 201 | } 202 | 203 | if (abstractMachineDef.sync === true) { 204 | if (!sourceMachineDef.sync) { 205 | incompatibleDetailsFound = true; 206 | machineDetailsIncompatError.expecting.sync = true; 207 | } 208 | } 209 | else { 210 | if (sourceMachineDef.sync) { 211 | incompatibleDetailsFound = true; 212 | machineDetailsIncompatError.expecting.sync = false; 213 | } 214 | } 215 | 216 | if (abstractMachineDef.habitat) { 217 | if (sourceMachineDef.habitat !== abstractMachineDef.habitat) { 218 | incompatibleDetailsFound = true; 219 | machineDetailsIncompatError.expecting.habitat = abstractMachineDef.habitat; 220 | } 221 | } 222 | 223 | if (incompatibleDetailsFound) { 224 | comparisonReport.errors.push(machineDetailsIncompatError); 225 | } 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | // Check for unrecognized inputs. 234 | _.each(sourceMachineDef.inputs, function (unused, inputCodeName){ 235 | var isRecognized = _.contains(_.keys(abstractMachineDef.inputs), inputCodeName); 236 | if (!isRecognized) { 237 | comparisonReport.warnings.push({ 238 | problem: 'unrecognizedInput', 239 | machine: abstractMachineDef.identity, 240 | input: inputCodeName 241 | }); 242 | } 243 | }); 244 | 245 | 246 | 247 | // Check for missing or incompatible inputs. 248 | _.each(abstractMachineDef.inputs, function (abstractInputDef, abstractInputCodeName){ 249 | 250 | // Missing 251 | var sourceInputDef = sourceMachineDef.inputs[abstractInputCodeName]; 252 | if (!sourceInputDef) { 253 | comparisonReport.errors.push({ 254 | problem: 'missingInput', 255 | machine: abstractMachineDef.identity, 256 | input: abstractInputCodeName 257 | }); 258 | return; 259 | } 260 | 261 | // Incompatible 262 | var isIncompat; 263 | var abstractTypeSchema = rttc.infer(abstractInputDef.example); 264 | var incompatError = { 265 | problem: 'incompatibleInput', 266 | machine: abstractMachineDef.identity, 267 | input: abstractInputCodeName, 268 | expecting: {} 269 | }; 270 | // should be required 271 | if ( abstractInputDef.required ) { 272 | if (!sourceInputDef.required) { 273 | isIncompat = true; 274 | incompatError.expecting.required = true; 275 | } 276 | } 277 | // should NOT be required 278 | else { 279 | if (sourceInputDef.required) { 280 | isIncompat = true; 281 | incompatError.expecting.required = false; 282 | } 283 | } 284 | // should be readOnly 285 | if ( abstractInputDef.readOnly ) { 286 | if (!sourceInputDef.readOnly) { 287 | isIncompat = true; 288 | incompatError.expecting.readOnly = true; 289 | } 290 | } 291 | // should NOT be readOnly 292 | else { 293 | if (sourceInputDef.readOnly) { 294 | isIncompat = true; 295 | incompatError.expecting.readOnly = false; 296 | } 297 | } 298 | // should have `protect: true` 299 | if (abstractInputDef.protect) { 300 | if (!sourceInputDef.protect) { 301 | isIncompat = true; 302 | incompatError.expecting.protect = true; 303 | } 304 | } 305 | // should NOT have `protect: true` 306 | else { 307 | if (sourceInputDef.protect) { 308 | isIncompat = true; 309 | incompatError.expecting.protect = false; 310 | } 311 | } 312 | // should be constant 313 | if ( abstractInputDef.constant ) { 314 | if (!sourceInputDef.constant) { 315 | isIncompat = true; 316 | incompatError.expecting.constant = true; 317 | } 318 | } 319 | // should NOT be constant 320 | else { 321 | if (sourceInputDef.constant) { 322 | isIncompat = true; 323 | incompatError.expecting.constant = false; 324 | } 325 | } 326 | // should be an exemplar 327 | if (abstractInputDef.isExemplar) { 328 | if (!sourceInputDef.isExemplar) { 329 | isIncompat = true; 330 | incompatError.expecting.isExemplar = true; 331 | } 332 | } 333 | // should NOT be an exemplar 334 | else { 335 | if (sourceInputDef.isExemplar) { 336 | isIncompat = true; 337 | incompatError.expecting.isExemplar = false; 338 | } 339 | } 340 | // should have a defaultsTo 341 | if ( !_.isUndefined(abstractInputDef.defaultsTo) ) { 342 | if ( _.isUndefined(sourceInputDef.defaultsTo) ) { 343 | isIncompat = true; 344 | incompatError.expecting.defaultsTo = abstractInputDef.defaultsTo; 345 | } 346 | // and it should be like this 347 | else { 348 | var areDefaultTosEqual = rttc.isEqual( abstractInputDef.defaultsTo, sourceInputDef.defaultsTo, abstractTypeSchema ); 349 | if (!areDefaultTosEqual) { 350 | isIncompat = true; 351 | incompatError.expecting.defaultsTo = abstractInputDef.defaultsTo; 352 | } 353 | } 354 | } 355 | // should NOT have a defaultsTo 356 | else { 357 | if ( !_.isUndefined(sourceInputDef.defaultsTo) ) { 358 | isIncompat = true; 359 | ///////////////////////////////////////////////////////////////////////////// 360 | // Note: We might consider making this a warning instead of an error. 361 | ///////////////////////////////////////////////////////////////////////////// 362 | incompatError.expecting.noDefaultsTo = '`defaultsTo` should not be specified.'; 363 | } 364 | } 365 | 366 | // Should have an example which implies an equivalent type schema 367 | // (note that `example` might not be defined if `isExemplar: true`) 368 | if (!_.isUndefined(sourceInputDef.example)) { 369 | var sourceTypeSchema = rttc.infer(sourceInputDef.example); 370 | if (!_.isEqual(abstractTypeSchema, sourceTypeSchema)) { 371 | isIncompat = true; 372 | incompatError.expecting.example = abstractInputDef.example; 373 | } 374 | } 375 | 376 | ///////////////////////////////////////////////////////////////////////////// 377 | // Note: We might consider checking tolerating not-equal _but compatible_ 378 | // type schemas (but still push warning either way). E.g.: 379 | // ``` 380 | // rttc.validate(sourceInputDef.example, abstractTypeSchema); 381 | // ``` 382 | ///////////////////////////////////////////////////////////////////////////// 383 | 384 | if (isIncompat) { 385 | comparisonReport.errors.push(incompatError); 386 | } 387 | 388 | }); 389 | 390 | 391 | 392 | 393 | 394 | 395 | // Check for unrecognized exits. 396 | _.each(sourceMachineDef.exits, function (unused, exitCodeName){ 397 | // If this is the `error` exit, then we won't consider it unrecognized. 398 | // (But it really shouldn't be there in the first place.) 399 | if (exitCodeName === 'error') { return; } 400 | 401 | var isRecognized = _.contains(_.keys(abstractMachineDef.exits), exitCodeName); 402 | if (!isRecognized) { 403 | comparisonReport.warnings.push({ 404 | problem: 'unrecognizedExit', 405 | machine: abstractMachineDef.identity, 406 | exit: exitCodeName 407 | }); 408 | } 409 | }); 410 | 411 | // Check for missing or incompatible exits. 412 | _.each(abstractMachineDef.exits, function (abstractExitDef, abstractExitCodeName){ 413 | 414 | // If this is the `error` exit, then we won't check if it's missing, 415 | // and we won't bother comparing its properties. 416 | // (it really shouldn't be there in the first place.) 417 | if (abstractExitCodeName === 'error') { return; } 418 | 419 | // Missing 420 | var sourceExitDef = sourceMachineDef.exits[abstractExitCodeName]; 421 | if (!sourceExitDef) { 422 | comparisonReport.errors.push({ 423 | problem: 'missingExit', 424 | machine: abstractMachineDef.identity, 425 | exit: abstractExitCodeName 426 | }); 427 | return; 428 | } 429 | 430 | // Incompatible 431 | var isIncompat; 432 | var sourceOutputExemplar; 433 | if (!_.isUndefined(sourceExitDef.outputExample)) { 434 | sourceOutputExemplar = sourceExitDef.outputExample; 435 | } 436 | else { 437 | sourceOutputExemplar = sourceExitDef.example; 438 | } 439 | var abstractOutputExemplar; 440 | if (!_.isUndefined(abstractExitDef.outputExample)) { 441 | abstractOutputExemplar = abstractExitDef.outputExample; 442 | } 443 | else { 444 | abstractOutputExemplar = abstractExitDef.example; 445 | } 446 | var abstractTypeSchema = rttc.infer(abstractOutputExemplar); 447 | var incompatError = { 448 | problem: 'incompatibleExit', 449 | machine: abstractMachineDef.identity, 450 | exit: abstractExitCodeName, 451 | expecting: {} 452 | }; 453 | // should have an output example 454 | if (!_.isUndefined(abstractOutputExemplar) && !_.isNull(abstractOutputExemplar)) { 455 | incompatError.expecting.outputStyle = 'example'; 456 | if ( _.isUndefined(sourceOutputExemplar) || _.isNull(sourceOutputExemplar) ) { 457 | isIncompat = true; 458 | incompatError.expecting.example = abstractOutputExemplar; 459 | } 460 | // and it should be like this 461 | else { 462 | // Should have an example which implies an equivalent type schema 463 | var sourceTypeSchema = rttc.infer(sourceOutputExemplar); 464 | if (!_.isEqual(abstractTypeSchema, sourceTypeSchema)) { 465 | isIncompat = true; 466 | incompatError.expecting.example = abstractOutputExemplar; 467 | } 468 | ///////////////////////////////////////////////////////////////////////////// 469 | // Note: We might consider checking tolerating not-equal _but compatible_ 470 | // type schemas (but still push warning either way). E.g.: 471 | // ``` 472 | // rttc.validate(sourceOutputExemplar, abstractTypeSchema); 473 | // ``` 474 | ///////////////////////////////////////////////////////////////////////////// 475 | } 476 | } 477 | // should have a `like` 478 | else if ( !_.isUndefined(abstractExitDef.like) && !_.isNull(abstractExitDef.like) ) { 479 | incompatError.expecting.outputStyle = 'like'; 480 | if ( _.isUndefined(sourceExitDef.like) || _.isNull(sourceExitDef.like) ) { 481 | isIncompat = true; 482 | incompatError.expecting.like = abstractExitDef.like; 483 | } 484 | // and it should be like this 485 | else { 486 | if (sourceExitDef.like !== abstractExitDef.like) { 487 | isIncompat = true; 488 | incompatError.expecting.like = abstractExitDef.like; 489 | } 490 | } 491 | } 492 | // should have an `itemOf` 493 | else if ( !_.isUndefined(abstractExitDef.itemOf) && !_.isNull(abstractExitDef.itemOf) ) { 494 | incompatError.expecting.outputStyle = 'itemOf'; 495 | if ( _.isUndefined(sourceExitDef.itemOf) || _.isNull(sourceExitDef.itemOf) ) { 496 | isIncompat = true; 497 | incompatError.expecting.itemOf = abstractExitDef.itemOf; 498 | } 499 | // and it should be like this 500 | else { 501 | if (sourceExitDef.itemOf !== abstractExitDef.itemOf) { 502 | isIncompat = true; 503 | incompatError.expecting.itemOf = abstractExitDef.itemOf; 504 | } 505 | } 506 | } 507 | // should have a getExample 508 | else if ( !_.isUndefined(abstractExitDef.getExample) && !_.isNull(abstractExitDef.getExample) ) { 509 | incompatError.expecting.outputStyle = 'getExample'; 510 | if ( _.isUndefined(sourceExitDef.getExample) || _.isNull(sourceExitDef.getExample) ) { 511 | isIncompat = true; 512 | } 513 | } 514 | // should have no output 515 | else { 516 | incompatError.expecting.outputStyle = 'void'; 517 | if ( 518 | ( !_.isUndefined(sourceOutputExemplar) && !_.isNull(sourceOutputExemplar) ) || 519 | ( !_.isUndefined(sourceExitDef.like) && !_.isNull(sourceExitDef.like) ) || 520 | ( !_.isUndefined(sourceExitDef.itemOf) && !_.isNull(sourceExitDef.itemOf) ) || 521 | ( !_.isUndefined(sourceExitDef.getExample) && !_.isNull(sourceExitDef.getExample) ) 522 | ) { 523 | isIncompat = true; 524 | } 525 | } 526 | 527 | if (isIncompat) { 528 | comparisonReport.errors.push(incompatError); 529 | } 530 | }); 531 | 532 | }); 533 | 534 | // Finally, return the comparison report. 535 | return exits.success(comparisonReport); 536 | });// 537 | }// 538 | 539 | 540 | }).exec({ 541 | // An unexpected error occurred. 542 | error: function(err) { 543 | console.error('An error occurred:\n',err.stack); 544 | }, 545 | 546 | // OK. 547 | success: function (comparison){ 548 | var util = require('util'); 549 | var _ = require('lodash'); 550 | var chalk = require('chalk'); 551 | // console.log('_____________ * * * ~*~ * * * _____________'); 552 | // console.log(' * * * COMPATIBILITY REPORT * * *'); 553 | 554 | console.log(); 555 | console.log( 556 | ' %s\n'+ 557 | ' - vs. -\n'+ 558 | ' %s', 559 | chalk.bold(chalk.cyan(comparison.sourcePackInfo.npmPackageName)), 560 | chalk.blue(comparison.abstractPackInfo.npmPackageName) 561 | ); 562 | console.log(); 563 | 564 | 565 | console.log(); 566 | if (comparison.errors.length > 0) { 567 | console.log('There are %s compatibility '+chalk.red('error(s)')+':', chalk.bold(chalk.red(comparison.errors.length))); 568 | console.log('-------------------------------------------------------'); 569 | } 570 | else { 571 | console.log('No compatibility '+chalk.red('errors')+'! \\o/ '); 572 | } 573 | _.each(comparison.errors, function (error, i) { 574 | 575 | switch (error.problem) { 576 | case 'missingMachine': 577 | console.log( 578 | chalk.red(' %d.')+' Missing machine (`%s`).', 579 | i+1, error.machine 580 | ); 581 | break; 582 | 583 | 584 | case 'incompatibleMachineDetails': 585 | console.log( 586 | chalk.red(' %d.')+' Incompatible machine details in `%s`.\n%sExpecting:', 587 | i+1, error.machine, ' '+_.repeat(' ',(i+1)/10)+' • ', util.inspect(error.expecting, {depth: null}) 588 | ); 589 | console.log(); 590 | break; 591 | 592 | 593 | case 'missingInput': 594 | console.log( 595 | chalk.red(' %d.')+' Missing input (`%s`) in `%s` machine.', 596 | i+1, error.input, error.machine 597 | ); 598 | break; 599 | 600 | 601 | case 'incompatibleInput': 602 | console.log( 603 | chalk.red(' %d.')+' Incompatible input (`%s`) in `%s` machine.\n%sExpecting:', 604 | i+1, error.input, error.machine, ' '+_.repeat(' ',(i+1)/10)+' • ', util.inspect(error.expecting, {depth: null}) 605 | ); 606 | console.log(); 607 | break; 608 | 609 | 610 | case 'missingExit': 611 | console.log( 612 | chalk.red(' %d.')+' Missing exit (`%s`) in `%s` machine.', 613 | i+1, error.exit, error.machine 614 | ); 615 | break; 616 | 617 | 618 | case 'incompatibleExit': 619 | console.log( 620 | chalk.red(' %d.')+' Incompatible exit (`%s`) in `%s` machine.\n%sExpecting:', 621 | i+1, error.exit, error.machine, ' '+_.repeat(' ',(i+1)/10)+' • ', util.inspect(error.expecting, {depth: null}) 622 | ); 623 | console.log(); 624 | break; 625 | 626 | 627 | default: throw new Error('Consistency violation: Unrecognized problem code (`'+error.problem+'`)'); 628 | } 629 | });// 630 | 631 | console.log(); 632 | if (comparison.warnings.length > 0) { 633 | console.log('There are %s compatibility '+chalk.yellow('warning(s)')+':', chalk.bold(chalk.yellow(comparison.warnings.length))); 634 | console.log('-------------------------------------------------------'); 635 | } 636 | else { 637 | console.log('No compatibility '+chalk.yellow('warnings')+'! \\o/ '); 638 | } 639 | _.each(comparison.warnings, function (warning, i) { 640 | switch (warning.problem) { 641 | case 'unrecognizedInput': 642 | console.log( 643 | chalk.yellow(' %d.')+' Unrecognized input (`%s`) in `%s` machine.', 644 | i+1, warning.input, warning.machine 645 | ); 646 | break; 647 | case 'unrecognizedExit': 648 | console.log( 649 | chalk.yellow(' %d.')+' Unrecognized exit (`%s`) in `%s` machine.', 650 | i+1, warning.exit, warning.machine 651 | ); 652 | break; 653 | default: throw new Error('Consistency violation: Unrecognized problem code (`'+warning.problem+'`)'); 654 | } 655 | });// 656 | 657 | 658 | console.log(); 659 | if (comparison.notices.length > 0) { 660 | if (comparison.generatedWithOpts.verbose) { 661 | console.log('There are also %s harmless compatibility '+chalk.green('notices(s)')+':', chalk.bold(chalk.green(comparison.notices.length))); 662 | console.log('-------------------------------------------------------'); 663 | _.each(comparison.notices, function (notice, i) { 664 | switch (notice.problem) { 665 | case 'unrecognizedMachine': 666 | console.log( 667 | chalk.green(' %d.')+' Extra machine (`%s`).', 668 | i+1, notice.machine 669 | ); 670 | break; 671 | default: throw new Error('Consistency violation: Unrecognized problem code (`'+notice.problem+'`)'); 672 | } 673 | });// 674 | } 675 | else { 676 | console.log(chalk.gray('There are also %s harmless compatibility notice(s).'), chalk.bold(comparison.notices.length)); 677 | console.log(chalk.gray('(to display them, run `mp compare --verbose`)')); 678 | } 679 | } 680 | 681 | // TODO (as time permits): prettify and/or group output 682 | // 683 | // e.g. 684 | // ``` 685 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * 686 | // Comparison report: 687 | // ----------------------------------------------------- 688 | // `machinepack-whatever` 689 | // - vs. - 690 | // `waterline-driver-interface` (an abstract pack) 691 | // * * * * * * * * * * * * * * * * * * * * * * * * * * * 692 | // 693 | // • Missing 2 machine(s): 694 | // ° eat-with-spoon 695 | // ° eat-with-chopsticks 696 | // 697 | // • `eat-with-fork` machine has 4 issue(s): 698 | // ° Incompatible machine details: 699 | // => Expecting `cacheable: true` 700 | // => Expecting `sync: false` 701 | // ° Missing 1 input(s): 702 | // => `numProngs` 703 | // ° Missing 1 exit(s): 704 | // => `accidentallyPokedUser` 705 | // ° 3 incompatible exit(s): 706 | // => `success` 707 | // - Output example must imply the following type schema: 708 | // { 709 | // gramsConsumed: 'number', 710 | // stillHungry: 'boolean' 711 | // } 712 | // => `foodTooSpoiled` 713 | // - Must specify `like: 'food'` 714 | // => `lostProngAndAccidentallyAteIt` 715 | // - Must specify `getExample` 716 | // 717 | // • ...and so forth 718 | // ``` 719 | // 720 | 721 | console.log(); 722 | console.log(); 723 | } 724 | }); 725 | 726 | 727 | 728 | 729 | // // FAKE 730 | // var FIXTURE_REPRESENTING_EXAMPLE_OF_RESULTS_FROM_COMPARISON = { 731 | 732 | // // Note that where we see a dictionary of `expected` things below, 733 | // // the properties in that dictionary should only exist if that aspect 734 | // // of the input/exit/machine are incorrect. For example, if an input 735 | // // is supposed to be required and have a numeric example, and the example 736 | // // is correct but the input is optional, then we'd see: 737 | // // ``` 738 | // // expected: { 739 | // // required: true 740 | // // } 741 | // // ``` 742 | 743 | // // breaking: 744 | // errors: [ 745 | // // MACHINE-RELATED-STUFF 746 | // { problem: 'missingMachine', machine: 'eat-with-spoon' }, 747 | // { 748 | // problem: 'incompatibleMachineDetails', 749 | // machine: 'eat-with-fork', 750 | // expected: { 751 | // sideEffects: '',// ("" or "idempotent" or "cacheable") 752 | // sync: false 753 | // } 754 | // }, 755 | 756 | // // INPUT-RELATED-STUFF 757 | // { problem: 'missingInput', machine: 'eat-with-fork', input: 'numProngs' }, 758 | // { 759 | // problem: 'incompatibleInput', 760 | // machine: 'eat-with-fork', 761 | // input: 'food', 762 | // expecting: { 763 | // example: { calories: 3293, liveDbConnection: '===' }, 764 | // readOnly: true 765 | // } 766 | // }, 767 | // { 768 | // problem: 'incompatibleInput', 769 | // machine: 'eat-with-fork', 770 | // input: 'forkDepotStreetAddress', 771 | // expecting: { 772 | // required: false, 773 | // constant: true, 774 | // defaultsTo: '300 Forkimus Ave.' 775 | // } 776 | // }, 777 | // // Note that input contracts are not currently supported, 778 | // // and when they are, they should probably be handled by 779 | // // a separate problem code. 780 | 781 | 782 | 783 | // // EXIT-RELATED-STUFF 784 | // { problem: 'missingExit', machine: 'eat-with-fork', exit: 'accidentallyPokedUser' }, 785 | // { 786 | // problem: 'incompatibleExit', 787 | // machine: 'eat-with-fork', 788 | // exit: 'foodTooDry', 789 | // expecting: { 790 | // outputStyle: 'void' 791 | // // (^means that exit has like/itemOf/getExample/example, 792 | // // but it was supposed to have none of those) 793 | // } 794 | // }, 795 | // { 796 | // problem: 'incompatibleExit', 797 | // machine: 'eat-with-fork', 798 | // exit: 'success', 799 | // expecting: { 800 | // outputStyle: 'example', 801 | // // (^means that exit was supposed to have example, 802 | // // but instead it has like,itemOf,getExample,or nothing) 803 | // example: { gramsConsumed: 39, stillHungry: true } 804 | // // in this case we also include `example`. If the exit 805 | // // has an example, and it just isn't compatible, then 806 | // // we would _only_ include `example` (i.e. we'd omit 807 | // // `outputStyle` since that aspect would be accurate) 808 | // } 809 | // }, 810 | // { 811 | // problem: 'incompatibleExit', 812 | // machine: 'eat-with-fork', 813 | // exit: 'lostProngAndAccidentallyAteIt', 814 | // expecting: { 815 | // outputStyle: 'getExample' 816 | // // (^means that exit was supposed to have getExample(), 817 | // // but instead it has like,itemOf,example,or nothing) 818 | // } 819 | // }, 820 | // { problem: 'incompatibleExit', 821 | // machine: 'eat-with-fork', 822 | // exit: 'foodTooSpoiled', 823 | // expecting: { 824 | // outputStyle: 'like', 825 | // // (^means that exit was supposed to have like, 826 | // // but instead it has itemOf,example,getExample,or nothing. 827 | // like: 'food' 828 | // // in this case we also include `like`. If the exit 829 | // // has a `like`, and it just isn't the right input id, then 830 | // // we would _only_ include `like` (i.e. we'd omit 831 | // // `outputStyle` since that aspect would be accurate) 832 | // } 833 | // // Note that `outputStyle: 'itemOf'` and e.g. `itemOf: 'foods'` 834 | // // work exactly the same way. 835 | // } 836 | // ], 837 | 838 | // // non-breaking: 839 | // warnings: [ 840 | // { problem: 'unrecognizedMachine', machine: 'eat-with-lawnmower' }, 841 | // { problem: 'unrecognizedInput', machine: 'eat-with-other-utensil', input: 'utensilName' }, 842 | // { problem: 'unrecognizedExit', machine: 'eat-with-other-utensil', exit: 'tooPorous' }, 843 | // // note: could potentially allow _compatible_ types and have them show up 844 | // // as warnings here instead of errors-- but decided against doing that for now 845 | // // in the interest of specificity/correctness. 846 | // ] 847 | // }; 848 | -------------------------------------------------------------------------------- /bin/machinepack-cp.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | 7 | var Path = require('path'); 8 | var program = require('commander'); 9 | var Machinepacks = require('machinepack-localmachinepacks'); 10 | 11 | 12 | program 13 | .usage('[options] ') 14 | .parse(process.argv); 15 | 16 | 17 | var originalIdentity = program.args[0]; 18 | if (!program.args[0]) { 19 | console.error('`originalIdentity` required'); 20 | process.exit(1); 21 | } 22 | 23 | var newIdentity = program.args[1]; 24 | if (!program.args[1]) { 25 | console.error('`newIdentity` required'); 26 | process.exit(1); 27 | } 28 | 29 | 30 | 31 | Machinepacks.copyMachine({ 32 | originalIdentity: originalIdentity, 33 | newIdentity: newIdentity, 34 | dir: process.cwd() 35 | }).exec({ 36 | error: function (err){ 37 | console.error('Unexpected error occurred:\n',err); 38 | }, 39 | success: function (){ 40 | console.log('Copied: `%s` to new machine: `%s`', originalIdentity, newIdentity); 41 | } 42 | }); 43 | -------------------------------------------------------------------------------- /bin/machinepack-exec.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | 7 | var program = require('commander'); 8 | var chalk = require('chalk'); 9 | var Machine = require('machine'); 10 | var _ = require('lodash'); 11 | var util = require('util'); 12 | var yargs = require('yargs'); 13 | // var MPProcess = require('machinepack-process'); 14 | 15 | // Build CLI options 16 | var cliOpts = yargs.argv; 17 | delete cliOpts._; 18 | delete cliOpts.$0; 19 | 20 | program 21 | .usage('[options] ') 22 | .parse(process.argv); 23 | 24 | 25 | // if (!program.args[0]) { 26 | // console.error('`identity` required'); 27 | // process.exit(1); 28 | // } 29 | 30 | 31 | var identity = program.args[0]; 32 | 33 | 34 | 35 | // exposed via closure simply for convenience 36 | var machinepackVarName; 37 | var machineMethodName; 38 | 39 | 40 | Machine.build({ 41 | inputs: { 42 | identity: { 43 | example: 'do-stuff' 44 | }, 45 | dir: { 46 | example: '/Users/mikermcneil/machinepack-foo/' 47 | } 48 | }, 49 | defaultExit: 'success', 50 | exits: { 51 | success: { 52 | example: { 53 | withInputs: [ 54 | { 55 | name: 'foobar', 56 | value: 'fiddle diddle' 57 | // ^^^^^ this is ok because it's always a string entered on the CLI interactive prompt 58 | } 59 | ], 60 | exited: { 61 | outcome: 'success', 62 | output: '===', 63 | jsonStringifiedOutput: '{"stuff": "things"}', 64 | inspectedOutput: '{stuff: "things"}', 65 | duration: 2395 66 | } 67 | } 68 | }, 69 | error: {}, 70 | noMachines: {}, 71 | invalidMachine: {} 72 | }, 73 | fn: function (inputs, exits){ 74 | 75 | // Dependencies 76 | var Path = require('path'); 77 | var _ = require('lodash'); 78 | var inquirer = require('inquirer'); 79 | var Filesystem = require('machinepack-fs'); 80 | var Machinepacks = require('machinepack-localmachinepacks'); 81 | 82 | var machinepackPath = Path.resolve(process.cwd(), inputs.dir); 83 | 84 | Machinepacks.readPackageJson({dir: inputs.dir}).exec({ 85 | error: exits, 86 | success: function (machinepack){ 87 | 88 | // If no identity was provided, choose the machine to run from a list, interactively. 89 | (function (next){ 90 | if (identity) { 91 | return next(null, identity); 92 | } 93 | 94 | if (machinepack.machines.length < 1) { 95 | return next((function (){ 96 | var err = new Error('There are no machines in this machinepack.'); 97 | err.exit = err.code = 'noMachines'; 98 | return err; 99 | })()); 100 | } 101 | 102 | inquirer.prompt([{ 103 | name: 'machine', 104 | message: 'Please choose a machine to run.', 105 | type: 'list', 106 | // when: function (){ 107 | // return !machine; 108 | // }, 109 | choices: _.sortBy(_.reduce(machinepack.machines, function (memo, machine){ 110 | memo.push({ 111 | name: machine, 112 | value: machine 113 | }); 114 | return memo; 115 | }, []), 'name') 116 | }], function (answers){ 117 | next(null, answers.machine); 118 | }); 119 | 120 | })(function (err, _identity){ 121 | if (err) { return exits(err); } 122 | 123 | // Expose _identity on closure scope for convenience. (this is a hack) 124 | identity = _identity; 125 | 126 | // Calculate appropriate variable name for machinepack and expose in closure scope (quick hack) 127 | machinepackVarName = machinepack.variableName; 128 | // Calculate appropriate machine method name and expose in closure scope (quick hack) 129 | // TODO: use machinepack-javascript to do this 130 | machineMethodName = (function(identity){ 131 | return identity.replace(/-[a-z]/ig, function (match) { 132 | return match.slice(1).toUpperCase(); 133 | }); 134 | })(identity); 135 | 136 | console.log('\n'+chalk.gray(' Running machine...')); 137 | console.log(); 138 | 139 | Machinepacks.runMachineInteractive({ 140 | machinepackPath: machinepackPath, 141 | identity: identity, 142 | inputValues: (function (){ 143 | return _.reduce(cliOpts, function (memo, inputValue, inputName){ 144 | memo.push({ 145 | name: inputName, 146 | value: inputValue, 147 | protect: false 148 | }); 149 | return memo; 150 | }, []); 151 | })() 152 | }).exec({ 153 | error: exits.error, 154 | invalidMachine: exits.invalidMachine, 155 | success: function (result){ 156 | return exits.success(result); 157 | } 158 | }); 159 | }); 160 | } 161 | }); 162 | 163 | } 164 | }).configure({ 165 | identity: identity, 166 | dir: process.cwd() 167 | }).exec({ 168 | error: function (err){ 169 | console.error('Unexpected error occurred:\n',typeof err === 'object' && err instanceof Error ? err.stack : err); 170 | }, 171 | notFound: function (){ 172 | console.error('Cannot run machine `'+chalk.red(identity)+'`. No machine with that identity exists in this machinepack.'); 173 | }, 174 | invalidMachine: function (err){ 175 | console.error('Cannot run machine `'+chalk.red(identity)+'`. Machine is invalid. Error details:\n',err); 176 | }, 177 | noMachines: function (err){ 178 | console.error(chalk.gray('There are no machines in this machinepack...\n(you should make some!)')); 179 | }, 180 | success: function (result){ 181 | 182 | console.log('___'+repeatChar('_')+'_˛'); 183 | console.log(' '+repeatChar(' ')+' '); 184 | console.log(' '+chalk.gray('%s.%s()'), chalk.bold(chalk.white(machinepackVarName)), chalk.bold(chalk.yellow(machineMethodName))); 185 | 186 | // console.log(''); 187 | // console.log(chalk.white(' * * * * * * * * * * * * * * * * * * * * * * * * ')); 188 | // console.log(chalk.white(' * OUTCOME * ')); 189 | // console.log(chalk.white(' * * * * * * * * * * * * * * * * * * * * * * * * ')); 190 | // console.log(''); 191 | // console.log(' using input values:\n', chalk.bold(chalk.yellow(identity)), _.reduce(result.withInputs, function(memo, configuredInput) { 192 | 193 | // console.log(' Used input values:\n', _.reduce(result.withInputs, function(memo, configuredInput) { 194 | console.log(' '); 195 | console.log(_.reduce(result.withInputs, function(memo, configuredInput) { 196 | memo += ' » ' + chalk.white(configuredInput.name) + ' ' + chalk.gray(JSON.stringify(configuredInput.value)); 197 | memo += '\n'; 198 | return memo; 199 | }, '')); 200 | console.log('___'+repeatChar('_')+'_¸ '); 201 | console.log(' | '); 202 | 203 | // console.log(' Triggered '+chalk.blue(result.exited.outcome)+' callback'+(function (){ 204 | // if (!result.exited.void) { 205 | // return ', returning:\n ' + chalk.gray(result.exited.jsonValue); 206 | // } 207 | // return '.'; 208 | // })()); 209 | 210 | // Determine chalk color 211 | var exitColor = (function (){ 212 | if (result.exited.outcome === 'error') { 213 | return 'red'; 214 | } 215 | if (result.exited.outcome === 'success') { 216 | return 'green'; 217 | } 218 | return 'blue'; 219 | })(); 220 | 221 | console.log(' '+chalk.bold(chalk[exitColor]('•'))+' \n The machine triggered its '+chalk.bold(chalk[exitColor](result.exited.outcome))+' exit'+(function (){ 222 | if (!_.isUndefined(result.exited.output)) { 223 | return ' and returned a value:\n '+chalk.gray(result.exited.inspectedOutput); 224 | } 225 | return '.'; 226 | })()); 227 | console.log(); 228 | console.log(); 229 | 230 | // Compute command to use when running again 231 | var cmd = ' machinepack exec '+identity; 232 | _.each(result.withInputs, function (configuredInputInfo){ 233 | 234 | // Skip protected inputs (they need to be re-entered) 235 | if (configuredInputInfo.protect) { return; } 236 | 237 | // Skip `--version` (because it doesn't work) 238 | if (configuredInputInfo.name === 'version') { return; } 239 | 240 | cmd += ' '; 241 | cmd += '--'+configuredInputInfo.name+'=\''+configuredInputInfo.value.replace(/'/g,'\'\\\'\'')+'\''; 242 | }); 243 | 244 | console.log(chalk.white(' To run again:')); 245 | console.log(chalk.white(cmd)); 246 | 247 | 248 | // Now attempt to add it to the shell history automatically 249 | // (see `http://superuser.com/a/135654` for how this works) 250 | // MPProcess.addToHistory({ 251 | // command: cmd 252 | // }).exec({ 253 | // error: function (err){ 254 | // if (cliOpts.verbose) { 255 | // console.log(); 256 | // console.log(' Command could not be automatically added to your shell history.\n\nDetails:\n------------------------------\n',chalk.red(_.isString(err) ? err : util.inspect(err, false, null))); 257 | // } 258 | // else { 259 | // console.log(' (command could not be automatically added to your shell history; use `--verbose` next time for more info)'); 260 | // } 261 | // console.log(); 262 | // }, 263 | // success: function (){ 264 | // console.log(chalk.gray(' (also appended this command to your CLI history')); 265 | // console.log(); 266 | // } 267 | // }); 268 | 269 | 270 | } 271 | }); 272 | 273 | 274 | 275 | /** 276 | * private helper fn 277 | * @param {[type]} char [description] 278 | * @param {[type]} width [description] 279 | * @return {[type]} [description] 280 | */ 281 | function repeatChar(char,width){ 282 | width = width || 60; 283 | var borderStr = ''; 284 | for (var i=0;i 0) { 58 | console.log(chalk.bold('URLS')); 59 | _.each(urls, function(url) { 60 | console.log(' ' + chalk.underline(url)); 61 | }); 62 | console.log(); 63 | console.log(); 64 | } 65 | 66 | console.log(chalk.bold('INSTALLATION')); 67 | // console.log(); 68 | console.log(chalk.white(util.format(' npm install %s@^%s --save %s', 69 | chalk.bold(machinepack.npmPackageName), 70 | machinepack.version, 71 | (machinepack.usesPublicRegistry === false) ? '--registry=' + machinepack.registry : '' 72 | ))); 73 | 74 | console.log(); 75 | console.log(); 76 | 77 | console.log(chalk.bold('USAGE')); 78 | // console.log(); 79 | console.log(' var ' + machinepack.variableName + ' = require(\'' + machinepack.npmPackageName + '\');'); 80 | console.log(); 81 | console.log(); 82 | 83 | console.log(chalk.bold('AVAILABLE METHODS')); 84 | // console.log(); 85 | 86 | if (machinepack.machines.length < 1) { 87 | console.log(chalk.gray(' none')); 88 | } else { 89 | // console.log('%s %s:', chalk.bold(chalk.blue(machinepack.machines.length)), machinepack.machines.length===1?'Machine':'Machines'); 90 | _.each(machinepack.machines.sort(), function(machineIdentity) { 91 | // Calculate appropriate machine method name 92 | var methodName = Javascript.convertToEcmascriptCompatibleVarname({ 93 | string: machineIdentity, 94 | force: true 95 | }).execSync(); 96 | console.log(' %s.%s() %s', chalk.white(machinepack.variableName), chalk.yellow(methodName), chalk.gray('(' + machineIdentity + ')')); 97 | }); 98 | } 99 | // console.log(); 100 | // console.log(' '+chalk.gray('Run ')+chalk.bold.gray('machinepack show ')+chalk.gray(' for details')); 101 | // console.log(chalk.gray(' to take a closer look: ')+chalk.bold.gray('machinepack show ')); 102 | console.log(); 103 | console.log(); 104 | // console.log(chalk.gray.bold('NPM')+'\n' + chalk.gray(machinepack.npmPackageName + '\n' + 'v'+machinepack.version)); 105 | } 106 | }); 107 | -------------------------------------------------------------------------------- /bin/machinepack-init.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | 7 | var Path = require('path'); 8 | var program = require('commander'); 9 | var Machinepacks = require('machinepack-localmachinepacks'); 10 | 11 | 12 | program 13 | .usage('[options]') 14 | .parse(process.argv); 15 | 16 | 17 | 18 | Machinepacks.initialize({ 19 | dir: process.cwd() 20 | }, { 21 | error: function (err){ 22 | console.error('Unexpected error occurred:\n', err); 23 | }, 24 | success: function (machines){ 25 | console.log('Initialized current module as a machinepack'); 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /bin/machinepack-ls.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | 7 | var Path = require('path'); 8 | var _ = require('lodash'); 9 | var chalk = require('chalk'); 10 | var Machinepacks = require('machinepack-localmachinepacks'); 11 | var program = require('commander'); 12 | 13 | program 14 | .usage('[options]') 15 | .parse(process.argv); 16 | 17 | 18 | Machinepacks.listMachines({ 19 | dir: process.cwd() 20 | }, { 21 | error: function (err){ 22 | console.error('Unexpected error occurred:\n', err); 23 | }, 24 | notMachinepack: function (){ 25 | console.error('This is '+chalk.red('not a machinepack')+'.'); 26 | console.error('Be sure and check that the package.json file has a valid `machinepack` property, or run `machinepack init` if you aren\'t sure.'); 27 | }, 28 | success: function (machines){ 29 | console.log(); 30 | if (machines.length === 0){ 31 | console.log('There are '+chalk.blue('no machines')+' in this machinepack.'); 32 | } 33 | else if (machines.length === 1) { 34 | console.log('There is only '+chalk.blue('1')+' machine in this machinepack.'); 35 | } 36 | else { 37 | console.log('There are '+chalk.blue('%d')+' machines in this machinepack:',machines.length); 38 | } 39 | console.log(chalk.gray('=============================================')); 40 | _.each(machines, function (machineIdentity){ 41 | console.log(' • '+machineIdentity); 42 | }); 43 | console.log(); 44 | } 45 | }); 46 | -------------------------------------------------------------------------------- /bin/machinepack-mv.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | 7 | var Path = require('path'); 8 | var program = require('commander'); 9 | var Machinepacks = require('machinepack-localmachinepacks'); 10 | 11 | 12 | program 13 | .usage('[options] ') 14 | .parse(process.argv); 15 | 16 | 17 | var originalIdentity = program.args[0]; 18 | if (!originalIdentity) { 19 | console.error('`originalIdentity` required'); 20 | process.exit(1); 21 | } 22 | 23 | var newIdentity = program.args[1]; 24 | if (!newIdentity) { 25 | console.error('`newIdentity` required'); 26 | process.exit(1); 27 | } 28 | 29 | 30 | Machinepacks.renameMachine({ 31 | originalIdentity: originalIdentity, 32 | newIdentity: newIdentity, 33 | dir: process.cwd() 34 | }).exec({ 35 | error: function (err){ 36 | console.error('Unexpected error occurred:\n',err); 37 | }, 38 | success: function (){ 39 | console.log('Machine with former identity: `%s` is now: `%s`', originalIdentity, newIdentity); 40 | } 41 | }); 42 | -------------------------------------------------------------------------------- /bin/machinepack-rm.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | 7 | var program = require('commander'); 8 | var chalk = require('chalk'); 9 | var Machinepacks = require('machinepack-localmachinepacks'); 10 | 11 | 12 | 13 | program 14 | .usage('[options] ') 15 | .parse(process.argv); 16 | 17 | var identity = program.args[0]; 18 | if (!identity) { 19 | console.error('`identity` required'); 20 | process.exit(1); 21 | } 22 | 23 | 24 | 25 | Machinepacks.removeMachine({ 26 | identity: identity, 27 | dir: process.cwd() 28 | }).exec({ 29 | error: function (err){ 30 | console.error('Unexpected error occurred:\n',err); 31 | }, 32 | notFound: function (){ 33 | console.error('Cannot remove machine `' + chalk.red(identity) + '`. No machine with that identity exists in this machinepack.'); 34 | }, 35 | success: function (){ 36 | console.log('`%s` has been removed from this machinepack.', chalk.blue(identity)); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /bin/machinepack-scrub.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | 7 | var program = require('commander'); 8 | var chalk = require('chalk'); 9 | 10 | program 11 | .usage('[options]') 12 | .parse(process.argv); 13 | 14 | 15 | 16 | var scrubPack = require('machine').build({ 17 | identity: 'browse-to-machinepack-url', 18 | sync: true, 19 | inputs: { 20 | dir: { 21 | example: '/Users/mikermcneil/machinepack-foo/', 22 | required: true 23 | } 24 | }, 25 | defaultExit: 'success', 26 | exits: { 27 | error: {}, 28 | notMachinepack: {}, 29 | success: { 30 | example: 'http://node-machine.org/machinepack-facebook' 31 | }, 32 | }, 33 | fn: function (inputs, exits) { 34 | 35 | var path = require('path'); 36 | var util = require('util'); 37 | var _ = require('lodash'); 38 | var Machines = require('machinepack-localmachinepacks'); 39 | var Filesystem = require('machinepack-fs'); 40 | var gi = require('git-info'); 41 | 42 | // IDEA: if a machine file exists in the machines folder, but is not in the package.json, add it? not sure if this would actually be a good thing. probably as a different command...? 43 | 44 | // Resolve provided `dir` path from cwd if it's relative 45 | inputs.dir = path.resolve(inputs.dir); 46 | 47 | var packageJsonPath = path.resolve(inputs.dir, 'package.json'); 48 | 49 | // Ensure package.json file has proper `npm test` script and devDependency on `test-machinepack-mocha`: 50 | Filesystem.read({ 51 | source: packageJsonPath, 52 | }).exec({ 53 | // An unexpected error occurred. 54 | error: exits.error, 55 | // No file exists at the provided `source` path 56 | doesNotExist: exits.notMachinepack, 57 | // OK. 58 | success: function(jsonString) { 59 | 60 | var jsonData; 61 | try { 62 | jsonData = JSON.parse(jsonString); 63 | 64 | // Ensure a devDependency exists for test-machinepack-mocha 65 | jsonData.devDependencies = jsonData.devDependencies||{}; 66 | if (!jsonData.devDependencies['test-machinepack-mocha']) { 67 | jsonData.devDependencies['test-machinepack-mocha'] = '^2.0.0'; 68 | } 69 | // Ensure a script exists for use with `npm test` 70 | jsonData.scripts = jsonData.scripts||{}; 71 | if (!jsonData.scripts.test) { 72 | jsonData.scripts.test = 'node ./node_modules/test-machinepack-mocha/bin/testmachinepack-mocha.js'; 73 | } 74 | } 75 | catch (e) { 76 | return exits.error(buildError('Unexpected error parsing or modifying package.json data:\n', e)); 77 | } 78 | 79 | 80 | // While we're here, see if there's anything exciting in the git directory and 81 | // update the package.json file with the repo url if we can get it. 82 | gi('repository', function(err, repoInfo) { 83 | 84 | try { 85 | 86 | // Ignore errors-- just use the repo url if we can get it, otherwise ignore it. 87 | repoInfo = repoInfo || {}; 88 | if (!err && _.isObject(repoInfo)) { 89 | 90 | // Create alias to make it easier to remember 91 | repoInfo.gitUrl = repoInfo.repository; 92 | 93 | // Assume github conventions and rip out metadata by matching against the 94 | // `owner/reponame` pattern in the repo URL 95 | repoInfo.ownerStr = repoInfo.gitUrl.replace(/^.*github\.com[:\/]/, ''); 96 | repoInfo.ownerStr = repoInfo.ownerStr.replace(/^\/*/, ''); 97 | repoInfo.ownerStr = repoInfo.ownerStr.replace(/\.git$/, ''); 98 | repoInfo.ownerStr = repoInfo.ownerStr.replace(/\/*$/, ''); 99 | 100 | // Build tests URL using the ownerStr and assuming test runner is Travis CI. 101 | repoInfo.testsUrl = util.format('https://travis-ci.org/%s',repoInfo.ownerStr); 102 | } 103 | 104 | // If `repository` key is not set in package.json, and we have the data, set it. 105 | if (!jsonData.repository || !jsonData.repository.url) { 106 | if (repoInfo.gitUrl) { 107 | jsonData.repository = { 108 | type: 'git', 109 | url: repoInfo.gitUrl 110 | }; 111 | } 112 | } 113 | 114 | // Now take a guess at a `testsUrl` and add it to the `machinepack` object 115 | // in the package.json data (unless one already exists) 116 | if (!jsonData.machinepack.testsUrl) { 117 | if (repoInfo.testsUrl) { 118 | // Build tests URL using the ownerRepo string and assuming test runner is Travis CI. 119 | jsonData.machinepack.testsUrl = repoInfo.testsUrl; 120 | } 121 | } 122 | 123 | Filesystem.writeJson({ 124 | json: jsonData, 125 | destination: packageJsonPath, 126 | force: true 127 | }).exec({ 128 | 129 | // An unexpected error occurred. 130 | error: exits.error, 131 | 132 | // OK. 133 | success: function (){ 134 | 135 | // Ensure tests exist for each machine (don't overwrite any test files which already exist though) 136 | Machines.scaffoldTests({ 137 | dir: inputs.dir 138 | }, { 139 | error: exits.error, 140 | notMachinepack: exits.notMachinepack, 141 | success: function (){ 142 | 143 | // Copy file or directory located at source path to the destination path. 144 | Filesystem.cp({ 145 | source: path.resolve(__dirname,'../templates/travis.template.yml'), 146 | destination: path.resolve(inputs.dir, '.travis.yml'), 147 | }).exec({ 148 | // An unexpected error occurred. 149 | error: exits.error, 150 | // OK. 151 | success: function() { 152 | // (Re)generate a README file using the boilerplate, using fresh description and module name from package.json. 153 | // --> (read file at source path, replace substrings with provided data, then write to destination path.) 154 | Filesystem.template({ 155 | source: path.resolve(__dirname,'../templates/README.template.md'), 156 | destination: path.resolve(inputs.dir, 'README.md'), 157 | data: { 158 | moduleName: jsonData.name, 159 | description: jsonData.description, 160 | copyrightYear: (new Date()).getFullYear(), 161 | author: jsonData.author, 162 | testsUrl: jsonData.machinepack.testsUrl, 163 | license: jsonData.license||'MIT' 164 | }, 165 | force: true 166 | }).exec({ 167 | // An unexpected error occurred. 168 | error: exits.error, 169 | // OK. 170 | success: exits.success 171 | }); 172 | } 173 | }); 174 | } 175 | }); 176 | } 177 | }); 178 | 179 | } 180 | catch (e) { 181 | return exits.error(e); 182 | } 183 | 184 | }); 185 | 186 | }, 187 | }); 188 | 189 | } 190 | }); 191 | 192 | 193 | 194 | scrubPack({ 195 | dir: process.cwd() 196 | }).exec({ 197 | error: function (err){ 198 | console.error('Unexpected error occurred:\n', err); 199 | }, 200 | notMachinepack: function() { 201 | console.error('This is '+chalk.red('not a machinepack')+'.'); 202 | console.error('Be sure and check that the package.json file has a valid `machinepack` property, or run `machinepack init` if you aren\'t sure.'); 203 | }, 204 | success: function() { 205 | console.log(); 206 | console.log('Whew! I gave this pack a good scrubbing.\n'); 207 | console.log( 208 | ' • regenerated the README.md file using the latest info from your package.json file\n'+ 209 | ' • made sure your package.json file has a repo url in it; assuming this pack has a local repo (i.e. `.git` folder)\n'+ 210 | ' • double-checked that each machine in this pack has a test in the tests folder and created new ones if necessary\n'+ 211 | ' • ensured a `devDependency` on the proper version of `test-machinepack-mocha` in your package.json file\n'+ 212 | ' • ensured you have the proper `npm test` script in your package.json file\n', 213 | ' • ensured you have a .travis.yml file (note that you\'ll still need to enable the repo in on travis-ci.org)\n'+ 214 | ' • attempted to infer a `testsUrl` and, if successful, added it to your package.json file\n' 215 | ); 216 | console.log(); 217 | }, 218 | }); 219 | -------------------------------------------------------------------------------- /bin/machinepack.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * Module dependencies 5 | */ 6 | 7 | var util = require('util'); 8 | var program = require('commander'); 9 | var chalk = require('chalk'); 10 | var _ = require('lodash'); 11 | 12 | 13 | var VERSION = require('../package.json').version; 14 | 15 | 16 | 17 | program 18 | .version(VERSION) 19 | // Allow unknown options. 20 | .unknownOption = function NOOP(){}; 21 | program.usage(chalk.gray('[options]')+' '+chalk.bold('')) 22 | .command('browse', 'view on node-machine.org') 23 | .command('info', 'get pack metadata') 24 | .command('ls', 'list machines') 25 | .command('add', 'add a new machine') 26 | .command('exec ', 'run machine') 27 | .command('rm ', 'delete existing machine') 28 | .command('mv ', 'rename machine') 29 | .command('cp ', 'copy machine') 30 | .command('init', 'make this module a machinepack') 31 | .command('scrub', 'scrub pack; generate missing tests, etc.') 32 | .command('compare', 'compare pack vs. an abstract interface') 33 | .command('about', 'about this module') 34 | .command('browserify', 'get a browser-ready version of this pack') 35 | .parse(process.argv); 36 | 37 | 38 | // When `machinepack help` is called, `program.help()` is triggered automatically by commander. 39 | // To trigger `help` manually: 40 | // program.outputHelp(); 41 | 42 | 43 | // $ machinepack 44 | // 45 | // (i.e. with no CLI arguments...) 46 | if (program.args.length === 0) { 47 | return _alias('about'); 48 | } 49 | 50 | 51 | // $ machinepack ls 52 | // $ machinepack cp 53 | // ... 54 | // 55 | // (i.e. matched one of the overtly exposed commands) 56 | var matchedCommand = !!program.runningCommand; 57 | if (matchedCommand){ 58 | return; 59 | } 60 | 61 | 62 | // $ machinepack * 63 | // 64 | // (i.e. check aliases, since wasn't matched by any overtly exposed commands) 65 | if (program.args[0] === 'list' || program.args[0] === 'machines') { 66 | return _alias('ls'); 67 | } 68 | if (program.args[0] === 'initialize' || program.args[0] === 'new') { 69 | return _alias('init'); 70 | } 71 | if (program.args[0] === 'remove') { 72 | return _alias('rm'); 73 | } 74 | if (program.args[0] === 'run' || program.args[0] === 'test') { 75 | return _alias('exec'); 76 | } 77 | if (program.args[0] === 'touch') { 78 | return _alias('add'); 79 | } 80 | if (program.args[0] === 'move') { 81 | return _alias('mv'); 82 | } 83 | if (program.args[0] === 'copy') { 84 | return _alias('cp'); 85 | } 86 | if (program.args[0] === 'man') { 87 | return _alias('info'); 88 | } 89 | if (program.args[0] === 'show') { 90 | return _alias('browse'); 91 | } 92 | 93 | 94 | // $ machinepack * 95 | // 96 | // (i.e. final handler) 97 | (function unknownCommand(){ 98 | 99 | // Display usage (i.e. "help"): 100 | program.outputHelp(); 101 | })(); 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | /** 115 | * Helper fn 116 | * @param {String} aliasFor [string command to redirect to] 117 | */ 118 | function _alias (aliasFor){ 119 | process.argv.splice(process.argv.indexOf(program.args[0]),1); 120 | require('./machinepack-'+aliasFor); 121 | } 122 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "machinepack", 3 | "version": "3.4.4", 4 | "description": "CLI tool for working with machinepacks and their machines.", 5 | "bin": { 6 | "machinepack": "./bin/machinepack.js", 7 | "machinepack-about": "./bin/machinepack-about.js", 8 | "machinepack-info": "./bin/machinepack-info.js", 9 | "machinepack-init": "./bin/machinepack-init.js", 10 | "machinepack-add": "./bin/machinepack-add.js", 11 | "machinepack-ls": "./bin/machinepack-ls.js", 12 | "machinepack-mv": "./bin/machinepack-mv.js", 13 | "machinepack-cp": "./bin/machinepack-cp.js", 14 | "machinepack-rm": "./bin/machinepack-rm.js", 15 | "machinepack-exec": "./bin/machinepack-exec.js", 16 | "machinepack-browse": "./bin/machinepack-browse.js", 17 | "machinepack-scrub": "./bin/machinepack-scrub.js", 18 | "machinepack-browserify": "./bin/machinepack-browserify.js", 19 | "machinepack-compare": "./bin/machinepack-compare.js", 20 | "mp": "./bin/machinepack.js", 21 | "mp-about": "./bin/machinepack-about.js", 22 | "mp-info": "./bin/machinepack-info.js", 23 | "mp-init": "./bin/machinepack-init.js", 24 | "mp-add": "./bin/machinepack-add.js", 25 | "mp-ls": "./bin/machinepack-ls.js", 26 | "mp-mv": "./bin/machinepack-mv.js", 27 | "mp-cp": "./bin/machinepack-cp.js", 28 | "mp-rm": "./bin/machinepack-rm.js", 29 | "mp-exec": "./bin/machinepack-exec.js", 30 | "mp-browse": "./bin/machinepack-browse.js", 31 | "mp-scrub": "./bin/machinepack-scrub.js", 32 | "mp-browserify": "./bin/machinepack-browserify.js", 33 | "mp-compare": "./bin/machinepack-compare.js" 34 | }, 35 | "scripts": { 36 | "test": "echo 'N/A'" 37 | }, 38 | "keywords": [ 39 | "node-machine", 40 | "machinepack", 41 | "machinepacks", 42 | "machines", 43 | "utility", 44 | "cli" 45 | ], 46 | "author": "Mike McNeil", 47 | "license": "MIT", 48 | "dependencies": { 49 | "async": "2.0.1", 50 | "chalk": "1.1.3", 51 | "commander": "2.8.1", 52 | "git-info": "1.0.1", 53 | "inquirer": "0.8.5", 54 | "lodash": "3.10.1", 55 | "machine": "^12.3.0", 56 | "machine-as-script": "^4.0.6", 57 | "machinepack-browserify": "^1.0.0", 58 | "machinepack-fs": "^1.6.0", 59 | "machinepack-javascript": "^0.2.1", 60 | "machinepack-localmachinepacks": "^1.1.0", 61 | "machinepack-process": "^1.0.2", 62 | "open": "0.0.5", 63 | "rttc": "^9.3.0", 64 | "yargs": "1.3.3" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /templates/README.template.md: -------------------------------------------------------------------------------- 1 | <% 2 | // 3 | // 4 | // 5 | //********************************* 6 | // WARNING: 7 | // THIS IS A TEMPLATE. 8 | //********************************* 9 | // 10 | // 11 | // 12 | // 13 | // 14 | %>

15 | node-machine logo 16 | <%=moduleName%> 17 |

18 | 19 | ### [Docs](http://node-machine.org/<%= moduleName %>)   [Browse other machines](http://node-machine.org/machinepacks)   [FAQ](http://node-machine.org/implementing/FAQ)   [Newsgroup](https://groups.google.com/forum/?hl=en#!forum/node-machine) 20 | 21 | <%= description %> 22 | 23 | 24 | ## Installation   [![NPM version](https://badge.fury.io/js/<%= moduleName %>.svg)](http://badge.fury.io/js/<%= moduleName %>) [![Build Status](<%= testsUrl %>.png?branch=master)](<%= testsUrl %>) 25 | 26 | ```sh 27 | $ npm install <%= moduleName %> 28 | ``` 29 | 30 | ## Usage 31 | 32 | For the latest usage documentation, version information, and test status of this module, see http://node-machine.org/<%= moduleName %>. The generated manpages for each machine contain a complete reference of all expected inputs, possible exit states, and example return values. If you need more help, or find a bug, jump into [Gitter](https://gitter.im/node-machine/general) or leave a message in the project [newsgroup](https://groups.google.com/forum/?hl=en#!forum/node-machine). 33 | 34 | ## About   [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/node-machine/general?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 35 | 36 | This is a [machinepack](http://node-machine.org/machinepacks), an NPM module which exposes a set of related Node.js [machines](http://node-machine.org/spec/machine) according to the [machinepack specification](http://node-machine.org/spec/machinepack). 37 | Documentation pages for the machines contained in this module (as well as all other NPM-hosted machines for Node.js) are automatically generated and kept up-to-date on the public registry. 38 | Learn more at http://node-machine.org/implementing/FAQ. 39 | 40 | ## License 41 | 42 | <%= license %> © <%= copyrightYear %> <%= (typeof author !== 'undefined' && author) ? author+' and ' : '' %>contributors 43 | 44 | -------------------------------------------------------------------------------- /templates/travis.template.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "0.10" 5 | - "0.12" 6 | 7 | # whitelisted branches 8 | branches: 9 | only: 10 | - master 11 | --------------------------------------------------------------------------------