├── .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 |
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 [](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 [](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 |
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 [](http://badge.fury.io/js/<%= moduleName %>) [](<%= 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 [](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 |
--------------------------------------------------------------------------------