├── .editorconfig
├── .eslintrc
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── bin
└── wombat-cli.js
├── commands
├── hook.js
├── package.js
├── versions.js
└── whoami.js
├── lib
├── config.js
├── registry.js
└── report.js
├── package.json
└── test
├── 01-config.js
├── 02-registry.js
├── 03-whoami.js
├── 04-hooks.js
├── 05-package.js
└── fixtures
└── scurry.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | insert_final_newline = true
6 |
7 | [*.js]
8 | indent_style = tab
9 | indent_size = 4
10 | trim_trailing_whitespace = true
11 | curly_bracket_next_line = true
12 | indent_brace_style = Allman
13 | quote_type = single
14 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": false,
4 | "node": true
5 | },
6 |
7 | "globals": {
8 | "crypto": true,
9 | "escape": false,
10 | "unescape": false
11 | },
12 |
13 | "ecmaFeatures": {
14 | "arrowFunctions": true,
15 | "binaryLiterals": true,
16 | "blockBindings": true,
17 | "defaultParams": true,
18 | "forOf": true,
19 | "generators": true,
20 | "objectLiteralComputedProperties": true,
21 | "objectLiteralDuplicateProperties": false,
22 | "objectLiteralShorthandMethods": true,
23 | "objectLiteralShorthandProperties": true,
24 | "octalLiterals": false,
25 | "regexUFlag": true,
26 | "regexYFlag": true,
27 | "superInFunctions": true,
28 | "templateStrings": true,
29 | "unicodeCodePointEscapes": true,
30 | "globalReturn": true
31 | },
32 |
33 | "rules": {
34 | "block-scoped-var": 0,
35 | "brace-style": [2, "allman", { "allowSingleLine": true }],
36 | "camelcase": 0,
37 | "comma-style": [2, "last"],
38 | "complexity": 0,
39 | "consistent-return": 0,
40 | "consistent-this": 0,
41 | "curly": 0,
42 | "default-case": 0,
43 | "dot-notation": 0,
44 | "eol-last": 2,
45 | "eqeqeq": [2, "allow-null"],
46 | "func-names": 0,
47 | "func-style": [0, "declaration"],
48 | "generator-star": 0,
49 | "global-strict": 0,
50 | "guard-for-in": 0,
51 | "handle-callback-err": [2, "^(err|error|anySpecificError)$" ],
52 | "max-depth": 0,
53 | "max-len": 0,
54 | "max-nested-callbacks": 0,
55 | "max-params": 0,
56 | "max-statements": 0,
57 | "new-cap": [2, { "newIsCap": true, "capIsNew": false }],
58 | "new-parens": 2,
59 | "no-alert": 2,
60 | "no-array-constructor": 2,
61 | "no-bitwise": 0,
62 | "no-caller": 2,
63 | "no-catch-shadow": 0,
64 | "no-cond-assign": 2,
65 | "no-console": 0,
66 | "no-constant-condition": 0,
67 | "no-control-regex": 2,
68 | "no-debugger": 2,
69 | "no-delete-var": 2,
70 | "no-div-regex": 0,
71 | "no-dupe-keys": 2,
72 | "no-else-return": 0,
73 | "no-empty": 0,
74 | "no-empty-character-class": 2,
75 | "no-eq-null": 0,
76 | "no-eval": 2,
77 | "no-ex-assign": 2,
78 | "no-extend-native": 2,
79 | "no-extra-bind": 2,
80 | "no-extra-boolean-cast": 2,
81 | "no-extra-parens": 0,
82 | "no-extra-semi": 2,
83 | "no-fallthrough": 2,
84 | "no-floating-decimal": 2,
85 | "no-func-assign": 2,
86 | "no-implied-eval": 2,
87 | "no-inline-comments": 0,
88 | "no-inner-declarations": [2, "functions"],
89 | "no-invalid-regexp": 2,
90 | "no-irregular-whitespace": 2,
91 | "no-iterator": 2,
92 | "no-label-var": 2,
93 | "no-labels": 2,
94 | "no-lone-blocks": 2,
95 | "no-lonely-if": 0,
96 | "no-loop-func": 0,
97 | "no-mixed-requires": [0, false],
98 | "no-mixed-spaces-and-tabs": [2, false],
99 | "no-multi-str": 2,
100 | "no-multiple-empty-lines": 2,
101 | "no-native-reassign": 2,
102 | "no-negated-in-lhs": 2,
103 | "no-nested-ternary": 0,
104 | "no-new": 2,
105 | "no-new-func": 2,
106 | "no-new-object": 2,
107 | "no-new-require": 2,
108 | "no-new-wrappers": 2,
109 | "no-obj-calls": 2,
110 | "no-octal": 2,
111 | "no-octal-escape": 2,
112 | "no-path-concat": 0,
113 | "no-plusplus": 0,
114 | "no-process-env": 0,
115 | "no-process-exit": 0,
116 | "no-proto": 2,
117 | "no-redeclare": 2,
118 | "no-regex-spaces": 2,
119 | "no-reserved-keys": 0,
120 | "no-restricted-modules": 0,
121 | "no-return-assign": 2,
122 | "no-script-url": 2,
123 | "no-self-compare": 2,
124 | "no-sequences": 2,
125 | "no-shadow": 0,
126 | "no-shadow-restricted-names": 2,
127 | "no-space-before-semi": 0,
128 | "no-spaced-func": 2,
129 | "no-sparse-arrays": 2,
130 | "no-sync": 0,
131 | "no-ternary": 0,
132 | "no-trailing-spaces": 2,
133 | "no-undef": 2,
134 | "no-undef-init": 2,
135 | "no-undefined": 0,
136 | "no-underscore-dangle": 0,
137 | "no-unreachable": 2,
138 | "no-unused-expressions": 0,
139 | "no-unused-vars": [2, {"vars": "local", "args": "none", "varsIgnorePattern": "demand"}],
140 | "no-use-before-define": 0,
141 | "no-var": 0,
142 | "no-void": 0,
143 | "no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
144 | "no-with": 2,
145 | "one-var": 0,
146 | "operator-assignment": [0, "always"],
147 | "padded-blocks": 0,
148 | "quote-props": 0,
149 | "quotes": [2, "single", "avoid-escape"],
150 | "radix": 2,
151 | "semi": [2, "always"],
152 | "sort-vars": 0,
153 | "space-before-function-paren": [2, "never"],
154 | "space-before-blocks": 0,
155 | "space-in-brackets": 0,
156 | "space-in-parens": [2, "never"],
157 | "space-infix-ops": 2,
158 | "space-unary-ops": [2, { "words": true, "nonwords": false }],
159 | "strict": 0,
160 | "use-isnan": 2,
161 | "valid-jsdoc": 0,
162 | "valid-typeof": 2,
163 | "vars-on-top": 0,
164 | "wrap-iife": [2, "any"],
165 | "wrap-regex": 0,
166 | "yoda": [2, "never"]
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
18 | .grunt
19 |
20 | # node-waf configuration
21 | .lock-wscript
22 |
23 | # Compiled binary addons (http://nodejs.org/api/addons.html)
24 | build/Release
25 |
26 | # Dependency directory
27 | node_modules
28 |
29 | # Optional npm cache directory
30 | .npm
31 |
32 | # Optional REPL history
33 | .node_repl_history
34 | .nyc_output
35 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "4"
5 | - "5"
6 | - "6"
7 | - "7"
8 | script: npm run travis
9 | after_success: npm run coverage
10 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change Log
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 |
6 | ## [1.0.5](https://github.com/npm/wombat-cli/compare/v1.0.4...v1.0.5) (2017-01-06)
7 |
8 |
9 |
10 |
11 | ## [1.0.2](https://github.com/npm/wombat-cli/compare/v1.0.1...v1.0.2) (2016-07-21)
12 |
13 |
14 |
15 |
16 | ## [1.0.1](https://github.com/npm/wombat-cli/compare/v1.0.0...v1.0.1) (2016-07-21)
17 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2016, npm
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any
4 | purpose with or without fee is hereby granted, provided that the above
5 | copyright notice and this permission notice appear in all copies.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # wombat-cli
2 |
3 | [](https://travis-ci.org/npm/wombat-cli) [](https://coveralls.io/github/npm/wombat-cli?branch=master)
4 |
5 | The wombat cli tool.
6 |
7 |
8 |
9 | ,.--""""--.._
10 | ." .' `-.
11 | ; ; ;
12 | ' ; )
13 | / ' . ;
14 | / ; `. `;
15 | ,.' : . : )
16 | ;|\' : `./|) \ ;/
17 | ;| \" -,- "-./ |; ).;
18 | /\/ \/ );
19 | : \ ;
20 | : _ _ ; )
21 | `. \;\ /;/ ; /
22 | ! : : ,/ ;
23 | (`. : _ : ,/"" ;
24 | \\\`"^" ` : ;
25 | ( )
26 | ////
27 |
28 | ```
29 | the helpful wombat tool
30 |
31 | Commands:
32 | hook control your hooks
33 | package see information about the named package
34 | versions see all available versions for the named package
35 | whoami the username you are authenticated as
36 |
37 | Options:
38 | --registry, -r the registry configuration to use [default: "default"]
39 | --json, -j print output as json [boolean] [default: false]
40 | --help Show help [boolean]
41 | --version show version information [boolean]
42 | ```
43 |
44 | Help is available for each of the supported commands.
45 |
46 | You may also do fun things like `wombat ls --depth=0` and `npm` will be invoked.
47 |
48 | ## configuration
49 |
50 | Wombat reads its config from the file `~/.wombatrc`. This file is parsed as [TOML](https://github.com/toml-lang/toml). The defaults look like this:
51 |
52 | ```toml
53 | [default]
54 | registry = "https://registry.npmjs.org"
55 | api = "https://registry.npmjs.org/-/npm"
56 | ```
57 |
58 | You can add sections for other registries to talk to and point wombat to them using the name of the config section, or change the default to a registry you use more often. For example:
59 |
60 | ```toml
61 | [default]
62 | registry = "https://registry.npmjs.org"
63 | api = "https://registry.npmjs.org/-/npm"
64 |
65 | [enterprise]
66 | registry = "https://npm-enterprise.private.npmjs.com"
67 | api = "https://api.private.npmjs.com"
68 | ```
69 |
70 | Then run something like `wombat -r enterprise package @secret/private-package`
71 |
72 | ## web hooks
73 |
74 | ```
75 | wombat hook
76 |
77 | Commands:
78 | ls [pkg] list your hooks
79 | add add a hook to the named package
80 | update [secret] update an existing hook
81 | rm remove a hook
82 |
83 | Examples:
84 | wombat hook add lodash https://example.com/webhook my-shared-secret
85 | wombat hook ls lodash
86 | wombat hook ls --json
87 | wombat hook rm id-ers83f
88 | ```
89 |
90 | ## viewing packages
91 |
92 | `wombat package yargs` shows you a formatted description of the package meta-data. Pass `--readme` to get the package readme rendered in your terminal as markdown!
93 |
94 | `wombat versions yargs` shows you a list of all dist-tags and versions for the named package (in this case, yargs).
95 |
96 | ## whoami
97 |
98 | Find out who you are logged in as for the registry you're using.
99 |
100 | ## License
101 |
102 | ISC
103 |
--------------------------------------------------------------------------------
/bin/wombat-cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var updater = require('update-notifier'),
4 | pkg = require('../package.json');
5 |
6 | updater({pkg: pkg}).notify();
7 |
8 | var yargs = require('yargs')
9 | .option('registry', {
10 | alias: 'r',
11 | description: 'the registry configuration to use',
12 | default: 'default'
13 | })
14 | .option('json', {
15 | alias: 'j',
16 | description: 'print output as json',
17 | type: 'boolean',
18 | default: false,
19 | })
20 | .help('help')
21 | .alias('help', 'h')
22 | .version(function() { return require('../package').version; })
23 | .describe('version', 'show version information')
24 | .alias('version', 'v')
25 | .usage('the helpful wombat tool');
26 |
27 | var requireDirectory = require('require-directory'),
28 | commands = requireDirectory(module, '../commands');
29 |
30 | Object.keys(commands).forEach(function(c)
31 | {
32 | var cmd = commands[c];
33 | yargs.command(cmd);
34 |
35 | if (cmd.aliases)
36 | {
37 | cmd.aliases.forEach(function(alias) { yargs.command(alias, false, cmd); });
38 | }
39 | });
40 |
41 | var argv = yargs.argv;
42 |
43 | // wombat : npm :: hub : git
44 | if (!argv._handled)
45 | {
46 | var spawn = require('child_process').spawn;
47 | var opts = {
48 | cwd: process.cwd,
49 | env: process.env,
50 | stdio: 'inherit',
51 | };
52 | var original = process.argv.slice(2);
53 | spawn('npm', original, opts)
54 | .on('exit',function(code){ process.exit(code); });
55 | }
56 |
--------------------------------------------------------------------------------
/commands/hook.js:
--------------------------------------------------------------------------------
1 | var
2 | moment = require('moment'),
3 | Table = require('cli-table2'),
4 | Registry = require('../lib/registry'),
5 | report = require('../lib/report')
6 | ;
7 |
8 | function hooks(argv)
9 | {
10 | // set this here and it's set for all the subcommands
11 | argv._handled = true;
12 | }
13 |
14 | hooks.add = function add(argv)
15 | {
16 | var pkg = argv.pkg + '';
17 | // if the package is just a scope name set type to scope and save it!
18 | var type = argv.type;
19 |
20 | // @npm
21 | // not @npm/foo
22 | if ((pkg.indexOf('@') === 0 && pkg.indexOf('/') === -1) || argv.type === 'scope')
23 | {
24 | type = 'scope';
25 | }
26 |
27 | if (type) argv.type = type;
28 |
29 | if (type === 'scope' && pkg[0] !== '@' && pkg.length)
30 | {
31 | argv.pkg = '@' + pkg;
32 | }
33 |
34 | var reg = argv.reg || new Registry(argv);
35 | var opts = {
36 | method: 'POST',
37 | uri: '/v1/hooks/hook/',
38 | body: {
39 | type: argv.type,
40 | name: argv.pkg,
41 | endpoint: argv.url,
42 | secret: argv.secret
43 | }
44 | };
45 |
46 | reg.authed(opts, function(err, res, hook)
47 | {
48 | if (err)
49 | return report.failure('hook add', err.message);
50 | if (!hook || res.statusCode < 200 || res.statusCode >= 400)
51 | return report.failure('hook add', res.statusCode + ' ' + JSON.stringify(hook));
52 |
53 | if (argv.json)
54 | report.json(hook);
55 | else
56 | report.success('+', hook.name + ' ➜ ' + hook.endpoint);
57 | });
58 | };
59 |
60 | hooks.rm = function rm(argv)
61 | {
62 | var reg = argv.reg || new Registry(argv);
63 | var opts = {
64 | method: 'DELETE',
65 | uri: '/v1/hooks/hook/' + encodeURIComponent(argv.id),
66 | };
67 |
68 | reg.authed(opts, function(err, res, hook)
69 | {
70 | if (err)
71 | return report.failure('hook rm', err.message);
72 | if (!hook || res.statusCode < 200 || res.statusCode >= 400)
73 | return report.failure('hook rm', res.statusCode + ' ' + JSON.stringify(hook));
74 |
75 | if (argv.json)
76 | report.json(hook);
77 | else
78 | report.success('–', hook.name + ' ✘ ' + hook.endpoint);
79 | });
80 | };
81 |
82 | hooks.ls = function ls(argv)
83 | {
84 | var reg = argv.reg || new Registry(argv);
85 | var uri = '/v1/hooks';
86 | if (argv.pkg)
87 | uri += '?package=' + encodeURIComponent(argv.pkg);
88 |
89 | reg.authed({ uri: uri }, function(err, res, body)
90 | {
91 | if (err)
92 | return report.failure('hook ls', err.message);
93 | if (!body || res.statusCode < 200 || res.statusCode >= 400)
94 | return report.failure('hook ls', res.statusCode + ' ' + JSON.stringify(body));
95 |
96 | if (argv.json)
97 | report.json(body.objects);
98 | else
99 | {
100 | if (body.objects.length === 0)
101 | {
102 | report.success('hooks', 'you do not have any hooks configured yet.');
103 | return;
104 | }
105 |
106 | if (body.objects.length === 1)
107 | report.success('hooks', 'you have one hook');
108 | else
109 | report.success('hooks', 'you have ' + body.objects.length + ' hooks');
110 |
111 | var table = new Table({ head: ['id', 'type', 'target', 'endpoint'], });
112 | body.objects.forEach(function eachHook(hook)
113 | {
114 | table.push([
115 | {rowSpan: 2, content: hook.id},
116 | hook.type,
117 | hook.name,
118 | hook.endpoint]);
119 | if (hook.last_delivery)
120 | {
121 | table.push([{
122 | colSpan: 2,
123 | content: 'triggered ' + moment(hook.last_delivery).format('lll')},
124 | hook.response_code]);
125 | }
126 | else
127 | table.push([{colSpan:3, content:'never triggered'}]);
128 | });
129 | console.log(table.toString());
130 | }
131 | });
132 | };
133 |
134 | hooks.update = function update(argv)
135 | {
136 | var reg = argv.reg || new Registry(argv);
137 | var opts = {
138 | method: 'PUT',
139 | uri: '/v1/hooks/hook/' + encodeURIComponent(argv.id),
140 | body: {
141 | endpoint: argv.url,
142 | secret: argv.secret
143 | }
144 | };
145 |
146 | reg.authed(opts, function(err, res, hook)
147 | {
148 | if (err)
149 | return report.failure('hook update', err.message);
150 | if (!hook || res.statusCode < 200 || res.statusCode >= 400)
151 | return report.failure('hook update', res.statusCode + ' ' + JSON.stringify(hook));
152 |
153 | if (argv.json)
154 | report.json(hook);
155 | else
156 | {
157 | report.success('+', hook.name + ' ➜ ' + hook.endpoint);
158 | }
159 | });
160 | };
161 |
162 | function noop() {}
163 |
164 | function builder(yargs)
165 | {
166 | return yargs
167 | .command('ls [pkg]', 'list your hooks', noop, hooks.ls)
168 | .command('add ', 'add a hook to the named package or object', function buildAdd(yargs)
169 | {
170 | return yargs.option('type', {
171 | alias: 't',
172 | description: 'one of owner, scope, or package.\nyou can hook specific packages but also objects that are related to many packages.\t@scope and package are taken care of for you, but to make an owner hook (like all packages substack maintains) don\'t forget --type owner',
173 | default: 'package'
174 | });
175 | }, hooks.add)
176 | .command('update [secret]', 'update an existing hook', noop, hooks.update)
177 | .command('rm ', 'remove a hook', noop, hooks.rm)
178 | .usage('wombat hook [options]\nadd or change web hooks')
179 | .example('wombat hook add lodash https://example.com/ my-shared-secret')
180 | .example('wombat hook add --type=owner substack https://example.com/ my-shared-secret')
181 | .example('wombat hook ls lodash')
182 | .example('wombat hook rm id-ers83f')
183 | .demand(2)
184 | ;
185 | }
186 |
187 | module.exports = {
188 | command: 'hook',
189 | describe: 'control your hooks',
190 | builder: builder,
191 | handler: hooks,
192 | aliases: [ 'hooks' ]
193 | };
194 |
--------------------------------------------------------------------------------
/commands/package.js:
--------------------------------------------------------------------------------
1 | var
2 | chalk = require('chalk'),
3 | columns = require('cli-columns'),
4 | moment = require('moment'),
5 | Registry = require('../lib/registry'),
6 | report = require('../lib/report')
7 | ;
8 |
9 | function encodePackageName(input)
10 | {
11 | return encodeURIComponent(input).replace('%40', '@');
12 | }
13 |
14 | function formatUser(user)
15 | {
16 | return chalk.blue(user.name) + ' <' + user.email + '>';
17 | }
18 |
19 | function builder(yargs)
20 | {
21 | return yargs.option('readme', {
22 | alias: 'r',
23 | description: 'render the readme as well as package meta info',
24 | type: 'boolean',
25 | });
26 | }
27 |
28 | function view(argv)
29 | {
30 | argv._handled = true;
31 | var reg = argv.reg || new Registry(argv);
32 | var opts = {
33 | uri: encodePackageName(argv.package),
34 | legacy: true
35 | };
36 |
37 | reg.authed(opts, function(err, response, pkg)
38 | {
39 | if (err)
40 | return report.failure('package', err.message);
41 | if (response.statusCode === 404)
42 | return report.success('package', 'package ' + argv.package + ' was not found.');
43 | if (!pkg)
44 | return report.failure('package', 'unexpected registry response! ' + JSON.stringify(pkg));
45 |
46 | require('normalize-package-data')(pkg);
47 |
48 | if (argv.json)
49 | {
50 | report.json(pkg);
51 | return;
52 | }
53 |
54 | if (!pkg['dist-tags'])
55 | {
56 | report.failure('package', 'this package is probably broken');
57 | console.log(Object.keys(pkg));
58 | report.json(pkg);
59 | return;
60 | }
61 |
62 | var latest = pkg['dist-tags'].latest;
63 | var version = pkg.versions[latest];
64 |
65 | console.log('');
66 | console.log(chalk.blue(pkg.name) + '@' + chalk.blue(latest));
67 | console.log('published ' + moment(pkg.time[latest]).format('lll'));
68 | if (version._npmUser)
69 | console.log('by ' + formatUser(version._npmUser));
70 | console.log('');
71 | console.log(pkg.description);
72 | console.log('');
73 |
74 | if (pkg.license) console.log('license: ' + chalk.magenta(pkg.license));
75 | console.log('on npm: ' + chalk.magenta('https://www.npmjs.com/package/' + encodePackageName(pkg.name)));
76 | if (pkg.homepage) console.log('homepage: ' + chalk.magenta(pkg.homepage));
77 | console.log('tarball: ' + chalk.magenta(version.dist.tarball));
78 | console.log('shasum: ' + chalk.magenta(version.dist.shasum));
79 |
80 | console.log('');
81 | console.log(chalk.blue('maintainers:'));
82 | pkg.maintainers.forEach(function(m) { console.log(formatUser(m)); });
83 |
84 | console.log('');
85 | console.log(chalk.blue('dependencies:'));
86 | var deps = [];
87 | Object.keys(version.dependencies || []).forEach(function(d)
88 | {
89 | deps.push(chalk.yellow(d) + ': ' + version.dependencies[d]);
90 | });
91 | if (!deps.length)
92 | console.log('none');
93 | else
94 | console.log(columns(deps));
95 |
96 | console.log('');
97 | console.log(chalk.blue('development dependencies:'));
98 | deps = [];
99 | Object.keys(version.devDependencies || []).forEach(function(d)
100 | {
101 | deps.push(chalk.yellow(d) + ': ' + version.devDependencies[d]);
102 | });
103 | if (!deps.length)
104 | console.log('none');
105 | else
106 | console.log(columns(deps));
107 |
108 | console.log('');
109 | console.log(chalk.blue('versions & dist tags:'));
110 | var versions = [];
111 | Object.keys(pkg.versions).forEach(function(v)
112 | {
113 | versions.push(chalk.yellow(v) + ': ' + moment(pkg.time[v]).format('lll'));
114 | });
115 | if (versions.length === 1)
116 | console.log('1 version published');
117 | else if (versions.length > 10)
118 | console.log(versions.length + ' versions published');
119 | else
120 | {
121 | console.log(columns(versions));
122 | }
123 | var disttags = [];
124 | Object.keys(pkg['dist-tags']).forEach(function(tag)
125 | {
126 | disttags.push(chalk.yellow(tag) + ': ' + pkg['dist-tags'][tag]);
127 | });
128 | console.log(columns(disttags));
129 |
130 | if (argv.readme)
131 | {
132 | var markdown = require('markdown-it')();
133 | var terminal = require('markdown-it-terminal');
134 |
135 | markdown.use(terminal);
136 | console.log('');
137 | console.log(markdown.render(pkg.readme));
138 | }
139 | });
140 | }
141 |
142 | module.exports = {
143 | command: 'package ',
144 | describe: 'see information about the named package',
145 | handler: view,
146 | builder: builder
147 | };
148 |
--------------------------------------------------------------------------------
/commands/versions.js:
--------------------------------------------------------------------------------
1 | var
2 | chalk = require('chalk'),
3 | columns = require('cli-columns'),
4 | moment = require('moment'),
5 | Registry = require('../lib/registry'),
6 | report = require('../lib/report')
7 | ;
8 |
9 | function encodePackageName(input)
10 | {
11 | return encodeURIComponent(input).replace('%40', '@');
12 | }
13 |
14 | function versions(argv)
15 | {
16 | argv._handled = true;
17 | var reg = argv.reg || new Registry(argv);
18 | var opts = {
19 | uri: encodePackageName(argv.package),
20 | legacy: true
21 | };
22 |
23 | reg.authed(opts, function(err, response, pkg)
24 | {
25 | if (err)
26 | return report.failure('package', err.message);
27 | if (response.statusCode === 404)
28 | return report.success('package', 'package ' + argv.package + ' was not found.');
29 | if (!pkg)
30 | return report.failure('package', 'unexpected registry response! ' + JSON.stringify(pkg));
31 |
32 | require('normalize-package-data')(pkg);
33 |
34 | if (argv.json)
35 | {
36 | report.json(pkg);
37 | return;
38 | }
39 |
40 | if (!pkg['dist-tags'])
41 | {
42 | report.failure('package', 'this package is probably broken');
43 | console.log(Object.keys(pkg));
44 | report.json(pkg);
45 | return;
46 | }
47 |
48 | var latest = pkg['dist-tags'].latest;
49 |
50 | console.log('');
51 | console.log(chalk.blue(pkg.name) + '@' + chalk.blue(latest));
52 | console.log('published ' + moment(pkg.time[latest]).format('lll'));
53 |
54 | var disttags = [];
55 | Object.keys(pkg['dist-tags']).forEach(function(tag)
56 | {
57 | disttags.push(chalk.yellow(tag) + ': ' + pkg['dist-tags'][tag]);
58 | });
59 | console.log('\ndist tags:');
60 | console.log(columns(disttags));
61 | console.log('');
62 |
63 | var versions = [];
64 | Object.keys(pkg.versions).forEach(function(v)
65 | {
66 | versions.push(chalk.yellow(v) + ': ' + moment(pkg.time[v]).format('lll'));
67 | });
68 | if (versions.length === 1)
69 | console.log('1 version published');
70 | else
71 | console.log(versions.length + ' versions published');
72 | console.log(columns(versions));
73 |
74 | });
75 | }
76 |
77 | module.exports = {
78 | command: 'versions ',
79 | describe: 'see all available versions for the named package',
80 | handler: versions,
81 | builder: function(){}
82 | };
83 |
--------------------------------------------------------------------------------
/commands/whoami.js:
--------------------------------------------------------------------------------
1 | var
2 | Registry = require('../lib/registry'),
3 | report = require('../lib/report')
4 | ;
5 |
6 | function whoami(argv)
7 | {
8 | var reg = argv.reg || new Registry(argv);
9 |
10 | reg.authed({ method: 'GET', uri: '/-/whoami', legacy: true }, function(err, res, body)
11 | {
12 | if (err)
13 | return report.failure('whoami', err.message);
14 | if (!body || !body.username)
15 | return report.failure('whoami', 'unexpected registry response! ' + JSON.stringify(body));
16 |
17 | console.log(body.username.trim());
18 | });
19 | argv._handled = true;
20 | }
21 |
22 | module.exports = {
23 | command: 'whoami',
24 | describe: 'the username you are authenticated as',
25 | handler: whoami,
26 | builder: function(){}
27 | };
28 |
--------------------------------------------------------------------------------
/lib/config.js:
--------------------------------------------------------------------------------
1 | var
2 | cloneDeep = require('lodash.clonedeep'),
3 | fs = require('fs'),
4 | path = require('path'),
5 | rc = require('rc'),
6 | toml = require('toml'),
7 | tomlify = require('tomlify-j0.4')
8 | ;
9 |
10 | var DEFAULTS = {
11 | 'default': {
12 | 'registry': 'https://registry.npmjs.org',
13 | 'api': 'https://registry.npmjs.org/-/npm'
14 | }
15 | };
16 |
17 | function WombatRC(argv, cfg)
18 | {
19 | argv = argv || { registry: 'default' };
20 | if (!(this instanceof WombatRC)) return new WombatRC(argv, cfg);
21 |
22 | this.config = cfg || rc('wombat', cloneDeep(DEFAULTS), null, toml.parse); // injection for testing
23 | this.load(argv.registry);
24 | if (this.config.configs)
25 | this.cpath = this.config.configs[0];
26 | else
27 | {
28 | // I will just guess that this is not enough.
29 | this.cpath = path.join(process.env.HOME, '.wombatrc');
30 | this._dirty = true;
31 | }
32 | }
33 |
34 | WombatRC.prototype.section = 'default';
35 | WombatRC.prototype.config = null;
36 | WombatRC.prototype._dirty = false;
37 |
38 | WombatRC.prototype.load = function section(desired)
39 | {
40 | var section = desired || 'default';
41 | if (this.config.hasOwnProperty(section))
42 | this.section = section;
43 |
44 | return this.config[section];
45 | };
46 |
47 | WombatRC.prototype.write = function write(callback)
48 | {
49 | if (!callback) callback = function() {};
50 |
51 | var self = this;
52 | if (!self._dirty) return callback();
53 | var data = tomlify(self.config, null, 4);
54 | fs.writeFile(self.cpath, data, callback);
55 | };
56 |
57 | WombatRC.prototype.get = function get(key)
58 | {
59 | return this.config[this.section][key];
60 | };
61 |
62 | WombatRC.prototype.set = function set(key, value)
63 | {
64 | this.config[this.section][key] = value;
65 | this._dirty = true;
66 | };
67 |
68 | module.exports = WombatRC;
69 | WombatRC.DEFAULTS = DEFAULTS;
70 |
--------------------------------------------------------------------------------
/lib/registry.js:
--------------------------------------------------------------------------------
1 | var
2 | Config = require('./config'),
3 | getAuthToken = require('registry-auth-token'),
4 | Request = require('request'),
5 | url = require('url')
6 | ;
7 |
8 | /*
9 | Construct one of these and then make registry API calls with it.
10 |
11 | Call authed() to do something while passing an auth token.
12 | Call anonymous() if you don't need to be logged in to do it.
13 | Pass the `legacy: true` field in the opts argument if you need to hit an old
14 | registry endpoint.
15 |
16 | Set the `registry` property if you want to specify one of several registries in the
17 | wombatrc. If you pass the yargs `argv` options object to the constructor, it'll
18 | pick the one set in the `--registry` option. Otherwise it'll use what's in the npmrc.
19 | */
20 |
21 | var Registry = module.exports = function Registry(argv)
22 | {
23 | if (!(this instanceof Registry)) return new Registry();
24 |
25 | argv = argv || {};
26 | this.config = new Config(argv);
27 |
28 | // so we can inject dependencies for testing
29 | this.requestfunc = Request;
30 | this.getAuthToken = getAuthToken;
31 | };
32 |
33 | Registry.prototype._registry = null;
34 | Registry.prototype._config = null;
35 |
36 | Registry.prototype.authed = function authed(opts, callback)
37 | {
38 | var auth = this.getAuthToken(this.registry) || {};
39 |
40 | var hostname = opts.legacy ? this.registry : this.api;
41 |
42 | // add a slash if it missing.
43 | // not using url.resolve so we can support api through the registry url paths. url.resolve(hostname, opts.uri),
44 | // https://registry.npmjs.org/-/api/v1/hooks/hook etc.
45 | // we should remove the default api url probably?
46 | if (opts.uri.indexOf('/') !== 0)
47 | opts.uri = '/' + opts.uri;
48 |
49 | var options = {
50 | url: hostname + opts.uri,
51 | method: opts.method || 'GET',
52 | json: opts.json || opts.body || true,
53 | };
54 | if (auth.token)
55 | options.auth = {bearer: auth.token};
56 | this.requestfunc(options, callback);
57 | };
58 |
59 | Registry.prototype.anonymous = function anonymous(opts, callback)
60 | {
61 | var hostname = opts.legacy ? this.registry : this.api;
62 | var options = {
63 | url: url.resolve(hostname, opts.uri),
64 | method: opts.method,
65 | json: opts.json || opts.body || true
66 | };
67 |
68 | this.requestfunc(options, callback);
69 | };
70 |
71 | function getReg()
72 | {
73 | return this._registry;
74 | }
75 |
76 | function setReg(v)
77 | {
78 | if (!v.match(/^(http:\/\/|https:\/\/)/))
79 | v = 'https://' + v;
80 | v = v.replace(/\/$/, '');
81 |
82 | this._registry = v;
83 | return this._registry;
84 | }
85 | Object.defineProperty(Registry.prototype, 'registry', { set: setReg, get: getReg, enumerable: true });
86 |
87 | function getConfig()
88 | {
89 | return this._config;
90 | }
91 |
92 | function setConfig(v)
93 | {
94 | this._config = v;
95 | this.api = this.config.get('api');
96 | this.registry = this.config.get('registry');
97 | }
98 | Object.defineProperty(Registry.prototype, 'config', { set: setConfig, get: getConfig, enumerable: true });
99 |
100 | module.exports = Registry;
101 |
--------------------------------------------------------------------------------
/lib/report.js:
--------------------------------------------------------------------------------
1 | var
2 | chalk = require('chalk'),
3 | util = require('util');
4 |
5 | function success(prefix, message)
6 | {
7 | message = message || '';
8 | if (prefix)
9 | message = chalk.yellow(prefix) + ' ' + message;
10 | console.log(message);
11 | }
12 |
13 | function failure(prefix, message)
14 | {
15 | message = chalk.red('ERROR') + ': ' + message;
16 | if (prefix)
17 | message = chalk.yellow(prefix) + ' ' + message;
18 | console.log(message);
19 | }
20 |
21 | function json(obj)
22 | {
23 | console.log(util.inspect(obj, { colors: true }));
24 | }
25 |
26 | module.exports = {
27 | success: success,
28 | failure: failure,
29 | json: json
30 | };
31 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wombat",
3 | "description": "The wombat cli tool.",
4 | "version": "1.0.5",
5 | "author": "C J Silverio ",
6 | "bin": {
7 | "wombat": "bin/wombat-cli.js",
8 | "bat": "bin/wombat-cli.js"
9 | },
10 | "bugs": {
11 | "url": "https://github.com/npm/wombat-cli/issues"
12 | },
13 | "dependencies": {
14 | "chalk": "~1.1.3",
15 | "cli-columns": "~2.0.2",
16 | "cli-table2": "~0.2.0",
17 | "lodash.clonedeep": "~4.5.0",
18 | "markdown-it": "~8.2.1",
19 | "markdown-it-terminal": "0.0.4",
20 | "moment": "~2.17.0",
21 | "normalize-package-data": "~2.3.5",
22 | "rc": "~1.1.6",
23 | "registry-auth-token": "~3.1.0",
24 | "request": "~2.79.0",
25 | "require-directory": "~2.1.1",
26 | "toml": "~2.3.0",
27 | "tomlify-j0.4": "~1.0.1",
28 | "update-notifier": "~2.0.0",
29 | "yargs": "~6.6.0"
30 | },
31 | "devDependencies": {
32 | "coveralls": "~2.11.16",
33 | "eslint": "~3.15.0",
34 | "lodash.assign": "~4.2.0",
35 | "mocha": "~3.2.0",
36 | "must": "~0.13.4",
37 | "nyc": "~10.1.2",
38 | "sinon": "~1.17.5",
39 | "standard-version": "~4.0.0"
40 | },
41 | "homepage": "https://github.com/npm/wombat-cli#readme",
42 | "keywords": [
43 | "npm",
44 | "cli",
45 | "npm hooks",
46 | "webhooks"
47 | ],
48 | "license": "ISC",
49 | "engines": {
50 | "node": ">=0.10"
51 | },
52 | "main": "index.js",
53 | "repository": {
54 | "type": "git",
55 | "url": "git+https://github.com/npm/wombat-cli.git"
56 | },
57 | "scripts": {
58 | "coverage": "nyc report --reporter=text-lcov | coveralls",
59 | "lint": "eslint .",
60 | "release": "standard-version",
61 | "test": "nyc mocha -R spec test/",
62 | "travis": "npm run lint && npm test"
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/test/01-config.js:
--------------------------------------------------------------------------------
1 | /*global describe:true, it:true, before:true, after:true, beforeEach: true, afterEach:true */
2 | 'use strict';
3 |
4 | var
5 | demand = require('must'),
6 | Config = require('../lib/config'),
7 | assign = require('lodash.assign')
8 | ;
9 |
10 | describe('configuration', function()
11 | {
12 | it('exports a constructor', function()
13 | {
14 | var cfg = new Config({ registry: 'default' }, assign({}, Config.DEFAULTS));
15 | cfg.must.be.instanceof(Config);
16 | cfg.must.have.property('config');
17 | cfg.get.must.be.a.function();
18 | cfg.load.must.be.a.function();
19 | });
20 |
21 | it('makes one even without the new', function()
22 | {
23 | var cfg = new Config({ registry: 'default' }, assign({}, Config.DEFAULTS));
24 | cfg.must.be.instanceof(Config);
25 | cfg.must.have.property('config');
26 | });
27 |
28 | it('the constructor sets a section', function()
29 | {
30 | var cfg = new Config({ registry: 'default' }, assign({}, Config.DEFAULTS));
31 | cfg.section.must.equal('default');
32 | });
33 |
34 | it('load() returns the named section', function()
35 | {
36 | var cfg = new Config({ registry: 'default' }, assign({}, Config.DEFAULTS));
37 | cfg.load.must.be.a.function();
38 | var chunk = cfg.load('default');
39 | chunk.must.be.an.object();
40 | chunk.must.have.property('api');
41 |
42 | var chunk2 = cfg.load({ registry: 'blort' });
43 | demand(chunk2).not.exist();
44 | });
45 |
46 | it('get() returns the key for the named value', function()
47 | {
48 | var cfg = new Config({ registry: 'default' }, assign({}, Config.DEFAULTS));
49 | var value = cfg.get('api');
50 | value.must.equal('https://registry.npmjs.org/-/npm');
51 | });
52 |
53 | it('set() sets a value in the current config section', function()
54 | {
55 | var cfg = new Config({ registry: 'default' }, assign({}, Config.DEFAULTS));
56 | cfg.set('username', 'dimwitflathead');
57 | cfg.config.default.must.have.property('username');
58 | cfg.config.default.username.must.equal('dimwitflathead');
59 | cfg._dirty.must.be.true();
60 | cfg.get('username').must.equal('dimwitflathead');
61 | });
62 |
63 | it('write() is a thing that exists', function()
64 | {
65 | var cfg = new Config({ registry: 'default' }, assign({}, Config.DEFAULTS));
66 | cfg.must.have.property('write');
67 | cfg.write.must.be.a.function();
68 | });
69 | });
70 |
--------------------------------------------------------------------------------
/test/02-registry.js:
--------------------------------------------------------------------------------
1 | /*global describe:true, it:true, before:true, after:true, beforeEach: true, afterEach:true */
2 | 'use strict';
3 |
4 | var
5 | assign = require('lodash.assign'),
6 | demand = require('must'),
7 | sinon = require('sinon'),
8 | Registry = require('../lib/registry'),
9 | Config = require('../lib/config')
10 | ;
11 |
12 | describe('registry client', function()
13 | {
14 | describe('constructor', function()
15 | {
16 | it('can be constructed', function()
17 | {
18 | Registry.must.be.a.function();
19 | });
20 |
21 | it('exports some functions', function()
22 | {
23 | var reg = Registry();
24 |
25 | reg.must.have.property('authed');
26 | reg.authed.must.be.a.function();
27 | reg.must.have.property('anonymous');
28 | reg.anonymous.must.be.a.function();
29 |
30 | reg.must.have.property('registry');
31 | });
32 | });
33 |
34 | describe('anonymous()', function()
35 | {
36 | it('calls request with the passed uri', function(done)
37 | {
38 | var reg = Registry();
39 | reg.config = new Config({ registry: 'default' }, assign({}, Config.DEFAULTS));
40 |
41 | var expected = {
42 | url: 'https://registry.npmjs.org/foo',
43 | method: 'GET',
44 | json: true,
45 | };
46 |
47 | var spy = sinon.stub();
48 | spy.yields(null, 'response', 'body');
49 | reg.requestfunc = spy;
50 |
51 | reg.anonymous({ method: 'GET', uri: '/foo' }, function(err, res, body)
52 | {
53 | demand(err).not.exist();
54 | res.must.equal('response');
55 | body.must.equal('body');
56 | spy.args[0][0].must.eql(expected);
57 |
58 | done();
59 | });
60 | });
61 |
62 | it('passes along a body parameter', function(done)
63 | {
64 | var expected = {
65 | url: 'https://registry.npmjs.org/foo',
66 | method: 'POST',
67 | json: { data: 'yes' },
68 | };
69 |
70 | var requestSpy = sinon.stub();
71 | requestSpy.yields(null, 'response', 'body');
72 |
73 | var reg = Registry();
74 | reg.config = new Config({ registry: 'default' }, assign({}, Config.DEFAULTS));
75 | reg.requestfunc = requestSpy;
76 |
77 | reg.anonymous({ method: 'POST', uri: '/foo', json: { data: 'yes' } }, function(err, res, body)
78 | {
79 | demand(err).not.exist();
80 | res.must.equal('response');
81 | body.must.equal('body');
82 | requestSpy.calledWith(expected).must.be.true();
83 |
84 | done();
85 | });
86 | });
87 | });
88 |
89 | describe('authed()', function()
90 | {
91 | it('calls request with the passed uri', function(done)
92 | {
93 | var expected = {
94 | url: 'https://registry.npmjs.org/-/npm/foo',
95 | method: 'GET',
96 | json: true,
97 | auth: { bearer: 'i-am-a-token' },
98 | };
99 | var requestSpy = sinon.stub();
100 | requestSpy.yields(null, 'response', 'body');
101 |
102 | var authstub = sinon.stub();
103 | authstub.returns({token: 'i-am-a-token'});
104 |
105 | var reg = Registry();
106 | reg.config = new Config({ registry: 'default' }, assign({}, Config.DEFAULTS));
107 | reg.getAuthToken = authstub;
108 | reg.requestfunc = requestSpy;
109 |
110 | reg.authed({ method: 'GET', uri: '/foo' }, function(err, res, body)
111 | {
112 | demand(err).not.exist();
113 | res.must.equal('response');
114 | body.must.equal('body');
115 | requestSpy.calledWith(expected).must.be.true();
116 |
117 | done();
118 | });
119 | });
120 |
121 | it('passes along a body parameter', function(done)
122 | {
123 | var expected = {
124 | url: 'https://registry.npmjs.org/-/npm/foo',
125 | method: 'GET',
126 | json: { data: 'yes' },
127 | auth: { bearer: 'i-am-a-token' },
128 | };
129 |
130 | var requestSpy = sinon.stub();
131 | requestSpy.yields(null, 'response', 'body');
132 |
133 | var authstub = sinon.stub();
134 | authstub.returns({token: 'i-am-a-token'});
135 |
136 | var reg = Registry();
137 | reg.config = new Config({ registry: 'default' }, assign({}, Config.DEFAULTS));
138 | reg.getAuthToken = authstub;
139 | reg.requestfunc = requestSpy;
140 |
141 | reg.authed({ method: 'GET', uri: '/foo', body: { data: 'yes' } }, function(err, res, body)
142 | {
143 | demand(err).not.exist();
144 | res.must.equal('response');
145 | body.must.equal('body');
146 | requestSpy.calledWith(expected).must.be.true();
147 |
148 | done();
149 | });
150 | });
151 | });
152 | });
153 |
--------------------------------------------------------------------------------
/test/03-whoami.js:
--------------------------------------------------------------------------------
1 | /*global describe:true, it:true, before:true, after:true, beforeEach: true, afterEach:true */
2 | 'use strict';
3 |
4 | var
5 | demand = require('must'),
6 | Registry = require('../lib/registry'),
7 | Report = require('../lib/report'),
8 | sinon = require('sinon'),
9 | whoami = require('../commands/whoami')
10 | ;
11 |
12 | describe('whoami command', function()
13 | {
14 | it('is a yargs command module', function()
15 | {
16 | whoami.must.be.an.object();
17 |
18 | whoami.must.have.property('command');
19 | whoami.command.must.be.a.string();
20 | whoami.command.must.equal('whoami');
21 |
22 | whoami.must.have.property('describe');
23 | whoami.describe.must.be.a.string();
24 |
25 | whoami.must.have.property('builder');
26 | whoami.builder.must.be.a.function();
27 |
28 | whoami.must.have.property('handler');
29 | whoami.handler.must.be.a.function();
30 | });
31 |
32 | it('calls Registry.authed', function()
33 | {
34 | var spy = sinon.spy(console, 'log');
35 | var reg = Registry();
36 | var stub = sinon.stub(reg, 'authed');
37 | stub.yields(null, { statusCode: 200 }, { username: 'test '});
38 |
39 | whoami.handler({ reg: reg });
40 |
41 | stub.calledOnce.must.be.true();
42 | stub.calledWith({ method: 'GET', uri: '/-/whoami', legacy: true }).must.be.true();
43 | stub.restore();
44 | spy.calledWith('test').must.be.true();
45 | spy.restore();
46 | });
47 |
48 | it('logs an error on networking error', function()
49 | {
50 | var spy = sinon.spy(Report, 'failure');
51 | var reg = Registry();
52 | var stub = sinon.stub(reg, 'authed');
53 | stub.yields(new Error('wat'));
54 | whoami.handler({ reg: reg });
55 | stub.calledOnce.must.be.true();
56 | stub.restore();
57 | spy.calledOnce.must.be.true();
58 | spy.restore();
59 | });
60 |
61 | it('logs an error on unexpected response', function()
62 | {
63 | var spy = sinon.spy(Report, 'failure');
64 | var reg = Registry();
65 | var stub = sinon.stub(reg, 'authed');
66 | stub.yields(null, { statusCode: 401 }, { error: 'wat' });
67 | whoami.handler({ reg: reg });
68 | stub.calledOnce.must.be.true();
69 | stub.restore();
70 | spy.calledOnce.must.be.true();
71 | spy.restore();
72 | });
73 |
74 | it('sets the ._handled field on its input', function()
75 | {
76 | var reg = Registry();
77 | var stub = sinon.stub(reg, 'authed');
78 | stub.yields(null, { statusCode: 200 }, { username: 'test '});
79 | var argv = { reg: reg };
80 | whoami.handler(argv);
81 | stub.restore();
82 | argv.must.have.property('_handled');
83 | argv._handled.must.be.true();
84 | });
85 | });
86 |
--------------------------------------------------------------------------------
/test/04-hooks.js:
--------------------------------------------------------------------------------
1 | /*global describe:true, it:true, before:true, after:true, beforeEach: true, afterEach:true */
2 | 'use strict';
3 |
4 | var
5 | demand = require('must'),
6 | Registry = require('../lib/registry'),
7 | Report = require('../lib/report'),
8 | sinon = require('sinon'),
9 | hook = require('../commands/hook')
10 | ;
11 |
12 | describe('hook command', function()
13 | {
14 | describe('exports', function()
15 | {
16 | it('is a yargs command module', function()
17 | {
18 | hook.must.be.an.object();
19 |
20 | hook.must.have.property('command');
21 | hook.command.must.be.a.string();
22 | hook.command.must.equal('hook');
23 |
24 | hook.must.have.property('describe');
25 | hook.describe.must.be.a.string();
26 |
27 | hook.must.have.property('builder');
28 | hook.builder.must.be.a.function();
29 |
30 | hook.must.have.property('handler');
31 | hook.handler.must.be.a.function();
32 | });
33 |
34 | it('exports the expected commands', function()
35 | {
36 | ['ls', 'add', 'update', 'rm'].forEach(function(cmd)
37 | {
38 | hook.handler.must.have.property(cmd);
39 | hook.handler[cmd].must.be.a.function();
40 | });
41 | });
42 | });
43 |
44 | describe('hook.ls', function()
45 | {
46 | it('calls Registry.authed', function()
47 | {
48 | var spy = sinon.spy(console, 'log');
49 | var reg = Registry();
50 | var stub = sinon.stub(reg, 'authed');
51 | stub.yields(null, { statusCode: 200 }, { objects: [] });
52 |
53 | hook.handler.ls({ reg: reg });
54 |
55 | stub.calledOnce.must.be.true();
56 | stub.calledWith({ uri: '/v1/hooks' }).must.be.true();
57 | stub.restore();
58 | spy.called.must.be.true();
59 | spy.calledWith('\u001b[33mhooks\u001b[39m you do not have any hooks configured yet.').must.be.true();
60 | spy.restore();
61 | });
62 | });
63 |
64 |
65 | describe('hook.add', function()
66 | {
67 | it('calls Registry.authed', function()
68 | {
69 | var spy = sinon.spy(Report, 'success');
70 | var reg = Registry();
71 | var stub = sinon.stub(reg, 'authed');
72 | stub.yields(null, { statusCode: 200 }, { id: 'foo', name: 'bar', endpoint: 'baz' });
73 | var payload = { reg: reg, pkg: 'foo', type: 'package', url: 'url', secret: 'secret' };
74 |
75 | hook.handler.add(payload);
76 |
77 | stub.calledOnce.must.be.true();
78 | stub.calledWith({
79 | method: 'POST',
80 | uri: '/v1/hooks/hook/',
81 | body: {
82 | type: payload.type,
83 | name: payload.pkg,
84 | endpoint: payload.url,
85 | secret: payload.secret,
86 | }
87 | }).must.be.true();
88 | stub.restore();
89 | spy.called.must.be.true();
90 | spy.calledWith('+', 'bar ➜ baz').must.be.true();
91 | spy.restore();
92 | });
93 | });
94 |
95 | describe('hook.update', function()
96 | {
97 | it('calls Registry.authed', function()
98 | {
99 | var spy = sinon.spy(Report, 'success');
100 | var reg = Registry();
101 | var stub = sinon.stub(reg, 'authed');
102 | stub.yields(null, { statusCode: 200 }, { id: 'foo', name: 'bar', endpoint: 'baz' });
103 | var payload = { reg: reg, id: 'wat', url: 'url', secret: 'secret' };
104 |
105 | hook.handler.update(payload);
106 |
107 | stub.calledOnce.must.be.true();
108 | stub.calledWith({
109 | method: 'PUT',
110 | uri: '/v1/hooks/hook/wat',
111 | body: {
112 | endpoint: payload.url,
113 | secret: payload.secret,
114 | }
115 | }).must.be.true();
116 | stub.restore();
117 | spy.called.must.be.true();
118 | spy.calledWith('+', 'bar ➜ baz').must.be.true();
119 | spy.restore();
120 | });
121 | });
122 |
123 | describe('hook.rm', function()
124 | {
125 | it('calls Registry.authed', function()
126 | {
127 | var spy = sinon.spy(Report, 'success');
128 | var reg = Registry();
129 | var stub = sinon.stub(reg, 'authed');
130 | stub.yields(null, { statusCode: 200 }, { id: 'foo', name: 'watched', endpoint: 'no more' });
131 | var payload = { reg: reg, id: 'wat', url: 'url', secret: 'secret' };
132 |
133 | hook.handler.rm(payload);
134 |
135 | stub.calledOnce.must.be.true();
136 | stub.calledWith({
137 | method: 'DELETE',
138 | uri: '/v1/hooks/hook/wat',
139 | }).must.be.true();
140 | stub.restore();
141 | spy.called.must.be.true();
142 | spy.calledWith('–', 'watched ✘ no more').must.be.true();
143 | spy.restore();
144 | });
145 | });
146 | });
147 |
--------------------------------------------------------------------------------
/test/05-package.js:
--------------------------------------------------------------------------------
1 | /*global describe:true, it:true, before:true, after:true, beforeEach: true, afterEach:true */
2 | 'use strict';
3 |
4 | var
5 | demand = require('must'),
6 | Registry = require('../lib/registry'),
7 | sinon = require('sinon'),
8 | Package = require('../commands/package')
9 | ;
10 |
11 | var pkgFixture = require('./fixtures/scurry.json');
12 |
13 | describe('package command', function()
14 | {
15 | it('is a yargs command module', function()
16 | {
17 | Package.must.be.an.object();
18 |
19 | Package.must.have.property('command');
20 | Package.command.must.be.a.string();
21 | Package.command.must.equal('package ');
22 |
23 | Package.must.have.property('describe');
24 | Package.describe.must.be.a.string();
25 |
26 | Package.must.have.property('builder');
27 | Package.builder.must.be.a.function();
28 |
29 | Package.must.have.property('handler');
30 | Package.handler.must.be.a.function();
31 | });
32 |
33 | it('calls Registry.authed', function()
34 | {
35 | var reg = Registry();
36 | var stub = sinon.stub(reg, 'authed');
37 | stub.yields(null, { statusCode: 200 }, pkgFixture);
38 | var spy = sinon.stub(console, 'log');
39 |
40 | Package.handler({ reg: reg, package: 'scurry' });
41 |
42 | stub.calledOnce.must.be.true();
43 | stub.calledWith({ uri: 'scurry', legacy: true }).must.be.true();
44 | stub.restore();
45 | spy.called.must.be.true();
46 | spy.restore();
47 | });
48 |
49 | });
50 |
--------------------------------------------------------------------------------
/test/fixtures/scurry.json:
--------------------------------------------------------------------------------
1 | {
2 | "_id": "scurry",
3 | "_rev": "34-59c3468b274f24353037be6b09af69ec",
4 | "name": "scurry",
5 | "description": "A leveldb-backed consistent hash ring, for your toy caching needs.",
6 | "dist-tags":
7 | {
8 | "latest": "0.0.7"
9 | },
10 | "versions":
11 | {
12 | "0.0.1":
13 | {
14 | "name": "scurry",
15 | "version": "0.0.1",
16 | "description": "A leveldb-backed consistent hash ring, for your toy caching needs.",
17 | "author":
18 | {
19 | "name": "C J Silverio",
20 | "email": "ceejceej@gmail.com"
21 | },
22 | "license": "MIT",
23 | "main": "index.js",
24 | "scripts":
25 | {
26 | "start": "node index.js",
27 | "test": "mocha -R spec test/test*.js",
28 | "node1": "node index.js --id=node-one -s -p 3332 -g 4114 -d ./db | ./node_modules/.bin/bunyan -o short",
29 | "node2": "node index.js --id=node-two -p 3333 -g 4114 -h 10.0.0.5 -d ./db2 | ./node_modules/.bin/bunyan -o short"
30 | },
31 | "repository":
32 | {
33 | "type": "git",
34 | "url": "https://github.com/ceejbot/scurry"
35 | },
36 | "keywords": ["leveldb", "scuttlebutt"],
37 | "gitHead": "8b34720988b12cfb98a7c9c406f0e80e7d43974c",
38 | "bugs":
39 | {
40 | "url": "https://github.com/ceejbot/scurry/issues"
41 | },
42 | "dependencies":
43 | {
44 | "bunyan": "*",
45 | "crdt": "*",
46 | "level-sublevel": "*",
47 | "leveldown": "*",
48 | "levelup": "*",
49 | "light-cycle": "*",
50 | "my-local-ip": "*",
51 | "node-uuid": "*",
52 | "optimist": "*",
53 | "p-promise": "*",
54 | "request": "*",
55 | "restify": "*",
56 | "scuttlebutt": "*"
57 | },
58 | "_id": "scurry@0.0.1",
59 | "dist":
60 | {
61 | "shasum": "759abaf18b080e9853299106aa0f606a603aaaee",
62 | "tarball": "https://registry.npmjs.org/scurry/-/scurry-0.0.1.tgz"
63 | },
64 | "_from": ".",
65 | "_npmVersion": "1.3.2",
66 | "_npmUser":
67 | {
68 | "name": "ceejbot",
69 | "email": "ceejceej@gmail.com"
70 | },
71 | "maintainers": [
72 | {
73 | "name": "ceejbot",
74 | "email": "ceejceej@gmail.com"
75 | }],
76 | "directories":
77 | {}
78 | },
79 | "0.0.2":
80 | {
81 | "name": "scurry",
82 | "version": "0.0.2",
83 | "description": "A leveldb-backed consistent hash ring, for your toy caching needs.",
84 | "author":
85 | {
86 | "name": "C J Silverio",
87 | "email": "ceejceej@gmail.com"
88 | },
89 | "license": "MIT",
90 | "main": "index.js",
91 | "scripts":
92 | {
93 | "start": "node index.js",
94 | "test": "mocha -R spec test/test*.js",
95 | "node1": "node index.js -c ./examples/server.json | ./node_modules/.bin/bunyan -o short",
96 | "node2": "node index.js -c ./examples/node-two.json | ./node_modules/.bin/bunyan -o short",
97 | "node3": "node index.js -c ./examples/node-three.json | ./node_modules/.bin/bunyan -o short"
98 | },
99 | "repository":
100 | {
101 | "type": "git",
102 | "url": "https://github.com/ceejbot/scurry"
103 | },
104 | "keywords": ["leveldb", "scuttlebutt"],
105 | "gitHead": "8b34720988b12cfb98a7c9c406f0e80e7d43974c",
106 | "bugs":
107 | {
108 | "url": "https://github.com/ceejbot/scurry/issues"
109 | },
110 | "dependencies":
111 | {
112 | "bunyan": "*",
113 | "crdt": "*",
114 | "level-sublevel": "*",
115 | "level-ttl": "*",
116 | "leveldown": "*",
117 | "levelup": "*",
118 | "light-cycle": "*",
119 | "my-local-ip": "*",
120 | "node-uuid": "*",
121 | "optimist": "*",
122 | "p-promise": "*",
123 | "restify": "*",
124 | "scuttlebutt": "*"
125 | },
126 | "_id": "scurry@0.0.2",
127 | "dist":
128 | {
129 | "shasum": "951a4978323110425230ee1997e8b78d49d90aec",
130 | "tarball": "https://registry.npmjs.org/scurry/-/scurry-0.0.2.tgz"
131 | },
132 | "_from": ".",
133 | "_npmVersion": "1.3.2",
134 | "_npmUser":
135 | {
136 | "name": "ceejbot",
137 | "email": "ceejceej@gmail.com"
138 | },
139 | "maintainers": [
140 | {
141 | "name": "ceejbot",
142 | "email": "ceejceej@gmail.com"
143 | }],
144 | "directories":
145 | {}
146 | },
147 | "0.0.3":
148 | {
149 | "name": "scurry",
150 | "version": "0.0.3",
151 | "description": "A leveldb-backed consistent hash ring, for your toy caching needs.",
152 | "author":
153 | {
154 | "name": "C J Silverio",
155 | "email": "ceejceej@gmail.com"
156 | },
157 | "license": "MIT",
158 | "main": "index.js",
159 | "scripts":
160 | {
161 | "start": "node index.js --id=server -m -g 4114 -p 3333 --dbpath=./db | ./node_modules/.bin/bunyan -o short",
162 | "node1": "node index.js -c ./examples/server.json | ./node_modules/.bin/bunyan -o short",
163 | "node2": "node index.js -c ./examples/node-two.json | ./node_modules/.bin/bunyan -o short",
164 | "node3": "node index.js -c ./examples/node-three.json | ./node_modules/.bin/bunyan -o short",
165 | "test": "npm run test-suite && npm run coveralls && npm run test-cov",
166 | "test-suite": "./node_modules/.bin/mocha -R spec test/*.js",
167 | "test-cov": "./node_modules/.bin/mocha --require blanket -R travis-cov test/*.js",
168 | "coverage": "./node_modules/.bin/mocha --require blanket -R html-cov test/*.js > test/coverage.html",
169 | "coveralls": "NODE_ENV=test YOURPACKAGE_COVERAGE=1 ./node_modules/.bin/mocha --require blanket --reporter mocha-lcov-reporter test/*.js | ./node_modules/coveralls/bin/coveralls.js"
170 | },
171 | "repository":
172 | {
173 | "type": "git",
174 | "url": "https://github.com/ceejbot/scurry"
175 | },
176 | "keywords": ["leveldb", "scuttlebutt", "autosharding"],
177 | "gitHead": "8b34720988b12cfb98a7c9c406f0e80e7d43974c",
178 | "bugs":
179 | {
180 | "url": "https://github.com/ceejbot/scurry/issues"
181 | },
182 | "dependencies":
183 | {
184 | "bunyan": "*",
185 | "crdt": "*",
186 | "level-sublevel": "*",
187 | "level-ttl": "*",
188 | "leveldown": "*",
189 | "levelup": "*",
190 | "light-cycle": "*",
191 | "my-local-ip": "*",
192 | "node-uuid": "*",
193 | "optimist": "*",
194 | "p-promise": "*",
195 | "restify": "*",
196 | "scuttlebutt": "*"
197 | },
198 | "devDependencies":
199 | {
200 | "blanket": "*",
201 | "chai": "*",
202 | "mocha": "*",
203 | "travis-cov": "*",
204 | "mocha-lcov-reporter": "*",
205 | "coveralls": "*"
206 | },
207 | "_id": "scurry@0.0.3",
208 | "dist":
209 | {
210 | "shasum": "63b60c935d29bff4a3dab8b06e8797c5f1f479e1",
211 | "tarball": "https://registry.npmjs.org/scurry/-/scurry-0.0.3.tgz"
212 | },
213 | "_from": ".",
214 | "_npmVersion": "1.3.2",
215 | "_npmUser":
216 | {
217 | "name": "ceejbot",
218 | "email": "ceejceej@gmail.com"
219 | },
220 | "maintainers": [
221 | {
222 | "name": "ceejbot",
223 | "email": "ceejceej@gmail.com"
224 | }],
225 | "directories":
226 | {}
227 | },
228 | "0.0.4":
229 | {
230 | "name": "scurry",
231 | "version": "0.0.4",
232 | "description": "A leveldb-backed consistent hash ring, for your toy caching needs.",
233 | "author":
234 | {
235 | "name": "C J Silverio",
236 | "email": "ceejceej@gmail.com"
237 | },
238 | "license": "MIT",
239 | "main": "index.js",
240 | "scripts":
241 | {
242 | "start": "node index.js --id=server -m -g 4114 -p 3333 --dbpath=./db | ./node_modules/.bin/bunyan -o short",
243 | "node1": "node index.js -c ./examples/server.json | ./node_modules/.bin/bunyan -o short",
244 | "node2": "node index.js -c ./examples/node-two.json | ./node_modules/.bin/bunyan -o short",
245 | "node3": "node index.js -c ./examples/node-three.json | ./node_modules/.bin/bunyan -o short",
246 | "test": "npm run test-suite && npm run coveralls && npm run test-cov",
247 | "test-suite": "./node_modules/.bin/mocha -R spec test/*.js",
248 | "test-cov": "./node_modules/.bin/mocha --require blanket -R travis-cov test/*.js",
249 | "coverage": "./node_modules/.bin/mocha --require blanket -R html-cov test/*.js > test/coverage.html",
250 | "coveralls": "NODE_ENV=test YOURPACKAGE_COVERAGE=1 ./node_modules/.bin/mocha --require blanket --reporter mocha-lcov-reporter test/*.js | ./node_modules/coveralls/bin/coveralls.js"
251 | },
252 | "repository":
253 | {
254 | "type": "git",
255 | "url": "https://github.com/ceejbot/scurry"
256 | },
257 | "keywords": ["leveldb", "scuttlebutt", "autosharding"],
258 | "gitHead": "8b34720988b12cfb98a7c9c406f0e80e7d43974c",
259 | "bugs":
260 | {
261 | "url": "https://github.com/ceejbot/scurry/issues"
262 | },
263 | "dependencies":
264 | {
265 | "bunyan": "*",
266 | "crdt": "*",
267 | "level-sublevel": "*",
268 | "level-ttl": "*",
269 | "leveldown": "*",
270 | "levelup": "*",
271 | "light-cycle": "*",
272 | "lodash": "*",
273 | "my-local-ip": "*",
274 | "node-uuid": "*",
275 | "optimist": "*",
276 | "p-promise": "*",
277 | "restify": "*",
278 | "scuttlebutt": "*"
279 | },
280 | "devDependencies":
281 | {
282 | "blanket": "*",
283 | "chai": "*",
284 | "mocha": "*",
285 | "travis-cov": "*",
286 | "mocha-lcov-reporter": "*",
287 | "coveralls": "*"
288 | },
289 | "_id": "scurry@0.0.4",
290 | "dist":
291 | {
292 | "shasum": "f505e94b6193ce3137f2d5c991d213e1c91ab4ca",
293 | "tarball": "https://registry.npmjs.org/scurry/-/scurry-0.0.4.tgz"
294 | },
295 | "_from": ".",
296 | "_npmVersion": "1.3.11",
297 | "_npmUser":
298 | {
299 | "name": "ceejbot",
300 | "email": "ceejceej@gmail.com"
301 | },
302 | "maintainers": [
303 | {
304 | "name": "ceejbot",
305 | "email": "ceejceej@gmail.com"
306 | }],
307 | "directories":
308 | {}
309 | },
310 | "0.0.5":
311 | {
312 | "name": "scurry",
313 | "version": "0.0.5",
314 | "description": "A leveldb-backed consistent hash ring, for your toy caching needs.",
315 | "author":
316 | {
317 | "name": "C J Silverio",
318 | "email": "ceejceej@gmail.com"
319 | },
320 | "license": "MIT",
321 | "main": "index.js",
322 | "scripts":
323 | {
324 | "start": "node index.js --id=server -m -g 4114 -p 3333 --dbpath=./db | ./node_modules/.bin/bunyan -o short",
325 | "node1": "node index.js -c ./examples/server.json | ./node_modules/.bin/bunyan -o short",
326 | "node2": "node index.js -c ./examples/node-two.json | ./node_modules/.bin/bunyan -o short",
327 | "node3": "node index.js -c ./examples/node-three.json | ./node_modules/.bin/bunyan -o short",
328 | "test": "npm run test-suite && npm run coveralls && npm run test-cov",
329 | "test-suite": "mocha -R spec test/*.js",
330 | "test-cov": "mocha --require blanket -R travis-cov test/*.js",
331 | "coverage": "mocha --require blanket -R html-cov test/*.js > test/coverage.html",
332 | "coveralls": "NODE_ENV=test YOURPACKAGE_COVERAGE=1 ./node_modules/.bin/mocha --require blanket --reporter mocha-lcov-reporter test/*.js | ./node_modules/coveralls/bin/coveralls.js"
333 | },
334 | "repository":
335 | {
336 | "type": "git",
337 | "url": "https://github.com/ceejbot/scurry"
338 | },
339 | "keywords": ["leveldb", "scuttlebutt", "autosharding"],
340 | "gitHead": "8b34720988b12cfb98a7c9c406f0e80e7d43974c",
341 | "bugs":
342 | {
343 | "url": "https://github.com/ceejbot/scurry/issues"
344 | },
345 | "dependencies":
346 | {
347 | "bunyan": "^0.22.1",
348 | "crdt": "^3.6",
349 | "level-sublevel": "^5.2",
350 | "level-ttl": "^0.5",
351 | "leveldown": "^0.10.2",
352 | "levelup": "^0.18.2",
353 | "light-cycle": "^0.1",
354 | "lodash": "^2.4",
355 | "my-local-ip": "*",
356 | "node-uuid": "*",
357 | "p-promise": "*",
358 | "restify": "^2.6.1",
359 | "scuttlebutt": "^5.6.8",
360 | "yargs": "^1.0"
361 | },
362 | "devDependencies":
363 | {
364 | "blanket": "latest",
365 | "chai": "latest",
366 | "mocha": "latest",
367 | "travis-cov": "latest",
368 | "mocha-lcov-reporter": "latest",
369 | "coveralls": "latest"
370 | },
371 | "homepage": "https://github.com/ceejbot/scurry",
372 | "_id": "scurry@0.0.5",
373 | "dist":
374 | {
375 | "shasum": "9cd8c4207f4844ceb5c66129fdaa7bbe35276aea",
376 | "tarball": "https://registry.npmjs.org/scurry/-/scurry-0.0.5.tgz"
377 | },
378 | "_from": ".",
379 | "_npmVersion": "1.3.24",
380 | "_npmUser":
381 | {
382 | "name": "ceejbot",
383 | "email": "ceejceej@gmail.com"
384 | },
385 | "maintainers": [
386 | {
387 | "name": "ceejbot",
388 | "email": "ceejceej@gmail.com"
389 | }],
390 | "directories":
391 | {}
392 | },
393 | "0.0.6":
394 | {
395 | "name": "scurry",
396 | "description": "A leveldb-backed consistent hash ring, for your toy caching needs.",
397 | "version": "0.0.6",
398 | "author":
399 | {
400 | "name": "C J Silverio",
401 | "email": "ceejceej@gmail.com"
402 | },
403 | "bugs":
404 | {
405 | "url": "https://github.com/ceejbot/scurry/issues"
406 | },
407 | "config":
408 | {
409 | "blanket":
410 | {
411 | "pattern": "//^((?!/test|examples|node_modules/).)*$/ig",
412 | "onlyCwd": true
413 | },
414 | "travis-cov":
415 | {
416 | "threshold": 30
417 | }
418 | },
419 | "dependencies":
420 | {
421 | "bunyan": "^0.22.1",
422 | "crdt": "^3.6",
423 | "level-sublevel": "^5.2",
424 | "level-ttl": "^0.6.1",
425 | "leveldown": "^0.10.2",
426 | "levelup": "^0.18.2",
427 | "light-cycle": "^0.1",
428 | "lodash": "^2.4",
429 | "my-local-ip": "*",
430 | "node-uuid": "*",
431 | "p-promise": "*",
432 | "restify": "^2.6.1",
433 | "scuttlebutt": "^5.6.8",
434 | "yargs": "^1.0"
435 | },
436 | "devDependencies":
437 | {
438 | "blanket": "latest",
439 | "chai": "latest",
440 | "coveralls": "latest",
441 | "mocha": "latest",
442 | "mocha-lcov-reporter": "latest",
443 | "travis-cov": "latest"
444 | },
445 | "gitHead": "8b34720988b12cfb98a7c9c406f0e80e7d43974c",
446 | "keywords": ["leveldb", "scuttlebutt", "autosharding"],
447 | "license": "MIT",
448 | "main": "index.js",
449 | "repository":
450 | {
451 | "type": "git",
452 | "url": "https://github.com/ceejbot/scurry"
453 | },
454 | "scripts":
455 | {
456 | "start": "node index.js --id=server -m -g 4114 -p 3333 --dbpath=./db | ./node_modules/.bin/bunyan -o short",
457 | "node1": "node index.js -c ./examples/server.json | ./node_modules/.bin/bunyan -o short",
458 | "node2": "node index.js -c ./examples/node-two.json | ./node_modules/.bin/bunyan -o short",
459 | "node3": "node index.js -c ./examples/node-three.json | ./node_modules/.bin/bunyan -o short",
460 | "test": "npm run test-suite && npm run coveralls && npm run test-cov",
461 | "test-suite": "mocha -R spec test/*.js",
462 | "test-cov": "mocha --require blanket -R travis-cov test/*.js",
463 | "coverage": "mocha --require blanket -R html-cov test/*.js > test/coverage.html",
464 | "coveralls": "NODE_ENV=test YOURPACKAGE_COVERAGE=1 ./node_modules/.bin/mocha --require blanket --reporter mocha-lcov-reporter test/*.js | ./node_modules/coveralls/bin/coveralls.js"
465 | },
466 | "homepage": "https://github.com/ceejbot/scurry",
467 | "_id": "scurry@0.0.6",
468 | "_shasum": "2c8823d8747ac6e213c8242b224c62011dd61899",
469 | "_from": ".",
470 | "_npmVersion": "1.4.13",
471 | "_npmUser":
472 | {
473 | "name": "ceejbot",
474 | "email": "ceejceej@gmail.com"
475 | },
476 | "maintainers": [
477 | {
478 | "name": "ceejbot",
479 | "email": "ceejceej@gmail.com"
480 | }],
481 | "dist":
482 | {
483 | "shasum": "2c8823d8747ac6e213c8242b224c62011dd61899",
484 | "tarball": "https://registry.npmjs.org/scurry/-/scurry-0.0.6.tgz"
485 | },
486 | "directories":
487 | {}
488 | },
489 | "0.0.7":
490 | {
491 | "name": "scurry",
492 | "description": "A leveldb-backed consistent hash ring, for your toy caching needs.",
493 | "version": "0.0.7",
494 | "author":
495 | {
496 | "name": "C J Silverio",
497 | "email": "ceejceej@gmail.com"
498 | },
499 | "bugs":
500 | {
501 | "url": "https://github.com/ceejbot/scurry/issues"
502 | },
503 | "config":
504 | {
505 | "blanket":
506 | {
507 | "pattern": "//^((?!/test|examples|node_modules/).)*$/ig",
508 | "onlyCwd": true
509 | },
510 | "travis-cov":
511 | {
512 | "threshold": 30
513 | }
514 | },
515 | "dependencies":
516 | {
517 | "bunyan": "~1.2.0",
518 | "crdt": "^3.6",
519 | "level-sublevel": "^5.2",
520 | "level-ttl": "^0.6.1",
521 | "leveldown": "^0.10.2",
522 | "levelup": "^0.18.2",
523 | "light-cycle": "~1.1.0",
524 | "lodash": "^2.4",
525 | "my-local-ip": "*",
526 | "node-uuid": "*",
527 | "p-promise": "~0.4.8",
528 | "restify": "~2.8.3",
529 | "scuttlebutt": "^5.6.8",
530 | "yargs": "~1.3.2"
531 | },
532 | "devDependencies":
533 | {
534 | "blanket": "latest",
535 | "chai": "latest",
536 | "coveralls": "latest",
537 | "mocha": "latest",
538 | "mocha-lcov-reporter": "latest",
539 | "travis-cov": "latest"
540 | },
541 | "gitHead": "8b34720988b12cfb98a7c9c406f0e80e7d43974c",
542 | "keywords": ["leveldb", "scuttlebutt", "autosharding"],
543 | "license": "MIT",
544 | "main": "index.js",
545 | "repository":
546 | {
547 | "type": "git",
548 | "url": "https://github.com/ceejbot/scurry"
549 | },
550 | "scripts":
551 | {
552 | "start": "node index.js --id=server -m -g 4114 -p 3333 --dbpath=./db | ./node_modules/.bin/bunyan -o short",
553 | "node1": "node index.js -c ./examples/server.json | ./node_modules/.bin/bunyan -o short",
554 | "node2": "node index.js -c ./examples/node-two.json | ./node_modules/.bin/bunyan -o short",
555 | "node3": "node index.js -c ./examples/node-three.json | ./node_modules/.bin/bunyan -o short",
556 | "test": "npm run test-suite && npm run coveralls && npm run test-cov",
557 | "test-suite": "mocha -R spec test/*.js",
558 | "test-cov": "mocha --require blanket -R travis-cov test/*.js",
559 | "coverage": "mocha --require blanket -R html-cov test/*.js > test/coverage.html",
560 | "coveralls": "NODE_ENV=test YOURPACKAGE_COVERAGE=1 ./node_modules/.bin/mocha --require blanket --reporter mocha-lcov-reporter test/*.js | ./node_modules/coveralls/bin/coveralls.js"
561 | },
562 | "homepage": "https://github.com/ceejbot/scurry",
563 | "_id": "scurry@0.0.7",
564 | "_shasum": "864d7d7f25483bb739cfb1d15288b6419a63c735",
565 | "_from": ".",
566 | "_npmVersion": "2.1.6",
567 | "_nodeVersion": "0.10.31",
568 | "_npmUser":
569 | {
570 | "name": "ceejbot",
571 | "email": "ceejceej@gmail.com"
572 | },
573 | "maintainers": [
574 | {
575 | "name": "ceejbot",
576 | "email": "ceejceej@gmail.com"
577 | }],
578 | "dist":
579 | {
580 | "shasum": "864d7d7f25483bb739cfb1d15288b6419a63c735",
581 | "tarball": "https://registry.npmjs.org/scurry/-/scurry-0.0.7.tgz"
582 | },
583 | "directories":
584 | {}
585 | }
586 | },
587 | "readme": "scurry\n======\n\nA leveldb-backed consistent hash ring, for your caching needs. I can see the day when you might want to put data in this & feel reasonably sort of confident you might get it back out again. If this scares you, it's supposed to.\n\n[](https://www.npmjs.org/package/scurry) [](http://travis-ci.org/ceejbot/scurry) [](https://coveralls.io/r/ceejbot/scurry) [](https://david-dm.org/ceejbot/scurry)\n\n\n## One part each\n\nRod Vagg's [levelup](https://github.com/rvagg/node-levelup) leveldb bindings for node + [sublevel](https://github.com/dominictarr/level-sublevel) to create buckets.\n\nDominic Tarr's [crtd](https://github.com/dominictarr/crdt), which uses his [scuttlebutt implementation](https://github.com/dominictarr/scuttlebutt) to keep a document in sync.\n\n[light-cycle](https://github.com/ceejbot/light-cycle), a lightweight consistent hash ring structure that can be mixed into most anything.\n\n[restify](http://mcavage.me/node-restify/) to provide a simple RESTful api to data in the buckets.\n\n## Shake with ice\n\nRun a server:\n\n`node index.js --id=node-one -m -p 3333 -g 4114 -d ./db | ./node_modules/.bin/bunyan -o short`\n\nRun a client or five:\n\n```shell\nnode index.js --id=node-two -p 3334 -g 4114 -s 10.0.0.5 -d ./db2 | ./node_modules/.bin/bunyan -o short\nnode index.js --id=node-three -p 3335 -g 4114 -s 10.0.0.5 -d ./db3 | ./node_modules/.bin/bunyan -o short\nnode index.js --id=node-four -p 3336 -g 4114 -s 10.0.0.5 -d ./db4 | ./node_modules/.bin/bunyan -o short\n```\n\nReplace `10.0.0.5` with the IP address of your server.\n\n## Strain into a chilled glass\n\nThen stuff some data in:\n\n```shell\nhttp PUT 10.0.0.5:3334/vodkas/1 name=\"Sobieski\" rating=5\nhttp PUT 10.0.0.5:3335/vodkas/2 name=\"Tito's Handmade\" rating=5\nhttp PUT 10.0.0.5:3335/vodkas/3 name=\"Bimber\" rating=4\n```\n\nGet it back out: `http GET 10.0.0.5:3336/vodkas/2 | json`\n\n(Human-friendly shell commands courtesy of [httpie](https://github.com/jkbr/httpie)).\n\n## Garnish with a twist of orange peel\n\nAPI endpoints exposed:\n\n- `GET /:bucket`: stream sorted keys for a bucket (works!)\n- `POST /:bucket`: add an item to the cache; id is generated for you & returned\n- `PUT /:bucket/:id`: add/update an item in the cache; 204 response\n- `GET /:bucket/:id`: get an item from the cache\n- `HEAD /:bucket/:id`: headers for an item\n- `DEL /:bucket/:id`: remove an item\n\n### TTLs\n\nSend this header to specify a time-to-live for your cached data: `X-Scurry-TTL: [seconds]`\n\n### Conditional requests\n\nScurry sends an ETag header and a last-modified timestamp.\n\n### Storage format\n\nAs of version 0.0.4, the data stored in the LevelDB nodes is json structured as follows:\n\n```javascript\n{\n\tversion: 1, // storage version\n ts: Date.now(), // timestamp of last set()\n payload: value, // base64-encoded string if buffer, JSON string if not\n etag: crc.digest('hex'), // md5 hex digest of payload\n base64: valueIsB64String, // true if the payload had to be base64 encoded\n 'content-type': metadata['content-type'] // content-type if passed in\n};\n\n```\n\n## TODO\n\nUpcoming releases:\n\n- Release 0.0.4 will probably make streaming keys work, maybe. Done!\n- Release 0.0.5 will finalize the storage format. Probably done!\n- Release 0.0.6 will contemplate eviction. Not yet!\n\nGeneral goals:\n\n- Implement key streaming from multiple nodes. See notes in endpoints.handleGetBucket().\n- The RESTful server needs error handling.\n- Reconnect on errors.\n- Error handling.\n- Better logging. Configurable, for one thing.\n- Back ends should be pluggable; the API is very small.\n- Stretch goal: replication?\n\n## License\n\nMIT.\n",
588 | "maintainers": [
589 | {
590 | "name": "ceejbot",
591 | "email": "ceejceej@gmail.com"
592 | }],
593 | "time":
594 | {
595 | "modified": "2016-05-18T22:45:42.899Z",
596 | "created": "2013-07-14T06:13:39.501Z",
597 | "0.0.1": "2013-07-14T06:13:40.932Z",
598 | "0.0.2": "2013-07-15T12:43:07.005Z",
599 | "0.0.3": "2013-08-03T17:42:21.130Z",
600 | "0.0.4": "2013-11-19T17:27:19.880Z",
601 | "0.0.5": "2014-02-06T18:00:18.543Z",
602 | "0.0.6": "2014-05-28T12:07:49.227Z",
603 | "0.0.7": "2014-10-24T22:53:47.211Z"
604 | },
605 | "author":
606 | {
607 | "name": "C J Silverio",
608 | "email": "ceejceej@gmail.com"
609 | },
610 | "repository":
611 | {
612 | "type": "git",
613 | "url": "https://github.com/ceejbot/scurry"
614 | },
615 | "readmeFilename": "README.md",
616 | "homepage": "https://github.com/ceejbot/scurry",
617 | "keywords": ["leveldb", "scuttlebutt", "autosharding"],
618 | "bugs":
619 | {
620 | "url": "https://github.com/ceejbot/scurry/issues"
621 | },
622 | "license": "MIT",
623 | "users":
624 | {
625 | "nguru": true,
626 | "soldair": true,
627 | "ceejbot": true
628 | },
629 | "_attachments":
630 | {}
631 | }
632 |
--------------------------------------------------------------------------------