├── .gitignore ├── LICENSE ├── README.md ├── package.json └── src └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Brian Mathews 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

recently-updated

2 | 3 |

4 | 5 | 6 | 7 | 8 | npm version 9 | 10 |

11 | 12 |

13 | See which packages you depend on were recently updated 14 |

15 | 16 | *** 17 | 18 | This cli tool shows you if packages you depend on had new versions published within the last 24 hours. If your CI builds start failing all of a sudden, or builds/tests are broken locally after a fresh npm install, seeing this list of packages and their latest publish times/versions might help you track the problem down quicker. 19 | 20 | ### Installation 21 | 22 | With npm, do: 23 | ``` 24 | npm install -g recently-updated 25 | ``` 26 | 27 | ### Usage 28 | In a project directory with a `./node_modules`, do: 29 | ``` 30 | recently-updated 31 | ``` 32 | 33 | ### Options 34 | Specify a cutoff by passing in `--hours`, `--days`, or `--weeks` (defaults to 24 hours): 35 | ``` 36 | recently-updated --days 3 37 | ``` 38 | 39 |
40 | 41 |
42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "recently-updated", 3 | "version": "1.0.3", 4 | "main": "src/index.js", 5 | "description": "See which packages you depend on were recently updated", 6 | "bin": { 7 | "recently-updated": "./src/index.js" 8 | }, 9 | "scripts": { 10 | "test": "echo \"Error: no test specified\" && exit 1" 11 | }, 12 | "author": "Brian Mathews ", 13 | "license": "MIT", 14 | "dependencies": { 15 | "argparse": "^1.0.9", 16 | "async.map": "^0.5.2", 17 | "chalk": "^1.1.3", 18 | "mute": "^2.0.6", 19 | "npm": "^3.10.9", 20 | "semver": "^5.3.0" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "https://github.com/bmathews/recently-updated.git" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | var npm = require('npm'); 4 | var asyncMap = require('async.map'); 5 | var semver = require('semver'); 6 | var chalk = require('chalk'); 7 | var inspect = require('util').inspect; 8 | var ArgumentParser = require('argparse').ArgumentParser; 9 | var pkg = require('../package.json'); 10 | 11 | var parser = new ArgumentParser({ 12 | version: pkg.version, 13 | addHelp: true, 14 | description: 'List recently updated npm packages. ' + 15 | 'If no options are passed, it will look for packages updated within ' + 16 | 'the last 24 hours. Multiple cutoff arguments can be combined by ' + 17 | 'adding them together.' 18 | }); 19 | parser.addArgument( 20 | [ '--hours' ], 21 | { 22 | help: 'time cutoff in hours', 23 | } 24 | ); 25 | parser.addArgument( 26 | [ '-d', '--days' ], 27 | { 28 | help: 'time cutoff in days' 29 | } 30 | ); 31 | parser.addArgument( 32 | [ '-w', '--weeks' ], 33 | { 34 | help: 'time cutoff in weeks' 35 | } 36 | ); 37 | var args = parser.parseArgs(); 38 | 39 | const _WEEKS = parseInt(args['weeks']) || 0; 40 | const _DAYS = parseInt(args['days']) || 0; 41 | const _HOURS = parseInt(args['hours']) || 0; 42 | 43 | var HOURS = _HOURS + 24 * (_DAYS + 7 * _WEEKS); 44 | if (!HOURS) { 45 | HOURS = 24; 46 | } 47 | 48 | npm.load({outfd: null}, function () { 49 | npm.commands.ls([], true, function (er, tree) { 50 | var flat = flatten(tree); 51 | addTimes(flat, function () { 52 | var lastFound = null; 53 | for (var key in flat) { 54 | var dep = flat[key]; 55 | var nameDisplayed = false; 56 | for (var version in dep.times) { 57 | var time = dep.times[version]; 58 | if (new Date().getTime() - new Date(time).getTime() < HOURS * 3600000) { 59 | dep.semvers.forEach(function (s) { 60 | if (semver.satisfies(version, s)) { 61 | lastFound = dep.name; 62 | if (!nameDisplayed) { 63 | console.log('\n' + dep.name + chalk.dim('\n\tfrom ranges [' + dep.semvers.join(', ') + ']')); 64 | nameDisplayed = true; 65 | } 66 | console.log(chalk.yellow('\t' + version + '\t' + time.toLocaleString())); 67 | } 68 | }); 69 | } 70 | } 71 | } 72 | 73 | if (lastFound) { 74 | console.log('\n--------') 75 | console.log('(use "npm ls ' + lastFound + '" to see what depends on that package)'); 76 | console.log('(use "npm issues ' + lastFound + '" to view the issues for that package)'); 77 | } else { 78 | console.warn('No recently updated dependencies were found'); 79 | } 80 | }); 81 | }); 82 | }); 83 | 84 | // flatten the tree and de-dupe 85 | function flatten(tree, out, parent) { 86 | if (!out) out = {}; 87 | 88 | // recursively iterate over tree 89 | if (tree.dependencies) { 90 | for (var dep in tree.dependencies) { 91 | out = flatten(tree.dependencies[dep], out, tree); 92 | } 93 | } 94 | 95 | if (tree.name) { 96 | // name -> details 97 | out[tree.name] = out[tree.name] || { 98 | name: tree.name, 99 | semvers: [], 100 | versions: {} 101 | }; 102 | 103 | // out['babel'].versions['0.1.3'] -> { version, parent } 104 | out[tree.name].versions[tree.version] = { 105 | version: tree.version, 106 | parent: parent ? out[parent.name] : null 107 | }; 108 | 109 | // rawSpec is the semver range that ended up pulling this package 110 | if (tree._requested) { 111 | out[tree.name].semvers.push(tree._requested.rawSpec); 112 | } 113 | } 114 | 115 | return out; 116 | } 117 | 118 | // fetch all the times a package was published 119 | function getTimesForPackage(dep, cb) { 120 | npm.commands.view([dep.name, 'time'], true, function (err, data) { 121 | cb(null, data); 122 | }); 123 | } 124 | 125 | // fetch and merge in publish times into the flattened tree 126 | function addTimes(flat, cb) { 127 | var keys = Object.keys(flat); 128 | asyncMap(keys, function (key, mapcb) { 129 | const dep = flat[key]; 130 | return getTimesForPackage(dep, function (err, res) { 131 | if (err || !res) { 132 | mapcb(null, dep); 133 | } else { 134 | const versionKeys = Object.keys(res); 135 | if (!versionKeys.length) { 136 | dep.times = {}; 137 | mapcb(null, dep); 138 | } else { 139 | var versionMap = res[versionKeys[0]].time; 140 | var versions = Object.keys(versionMap); 141 | versions.splice(0, 2); 142 | dep.times = {}; 143 | versions.forEach(function (v) { 144 | dep.times[v] = new Date(versionMap[v]); 145 | }); 146 | mapcb(null, dep); 147 | } 148 | 149 | } 150 | }); 151 | }, function (err, results) { 152 | cb(flat); 153 | }); 154 | } 155 | --------------------------------------------------------------------------------