├── .dont-break
├── .gitignore
├── .jshintrc
├── .npmrc
├── .travis.yml
├── Gruntfile.js
├── History.md
├── LICENSE-MIT
├── README.md
├── __snapshots__
├── command-spec.js.snap-shot
├── filter-allowed-spec.js.snap-shot
├── registry-spec.js.snap-shot
└── stats-spec.js.snap-shot
├── bin
└── next-update.js
├── circle.yml
├── complexity.json
├── docs
├── README.tmpl.md
├── api.md
├── badges.md
├── dev.md
├── footer.md
├── related.md
├── third-party.md
└── use.md
├── examples
└── qunit
│ ├── README.md
│ └── test.js
├── index.js
├── package.json
├── renovate.json
├── src
├── cli-options.js
├── dependencies.js
├── exec-test.js
├── filter-allowed-spec.js
├── filter-allowed-updates.js
├── get-known-dependencies.js
├── local-module-version.js
├── module-install.js
├── moduleName.js
├── next-update-as-module.js
├── next-update.js
├── npm-test.js
├── print-modules-table.js
├── registry-spec.js
├── registry.js
├── report-available.js
├── report-install-command.js
├── report.js
├── revert.js
├── stats-spec.js
├── stats.js
├── test-module-version.js
├── test
│ ├── all-local-deps.json
│ ├── exec-test.coffee
│ ├── get-known-dependencies.coffee
│ ├── git-url-deps.json
│ ├── local-module-version.coffee
│ ├── moduleName.coffee
│ ├── node_modules
│ │ └── sample-module
│ │ │ └── package.json
│ ├── npm-test.coffee
│ ├── registry.js
│ ├── report-available.coffee
│ ├── report-install-command.coffee
│ ├── report.coffee
│ └── stats.coffee
└── utils.js
└── test
├── as-parent-spec.js
├── as-parent.coffee
├── command-spec.js
├── e2e.coffee
├── file-deps-spec.js
├── revert-spec.js
├── scoped-packages-spec.js
├── single-module-check.coffee
├── test-command-map
├── .npmrc
├── __snapshots__
│ └── command-spec.js.snap-shot
├── lodash-test.js
└── package.json
├── test-file-deps
├── .npmrc
├── index.js
├── local-dependency
│ ├── index.js
│ └── package.json
└── package.json
├── test-next-updater
├── .npmrc
├── __snapshots__
│ └── as-parent-spec.js.snap-shot
├── index.js
└── package.json
├── test-scoped-names
├── .npmrc
├── index.js
└── package.json
└── time-limit-promise.js
/.dont-break:
--------------------------------------------------------------------------------
1 | next-updater
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /src/test/cover
3 | /cover
4 | /test/cover
5 | /src/test/npm-debug.log
6 | .idea/
7 | *.eml
8 | *.iml
9 | .DS_Store
10 | node_modules/
11 | npm-debug.log
12 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "maxerr" : 50,
3 |
4 | "indent" : 2,
5 | "smarttabs" : true,
6 | "bitwise" : true,
7 | "camelcase" : false,
8 | "curly" : true,
9 | "eqeqeq" : true,
10 | "forin" : true,
11 | "immed" : false,
12 | "latedef" : false,
13 | "newcap" : true,
14 | "noarg" : true,
15 | "noempty" : true,
16 | "nonew" : false,
17 | "plusplus" : false,
18 | "quotmark" : "single",
19 | "undef" : true,
20 | "unused" : true,
21 | "strict" : false,
22 | "trailing" : false,
23 | "maxparams" : 4,
24 | "maxdepth" : 3,
25 | "maxstatements" : 30,
26 | "maxcomplexity" : 10,
27 | "maxlen" : 100,
28 |
29 | "asi" : false,
30 | "boss" : false,
31 | "debug" : false,
32 | "eqnull" : false,
33 | "es5" : false,
34 | "esnext" : false,
35 | "moz" : false,
36 | "evil" : false,
37 | "expr" : false,
38 | "funcscope" : false,
39 | "globalstrict" : false,
40 | "iterator" : false,
41 | "lastsemic" : false,
42 | "laxbreak" : false,
43 | "laxcomma" : false,
44 | "loopfunc" : false,
45 | "multistr" : false,
46 | "proto" : false,
47 | "scripturl" : false,
48 | "shadow" : false,
49 | "sub" : false,
50 | "supernew" : false,
51 | "validthis" : false,
52 |
53 | "browser" : false,
54 | "couch" : false,
55 | "devel" : true,
56 | "dojo" : false,
57 | "jquery" : false,
58 | "mootools" : false,
59 | "node" : true,
60 | "nonstandard" : false,
61 | "prototypejs" : false,
62 | "rhino" : false,
63 | "worker" : false,
64 | "wsh" : false,
65 | "yui" : false,
66 |
67 | "nomen" : false,
68 | "onevar" : false,
69 | "passfail" : false,
70 | "white" : true,
71 |
72 | "globals" : {}
73 | }
74 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | registry=http://registry.npmjs.org/
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | cache:
4 | directories:
5 | - node_modules
6 | notifications:
7 | email: true
8 | node_js:
9 | - '8'
10 | before_script:
11 | - npm prune
12 | script:
13 | - npm test
14 | - npm run e2e
15 | # - $(npm bin)/stop-build
16 | after_success:
17 | - npm run semantic-release
18 | - npm run coveralls
19 | branches:
20 | except:
21 | - "/^v\\d+\\.\\d+\\.\\d+$/"
22 |
--------------------------------------------------------------------------------
/Gruntfile.js:
--------------------------------------------------------------------------------
1 | /* global module:false */
2 | module.exports = function (grunt) {
3 | require('time-grunt')(grunt)
4 | grunt.initConfig({
5 | pkg: grunt.file.readJSON('package.json'),
6 | lineending: {
7 | index: {
8 | options: {
9 | eol: 'lf'
10 | },
11 | files: {
12 | 'index.js': 'index.js'
13 | }
14 | }
15 | },
16 | jsonlint: {
17 | all: {
18 | src: ['*.json']
19 | }
20 | },
21 | 'nice-package': {
22 | all: {
23 | options: {
24 | blankLine: true
25 | }
26 | }
27 | },
28 |
29 | // TODO fix help, requires grunt-help fix to
30 | // work with optimist
31 | help: {
32 | options: {
33 | destination: 'docs/help.md'
34 | },
35 | all: {}
36 | },
37 |
38 | readme: {
39 | options: {
40 | readme: './docs/README.tmpl.md',
41 | docs: '.',
42 | templates: './docs'
43 | }
44 | },
45 | /* to bump version, then run grunt (to update readme), then commit
46 | grunt release
47 | */
48 | bump: {
49 | options: {
50 | commit: true,
51 | commitMessage: 'Release v%VERSION%',
52 | commitFiles: ['-a'], // '-a' for all files
53 | createTag: true,
54 | tagName: '%VERSION%',
55 | tagMessage: 'Version %VERSION%',
56 | push: true,
57 | pushTo: 'origin'
58 | }
59 | }
60 | })
61 |
62 | var plugins = require('matchdep').filterDev('grunt-*')
63 | plugins.forEach(grunt.loadNpmTasks)
64 |
65 | grunt.registerTask('pre-check', ['deps-ok', 'jsonlint', 'nice-package'])
66 | // if readme task crashes with error
67 | // TypeError: Cannot read property '1' of null
68 | // this is because it cannot parse package version "0.0.0-semantic-release"
69 | grunt.registerTask('default', ['pre-check', 'lineending', 'readme'])
70 | grunt.registerTask('release', ['bump-only:patch', 'readme', 'bump-commit'])
71 | }
72 |
--------------------------------------------------------------------------------
/History.md:
--------------------------------------------------------------------------------
1 |
2 | 0.2.0 / 2013-12-28
3 | ==================
4 |
5 | * using color in table, fixes #16
6 | * reporting install success for available modules, fixes #13
7 | * printing test command, fixes #15
8 | * added -h as alias to --help
9 | * running test before starting checking new versions, fixes #10
10 | * disabled error messages for 404 stats, fixes #12
11 | * running deps-ok first, fixes #9
12 | * rounding to percent, added link to http://npmt.abru.pt/
13 |
14 | 0.1.0 / 2013-12-25
15 | ==================
16 |
17 | * added sending and getting global update success statistics
18 | * Add a Bitdeli badge to README
19 |
20 | 0.0.16 / 2013-11-05
21 | ==================
22 |
23 | * grabbing npm registry url only once, seems to fix #5
24 |
25 | 0.0.15 / 2013-10-31
26 | ==================
27 |
28 | * getting npm registry from config, fixes #2
29 | * bumped dependencies
30 |
31 | 0.0.14 / 2013-10-31
32 | ==================
33 |
34 | * updated for latest check-types
35 |
36 | 0.0.13 / 2013-10-29
37 | ==================
38 |
39 | * bumped check-types
40 | * bumped some versions
41 | * updated deps
42 | * added pre-git
43 | * added jsonlint
44 | * moved jshint options into separate file
45 | * added pre-commit check
46 | * using matchdep
47 | * cleaned up gruntfile
48 | * Release v0.0.12
49 | * removed console.log that was generating a lot of output
50 | * Release v0.0.11
51 | * handling case when nothing can be updated, fixes #59
52 | * Release v0.0.10
53 | * using npm-tools to check urls
54 | * fixed jshint
55 |
--------------------------------------------------------------------------------
/LICENSE-MIT:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014 Gleb Bahmutov
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # next-update
2 |
3 | > Tests if module's dependencies can be updated to the newer version without breaking the tests
4 |
5 | [![NPM][next-update-icon] ][next-update-url]
6 |
7 | [![Build status][next-update-ci-image] ][next-update-ci-url]
8 | [![Circle CI][circle-ci-image] ][circle-ci-url]
9 | [![Coverage Status][next-update-coverage-image] ][next-update-coverage-url]
10 | [![semantic-release][semantic-image] ][semantic-url]
11 | [](https://snyk.io/test/github/bahmutov/next-update/230d136b5c68dadb1fd5459619df8f7678d28429)
12 | [![renovate-app badge][renovate-badge]][renovate-app]
13 |
14 | [renovate-badge]: https://img.shields.io/badge/renovate-app-blue.svg
15 | [renovate-app]: https://renovateapp.com/
16 |
17 | [next-update-icon]: https://nodei.co/npm/next-update.svg?downloads=true
18 | [next-update-url]: https://npmjs.org/package/next-update
19 | [next-update-ci-image]: https://travis-ci.org/bahmutov/next-update.svg?branch=master
20 | [next-update-ci-url]: https://travis-ci.org/bahmutov/next-update
21 | [next-update-coverage-image]: https://coveralls.io/repos/bahmutov/next-update/badge.svg
22 | [next-update-coverage-url]: https://coveralls.io/r/bahmutov/next-update
23 | [circle-ci-image]: https://circleci.com/gh/bahmutov/next-update.svg?style=svg
24 | [circle-ci-url]: https://circleci.com/gh/bahmutov/next-update
25 | [semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
26 | [semantic-url]: https://github.com/semantic-release/semantic-release
27 |
28 |
29 |
30 | **Note** I no longer maintain Node 0.12/4 compatibility. Please switch to
31 | Node 6.
32 |
33 | [](https://asciinema.org/a/21645)
34 |
35 | Also check out:
36 |
37 | * [next-updater](https://github.com/bahmutov/next-updater) can update all your repos
38 | * [dont-break](https://github.com/bahmutov/dont-break)
39 | that checks if your code is going to break everyone who depends on it.
40 | * [changed-log](https://github.com/bahmutov/changed-log) returns commit messages for
41 | the given NPM package or Github repo between two tags.
42 |
43 | ### Example
44 |
45 | Imagine your nodejs module *foo* has the following dependencies listed in *package.json*
46 |
47 | "dependencies": {
48 | "lodash": "~1.2.0",
49 | "async": "~0.2.5"
50 | }
51 |
52 | You would like to update lodash and async to latest versions, to not sure if
53 | this would break anything. With *next-update* it is easy: run command `next-update`
54 | in the folder with module *foo*. Here is the example output:
55 |
56 | next updates:
57 | lodash
58 | 1.2.1 PASS
59 | async
60 | 0.2.6 PASS
61 | 0.2.7 PASS
62 | 0.2.8 PASS
63 |
64 |
65 | Both *package.json* file and *node_modules* folder are left unchanged,
66 | and now you know that you can safely upgrade both libraries to later versions.
67 |
68 | #### It even tells you the install command ;)
69 |
70 | Use the following command to install working versions
71 | npm install --save lodash@2.1.0
72 |
73 | This might not appear like a big deal for a single module that is using
74 | popular 3rd party libraries with stable apis only. *next-update* is most useful
75 | in the larger development context, where multiple modules are being developed
76 | side by side, often by different teams. In such situations, checking if an upgrade
77 | is possible could be part of the continuous build pipeline.
78 |
79 | You can see if your dependencies are out of date by using
80 | [david](https://david-dm.org),
81 | it even has badges you can add to your README files.
82 |
83 | *next-update* reports the probability of success for a given dependency update using
84 | anonymous global statistics from [next-update](http://next-update.herokuapp.com/) server
85 |
86 | ```
87 | available updates:
88 | package available from version average success % successful updates failed updates
89 | -------------------- --------- ------------ ----------------- ------------------ --------------
90 | grunt-contrib-jshint 0.8.0 0.7.2 100% 34 0
91 | grunt-bump 0.0.13 0.0.12 100% 4 0
92 | ```
93 |
94 | ### Install
95 |
96 | You can install this tool globally
97 |
98 | npm install -g next-update // installs module globally
99 | next-update --help // shows command line options
100 |
101 | Then run inside any package folder
102 |
103 | /git/my-awesome-module
104 | $ next-update
105 |
106 | Or you can use this module as a devDependency and a script command
107 |
108 | npm install --save-dev next-update
109 |
110 | ```json
111 | {
112 | "scripts": {
113 | "next-update": "next-update -k true --tldr"
114 | }
115 | }
116 | ```
117 |
118 | This command will keep the successfuly version upgrades in the package.json file,
119 | but will not be very verbose when run.
120 |
121 | ### Anonymous usage collection
122 |
123 | After testing each module A upgrade from version X to Y, *next-update* sends
124 | anonymous result to [next-update.herokuapp.com/](http://next-update.herokuapp.com/).
125 | The only information transmitted is:
126 |
127 | ```json
128 | {
129 | "name": "lodash",
130 | "from": "1.0.0",
131 | "to": "2.0.0",
132 | "success": true
133 | }
134 | ```
135 |
136 | This information is used to answer the following questions later:
137 | what is the probability module A can be upgraded from X to Y?
138 | Thus even if you do not have tests covering this particular module,
139 | you can judge how compatible version X and Y really are over the entire
140 | internet.
141 |
142 | You can inspect data send in
143 | [stats.js](https://github.com/bahmutov/next-update/blob/master/src/stats.js).
144 |
145 | If the dependency module has been upgraded by anyone else, its statistics
146 | will be displayed with each test.
147 |
148 | ```sh
149 | stats: deps-ok 0.0.7 -> 0.0.8 success probability 44.44% 8 success(es) 10 failure(s)
150 | ```
151 |
152 | A lot of NPM modules [do not have tests](http://npmt.abru.pt/), but
153 | at least you can judge if someone else has success going from verion X to version Y
154 | of a dependency.
155 |
156 | ### Use
157 |
158 | Make sure the target module has unit / integration tests,
159 | and the tests can be run using `npm test` command.
160 |
161 | Run `next-update` from the command line in the same folder as
162 | the target module. In general this tool does the following:
163 |
164 | 1. Reads the module's dependencies (including dev) and their versions
165 | 2. Queries npm registry to see if there are newer versions
166 | 3. For each dependency that has newer versions available:
167 | 1. Installs each version
168 | 2. Runs command `npm test` to determine if the new version breaks the tests
169 | 3. Installs back the current version.
170 | 4. Reports results
171 |
172 | ### Checking specific modules
173 |
174 | You can check one or more specific modules (whitelist) using CLI flag
175 | `--module` or `-m`
176 |
177 | ```sh
178 | next-update --module foo,bar,baz
179 | ```
180 |
181 | ### Ignoring or skipping some modules
182 |
183 | **note** [prerelease](https://github.com/npm/node-semver#functions)
184 | versions like `1.2.0-alpha` are skipped by default. I believe `next-update` is
185 | meant to upgrade to *stable* versions.
186 |
187 | Some modules are hard to unit test, thus the automatic upgrades are not appropriate.
188 | For example [benv](https://npmjs.org/package/benv) upgrade brings a new
189 | [jsdom](https://npmjs.org/package/jsdom) version, which does not work on Node 0.12
190 | Similarly, upgrading [Q](https://npmjs.org/package/q) from 1.x.x to 2.x.x is usually
191 | a breaking change.
192 |
193 | You can skip a list of modules by name using `config` property in the `package.json`
194 |
195 | ```json
196 | "config": {
197 | "next-update": {
198 | "skip": ["benv", "q"]
199 | }
200 | }
201 | ```
202 |
203 | ### Custom test command per module
204 |
205 | Some modules are not really tested using the default `npm test` command or
206 | whatever is passed via `--test "..."` from CLI. For example a linter module
207 | should probably be tested using `npm run lint` command. You can set individual
208 | test commands for each module to override the default test command. In the
209 | `package.json` config object set "commands" object
210 |
211 | ```json
212 | "config": {
213 | "next-update": {
214 | "commands": {
215 | "git-issues": "npm run issues",
216 | "standard": "npm run lint"
217 | }
218 | }
219 | }
220 | ```
221 |
222 | Then when `git-issues` module is checked by itself, it will run
223 | `npm run issues` command; when module `standard` is tested by itself, the
224 | test will use `npm run lint` command.
225 |
226 | ### Misc
227 |
228 | * To see what has changed in the latest version of any module,
229 | use my companion tool [changed](https://npmjs.org/package/changed)
230 | like this `changed foo` (*foo* is package name)
231 | * When comparing versions, keywords *latest* and *** are both assumed to equal to "0.0.0".
232 | * A good workflow using *next-update*
233 | * see available new versions `next-update --available`
234 | * check latest version of each module using `next-update --latest`
235 | * install new versions of the desired modules using standard `npm i dependency@version --save`
236 | * You can use custom test command, for example `next-update -t "grunt test"`
237 | * `npm test` is used by default.
238 | * You can keep each working version in package.json by using `--keep` flag.
239 |
240 |
241 |
242 | ## API
243 |
244 | You can use `next-update` as a module. See file
245 | [src/next-update-as-module](./src/next-update-as-module) for all options.
246 |
247 | ```js
248 | const nextUpdate = require('next-update')
249 | nextUpdate({
250 | module: ['foo', 'bar']
251 | }).then(results => {
252 | console.log(results)
253 | })
254 | /*
255 | prints something like
256 | [[
257 | {
258 | "name": "foo",
259 | "version": "0.2.0",
260 | "from": "0.2.1",
261 | "works": true
262 | },
263 | {
264 | "name": "foo",
265 | "version": "0.2.0",
266 | "from": "0.3.0",
267 | "works": false
268 | }
269 | ], [
270 | {
271 | "name": "bar",
272 | "version": "1.5.1",
273 | "from": "2.0.0",
274 | "works": true
275 | }
276 | }}
277 | */
278 | ```
279 |
280 |
281 |
282 | ## Development
283 |
284 | Edit source, run unit tests, run end to end tests and release
285 | new version commands:
286 |
287 | ```sh
288 | npm test
289 | npm run e2e
290 | grunt release
291 | npm publish
292 | ```
293 |
294 |
295 | ### Related
296 |
297 | * [Painless modular development](http://glebbahmutov.com/blog/modular-development-using-nodejs/)
298 | * [Really painless modular development](http://glebbahmutov.com/blog/really-painless-modular-development/)
299 |
300 |
301 |
302 | ### 3rd party libraries
303 |
304 | * [lazy-ass](https://github.com/bahmutov/lazy-ass) and
305 | [check-more-types](https://github.com/kensho/check-more-types) are used to
306 | [defend against runtime errors](http://glebbahmutov.com/blog/lazy-and-async-assertions/).
307 | * [lo-dash](https://github.com/bestiejs/lodash) is used to deal with collections / iterators.
308 | * [check-types](https://github.com/philbooth/check-types.js) is used to verify arguments through out the code.
309 | * [optimist](https://github.com/substack/node-optimist) is used to process command line arguments.
310 | * [request](https://npmjs.org/package/request) is used to fetch NPM registry information.
311 | * [semver](https://npmjs.org/package/semver) is used to compare module version numbers.
312 | * [q](https://npmjs.org/package/q) library is used to handle promises. While developing this tool,
313 | I quickly ran into problems managing the asynchronous nature of fetching information, installing multiple modules,
314 | testing, etc. At first I used [async](https://npmjs.org/package/async), but it was still too complex.
315 | Using promises allowed to cut the program's code and the complexity to very manageable level.
316 | * [cli-color](https://npmjs.org/package/cli-color) prints colored text to the terminal.
317 |
318 |
319 |
320 | ### Small print
321 |
322 | Author: Gleb Bahmutov © 2014
323 |
324 | * [@bahmutov](https://twitter.com/bahmutov)
325 | * [glebbahmutov.com](https://glebbahmutov.com)
326 | * [blog](https://glebbahmutov.com/blog)
327 |
328 | License: MIT - do anything with the code, but don't blame me if it does not work.
329 |
330 | Spread the word: tweet, star on github, etc.
331 |
332 | Support: if you find any problems with this module, email / tweet /
333 | [open issue](https://github.com/bahmutov/next-update/issues?state=open) on Github
334 |
335 |
336 |
337 | ## MIT License
338 |
339 | Copyright (c) 2014 Gleb Bahmutov
340 |
341 | Permission is hereby granted, free of charge, to any person
342 | obtaining a copy of this software and associated documentation
343 | files (the "Software"), to deal in the Software without
344 | restriction, including without limitation the rights to use,
345 | copy, modify, merge, publish, distribute, sublicense, and/or sell
346 | copies of the Software, and to permit persons to whom the
347 | Software is furnished to do so, subject to the following
348 | conditions:
349 |
350 | The above copyright notice and this permission notice shall be
351 | included in all copies or substantial portions of the Software.
352 |
353 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
354 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
355 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
356 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
357 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
358 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
359 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
360 | OTHER DEALINGS IN THE SOFTWARE.
361 |
362 |
363 |
--------------------------------------------------------------------------------
/__snapshots__/command-spec.js.snap-shot:
--------------------------------------------------------------------------------
1 | exports['sorts by version 1'] = [
2 | {
3 | "name": "lodash",
4 | "version": "1.0.1",
5 | "from": "1.0.0",
6 | "works": true
7 | },
8 | {
9 | "name": "lodash",
10 | "version": "1.0.2",
11 | "from": "1.0.0",
12 | "works": true
13 | },
14 | {
15 | "name": "lodash",
16 | "version": "1.1.0",
17 | "from": "1.0.0",
18 | "works": false
19 | },
20 | {
21 | "name": "lodash",
22 | "version": "1.1.1",
23 | "from": "1.0.0",
24 | "works": false
25 | },
26 | {
27 | "name": "lodash",
28 | "version": "1.2.0",
29 | "from": "1.0.0",
30 | "works": false
31 | },
32 | {
33 | "name": "lodash",
34 | "version": "1.2.1",
35 | "from": "1.0.0",
36 | "works": false
37 | },
38 | {
39 | "name": "lodash",
40 | "version": "1.3.0",
41 | "from": "1.0.0",
42 | "works": false
43 | },
44 | {
45 | "name": "lodash",
46 | "version": "1.3.1",
47 | "from": "1.0.0",
48 | "works": false
49 | }
50 | ]
51 |
52 |
--------------------------------------------------------------------------------
/__snapshots__/filter-allowed-spec.js.snap-shot:
--------------------------------------------------------------------------------
1 | exports['allows major 1'] = {
2 | "current": {
3 | "q": {
4 | "name": "q",
5 | "version": "1.1.1",
6 | "type": "dev"
7 | }
8 | },
9 | "options": {
10 | "allowed": "major"
11 | },
12 | "allowed": [
13 | {
14 | "name": "q",
15 | "versions": [
16 | "1.3.0",
17 | "2.0.2",
18 | "3.0.0"
19 | ]
20 | }
21 | ]
22 | }
23 |
24 | exports['allows minor 1'] = {
25 | "current": {
26 | "q": {
27 | "name": "q",
28 | "version": "1.1.1",
29 | "type": "dev"
30 | }
31 | },
32 | "options": {
33 | "allowed": "minor"
34 | },
35 | "allowed": [
36 | {
37 | "name": "q",
38 | "versions": [
39 | "1.3.0"
40 | ]
41 | }
42 | ]
43 | }
44 |
45 | exports['allows patch 1'] = {
46 | "current": {
47 | "q": {
48 | "name": "q",
49 | "version": "1.1.1",
50 | "type": "dev"
51 | }
52 | },
53 | "options": {
54 | "allowed": "patch"
55 | },
56 | "allowed": []
57 | }
58 |
59 | exports['allows everything by default 1'] = {
60 | "current": {
61 | "q": {
62 | "name": "q",
63 | "version": "1.1.1",
64 | "type": "dev"
65 | }
66 | },
67 | "options": {},
68 | "allowed": [
69 | {
70 | "name": "q",
71 | "versions": [
72 | "1.3.0",
73 | "2.0.2",
74 | "3.0.0"
75 | ]
76 | }
77 | ]
78 | }
79 |
80 | exports['can custom filter 1'] = {
81 | "current": {
82 | "q": {
83 | "name": "q",
84 | "version": "1.1.1",
85 | "type": "dev"
86 | }
87 | },
88 | "options": {},
89 | "allowed": [
90 | {
91 | "name": "q",
92 | "versions": [
93 | "2.0.2"
94 | ]
95 | }
96 | ]
97 | }
98 |
99 | exports['detects prerelease 1'] = {
100 | "name": "isPrerelease",
101 | "behavior": [
102 | {
103 | "given": "3.0.0-alpha",
104 | "expect": true
105 | },
106 | {
107 | "given": "3.0.0",
108 | "expect": false
109 | },
110 | {
111 | "given": "0.1.0",
112 | "expect": false
113 | },
114 | {
115 | "given": "10.0.0-beta.2",
116 | "expect": true
117 | }
118 | ]
119 | }
120 |
121 |
--------------------------------------------------------------------------------
/__snapshots__/registry-spec.js.snap-shot:
--------------------------------------------------------------------------------
1 | exports['hasVersions 1'] = [
2 | {
3 | "name": "foo",
4 | "versions": [
5 | "1.0.0",
6 | "1.0.1"
7 | ]
8 | },
9 | {
10 | "name": "baz",
11 | "versions": [
12 | "3.0.0-alpha"
13 | ]
14 | }
15 | ]
16 |
17 | exports['filters out empty versions 1'] = [
18 | {
19 | "name": "foo",
20 | "versions": [
21 | "1.0.0",
22 | "1.0.1"
23 | ]
24 | }
25 | ]
26 |
27 | exports['filters latest versions 1'] = [
28 | {
29 | "name": "foo",
30 | "versions": [
31 | "1.0.1"
32 | ]
33 | }
34 | ]
35 |
36 | exports['filters out alpha releases 1'] = [
37 | {
38 | "name": "foo",
39 | "versions": [
40 | "1.0.0",
41 | "1.0.1"
42 | ]
43 | }
44 | ]
45 |
46 |
--------------------------------------------------------------------------------
/__snapshots__/stats-spec.js.snap-shot:
--------------------------------------------------------------------------------
1 | exports['formats stats message 1'] = "stats: foo 1.0.0 -> 2.0.0 success probability 50% 5 successes 5 failures"
2 |
3 | exports['formats stats message with singular failure 1'] = "stats: foo 1.0.0 -> 2.0.0 success probability 50% 1 success 1 failure"
4 |
5 |
--------------------------------------------------------------------------------
/bin/next-update.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | var q = require('q')
4 | q.longStackSupport = true
5 |
6 | const debug = require('debug')('next-update')
7 | var nextUpdate = require('../src/next-update')
8 | var program = require('../src/cli-options')
9 | var join = require('path').join
10 |
11 | var parentFolder = join(__dirname, '..')
12 | var pkg = require(join(parentFolder, 'package.json'))
13 | var info =
14 | pkg.name +
15 | '@' +
16 | pkg.version +
17 | '\n' +
18 | ' - ' +
19 | pkg.description +
20 | '\n' +
21 | ' installed in folder ' +
22 | parentFolder
23 |
24 | var report = require('../src/report').report
25 | var revert = require('../src/revert')
26 |
27 | if (program.available) {
28 | nextUpdate
29 | .available(program.module, {
30 | color: program.color
31 | })
32 | .done()
33 | } else if (program.revert) {
34 | debug('reverting %s', program.module ? program.module : 'all modules')
35 | revert(program.module).then(
36 | function () {
37 | console.log('done reverting')
38 | },
39 | function (error) {
40 | console.error('error while reverting\n', error)
41 | }
42 | )
43 | } else {
44 | if (!program.tldr) {
45 | console.log(info)
46 | }
47 |
48 | var updateNotifier = require('update-notifier')
49 | updateNotifier({
50 | pkg: pkg,
51 | name: pkg.name,
52 | version: pkg.version
53 | }).notify()
54 |
55 | const allow = program.allowed || program.allow
56 | const latest = program.allowWasSpecified ? false : program.latest
57 | debug(
58 | 'allow was specified %j option "%s" latest %j',
59 | program.allowWasSpecified,
60 | allow,
61 | latest
62 | )
63 |
64 | var opts = {
65 | names: program.module,
66 | latest: latest,
67 | testCommand: program.test,
68 | all: program.all,
69 | color: program.color,
70 | keep: program.keep,
71 | allow: allow,
72 | type: program.type,
73 | tldr: program.tldr,
74 | without: program.without,
75 | registry: program.registry,
76 | checkVersionTimeout: program['check-version-timeout'] || 10000
77 | }
78 |
79 | var checkCurrent = nextUpdate.checkCurrentInstall.bind(null, opts)
80 | var checkCurrentState = program.skip ? q : checkCurrent
81 | var checkUpdates = nextUpdate.checkAllUpdates.bind(null, opts)
82 |
83 | var reportTestResults = function reportTestResults (results) {
84 | if (Array.isArray(results)) {
85 | return report(results, {
86 | useColors: program.color,
87 | keptUpdates: program.keep,
88 | changedLog: program['changed-log']
89 | })
90 | }
91 | }
92 |
93 | checkCurrentState()
94 | .then(checkUpdates)
95 | .then(reportTestResults)
96 | .catch(function (error) {
97 | console.error('ERROR testing next working updates')
98 | if (error.stack) {
99 | console.error(error.stack)
100 | }
101 | process.exit(1)
102 | })
103 | }
104 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 6
4 | deployment:
5 | npm:
6 | branch: master
7 | commands:
8 | # login using environment variables
9 | - echo -e "$NPM_USERNAME\n$NPM_PASSWORD\n$NPM_EMAIL" | npm login
10 | - npm run 2npm
11 |
--------------------------------------------------------------------------------
/complexity.json:
--------------------------------------------------------------------------------
1 | {
2 | "src": ["!Gruntfile.js", "src/*.js", "!src/cli-options.js"],
3 | "options": {
4 | "errorsOnly": false,
5 | "cyclomatic": 7,
6 | "halstead": 18,
7 | "maintainability": 100
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/docs/README.tmpl.md:
--------------------------------------------------------------------------------
1 | # {%= name %}
2 |
3 | > {%= description %}
4 |
5 | {%= _.doc("./docs/badges.md") %}
6 |
7 | **Note** I no longer maintain Node 0.12/4 compatibility. Please switch to
8 | Node 6.
9 |
10 | [](https://asciinema.org/a/21645)
11 |
12 | {%= _.doc("./docs/use.md") %}
13 |
14 | {%= _.doc("./docs/api.md") %}
15 |
16 | {%= _.doc("./docs/dev.md") %}
17 |
18 | {%= _.doc("./docs/related.md") %}
19 |
20 | {%= _.doc("./docs/third-party.md") %}
21 |
22 | {%= _.doc("./docs/footer.md") %}
23 |
24 | ## MIT License
25 |
26 | {%= _.doc("./LICENSE-MIT") %}
27 |
--------------------------------------------------------------------------------
/docs/api.md:
--------------------------------------------------------------------------------
1 | # API
2 |
3 | You can use `next-update` as a module. See file
4 | [src/next-update-as-module](./src/next-update-as-module) for all options.
5 |
6 | ```js
7 | const nextUpdate = require('next-update')
8 | nextUpdate({
9 | module: ['foo', 'bar']
10 | }).then(results => {
11 | console.log(results)
12 | })
13 | /*
14 | prints something like
15 | [[
16 | {
17 | "name": "foo",
18 | "version": "0.2.0",
19 | "from": "0.2.1",
20 | "works": true
21 | },
22 | {
23 | "name": "foo",
24 | "version": "0.2.0",
25 | "from": "0.3.0",
26 | "works": false
27 | }
28 | ], [
29 | {
30 | "name": "bar",
31 | "version": "1.5.1",
32 | "from": "2.0.0",
33 | "works": true
34 | }
35 | ]]
36 | */
37 | ```
38 |
--------------------------------------------------------------------------------
/docs/badges.md:
--------------------------------------------------------------------------------
1 | [![NPM][next-update-icon] ][next-update-url]
2 |
3 | [![Build status][next-update-ci-image] ][next-update-ci-url]
4 | [![Circle CI][circle-ci-image] ][circle-ci-url]
5 | [![Coverage Status][next-update-coverage-image] ][next-update-coverage-url]
6 | [![semantic-release][semantic-image] ][semantic-url]
7 | [](https://snyk.io/test/github/bahmutov/next-update/230d136b5c68dadb1fd5459619df8f7678d28429)
8 |
9 | [next-update-icon]: https://nodei.co/npm/next-update.svg?downloads=true
10 | [next-update-url]: https://npmjs.org/package/next-update
11 | [next-update-ci-image]: https://travis-ci.org/bahmutov/next-update.svg?branch=master
12 | [next-update-ci-url]: https://travis-ci.org/bahmutov/next-update
13 | [next-update-coverage-image]: https://coveralls.io/repos/bahmutov/next-update/badge.svg
14 | [next-update-coverage-url]: https://coveralls.io/r/bahmutov/next-update
15 | [circle-ci-image]: https://circleci.com/gh/bahmutov/next-update.svg?style=svg
16 | [circle-ci-url]: https://circleci.com/gh/bahmutov/next-update
17 | [semantic-image]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
18 | [semantic-url]: https://github.com/semantic-release/semantic-release
19 |
--------------------------------------------------------------------------------
/docs/dev.md:
--------------------------------------------------------------------------------
1 | # Development
2 |
3 | Edit source, run unit tests, run end to end tests and release
4 | new version commands:
5 |
6 | ```sh
7 | npm test
8 | npm run e2e
9 | grunt release
10 | npm publish
11 | ```
--------------------------------------------------------------------------------
/docs/footer.md:
--------------------------------------------------------------------------------
1 | ## Small print
2 |
3 | Author: Gleb Bahmutov © 2014
4 |
5 | * [@bahmutov](https://twitter.com/bahmutov)
6 | * [glebbahmutov.com](https://glebbahmutov.com)
7 | * [blog](https://glebbahmutov.com/blog)
8 |
9 | License: MIT - do anything with the code, but don't blame me if it does not work.
10 |
11 | Spread the word: tweet, star on github, etc.
12 |
13 | Support: if you find any problems with this module, email / tweet /
14 | [open issue](https://github.com/bahmutov/next-update/issues?state=open) on Github
15 |
--------------------------------------------------------------------------------
/docs/related.md:
--------------------------------------------------------------------------------
1 | ## Related
2 |
3 | * [Painless modular development](http://glebbahmutov.com/blog/modular-development-using-nodejs/)
4 | * [Really painless modular development](http://glebbahmutov.com/blog/really-painless-modular-development/)
5 |
--------------------------------------------------------------------------------
/docs/third-party.md:
--------------------------------------------------------------------------------
1 | ## 3rd party libraries
2 |
3 | * [lazy-ass](https://github.com/bahmutov/lazy-ass) and
4 | [check-more-types](https://github.com/kensho/check-more-types) are used to
5 | [defend against runtime errors](http://glebbahmutov.com/blog/lazy-and-async-assertions/).
6 | * [lo-dash](https://github.com/bestiejs/lodash) is used to deal with collections / iterators.
7 | * [check-types](https://github.com/philbooth/check-types.js) is used to verify arguments through out the code.
8 | * [optimist](https://github.com/substack/node-optimist) is used to process command line arguments.
9 | * [request](https://npmjs.org/package/request) is used to fetch NPM registry information.
10 | * [semver](https://npmjs.org/package/semver) is used to compare module version numbers.
11 | * [q](https://npmjs.org/package/q) library is used to handle promises. While developing this tool,
12 | I quickly ran into problems managing the asynchronous nature of fetching information, installing multiple modules,
13 | testing, etc. At first I used [async](https://npmjs.org/package/async), but it was still too complex.
14 | Using promises allowed to cut the program's code and the complexity to very manageable level.
15 | * [cli-color](https://npmjs.org/package/cli-color) prints colored text to the terminal.
16 |
--------------------------------------------------------------------------------
/docs/use.md:
--------------------------------------------------------------------------------
1 | Also check out:
2 |
3 | * [next-updater](https://github.com/bahmutov/next-updater) can update all your repos
4 | * [dont-break](https://github.com/bahmutov/dont-break)
5 | that checks if your code is going to break everyone who depends on it.
6 | * [changed-log](https://github.com/bahmutov/changed-log) returns commit messages for
7 | the given NPM package or Github repo between two tags.
8 |
9 | ## Example
10 |
11 | Imagine your nodejs module *foo* has the following dependencies listed in *package.json*
12 |
13 | "dependencies": {
14 | "lodash": "~1.2.0",
15 | "async": "~0.2.5"
16 | }
17 |
18 | You would like to update lodash and async to latest versions, to not sure if
19 | this would break anything. With *next-update* it is easy: run command `next-update`
20 | in the folder with module *foo*. Here is the example output:
21 |
22 | next updates:
23 | lodash
24 | 1.2.1 PASS
25 | async
26 | 0.2.6 PASS
27 | 0.2.7 PASS
28 | 0.2.8 PASS
29 |
30 |
31 | Both *package.json* file and *node_modules* folder are left unchanged,
32 | and now you know that you can safely upgrade both libraries to later versions.
33 |
34 | ### It even tells you the install command ;)
35 |
36 | Use the following command to install working versions
37 | npm install --save lodash@2.1.0
38 |
39 | This might not appear like a big deal for a single module that is using
40 | popular 3rd party libraries with stable apis only. *next-update* is most useful
41 | in the larger development context, where multiple modules are being developed
42 | side by side, often by different teams. In such situations, checking if an upgrade
43 | is possible could be part of the continuous build pipeline.
44 |
45 | You can see if your dependencies are out of date by using
46 | [david](https://david-dm.org),
47 | it even has badges you can add to your README files.
48 |
49 | *next-update* reports the probability of success for a given dependency update using
50 | anonymous global statistics from [next-update](http://next-update.herokuapp.com/) server
51 |
52 | ```
53 | available updates:
54 | package available from version average success % successful updates failed updates
55 | -------------------- --------- ------------ ----------------- ------------------ --------------
56 | grunt-contrib-jshint 0.8.0 0.7.2 100% 34 0
57 | grunt-bump 0.0.13 0.0.12 100% 4 0
58 | ```
59 |
60 | ## Install
61 |
62 | You can install this tool globally
63 |
64 | npm install -g next-update // installs module globally
65 | next-update --help // shows command line options
66 |
67 | Then run inside any package folder
68 |
69 | /git/my-awesome-module
70 | $ next-update
71 |
72 | Or you can use this module as a devDependency and a script command
73 |
74 | npm install --save-dev next-update
75 |
76 | ```json
77 | {
78 | "scripts": {
79 | "next-update": "next-update -k true --tldr"
80 | }
81 | }
82 | ```
83 |
84 | This command will keep the successfuly version upgrades in the package.json file,
85 | but will not be very verbose when run.
86 |
87 | ## Anonymous usage collection
88 |
89 | After testing each module A upgrade from version X to Y, *next-update* sends
90 | anonymous result to [next-update.herokuapp.com/](http://next-update.herokuapp.com/).
91 | The only information transmitted is:
92 |
93 | ```json
94 | {
95 | "name": "lodash",
96 | "from": "1.0.0",
97 | "to": "2.0.0",
98 | "success": true
99 | }
100 | ```
101 |
102 | This information is used to answer the following questions later:
103 | what is the probability module A can be upgraded from X to Y?
104 | Thus even if you do not have tests covering this particular module,
105 | you can judge how compatible version X and Y really are over the entire
106 | internet.
107 |
108 | You can inspect data send in
109 | [stats.js](https://github.com/bahmutov/next-update/blob/master/src/stats.js).
110 |
111 | If the dependency module has been upgraded by anyone else, its statistics
112 | will be displayed with each test.
113 |
114 | ```sh
115 | stats: deps-ok 0.0.7 -> 0.0.8 success probability 44.44% 8 success(es) 10 failure(s)
116 | ```
117 |
118 | A lot of NPM modules [do not have tests](http://npmt.abru.pt/), but
119 | at least you can judge if someone else has success going from verion X to version Y
120 | of a dependency.
121 |
122 | ## Use
123 |
124 | Make sure the target module has unit / integration tests,
125 | and the tests can be run using `npm test` command.
126 |
127 | Run `next-update` from the command line in the same folder as
128 | the target module. In general this tool does the following:
129 |
130 | 1. Reads the module's dependencies (including dev) and their versions
131 | 2. Queries npm registry to see if there are newer versions
132 | 3. For each dependency that has newer versions available:
133 | 1. Installs each version
134 | 2. Runs command `npm test` to determine if the new version breaks the tests
135 | 3. Installs back the current version.
136 | 4. Reports results
137 |
138 | ## Checking specific modules
139 |
140 | You can check one or more specific modules (whitelist) using CLI flag
141 | `--module` or `-m`
142 |
143 | ```sh
144 | next-update --module foo,bar,baz
145 | ```
146 |
147 | ## Ignoring or skipping some modules
148 |
149 | **note** [prerelease](https://github.com/npm/node-semver#functions)
150 | versions like `1.2.0-alpha` are skipped by default. I believe `next-update` is
151 | meant to upgrade to *stable* versions.
152 |
153 | Some modules are hard to unit test, thus the automatic upgrades are not appropriate.
154 | For example [benv](https://npmjs.org/package/benv) upgrade brings a new
155 | [jsdom](https://npmjs.org/package/jsdom) version, which does not work on Node 0.12
156 | Similarly, upgrading [Q](https://npmjs.org/package/q) from 1.x.x to 2.x.x is usually
157 | a breaking change.
158 |
159 | You can skip a list of modules by name using `config` property in the `package.json`
160 |
161 | ```json
162 | "config": {
163 | "next-update": {
164 | "skip": ["benv", "q"]
165 | }
166 | }
167 | ```
168 |
169 | ## Custom test command per module
170 |
171 | Some modules are not really tested using the default `npm test` command or
172 | whatever is passed via `--test "..."` from CLI. For example a linter module
173 | should probably be tested using `npm run lint` command. You can set individual
174 | test commands for each module to override the default test command. In the
175 | `package.json` config object set "commands" object
176 |
177 | ```json
178 | "config": {
179 | "next-update": {
180 | "commands": {
181 | "git-issues": "npm run issues",
182 | "standard": "npm run lint"
183 | }
184 | }
185 | }
186 | ```
187 |
188 | Then when `git-issues` module is checked by itself, it will run
189 | `npm run issues` command; when module `standard` is tested by itself, the
190 | test will use `npm run lint` command.
191 |
192 | ## Misc
193 |
194 | * To see what has changed in the latest version of any module,
195 | use my companion tool [changed](https://npmjs.org/package/changed)
196 | like this `changed foo` (*foo* is package name)
197 | * When comparing versions, keywords *latest* and *** are both assumed to equal to "0.0.0".
198 | * A good workflow using *next-update*
199 | * see available new versions `next-update --available`
200 | * check latest version of each module using `next-update --latest`
201 | * install new versions of the desired modules using standard `npm i dependency@version --save`
202 | * You can use custom test command, for example `next-update -t "grunt test"`
203 | * `npm test` is used by default.
204 | * You can keep each working version in package.json by using `--keep` flag.
205 |
--------------------------------------------------------------------------------
/examples/qunit/README.md:
--------------------------------------------------------------------------------
1 | Install qunit global module
2 |
3 | npm i -g qunit
4 |
5 | Run unit tests
6 |
7 | qunit -c test.js -t test.js
8 |
--------------------------------------------------------------------------------
/examples/qunit/test.js:
--------------------------------------------------------------------------------
1 | test("a basic test example", function (assert) {
2 | ok(true, "this test is fine");
3 | var value = "hello";
4 | equal("hello", value, "We expect value to be hello");
5 | });
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var q = require('q')
2 | q.longStackSupport = true
3 |
4 | if (!module.parent) {
5 | throw new Error('Please run bin/next-update.js for stand alone CLI tool')
6 | }
7 |
8 | module.exports = require('./src/next-update-as-module')
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next-update",
3 | "description": "Tests if module's dependencies can be updated to the newer version without breaking the tests",
4 | "version": "0.0.0",
5 | "author": "Gleb Bahmutov ",
6 | "bin": {
7 | "next-update": "bin/next-update.js"
8 | },
9 | "bugs": {
10 | "url": "https://github.com/bahmutov/next-update/issues"
11 | },
12 | "config": {
13 | "pre-git": {
14 | "commit-msg": "simple",
15 | "pre-commit": [
16 | "npm run build",
17 | "npm test",
18 | "npm run e2e"
19 | ],
20 | "pre-push": [
21 | "npm run size"
22 | ],
23 | "post-commit": [],
24 | "post-merge": []
25 | }
26 | },
27 | "contributors": [],
28 | "dependencies": {
29 | "changed-log": "0.13.0",
30 | "chdir-promise": "0.4.0",
31 | "check-more-types": "2.24.0",
32 | "cli-color": "1.2.0",
33 | "common-tags": "1.4.0",
34 | "console.json": "0.2.1",
35 | "console.table": "0.8.0",
36 | "debug": "2.6.8",
37 | "deps-ok": "1.2.0",
38 | "easy-table": "0.3.0",
39 | "execa": "0.7.0",
40 | "is-online": "5.1.2",
41 | "lazy-ass": "1.5.0",
42 | "lodash": "3.10.1",
43 | "npm-utils": "1.7.1",
44 | "optimist": "0.6.1",
45 | "pluralize": "5.0.0",
46 | "q": "2.0.3",
47 | "quote": "0.4.0",
48 | "ramda": "0.24.1",
49 | "request": "2.74.0",
50 | "semver": "5.3.0",
51 | "update-notifier": "0.5.0"
52 | },
53 | "devDependencies": {
54 | "coveralls": "2.11.4",
55 | "git-issues": "1.3.1",
56 | "github-post-release": "1.12.1",
57 | "grunt": "0.4.5",
58 | "grunt-bump": "0.7.3",
59 | "grunt-cli": "0.1.13",
60 | "grunt-complexity": "0.3.0",
61 | "grunt-deps-ok": "0.9.0",
62 | "grunt-help": "0.5.0",
63 | "grunt-jsonlint": "1.1.0",
64 | "grunt-lineending": "1.0.0",
65 | "grunt-nice-package": "0.10.4",
66 | "grunt-readme": "0.4.5",
67 | "gt": "0.10.0",
68 | "matchdep": "1.0.1",
69 | "mocha": "3.4.2",
70 | "mockery": "1.7.0",
71 | "pre-git": "3.15.0",
72 | "prettier-standard": "5.1.0",
73 | "publish": "0.6.0",
74 | "semantic-release": "6.3.6",
75 | "simple-commit-message": "3.0.2",
76 | "snap-shot": "2.17.0",
77 | "standard": "10.0.2",
78 | "stop-build": "1.1.0",
79 | "time-grunt": "1.4.0"
80 | },
81 | "engines": {
82 | "node": ">= 0.8.0"
83 | },
84 | "files": [
85 | "bin",
86 | "index.js",
87 | "src",
88 | "!src/*-spec.js",
89 | "!src/test"
90 | ],
91 | "homepage": "https://github.com/bahmutov/next-update",
92 | "keywords": [
93 | "javascript",
94 | "node",
95 | "npm",
96 | "testing",
97 | "update",
98 | "version"
99 | ],
100 | "license": "MIT",
101 | "main": "index.js",
102 | "next-update-stats": "http://next-update.herokuapp.com",
103 | "preferGlobal": true,
104 | "readmeFilename": "README.md",
105 | "release": {
106 | "analyzeCommits": "simple-commit-message",
107 | "generateNotes": "github-post-release"
108 | },
109 | "repository": {
110 | "type": "git",
111 | "url": "https://github.com/bahmutov/next-update.git"
112 | },
113 | "scripts": {
114 | "2npm": "publish",
115 | "build": "grunt",
116 | "commit": "git-issues && commit-wizard",
117 | "coveralls": "cat cover/lcov.info | ./node_modules/coveralls/bin/coveralls.js",
118 | "dont-break": "dont-break",
119 | "e2e": "npm run install-for-tests && gt test/*.coffee --output",
120 | "install-for-tests": "cd test/test-next-updater && npm install",
121 | "issues": "git-issues",
122 | "limited": "gt --filter 'allow major' --output src/test/*.coffee",
123 | "lint": "standard --verbose --fix *.js src/*.js src/**/*.js bin/*.js test/*.js",
124 | "mocha": "mocha src/*-spec.js test/*-spec.js",
125 | "posttest": "npm run mocha",
126 | "prebuild": "npm run lint",
127 | "prelint": "npm run pretty",
128 | "pretest": "npm run build",
129 | "pretty": "prettier-standard 'bin/*.js' '*.js' 'test/*.js'",
130 | "self-update": "node bin/next-update.js -k true",
131 | "semantic-release": "semantic-release pre && npm publish && semantic-release post",
132 | "size": "t=\"$(npm pack .)\"; wc -c \"${t}\"; tar tvf \"${t}\"; rm \"${t}\";",
133 | "test": "npm run unit",
134 | "unit": "gt src/test/*.js src/test/*.coffee --output"
135 | }
136 | }
137 |
--------------------------------------------------------------------------------
/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["config:base"],
3 | "automerge": true,
4 | "major": {
5 | "automerge": false
6 | },
7 | "excludePackageNames": ["^grunt", "semantic-release", "github-post-release"]
8 | }
9 |
--------------------------------------------------------------------------------
/src/cli-options.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var optimist = require('optimist')
4 | var pkg = require('../package.json')
5 | var la = require('lazy-ass')
6 | var is = require('check-more-types')
7 | var _ = require('lodash')
8 | var path = require('path')
9 |
10 | var parentFolder = path.join(__dirname, '..')
11 | var info = pkg.name + ' - ' + pkg.description + '\n' +
12 | ' version: ' + pkg.version + '\n' +
13 | ' folder: ' + parentFolder + '\n' +
14 | ' author: ' + JSON.stringify(pkg.author)
15 |
16 | var program = optimist
17 | .options('revert', {
18 | boolean: true,
19 | description: 'install original module versions listed in package.json',
20 | default: false
21 | })
22 | .options('available', {
23 | boolean: true,
24 | alias: 'a',
25 | description: 'only query available later versions, do not test them',
26 | default: false
27 | })
28 | .options('module', {
29 | string: true,
30 | alias: 'm',
31 | description: 'checks specific module(s), can include version name@version',
32 | default: null
33 | })
34 | .options('without', {
35 | string: true,
36 | alias: 'w',
37 | description: 'skip checking this module(s)',
38 | default: null
39 | })
40 | .option('latest', {
41 | boolean: true,
42 | alias: 'l',
43 | description: 'only check latest available update',
44 | default: true
45 | })
46 | .option('color', {
47 | boolean: true,
48 | alias: 'c',
49 | description: 'color terminal output (if available)',
50 | default: true
51 | })
52 | .option('version', {
53 | boolean: true,
54 | alias: 'v',
55 | description: 'show version and exit',
56 | default: false
57 | })
58 | .option('test', {
59 | string: true,
60 | alias: 't',
61 | description: 'custom test command to run instead of npm test'
62 | })
63 | .option('skip', {
64 | boolean: true,
65 | description: 'skip running tests first',
66 | default: false
67 | })
68 | .option('all', {
69 | boolean: true,
70 | default: false,
71 | description: 'install all modules at once before testing'
72 | })
73 | .option('keep', {
74 | boolean: true,
75 | default: true,
76 | alias: 'k',
77 | description: 'keep tested version if it is working'
78 | })
79 | .option('allow', {
80 | string: true,
81 | default: 'major',
82 | description: 'allow major / minor / patch updates'
83 | })
84 | .options('type', {
85 | string: true,
86 | default: 'all',
87 | description: 'check dependencies of type (all, prod, dev, peer)'
88 | })
89 | .options('tldr', {
90 | boolean: true,
91 | default: false,
92 | description: 'only print VERY important log messages'
93 | })
94 | .options('changed-log', {
95 | boolean: true,
96 | default: true,
97 | alias: 'L',
98 | description: 'print commit changes between working versions'
99 | })
100 | .options('registry', {
101 | string: true,
102 | default: false,
103 | description: 'use a custom registry url'
104 | })
105 | .options('check-version-timeout', {
106 | number: true,
107 | default: 10000,
108 | description: 'define a custom timeout value for checking next versions'
109 | })
110 | .usage(info)
111 | .argv
112 |
113 | if (program.version) {
114 | console.log(info)
115 | process.exit(0)
116 | }
117 |
118 | if (program.help || program.h || program['?']) {
119 | optimist.showHelp()
120 | process.exit(0)
121 | }
122 |
123 | program.allowWasSpecified = _.includes(process.argv, '--allow')
124 |
125 | if (is.string(program.module)) {
126 | program.module = program.module.split(',').map(_.trim)
127 | la(is.array(program.module), 'expected list of modules', program.module)
128 | }
129 |
130 | if (is.string(program.without)) {
131 | program.without = program.without.split(',').map(_.trim)
132 | la(is.array(program.without),
133 | 'expected list of modules to skip in --without', program.without)
134 | }
135 |
136 | module.exports = program
137 |
--------------------------------------------------------------------------------
/src/dependencies.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var debug = require('debug')('next-update')
4 | var la = require('lazy-ass')
5 | var check = require('check-more-types')
6 | var path = require('path')
7 | var print = require('./print-modules-table')
8 | var nameVersionParser = require('./moduleName')
9 | var getKnownDependencies = require('./get-known-dependencies')
10 | const {getSkippedModules} = require('./utils')
11 | const pluralize = require('pluralize')
12 |
13 | require('console.table')
14 | var _ = require('lodash')
15 |
16 | la(check.fn(console.table), 'missing console.table method')
17 | la(check.fn(console.json), 'missing console.json method')
18 |
19 | // eslint-disable-next-line no-unused-vars
20 | function printCurrentModules (infos) {
21 | check.verify.array(infos, 'expected array of modules')
22 |
23 | var modules = []
24 | infos.forEach(function (nameVersionArray) {
25 | check.verify.array(nameVersionArray, 'expected name version in ' + modules)
26 | modules.push({
27 | name: nameVersionArray[0],
28 | version: nameVersionArray[1]
29 | })
30 | })
31 | print(modules)
32 | }
33 |
34 | function printTable (options, nameVersionPairs) {
35 | if (options.tldr) {
36 | return
37 | }
38 |
39 | var allowedType = options.type || 'all'
40 | var title = 'module\'s current dependencies:'
41 | var filtered = allowedType === 'all'
42 | ? nameVersionPairs
43 | : _.filter(nameVersionPairs, { type: allowedType })
44 |
45 | // TODO just use Ramda project
46 | console.table(title, _.map(filtered, function (nameVersion) {
47 | return {
48 | module: nameVersion.name,
49 | version: nameVersion.version,
50 | type: nameVersion.type
51 | }
52 | }))
53 | }
54 |
55 | function remove (nameVersionPairs, skipModules) {
56 | check.verify.array(skipModules, 'expected list of modules to skip')
57 | return nameVersionPairs.filter(function (dep) {
58 | check.verify.unemptyString(dep.name, 'missing name for dependency')
59 | return !_.includes(skipModules, dep.name)
60 | })
61 | }
62 |
63 | function normalizeModuleNames (moduleNames) {
64 | if (!moduleNames) {
65 | return
66 | }
67 | console.log('returning dependencies for')
68 | console.dir(moduleNames)
69 | if (check.string(moduleNames)) {
70 | moduleNames = [moduleNames]
71 | }
72 |
73 | if (check.object(moduleNames)) {
74 | var names = Object.keys(moduleNames)
75 | moduleNames = names
76 | }
77 |
78 | check.verify.array(moduleNames, 'expected module names array ' +
79 | JSON.stringify(moduleNames))
80 | return moduleNames
81 | }
82 |
83 | function getDependenciesToCheck (options, moduleNames) {
84 | check.verify.object(options, 'missing options')
85 | debug('get dependencies for options')
86 | debug(options)
87 | moduleNames = normalizeModuleNames(moduleNames)
88 | if (moduleNames) {
89 | debug('normalized module names', moduleNames)
90 | } else {
91 | debug('no --module filter')
92 | }
93 |
94 | var workingDirectory = process.cwd()
95 |
96 | var packageFilename = path.join(workingDirectory, 'package.json')
97 | var nameVersionPairs = getKnownDependencies(packageFilename)
98 |
99 | var skipModules = getSkippedModules(packageFilename)
100 | check.verify.array(skipModules, 'expected list of skipped modules')
101 | if (skipModules.length) {
102 | if (!options.tldr) {
103 | console.log('ignoring the following modules', skipModules.join(', '))
104 | }
105 | nameVersionPairs = remove(nameVersionPairs, skipModules)
106 | }
107 | if (options.without) {
108 | la(check.array(options.without),
109 | 'without should be an array', options.without)
110 | if (check.unempty(options.without)) {
111 | debug('checking without %s %s',
112 | pluralize('module', options.without.length),
113 | options.without.join(', '))
114 | nameVersionPairs = remove(nameVersionPairs, options.without)
115 | }
116 | }
117 | printTable(options, nameVersionPairs)
118 |
119 | var toCheck = nameVersionPairs
120 | if (moduleNames) {
121 | debug('matching module names', moduleNames)
122 | toCheck = nameVersionPairs.filter(function (nameVersion) {
123 | var name = nameVersion.name
124 | return moduleNames.some(function (aModule) {
125 | var moduleName = nameVersionParser(aModule).name
126 | return name === moduleName
127 | })
128 | })
129 | if (!options.tldr) {
130 | if (toCheck.length) {
131 | console.log('only checking modules')
132 | console.log(toCheck.map(function (m) {
133 | return m.name + '@' + m.version
134 | }))
135 | } else {
136 | console.log('Hmm, no modules to check')
137 | console.log('from initial list\n' +
138 | JSON.stringify(nameVersionPairs, null, 2))
139 | }
140 | }
141 | }
142 | return toCheck
143 | }
144 |
145 | module.exports = getDependenciesToCheck
146 |
--------------------------------------------------------------------------------
/src/exec-test.js:
--------------------------------------------------------------------------------
1 | var check = require('check-more-types')
2 | var verify = check.verify
3 | var spawn = require('child_process').spawn
4 | var q = require('q')
5 | var npmPath = require('./npm-test').npmPath
6 | var _ = require('lodash')
7 | const debug = require('debug')('next-update')
8 |
9 | // returns a promise
10 | // TODO switch to execa
11 | function test (options, testCommand) {
12 | options = options || {}
13 | var log = options.tldr ? _.noop : console.log.bind(console)
14 | debug('exec-test "%s"', testCommand)
15 |
16 | verify.unemptyString(testCommand, 'missing test command string')
17 | log(' ', testCommand)
18 |
19 | var testParts = testCommand.split(' ')
20 | console.assert(testParts.length > 0, 'missing any test words in ' + testCommand)
21 | var testExecutable = testParts.shift()
22 | verify.unemptyString(testExecutable, 'missing test executable for command ' + testCommand)
23 | if (testExecutable === 'npm') {
24 | testExecutable = npmPath
25 | }
26 | var testProcess = spawn(testExecutable, testParts)
27 | var testOutput = ''
28 | var testErrors = ''
29 |
30 | testProcess.stdout.setEncoding('utf-8')
31 | testProcess.stderr.setEncoding('utf-8')
32 |
33 | testProcess.stdout.on('data', function (data) {
34 | testOutput += data
35 | })
36 |
37 | testProcess.stderr.on('data', function (data) {
38 | testErrors += data
39 | })
40 |
41 | var deferred = q.defer()
42 | testProcess.on('error', function (err) {
43 | console.error('test command: "' + testCommand + '"')
44 | console.error(err)
45 | testErrors += err.toString()
46 | const e = new Error('test command failed')
47 | e.code = err.code
48 | e.errors = testErrors
49 | deferred.reject(e)
50 | })
51 |
52 | testProcess.on('exit', function (code) {
53 | if (code) {
54 | console.error('testProcess test returned', code)
55 | console.error('test errors:\n' + testErrors)
56 | console.error(testOutput)
57 | const e = new Error('test exit code means error')
58 | e.code = code
59 | e.errors = testErrors
60 | return deferred.reject(e)
61 | }
62 | deferred.resolve()
63 | })
64 | return deferred.promise
65 | }
66 |
67 | module.exports = test
68 |
--------------------------------------------------------------------------------
/src/filter-allowed-spec.js:
--------------------------------------------------------------------------------
1 | const la = require('lazy-ass')
2 | const snapShot = require('snap-shot')
3 | const filter = require('./filter-allowed-updates')
4 | const {clone, equals} = require('ramda')
5 | const {isPrerelease} = require('./utils')
6 |
7 | /* global describe, it, afterEach */
8 | describe('filter allowed', () => {
9 | const current = {
10 | q: { name: 'q', version: '1.1.1', type: 'dev' }
11 | }
12 | const available = [ { name: 'q',
13 | versions: [ '1.3.0', '2.0.2', '3.0.0', '3.0.0-alpha', '3.0.0-rc0' ] } ]
14 | const copy = clone(available)
15 |
16 | afterEach(() => {
17 | // sanity check to make sure we are not
18 | // mutating the input objects
19 | la(equals(available, copy),
20 | 'original available list is unchanged', available, copy)
21 | })
22 |
23 | it('detects prerelease', () => {
24 | snapShot(isPrerelease, '3.0.0-alpha', '3.0.0', '0.1.0', '10.0.0-beta.2')
25 | })
26 |
27 | it('allows major', () => {
28 | const options = {
29 | allowed: 'major'
30 | }
31 | const allowed = filter(current, available, options)
32 | snapShot({current, options, allowed})
33 | })
34 |
35 | it('allows minor', () => {
36 | const options = {
37 | allowed: 'minor'
38 | }
39 | const allowed = filter(current, available, options)
40 | snapShot({current, options, allowed})
41 | })
42 |
43 | it('allows patch', () => {
44 | const options = {
45 | allowed: 'patch'
46 | }
47 | const allowed = filter(current, available, options)
48 | snapShot({current, options, allowed})
49 | })
50 |
51 | it('allows everything by default', () => {
52 | const options = {}
53 | const allowed = filter(current, available, options)
54 | snapShot({current, options, allowed})
55 | })
56 |
57 | it('can custom filter', () => {
58 | const options = {
59 | limit: (name, version) => version === '2.0.2'
60 | }
61 | const allowed = filter(current, available, options)
62 | snapShot({current, options, allowed})
63 | })
64 | })
65 |
--------------------------------------------------------------------------------
/src/filter-allowed-updates.js:
--------------------------------------------------------------------------------
1 | var la = require('lazy-ass')
2 | var check = require('check-more-types')
3 | var semver = require('semver')
4 | var _ = require('lodash')
5 | const R = require('ramda')
6 | const debug = require('debug')('next-update')
7 | const {isPrerelease} = require('./utils')
8 |
9 | la(check.fn(semver.diff), 'semver missing diff method', semver)
10 |
11 | function isDiffAllowed (allowed, diff) {
12 | la(check.unemptyString(allowed), 'invalid allowed update', allowed)
13 | la(check.unemptyString(diff), 'invalid diff update', diff)
14 |
15 | switch (allowed) {
16 | case 'major':
17 | return true
18 | case 'minor':
19 | return diff === 'minor' || diff === 'patch'
20 | case 'patch':
21 | return diff === 'patch'
22 | default:
23 | throw new Error('Invalid allowed update ' + allowed)
24 | }
25 | }
26 |
27 | function isDependencyTypeAllowed (allowed, current) {
28 | la(check.unemptyString(allowed), 'expected allowed string', allowed)
29 | la(check.unemptyString(current), 'expected current string', current)
30 |
31 | allowed = allowed.trim().toLowerCase()
32 | current = current.trim().toLowerCase()
33 |
34 | if (allowed === 'all') {
35 | return true
36 | }
37 | return allowed === current
38 | }
39 |
40 | /*
41 | update is an object like this
42 | {
43 | name: 'check-types',
44 | versions: ['0.7.0', '0.7.1']
45 | }
46 | and limit function tells for each (name, version) if it should remain
47 | in this list
48 | */
49 | function limitUpdate (limit, update) {
50 | la(check.fn(limit), 'expected limit function', limit)
51 |
52 | const filter = R.filter(version => limit(update.name, version))
53 |
54 | return R.evolve({
55 | versions: filter
56 | })(update)
57 | }
58 |
59 | /*
60 | update is an object like this
61 | {
62 | name: 'check-types',
63 | versions: ['0.7.0', '0.7.1']
64 | }
65 | but the versions list could be empty
66 | */
67 | function hasVersionsToCheck (update) {
68 | return check.object(update) && check.unempty(update.versions)
69 | }
70 |
71 | function filterAllowedUpdates (current, available, options) {
72 | var allowed = options.allow || options.allowed || 'major'
73 | var isAllowed = _.partial(isDiffAllowed, allowed)
74 | const limit = options.limit || R.T
75 |
76 | var type = options.type || 'all'
77 | var isAllowedType = _.partial(isDependencyTypeAllowed, type)
78 |
79 | const askLimit = _.partial(limitUpdate, limit)
80 |
81 | // console.log('filtering available updates', available);
82 | // console.log('current versions', current);
83 |
84 | function filterVersions (fromVersion, toVersion) {
85 | var diff = semver.diff(fromVersion, toVersion)
86 | // console.log(availableUpdate.name, 'difference from', fromVersion, 'to', toVersion, diff);
87 | return isAllowed(diff)
88 | }
89 |
90 | function allowedDependencyType (availableUpdate) {
91 | var dependency = current[availableUpdate.name]
92 | la(check.object(dependency),
93 | 'cannot find dependency for update', availableUpdate, 'in', current)
94 | la(check.unemptyString(dependency.type),
95 | 'missing type of update', dependency)
96 | return isAllowedType(dependency.type)
97 | }
98 |
99 | function filterHasNewVersions (availableUpdate) {
100 | la(check.unemptyString(availableUpdate.name), 'missing name in available', availableUpdate)
101 |
102 | var fromVersion = current[availableUpdate.name].version
103 | la(check.unemptyString(fromVersion),
104 | 'cannot find current version for', availableUpdate.name, current)
105 |
106 | var versions = availableUpdate.versions
107 | la(check.array(versions), 'missing versions in update', availableUpdate)
108 |
109 | const filterByUpgradeType = _.partial(filterVersions, fromVersion)
110 | const notPrerelease = R.complement(isPrerelease)
111 |
112 | const filteredVersions = versions
113 | .filter(notPrerelease)
114 | .filter(filterByUpgradeType)
115 |
116 | availableUpdate.versions = filteredVersions
117 | return availableUpdate.versions.length > 0
118 | }
119 |
120 | debug('available updates')
121 | debug(available)
122 |
123 | const filtered = R.clone(available)
124 | .filter(filterHasNewVersions)
125 | .filter(allowedDependencyType)
126 | .map(askLimit)
127 | .filter(hasVersionsToCheck)
128 |
129 | debug('filtered updates')
130 | debug(filtered)
131 |
132 | return filtered
133 | }
134 |
135 | module.exports = check.defend(filterAllowedUpdates,
136 | check.object, 'expected object with all current dependencies',
137 | check.array, 'available list should be an array',
138 | check.object, 'options should be an object')
139 |
--------------------------------------------------------------------------------
/src/get-known-dependencies.js:
--------------------------------------------------------------------------------
1 | var check = require('check-more-types')
2 | var registry = require('./registry')
3 | var cleanVersions = registry.cleanVersions
4 |
5 | function format (label, deps) {
6 | check.verify.unemptyString(label, 'missing label')
7 | check.verify.object(deps, 'expected deps')
8 | return Object.keys(deps).map(function (name) {
9 | return {
10 | type: label,
11 | name: name,
12 | version: deps[name]
13 | }
14 | })
15 | }
16 |
17 | function getKnownDependencies (packageFilename) {
18 | check.verify.string(packageFilename, 'missing package filename string')
19 |
20 | var workingPackage = require(packageFilename)
21 |
22 | var dependencies = workingPackage.dependencies || {}
23 | var devDependencies = workingPackage.devDependencies || {}
24 | var peerDependencies = workingPackage.peerDependencies || {}
25 |
26 | var all = [].concat(
27 | format('prod', dependencies),
28 | format('dev', devDependencies),
29 | format('peer', peerDependencies)
30 | )
31 |
32 | var cleaned = cleanVersions(all)
33 | // console.log('nameVersionPairs', cleaned);
34 | return cleaned
35 | }
36 |
37 | module.exports = getKnownDependencies
38 |
--------------------------------------------------------------------------------
/src/local-module-version.js:
--------------------------------------------------------------------------------
1 | var check = require('check-more-types')
2 | var fs = require('fs')
3 | var path = require('path')
4 |
5 | // sync returns version
6 | function getLocalModuleVersion (name) {
7 | check.verify.string(name, 'missing name string')
8 |
9 | try {
10 | var filename = path.join('node_modules', name, 'package.json')
11 | var contents = fs.readFileSync(filename, 'utf-8')
12 | var pkg = JSON.parse(contents)
13 | return pkg.version
14 | } catch (error) {
15 | console.error('could not fetch version for local module', name)
16 | console.error(error)
17 | return null
18 | }
19 | }
20 |
21 | module.exports = getLocalModuleVersion
22 |
--------------------------------------------------------------------------------
/src/module-install.js:
--------------------------------------------------------------------------------
1 | const la = require('lazy-ass')
2 | var check = require('check-more-types')
3 | var spawn = require('child_process').spawn
4 | var q = require('q')
5 | var NPM_PATH = require('npm-utils').path
6 | var formInstallCommand = require('./report-install-command')
7 |
8 | // returns a promise
9 | function installModule (options, results) {
10 | check.verify.object(options, 'missing options')
11 | la(check.unemptyString(options.name),
12 | 'expected module name string', options.name, 'all options', options)
13 | check.verify.string(options.version, 'expected version string')
14 |
15 | if (options.keep) {
16 | console.assert(typeof options.keep === 'boolean', 'invalid keep')
17 | }
18 | if (results) {
19 | check.verify.array(results, 'missing results')
20 | }
21 |
22 | var cmd = formInstallCommand([[{
23 | name: options.name,
24 | version: options.version,
25 | works: true
26 | }]])
27 | check.verify.unemptyString(cmd, 'could not form install command')
28 | cmd = cmd.trim()
29 |
30 | var moduleVersion = options.name + '@' + options.version
31 | var npm
32 | if (options.keep) {
33 | console.log(' ', cmd)
34 | var args = cmd.split(' ')
35 | args.shift()
36 | args.push('--save-exact')
37 | npm = spawn(NPM_PATH, args)
38 | } else {
39 | if (!options.tldr) {
40 | console.log(' installing', moduleVersion)
41 | }
42 | npm = spawn(NPM_PATH, ['install', moduleVersion])
43 | }
44 |
45 | // eslint-disable-next-line no-unused-vars
46 | var testOutput = ''
47 | var testErrors = ''
48 |
49 | npm.stdout.setEncoding('utf-8')
50 | npm.stderr.setEncoding('utf-8')
51 |
52 | function hasError (str) {
53 | return /error/i.test(str)
54 | }
55 |
56 | npm.stdout.on('data', function (data) {
57 | if (hasError(data)) {
58 | console.log('stdout:', data)
59 | }
60 | testOutput += data
61 | })
62 |
63 | npm.stderr.on('data', function (data) {
64 | if (hasError(data)) {
65 | console.log('stderr:', data)
66 | }
67 | testErrors += data
68 | })
69 |
70 | npm.on('error', function (err) {
71 | console.error('error:', err)
72 | testErrors += err.toString()
73 | })
74 |
75 | var deferred = q.defer()
76 | npm.on('exit', function (code) {
77 | if (code) {
78 | console.error('npm returned', code)
79 | console.error('errors:\n' + testErrors)
80 | deferred.reject({
81 | code: code,
82 | errors: testErrors
83 | })
84 | } else {
85 | if (!options.tldr) {
86 | console.log(moduleVersion, 'installed successfully')
87 | }
88 | deferred.resolve(results)
89 | }
90 | })
91 | return deferred.promise
92 | }
93 |
94 | module.exports = installModule
95 |
--------------------------------------------------------------------------------
/src/moduleName.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var check = require('check-more-types')
4 |
5 | function isScopedName (str) {
6 | return str[0] === '@' && str.indexOf('/') !== -1
7 | }
8 |
9 | function parseScopedName (str) {
10 | var parsed = moduleName(str.substr(1))
11 | parsed.name = '@' + parsed.name
12 | return parsed
13 | }
14 |
15 | function moduleName (str) {
16 | check.verify.string(str, 'expected string module name')
17 |
18 | if (isScopedName(str)) {
19 | return parseScopedName(str)
20 | }
21 |
22 | var parts = str.split('@')
23 | return {
24 | name: parts[0],
25 | version: parts[1]
26 | }
27 | }
28 |
29 | module.exports = moduleName
30 |
--------------------------------------------------------------------------------
/src/next-update-as-module.js:
--------------------------------------------------------------------------------
1 | const nextUpdate = require('./next-update')
2 | const is = require('check-more-types')
3 | const {T} = require('ramda')
4 |
5 | module.exports = function nextUpdateTopLevel (options) {
6 | options = options || {}
7 | const opts = {
8 | names: options.module,
9 | testCommand: options.test,
10 | latest: Boolean(options.latest),
11 | keep: Boolean(options.keep),
12 | color: Boolean(options.color || options.colors),
13 | allow: options.allow || options.allowed,
14 | type: options.type,
15 | changedLog: options['changed-log'],
16 | limit: is.maybe.fn(options.limit) ? options.limit : T,
17 | without: options.without
18 | }
19 |
20 | const checkUpdates = nextUpdate.checkAllUpdates.bind(null, opts)
21 |
22 | return nextUpdate.checkCurrentInstall(opts)
23 | .then(checkUpdates)
24 | }
25 |
--------------------------------------------------------------------------------
/src/next-update.js:
--------------------------------------------------------------------------------
1 | var la = require('lazy-ass')
2 | var check = require('check-more-types')
3 | var verify = check.verify
4 | require('console.json')
5 |
6 | var log = require('debug')('next-update')
7 | var Q = require('q')
8 | Q.longStackSupport = true
9 | var depsOk = require('deps-ok')
10 | var _ = require('lodash')
11 |
12 | var nameVersionParser = require('./moduleName')
13 | var registry = require('./registry')
14 | var nextVersions = registry.nextVersions
15 | var testVersions = require('./test-module-version').testModulesVersions
16 | var runTest = require('./test-module-version').testPromise
17 | var getDependenciesToCheck = require('./dependencies')
18 | var reportAvailable = require('./report-available')
19 | var npmUtils = require('npm-utils')
20 |
21 | var boundConsoleLog = console.log.bind(console)
22 |
23 | // returns a promise
24 | function available (moduleName, options) {
25 | options = options || {}
26 | var toCheck = getDependenciesToCheck(options, moduleName)
27 | la(check.array(toCheck), 'expected object of deps to check, was', toCheck)
28 | var toCheckHash = _.zipObject(
29 | _.pluck(toCheck, 'name'),
30 | _.pluck(toCheck, 'version')
31 | )
32 |
33 | log('need to check these dependencies')
34 | log(toCheckHash)
35 |
36 | var nextVersionsPromise = nextVersions(options, toCheck)
37 | return nextVersionsPromise.then(function (info) {
38 | return reportAvailable(info, toCheckHash, options)
39 | }, function (error) {
40 | console.error('Could not fetch available modules\n', error)
41 | })
42 | }
43 |
44 | function checkDependenciesInstalled () {
45 | var defer = Q.defer()
46 | process.nextTick(function () {
47 | if (depsOk(process.cwd())) {
48 | defer.resolve()
49 | } else {
50 | var msg = 'Current installation is incomplete. Please run `npm install` or `bower install` first'
51 | defer.reject(new Error(msg))
52 | }
53 | })
54 | return defer.promise
55 | }
56 |
57 | function cleanDependencies () {
58 | var update = _.partial(npmUtils.test, 'npm update')
59 | var prune = _.partial(npmUtils.test, 'npm prune')
60 | return update().then(prune)
61 | }
62 |
63 | function checkCurrentInstall (options) {
64 | options = options || {}
65 | var log = options.tldr ? _.noop : boundConsoleLog
66 | log('checking if the current state works')
67 |
68 | return cleanDependencies()
69 | .then(checkDependenciesInstalled)
70 | .then(function () {
71 | log('running test command')
72 | return runTest(options, options.testCommand)()
73 | })
74 | .then(function () {
75 | console.log('> tests are passing at the start')
76 | })
77 | }
78 |
79 | var isOnline = Q.denodeify(require('is-online'))
80 |
81 | function isSingleItem (names) {
82 | return names &&
83 | check.array(names) &&
84 | names.length === 1
85 | }
86 |
87 | function makeSureValidModule (moduleNames, checkingModules) {
88 | la(check.maybe.array(moduleNames), 'expected list of modules', moduleNames)
89 | la(check.array(checkingModules), 'expected list of modules to check', checkingModules)
90 | if (isSingleItem(moduleNames) && check.empty(checkingModules)) {
91 | console.error('Could not find module "%s" in the list of dependencies', moduleNames[0])
92 | console.error('Please check the name')
93 | process.exit(-1)
94 | }
95 | }
96 |
97 | // returns promise
98 | function checkAllUpdates (options) {
99 | options = options || {}
100 | var moduleName = options.names
101 | var checkLatestOnly = !!options.latest
102 | var checkCommand = options.testCommand
103 | if (checkCommand) {
104 | verify.unemptyString(checkCommand, 'invalid test command ' + checkCommand)
105 | }
106 | var all = options.all
107 | if (all) {
108 | checkLatestOnly = true
109 | console.log('will check only latest versions because testing all')
110 | }
111 |
112 | if (check.string(options.without)) {
113 | options.without = [options.without]
114 | }
115 |
116 | if (check.string(moduleName)) {
117 | moduleName = [moduleName]
118 | }
119 | checkLatestOnly = !!checkLatestOnly
120 | if (checkCommand) {
121 | check.verify.string(checkCommand, 'expected string test command')
122 | }
123 | var toCheck = getDependenciesToCheck(options, moduleName)
124 | check.verify.array(toCheck, 'dependencies to check should be an array')
125 |
126 | makeSureValidModule(moduleName, toCheck)
127 |
128 | var testVersionsBound = testVersions.bind(null, {
129 | modules: toCheck,
130 | command: checkCommand,
131 | all: all,
132 | color: options.color,
133 | keep: options.keep,
134 | allowed: options.allow || options.allowed,
135 | tldr: options.tldr,
136 | type: options.type,
137 | limit: options.limit
138 | })
139 |
140 | return isOnline()
141 | .then(function (online) {
142 | if (!online) {
143 | throw new Error('Need to be online to check new modules')
144 | }
145 | }).then(function () {
146 | if (isSingleSpecificVersion(moduleName)) {
147 | var nv = nameVersionParser(moduleName[0])
148 | console.log('checking only specific:', nv.name, nv.version)
149 | var list = [{
150 | name: nv.name,
151 | versions: [nv.version]
152 | }]
153 | return testVersionsBound(list)
154 | } else {
155 | var nextVersionsPromise = nextVersions(options, toCheck, checkLatestOnly)
156 | return nextVersionsPromise.then(testVersionsBound)
157 | }
158 | })
159 | }
160 |
161 | function isSingleSpecificVersion (moduleNames) {
162 | if (!moduleNames) {
163 | return false
164 | }
165 | var name = moduleNames
166 | if (Array.isArray(moduleNames)) {
167 | if (moduleNames.length !== 1) {
168 | return false
169 | }
170 | name = moduleNames[0]
171 | }
172 | check.verify.string(name, 'expected module name string, not ' +
173 | JSON.stringify(name))
174 | var parsed = nameVersionParser(name)
175 | if (check.object(parsed)) {
176 | return false
177 | }
178 | return check.string(parsed.version)
179 | }
180 |
181 | module.exports = {
182 | checkCurrentInstall: checkCurrentInstall,
183 | checkAllUpdates: checkAllUpdates,
184 | available: available
185 | }
186 |
--------------------------------------------------------------------------------
/src/npm-test.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var debug = require('debug')('next-update')
4 | var check = require('check-more-types')
5 | var spawn = require('child_process').spawn
6 | var q = require('q')
7 | var _ = require('lodash')
8 |
9 | // hack to find npm bin script reliably
10 | function findNpmPath () {
11 | var os = require('os')
12 | var type = os.type()
13 | return (/windows/gi).test(type) ? 'npm.cmd' : 'npm'
14 | }
15 |
16 | var NPM_PATH = findNpmPath()
17 | debug('found npm path %s', NPM_PATH)
18 |
19 | function argsToString (arrayLike) {
20 | return Array.prototype.slice.call(arrayLike, 0).join(' ')
21 | }
22 |
23 | function writeToStderr () {
24 | process.stderr.write(argsToString(arguments))
25 | }
26 |
27 | function writeToStdout () {
28 | process.stderr.write(argsToString(arguments))
29 | }
30 |
31 | // returns a promise
32 | function test (options) {
33 | options = options || {}
34 | var log = options.tldr ? _.noop : writeToStdout
35 | var errorLog = options.tldr ? _.noop : writeToStderr
36 | log(' npm test')
37 |
38 | check.verify.string(NPM_PATH, 'missing npm path string')
39 | var npm = spawn(NPM_PATH, ['test'])
40 | var testOutput = ''
41 | var testErrors = ''
42 |
43 | npm.stdout.setEncoding('utf-8')
44 | npm.stderr.setEncoding('utf-8')
45 |
46 | npm.stdout.on('data', function (data) {
47 | testOutput += data
48 | log(data)
49 | })
50 |
51 | npm.stderr.on('data', function (data) {
52 | testErrors += data
53 | log(data)
54 | })
55 |
56 | npm.on('error', function (err) {
57 | errorLog(err)
58 | testErrors += err.toString()
59 | })
60 |
61 | var deferred = q.defer()
62 | npm.on('exit', function (code) {
63 | if (code) {
64 | errorLog('npm test returned', code, '\n')
65 | errorLog('test output:\n' + testOutput)
66 | errorLog('test errors:\n' + testErrors)
67 |
68 | const e = new Error('npm test exit code means error')
69 | e.code = code
70 | e.errors = testErrors
71 |
72 | deferred.reject(e)
73 | }
74 | deferred.resolve()
75 | })
76 | return deferred.promise
77 | }
78 |
79 | module.exports = {
80 | test: test,
81 | npmPath: findNpmPath()
82 | }
83 |
--------------------------------------------------------------------------------
/src/print-modules-table.js:
--------------------------------------------------------------------------------
1 | var verify = require('check-more-types').verify
2 | var Table = require('easy-table')
3 | var colorProbability = require('./stats').colorProbability
4 |
5 | function markProbability (val, width) {
6 | if (width === null) {
7 | return val
8 | }
9 | return Table.padLeft(val, width)
10 | }
11 |
12 | function printModulesTable (modules, options) {
13 | verify.array(modules, 'expect an array of modules')
14 | var haveStats = modules.some(function (m) {
15 | return typeof m.stats === 'object'
16 | })
17 |
18 | var t = new Table()
19 | modules.forEach(function (info) {
20 | verify.string(info.name, 'missing module name ' + info)
21 | verify.string(info.version, 'missing module version ' + info)
22 |
23 | t.cell('package', info.name)
24 | t.cell('current', info.from)
25 | t.cell('available', info.version)
26 |
27 | if (haveStats && info.stats) {
28 | var stats = info.stats
29 | verify.object(stats, 'expected stats object')
30 |
31 | var total = +stats.success + stats.failure
32 | var probability = total ? stats.success / total : null
33 | var probabilityStr = colorProbability(probability, options)
34 | t.cell('success %', probabilityStr, markProbability)
35 | t.cell('successful updates', info.stats.success, Table.Number(0))
36 | t.cell('failed updates', info.stats.failure, Table.Number(0))
37 | }
38 | t.newRow()
39 | })
40 | console.log(t.toString())
41 | }
42 |
43 | module.exports = printModulesTable
44 |
--------------------------------------------------------------------------------
/src/registry-spec.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | const snapShot = require('snap-shot')
3 | const {filterFetchedVersions, hasVersions} = require('./registry')
4 | const check = require('check-more-types')
5 | const la = require('lazy-ass')
6 |
7 | /* global describe, it */
8 | describe('registry', () => {
9 | describe('filterFetchedVersions', () => {
10 | const foo = {
11 | name: 'foo',
12 | versions: ['1.0.0', '1.0.1']
13 | }
14 | const bar = {
15 | name: 'bar',
16 | versions: []
17 | }
18 | const baz = {
19 | name: 'baz',
20 | versions: ['3.0.0-alpha']
21 | }
22 | const available = [foo, bar, baz]
23 |
24 | it('detects empty array property', () => {
25 | la(check.array(bar.versions), 'has versions')
26 | la(!check.unempty(bar.versions), 'is empty', bar.versions)
27 | })
28 |
29 | it('hasVersions', () => {
30 | const filtered = available.filter(hasVersions)
31 | snapShot(filtered)
32 | })
33 |
34 | it('filters out empty versions', () => {
35 | const filtered = filterFetchedVersions(false, available)
36 | snapShot(filtered)
37 | })
38 |
39 | it('filters latest versions', () => {
40 | const filtered = filterFetchedVersions(true, available)
41 | snapShot(filtered)
42 | })
43 |
44 | it('filters out alpha releases', () => {
45 | // make sure to filter out alpha releases
46 | // after fetching
47 | // https://github.com/bahmutov/next-update/issues/106
48 | const filtered = filterFetchedVersions(false, available)
49 | snapShot(filtered)
50 | })
51 | })
52 | })
53 |
--------------------------------------------------------------------------------
/src/registry.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var la = require('lazy-ass')
4 | var check = require('check-more-types')
5 | var log = require('debug')('next-update')
6 | const R = require('ramda')
7 |
8 | var request = require('request')
9 | var verify = check.verify
10 | var semver = require('semver')
11 | var q = require('q')
12 | var localVersion = require('./local-module-version')
13 | var isUrl = require('npm-utils').isUrl
14 | var _ = require('lodash')
15 | const {isPrerelease} = require('./utils')
16 |
17 | var _registryUrl = require('npm-utils').registryUrl
18 | la(check.fn(_registryUrl), 'expected registry url function')
19 | var registryUrl = _.once(_registryUrl)
20 |
21 | const notPrerelease = R.complement(isPrerelease)
22 |
23 | function cleanVersion (version, name) {
24 | var originalVersion = version
25 | verify.unemptyString(version, 'missing version string' + version)
26 | verify.unemptyString(name, 'missing name string' + name)
27 |
28 | if (isUrl(version)) {
29 | // version = version.substr(version.indexOf('#') + 1);
30 |
31 | // hmm, because we don't have a way to fetch git tags yet
32 | // just skip these dependencies
33 | console.log('Cannot handle git repos, skipping', name, 'at', version)
34 | return
35 | }
36 | if (version === 'original' ||
37 | version === 'modified' ||
38 | version === 'created') {
39 | return
40 | }
41 |
42 | version = version.replace('~', '').replace('^', '')
43 | var twoDigitVersion = /^\d+\.\d+$/
44 | if (twoDigitVersion.test(version)) {
45 | version += '.0'
46 | }
47 | if (version === 'latest' || version === '*') {
48 | console.log('Module', name, 'uses version', version)
49 | console.log('It is recommented to list a specific version number')
50 | version = localVersion(name)
51 | if (!version) {
52 | version = '0.0.1'
53 | }
54 | console.log('module', name, 'local version', version)
55 | }
56 | try {
57 | version = semver.clean(version)
58 | } catch (err) {
59 | console.error('exception when cleaning version', version)
60 | return
61 | }
62 | if (!version) {
63 | log('could not clean version ' + originalVersion + ' for ' + name)
64 | return
65 | }
66 | console.assert(version, 'missing clean version ' + originalVersion + ' for ' + name)
67 | return version
68 | }
69 |
70 | // eslint-disable-next-line no-unused-vars
71 | function cleanVersionPair (nameVersion) {
72 | check.verify.array(nameVersion, 'expected an array')
73 | console.assert(nameVersion.length === 2,
74 | 'expected 2 items, name and version ' + nameVersion)
75 | var name = nameVersion[0]
76 | check.verify.string(name, 'could not get module name from ' + nameVersion)
77 |
78 | var version = nameVersion[1]
79 | check.verify.string(version, 'could not get module version from ' + nameVersion)
80 | version = cleanVersion(version, name)
81 | if (!version) {
82 | return
83 | }
84 |
85 | nameVersion[1] = version
86 | return nameVersion
87 | }
88 |
89 | function cleanVersionObject (info) {
90 | check.verify.object(info, 'expected info')
91 | var name = info.name
92 | check.verify.string(name, 'could not get module name from ' + info)
93 |
94 | var version = info.version
95 | check.verify.string(version, 'could not get module version from ' + info)
96 | version = cleanVersion(version, name)
97 |
98 | if (!version) {
99 | return
100 | }
101 |
102 | info.version = version
103 | return info
104 | }
105 |
106 | function cleanVersions (nameVersionPairs) {
107 | check.verify.array(nameVersionPairs, 'expected array')
108 | var cleaned = nameVersionPairs
109 | .map(cleanVersionObject)
110 | .filter(check.object)
111 | return cleaned
112 | }
113 |
114 | function formNpmErrorMessage (name, info) {
115 | var reason = info.reason || info.error || JSON.stringify(info)
116 | var str = 'ERROR in npm info for ' + name + ' reason ' + reason
117 | return str
118 | }
119 |
120 | function cleanVersionFor (name, version) {
121 | return cleanVersion(version, name)
122 | }
123 |
124 | function extractVersions (info) {
125 | if (info.time) {
126 | return Object.keys(info.time)
127 | }
128 | if (info.versions) {
129 | return Object.keys(info.versions)
130 | }
131 | }
132 |
133 | function is404 (response) {
134 | return response && response.statusCode === 404
135 | }
136 |
137 | function isNotFound (str) {
138 | var moduleNotFound = (/not found/).test(str)
139 | var cannotConnect = (/ENOTFOUND/).test(str)
140 | var errorInNpm = (/ERROR in npm/).test(str)
141 | var couldNotFetch = (/could not fetch/i).test(str)
142 | return moduleNotFound || cannotConnect || errorInNpm || couldNotFetch
143 | }
144 |
145 | // fetching versions inspired by
146 | // https://github.com/jprichardson/npm-latest
147 | // returns a promise
148 | function fetchVersions (options, nameVersion) {
149 | // console.log(nameVersion);
150 | // TODO use check.schema
151 | check.verify.object(nameVersion, 'expected name, version object')
152 | var name = nameVersion.name
153 | var version = nameVersion.version
154 | check.verify.string(name, 'missing name string')
155 | check.verify.string(version, 'missing version string')
156 |
157 | var cleanVersionForName = _.partial(cleanVersionFor, name)
158 | function isLaterVersion (ver) {
159 | var later = semver.gt(ver, version)
160 | return later
161 | }
162 |
163 | // console.log('fetching versions for', name, 'current version', version);
164 | var MAX_WAIT_TIMEOUT = options.checkVersionTimeout || 25000
165 | var deferred = q.defer()
166 |
167 | function rejectOnTimeout () {
168 | var msg = 'timed out waiting for NPM for package ' + name +
169 | ' after ' + MAX_WAIT_TIMEOUT + 'ms'
170 | console.error(msg)
171 | deferred.reject(msg)
172 | }
173 |
174 | function escapeName (str) {
175 | return str.replace('/', '%2F')
176 | }
177 |
178 | registryUrl().then(function (npmUrl) {
179 | log('NPM registry url', npmUrl)
180 | la(check.webUrl(npmUrl), 'need npm registry url, got', npmUrl)
181 |
182 | npmUrl = npmUrl.replace(/^https:/, 'http:').trim()
183 | var url = (options.registry || npmUrl) + escapeName(name)
184 |
185 | // TODO how to detect if the registry is not responding?
186 |
187 | log('getting url', url)
188 | request.get(url, onNPMversions)
189 | var timer = setTimeout(rejectOnTimeout, MAX_WAIT_TIMEOUT)
190 |
191 | function onNPMversions (err, response, body) {
192 | log('got response for', url)
193 | clearTimeout(timer)
194 |
195 | if (err) {
196 | console.error('ERROR when fetching info for package', name)
197 | deferred.reject(err.message)
198 | return
199 | }
200 |
201 | if (is404(response)) {
202 | log('404 response for', url)
203 | deferred.resolve({
204 | name: name,
205 | versions: []
206 | })
207 | return
208 | }
209 |
210 | try {
211 | log('parsing response body')
212 | var info = JSON.parse(body)
213 | log('parsed response, error?', info.error)
214 | if (info.error) {
215 | log('error parsing\n' + body + '\n')
216 | var str = formNpmErrorMessage(name, info)
217 | console.error(str)
218 |
219 | if (isNotFound(info.error)) {
220 | deferred.resolve({
221 | name: name,
222 | versions: []
223 | })
224 | return
225 | }
226 |
227 | deferred.reject(str)
228 | return
229 | }
230 | log('extracting versions')
231 | var versions = extractVersions(info)
232 | log('%d versions', versions.length)
233 |
234 | if (!Array.isArray(versions)) {
235 | throw new Error('Could not get versions for ' + name +
236 | ' from ' + JSON.stringify(info) +
237 | ' response ' + JSON.stringify(response, null, 2))
238 | }
239 |
240 | var validVersions = versions.filter(cleanVersionForName)
241 | var newerVersions = validVersions.filter(isLaterVersion)
242 | log('%d valid versions', validVersions.length)
243 | if (validVersions.length) {
244 | log('last version %s', R.last(validVersions))
245 | }
246 | if (newerVersions.length) {
247 | log('%d newer versions', newerVersions.length)
248 | log('from %s to %s', R.head(newerVersions), R.last(newerVersions))
249 | }
250 |
251 | deferred.resolve({
252 | name: name,
253 | versions: newerVersions
254 | })
255 | return
256 | } catch (err) {
257 | console.error(err)
258 | deferred.reject('Could not fetch versions for ' + name)
259 | }
260 | }
261 | })
262 |
263 | return deferred.promise
264 | }
265 |
266 | const verboseLog = (options) => options.tldr ? _.noop : console.log
267 |
268 | function logFetched (fetched) {
269 | fetched.forEach(individual => {
270 | log('%s - %d versions', individual.name, individual.versions.length)
271 | })
272 | }
273 |
274 | const hasVersions = nameNewVersions =>
275 | nameNewVersions &&
276 | check.array(nameNewVersions.versions) &&
277 | check.unempty(nameNewVersions.versions)
278 |
279 | const filterVersions = R.evolve({
280 | versions: R.filter(notPrerelease)
281 | })
282 |
283 | function filterFetchedVersions (checkLatestOnly, results) {
284 | la(arguments.length === 2, 'expected two arguments', arguments)
285 | checkLatestOnly = Boolean(checkLatestOnly)
286 | check.verify.array(results, 'expected list of results')
287 | log('fetch all new version results')
288 | logFetched(results)
289 |
290 | let available = results
291 | .map(filterVersions)
292 | .filter(hasVersions)
293 |
294 | if (checkLatestOnly) {
295 | available = available.map(function (nameVersions) {
296 | if (nameVersions.versions.length > 1) {
297 | nameVersions.versions = nameVersions.versions.slice(-1)
298 | }
299 | return nameVersions
300 | })
301 | } else {
302 | verboseLog('checking ALL versions')
303 | }
304 | return available
305 | }
306 |
307 | // returns a promise with available new versions
308 | function nextVersions (options, nameVersionPairs, checkLatestOnly) {
309 | check.verify.object(options, 'expected object with options')
310 | check.verify.array(nameVersionPairs, 'expected array')
311 | nameVersionPairs = cleanVersions(nameVersionPairs)
312 |
313 | const verbose = verboseLog(options)
314 | verbose('checking NPM registry')
315 | var MAX_CHECK_TIMEOUT = options.checkVersionTimeout || 10000
316 |
317 | var fetchPromises = nameVersionPairs.map(fetchVersions.bind(null, options))
318 | var fetchAllPromise = q.all(fetchPromises)
319 | .timeout(MAX_CHECK_TIMEOUT, 'timed out waiting for NPM after ' + MAX_CHECK_TIMEOUT + 'ms')
320 |
321 | return fetchAllPromise.then(
322 | _.partial(filterFetchedVersions, checkLatestOnly),
323 | q.reject
324 | )
325 | }
326 |
327 | module.exports = {
328 | isUrl,
329 | cleanVersion,
330 | cleanVersions,
331 | fetchVersions,
332 | nextVersions,
333 | filterFetchedVersions,
334 | hasVersions
335 | }
336 |
--------------------------------------------------------------------------------
/src/report-available.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var la = require('lazy-ass')
4 | var check = require('check-more-types')
5 | var log = require('debug')('next-update')
6 |
7 | require('console.json')
8 | var print = require('./print-modules-table')
9 | var stats = require('./stats')
10 | var clc = require('cli-color')
11 | var getSuccess = stats.getSuccessStats
12 | var q = require('q')
13 |
14 | function ignore () {}
15 |
16 | function reportAvailable (available, currentVersions, options) {
17 | la(check.array(available), 'expect an array of info objects', available)
18 |
19 | if (!available.length) {
20 | console.log('nothing new is available')
21 | return []
22 | }
23 |
24 | log('report available')
25 | log(available)
26 | log('current versions')
27 | log(JSON.stringify(currentVersions))
28 |
29 | la(check.maybe.object(currentVersions),
30 | 'expected current versions as an object, but was', currentVersions)
31 |
32 | function getCurrentVersion (name) {
33 | la(check.unemptyString(name), 'missing name')
34 | if (!currentVersions) {
35 | return
36 | }
37 | if (check.string(currentVersions[name])) {
38 | return currentVersions[name]
39 | }
40 | if (check.object(currentVersions[name])) {
41 | la(check.string(currentVersions[name].version),
42 | 'missing version for', name, 'in', currentVersions)
43 | return currentVersions[name].version
44 | }
45 | }
46 |
47 | var chain = q()
48 | var updateStats = {}
49 |
50 | available.forEach(function (info) {
51 | la(check.unemptyString(info.name), 'missing module name', info)
52 | la(check.array(info.versions), 'missing module versions', info)
53 |
54 | var currentVersion = getCurrentVersion(info.name)
55 | log('version for', info.name, currentVersion)
56 |
57 | if (currentVersion) {
58 | la(check.unemptyString(currentVersion),
59 | 'missing version', currentVersion, 'for', info.name)
60 | updateStats[info.name] = {
61 | name: info.name,
62 | from: currentVersion
63 | }
64 | }
65 |
66 | if (info.versions.length === 1) {
67 | if (currentVersion) {
68 | chain = chain.then(function () {
69 | return getSuccess({
70 | name: info.name,
71 | from: currentVersion,
72 | to: info.versions[0]
73 | }).then(function (stats) {
74 | updateStats[info.name] = stats
75 | }).catch(ignore)
76 | })
77 | }
78 | }
79 | })
80 |
81 | return chain.then(function () {
82 | var modules = []
83 |
84 | available.forEach(function (info) {
85 | la(check.unemptyString(info.name), 'missing module name', info)
86 | la(check.array(info.versions), 'missing module versions', info)
87 |
88 | var sep = ', '
89 | var versions
90 |
91 | if (info.versions.length < 5) {
92 | versions = info.versions.join(sep)
93 | } else {
94 | versions = info.versions.slice(0, 2)
95 | .concat('...')
96 | .concat(info.versions.slice(info.versions.length - 2))
97 | .join(sep)
98 | }
99 |
100 | var stats = updateStats[info.name]
101 | if (!stats) {
102 | return
103 | }
104 | la(check.object(stats), 'could not get stats for', info.name,
105 | 'all', updateStats, 'available stats', info)
106 | modules.push({
107 | name: info.name,
108 | version: versions,
109 | from: stats.from,
110 | stats: stats
111 | })
112 | })
113 | console.log('\navailable updates:')
114 | if (modules.length) {
115 | print(modules, options)
116 | console.log('update stats from', clc.underline(stats.url))
117 | } else {
118 | console.log(available)
119 | console.log('no stats is available yet for these updates')
120 | }
121 | return available
122 | })
123 | }
124 |
125 | module.exports = reportAvailable
126 |
--------------------------------------------------------------------------------
/src/report-install-command.js:
--------------------------------------------------------------------------------
1 | var check = require('check-more-types')
2 |
3 | function loadPackage () {
4 | var filename = './package.json'
5 | var fs = require('fs')
6 | if (fs.existsSync(filename)) {
7 | var txt = fs.readFileSync(filename, 'utf-8')
8 | if (txt) {
9 | return JSON.parse(txt)
10 | }
11 | }
12 | }
13 |
14 | function saveOption (type) {
15 | var saveCommands = {
16 | dependencies: '--save --save-exact',
17 | devDependencies: '--save-dev --save-exact',
18 | peerDependencies: '--save-peer --save-exact'
19 | }
20 | return saveCommands[type]
21 | }
22 |
23 | function splitByType (updates, pkg) {
24 | check.verify.array(updates, 'expected array of updates')
25 | check.verify.object(pkg, 'expected package object')
26 |
27 | var result = {
28 | dependencies: [],
29 | devDependencies: [],
30 | peerDependencies: []
31 | }
32 |
33 | updates.forEach(function (moduleList) {
34 | if (!moduleList.length) {
35 | return
36 | }
37 | var moduleName = moduleList[0].name
38 | check.verify.string(moduleName, 'missing module name')
39 | if (pkg.dependencies && pkg.dependencies[moduleName]) {
40 | result.dependencies.push(moduleList)
41 | } else if (pkg.devDependencies && pkg.devDependencies[moduleName]) {
42 | result.devDependencies.push(moduleList)
43 | } else if (pkg.peerDependencies && pkg.peerDependencies[moduleName]) {
44 | result.peerDependencies.push(moduleList)
45 | } else {
46 | console.warn('Could not find dependency for', moduleName, 'assuming normal')
47 | result.dependencies.push(moduleList)
48 | }
49 | })
50 | return result
51 | }
52 |
53 | function installCommand (updates) {
54 | check.verify.array(updates, 'expected array of updates')
55 | if (!updates.length) {
56 | return
57 | }
58 |
59 | var cmd = ''
60 | var pkg = loadPackage()
61 | if (!pkg) {
62 | // assume all dependencies are normal
63 | cmd = installCommandType(updates, 'dependencies')
64 | } else {
65 | var updatesByDependencyType = splitByType(updates, pkg)
66 | console.assert(updatesByDependencyType, 'could not split updates by type')
67 |
68 | // console.log(updatesByDependencyType)
69 |
70 | var depCmd = installCommandType(updatesByDependencyType.dependencies, 'dependencies')
71 | var devCmd = installCommandType(updatesByDependencyType.devDependencies, 'devDependencies')
72 | var peerCmd = installCommandType(updatesByDependencyType.peerDependencies,
73 | 'peerDependencies')
74 | if (depCmd) {
75 | cmd += depCmd + '\n'
76 | }
77 | if (devCmd) {
78 | cmd += devCmd + '\n'
79 | }
80 | if (peerCmd) {
81 | cmd += peerCmd + '\n'
82 | }
83 | }
84 | if (cmd) {
85 | return cmd
86 | }
87 | }
88 |
89 | function getLatestWorkingVersion (versions) {
90 | check.verify.array(versions, 'expected array of versions')
91 | var working
92 | versions.forEach(function (info) {
93 | check.verify.string(info.name, 'missing package name ' + info)
94 | if (info.works) {
95 | working = info
96 | }
97 | })
98 |
99 | return working
100 | }
101 |
102 | function setWorkingVersion (updates) {
103 | updates.forEach(function (moduleVersions) {
104 | moduleVersions.latestWorkingVersion = getLatestWorkingVersion(moduleVersions)
105 | })
106 | }
107 |
108 | function installCommandType (updates, type) {
109 | check.verify.array(updates, 'expected array of updates')
110 | if (!updates.length) {
111 | return
112 | }
113 | check.verify.string(type, 'missing type')
114 |
115 | var saveCommand = saveOption(type)
116 | if (!saveCommand) {
117 | throw new Error('invalid dependency type ' + type)
118 | }
119 |
120 | setWorkingVersion(updates)
121 |
122 | var originalCmd, cmd
123 | originalCmd = cmd = 'npm install ' + saveCommand
124 | updates.forEach(function (moduleVersions) {
125 | var latestWorkingVersion = moduleVersions.latestWorkingVersion
126 | if (latestWorkingVersion) {
127 | cmd += ' ' + latestWorkingVersion.name + '@' + latestWorkingVersion.version
128 | }
129 | })
130 |
131 | if (originalCmd === cmd) {
132 | return
133 | }
134 |
135 | return cmd
136 | }
137 |
138 | module.exports = installCommand
139 |
--------------------------------------------------------------------------------
/src/report.js:
--------------------------------------------------------------------------------
1 | var colors = require('cli-color')
2 | var check = require('check-more-types')
3 | var formInstallCommand = require('./report-install-command')
4 | var _ = require('lodash')
5 | var changedLog = require('changed-log')
6 | var Q = require('q')
7 |
8 | var colorAvailable = process.stdout.isTTY
9 |
10 | function report (updates, options) {
11 | options = options || {}
12 | var useColors = Boolean(options.useColors) && colorAvailable
13 | check.verify.array(updates, 'expected array of updates')
14 | // sets latest working version for each module too
15 | var cmd = formInstallCommand(updates)
16 |
17 | console.log('\n> next updates:')
18 | updates.forEach(function (moduleVersions) {
19 | reportModule(moduleVersions, useColors)
20 | })
21 |
22 | var reportChanges = updates.map(function (moduleVersions) {
23 | return _.partial(printChangedLog, moduleVersions, useColors)
24 | })
25 |
26 | function printInstallCommand () {
27 | if (_.isUndefined(cmd)) {
28 | console.log('> nothing can be updated :(')
29 | } else {
30 | if (options.keptUpdates) {
31 | console.log('> kept working updates')
32 | } else {
33 | cmd = cmd.trim()
34 | var lines = cmd.split('\n').length
35 | if (lines === 1) {
36 | console.log('> use the following command to install working versions')
37 | } else {
38 | console.log('> use the following commands to install working versions')
39 | }
40 | console.log(cmd)
41 | }
42 | }
43 | }
44 |
45 | function printError (err) {
46 | console.error('Error reporting changes')
47 | console.error(err.message)
48 | }
49 | var start = options.changedLog
50 | ? reportChanges.reduce(Q.when, Q()).catch(printError)
51 | : Q()
52 | return start.then(printInstallCommand)
53 | }
54 |
55 | function reportModule (moduleVersions, useColors) {
56 | check.verify.array(moduleVersions, 'expected module / versions array')
57 | if (!moduleVersions.length) {
58 | return
59 | }
60 | var name = moduleVersions[0].name
61 | check.verify.unemptyString(name, 'missing module name from ' + JSON.stringify(moduleVersions))
62 | var fromVersion = moduleVersions[0].from
63 | check.verify.unemptyString(fromVersion, 'missing from version from ' + JSON.stringify(moduleVersions))
64 |
65 | if (useColors) {
66 | var colorVersions = moduleVersions.map(function (info) {
67 | return (info.works ? colors.greenBright : colors.redBright)(info.version)
68 | })
69 | var str = colorVersions.join(', ')
70 | console.log(name + ' ' + fromVersion + ' -> ' + str)
71 | } else {
72 | console.log(name + '@' + fromVersion)
73 | moduleVersions.forEach(function (info) {
74 | console.log(' ' + info.version + ' ' + (info.works ? 'PASS' : 'FAIL'))
75 | })
76 | }
77 | }
78 |
79 | function printChangedLog (moduleVersions, useColors) {
80 | var info = moduleVersions[0]
81 |
82 | if (!info.works) {
83 | return
84 | }
85 | return changedLog({
86 | name: info.name,
87 | from: info.from,
88 | to: info.version
89 | })
90 | }
91 |
92 | function reportSuccess (text, useColors) {
93 | if (colorAvailable && useColors) {
94 | console.log(colors.greenBright(text))
95 | } else {
96 | console.log('PASS', text)
97 | }
98 | }
99 |
100 | function reportFailure (text, useColors) {
101 | if (colorAvailable && useColors) {
102 | console.log(colors.redBright(text))
103 | } else {
104 | console.log('FAIL', text)
105 | }
106 | }
107 |
108 | module.exports = {
109 | report: report,
110 | reportSuccess: reportSuccess,
111 | reportFailure: reportFailure
112 | }
113 |
--------------------------------------------------------------------------------
/src/revert.js:
--------------------------------------------------------------------------------
1 | const debug = require('debug')('next-update')
2 | const is = require('check-more-types')
3 | const la = require('lazy-ass')
4 | var getDependenciesToCheck = require('./dependencies')
5 | var installModule = require('./module-install')
6 | var q = require('q')
7 |
8 | const isRevertInfo = is.schema({
9 | name: is.unemptyString,
10 | version: is.unemptyString
11 | })
12 |
13 | // returns promise
14 | function revert (moduleName) {
15 | if (moduleName) {
16 | console.log('reverting module', JSON.stringify(moduleName))
17 | }
18 |
19 | var toCheck = getDependenciesToCheck({}, moduleName)
20 | debug('need to check')
21 | debug(toCheck)
22 |
23 | var installPromises = toCheck.map(function (info) {
24 | la(isRevertInfo(info), 'invalid revert info', info)
25 | return installModule.bind(null, {
26 | name: info.name,
27 | version: info.version,
28 | tldr: false
29 | })
30 | })
31 | return installPromises.reduce(q.when, q())
32 | }
33 |
34 | module.exports = revert
35 |
--------------------------------------------------------------------------------
/src/stats-spec.js:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | const snapShot = require('snap-shot')
3 |
4 | /* global describe, it */
5 | describe('stats', () => {
6 | describe('formatStats', () => {
7 | const {formatStats} = require('./stats')
8 | it('formats stats message', () => {
9 | const message = formatStats({}, {
10 | name: 'foo',
11 | from: '1.0.0',
12 | to: '2.0.0',
13 | success: 5,
14 | failure: 5
15 | })
16 | snapShot(message)
17 | })
18 |
19 | it('formats stats message with singular failure', () => {
20 | const message = formatStats({}, {
21 | name: 'foo',
22 | from: '1.0.0',
23 | to: '2.0.0',
24 | success: 1,
25 | failure: 1
26 | })
27 | snapShot(message)
28 | })
29 | })
30 | })
31 |
--------------------------------------------------------------------------------
/src/stats.js:
--------------------------------------------------------------------------------
1 | var verify = require('check-more-types').verify
2 | var request = require('request')
3 | var Q = require('q')
4 | var colors = require('cli-color')
5 | var colorAvailable = process.stdout.isTTY
6 | const pluralize = require('pluralize')
7 | const {oneLine} = require('common-tags')
8 |
9 | var nextUpdateStatsUrl = require('../package.json')['next-update-stats'] ||
10 | 'http://next-update.herokuapp.com'
11 |
12 | function sendUpdateResult (options) {
13 | verify.unemptyString(options.name, 'missing name')
14 | verify.unemptyString(options.from, 'missing from version')
15 | verify.unemptyString(options.to, 'missing to version')
16 | if (options.success) {
17 | options.success = !!options.success
18 | }
19 |
20 | verify.webUrl(nextUpdateStatsUrl, 'missing next update stats server url')
21 | var sendOptions = {
22 | uri: nextUpdateStatsUrl + '/update',
23 | method: 'POST',
24 | json: options
25 | }
26 | request(sendOptions, function ignoreResponse () {})
27 | }
28 |
29 | function getSuccessStats (options) {
30 | verify.unemptyString(options.name, 'missing name')
31 | verify.unemptyString(options.from, 'missing from version')
32 | verify.unemptyString(options.to, 'missing to version')
33 |
34 | verify.webUrl(nextUpdateStatsUrl, 'missing next update stats server url')
35 | var opts = {
36 | uri: nextUpdateStatsUrl + '/package/' + options.name + '/' + options.from +
37 | '/' + options.to,
38 | method: 'GET',
39 | json: options
40 | }
41 | var defer = Q.defer()
42 | request(opts, function (err, response, stats) {
43 | if (err || response.statusCode !== 200) {
44 | if (response) {
45 | if (response.statusCode !== 404) {
46 | console.error('could not get success stats for', options.name)
47 | console.error(opts)
48 | }
49 | console.error('response status:', response.statusCode)
50 | }
51 | if (err) {
52 | console.error(err)
53 | }
54 | defer.reject(err)
55 | return
56 | }
57 | defer.resolve(stats)
58 | })
59 | return defer.promise
60 | }
61 |
62 | function colorProbability (probability, options) {
63 | if (probability === null) {
64 | return ''
65 | }
66 |
67 | options = options || {}
68 | var useColors = !!options.color && colorAvailable
69 | if (probability < 0 || probability > 1) {
70 | throw new Error('Expected probability between 0 and 1, not ' + probability)
71 | }
72 | var probabilityStr = (probability * 100).toFixed(0) + '%'
73 | if (useColors) {
74 | if (probability > 0.8) {
75 | probabilityStr = colors.greenBright(probabilityStr)
76 | } else if (probability > 0.5) {
77 | probabilityStr = colors.yellowBright(probabilityStr)
78 | } else {
79 | probabilityStr = colors.redBright(probabilityStr)
80 | }
81 | }
82 | return probabilityStr
83 | }
84 |
85 | function formatStats (options, stats) {
86 | verify.object(stats, 'missing stats object')
87 | verify.unemptyString(stats.name, 'missing name')
88 | verify.unemptyString(stats.from, 'missing from version')
89 | verify.unemptyString(stats.to, 'missing to version')
90 | stats.success = +stats.success || 0
91 | stats.failure = +stats.failure || 0
92 | var total = stats.success + stats.failure
93 | var probability = (total > 0 ? stats.success / total : 0)
94 | var probabilityStr = colorProbability(probability, options)
95 | return oneLine`
96 | stats: ${stats.name} ${stats.from} -> ${stats.to}
97 | success probability ${probabilityStr}
98 | ${stats.success} ${pluralize('success', stats.success)}
99 | ${stats.failure} ${pluralize('failure', stats.failure)}
100 | `
101 | }
102 |
103 | function printStats (options, stats) {
104 | const message = formatStats(options, stats)
105 | console.log(message)
106 | }
107 |
108 | module.exports = {
109 | sendUpdateResult: sendUpdateResult,
110 | getSuccessStats: getSuccessStats,
111 | formatStats: formatStats,
112 | printStats: printStats,
113 | colorProbability: colorProbability,
114 | url: nextUpdateStatsUrl
115 | }
116 |
--------------------------------------------------------------------------------
/src/test-module-version.js:
--------------------------------------------------------------------------------
1 | var la = require('lazy-ass')
2 | var check = require('check-more-types')
3 | var verify = check.verify
4 | var q = require('q')
5 | var _ = require('lodash')
6 | var semver = require('semver')
7 | var quote = require('quote')
8 | var installModule = require('./module-install')
9 | var reportSuccess = require('./report').reportSuccess
10 | var reportFailure = require('./report').reportFailure
11 | const {getTestCommand} = require('./utils')
12 | const path = require('path')
13 | const debug = require('debug')('next-update')
14 | var stats = require('./stats')
15 |
16 | var cleanVersions = require('./registry').cleanVersions
17 | check.verify.fn(cleanVersions, 'cleanVersions should be a function')
18 |
19 | var revertModules = require('./revert')
20 | check.verify.fn(revertModules, 'revert is not a function, but ' +
21 | JSON.stringify(revertModules))
22 |
23 | var npmTest = require('./npm-test').test
24 | var execTest = require('./exec-test')
25 | var report = require('./report-available')
26 | var filterAllowed = require('./filter-allowed-updates')
27 |
28 | // expect array of objects, each {name, versions (Array) }
29 | // returns promise
30 | function testModulesVersions (options, available) {
31 | verify.object(options, 'missing options')
32 | verify.array(available, 'expected array of available modules')
33 |
34 | var cleaned = cleanVersions(options.modules)
35 | // console.log('cleaned', cleaned);
36 | // var listed = _.zipObject(cleaned);
37 | var names = _.pluck(cleaned, 'name')
38 | var listed = _.zipObject(names, cleaned)
39 |
40 | /*
41 | console.log('testing module versions');
42 | console.log('current versions', listed);
43 | console.log('options', options);
44 | console.log('available', available);
45 | */
46 |
47 | var allowed = filterAllowed(listed, available, options)
48 | la(check.array(allowed), 'could not filter allowed updates', listed, available, options)
49 |
50 | if (available.length && !allowed.length) {
51 | console.log('No updates allowed using option', quote(options.allow || options.allowed))
52 | console.log(available.length + ' available updates filtered')
53 | return q(listed)
54 | }
55 |
56 | // console.log('allowed', allowed);
57 | return q.when(report(allowed, listed, options))
58 | .then(function testInstalls () {
59 | // console.log('testing installs');
60 | if (options.all) {
61 | var install = installAll(allowed, options)
62 | console.assert(install, 'could not get install all promise')
63 | var test = testPromise(options, options.command)
64 | console.assert(test, 'could not get test promise for command', options.command)
65 | // console.dir(listed);
66 | // console.dir(options.modules);
67 |
68 | var installThenTest = install.then(test)
69 | if (options.keep) {
70 | return installThenTest
71 | }
72 |
73 | var revert = revertModules.bind(null, listed)
74 | console.assert(revert, 'could not get revert promise')
75 | return installThenTest.then(revert)
76 | }
77 |
78 | return installEachTestRevert(listed, allowed,
79 | options.command, options.color, options.keep, options.tldr)
80 | })
81 | }
82 |
83 | // returns promise, does not revert
84 | function installAll (available, options) {
85 | verify.array(available, 'expected array')
86 |
87 | var installFunctions = available.map(function (nameVersions) {
88 | var name = nameVersions.name
89 | var version = nameVersions.versions[0]
90 | verify.string(name, 'missing module name from ' +
91 | JSON.stringify(nameVersions))
92 | verify.string(version, 'missing module version from ' +
93 | JSON.stringify(nameVersions))
94 |
95 | var installOptions = {
96 | name: name,
97 | version: version,
98 | keep: options.keep,
99 | tldr: options.tldr
100 | }
101 | var installFunction = installModule.bind(null, installOptions)
102 | return installFunction
103 | })
104 | var installAllPromise = installFunctions.reduce(q.when, q())
105 | return installAllPromise
106 | }
107 |
108 | function installEachTestRevert (listed, available, command, color, keep, tldr) {
109 | verify.object(listed, 'expected listed object')
110 | verify.array(available, 'expected array')
111 |
112 | const packageFilename = path.resolve('./package.json')
113 | const getCommand = _.partial(getTestCommand, packageFilename)
114 |
115 | var checkModulesFunctions = available.map(function (nameVersion) {
116 | var name = nameVersion.name
117 | la(check.unemptyString(name), 'missing name', nameVersion)
118 | var currentVersion = listed[name].version
119 | la(check.string(currentVersion), 'cannot find current version for', name,
120 | 'among current dependencies', listed)
121 |
122 | var installOptions = {
123 | name: name,
124 | version: currentVersion,
125 | keep: keep,
126 | tldr: tldr
127 | }
128 | var revertFunction = installModule.bind(null, installOptions)
129 |
130 | const testCommand = getCommand(name) || command
131 | debug('module %s should use test command "%s"', name, testCommand)
132 | var checkModuleFunction = testModuleVersions.bind(null, {
133 | moduleVersions: nameVersion,
134 | revertFunction: revertFunction,
135 | command: testCommand,
136 | color: color,
137 | currentVersion: currentVersion,
138 | keep: keep,
139 | tldr: tldr
140 | })
141 | return checkModuleFunction
142 | })
143 | var checkAllPromise = checkModulesFunctions.reduce(q.when, q())
144 | return checkAllPromise
145 | }
146 |
147 | // test particular dependency with multiple versions
148 | // returns promise
149 | function testModuleVersions (options, results) {
150 | verify.object(options, 'missing options')
151 | var nameVersions = options.moduleVersions
152 | var restoreVersionFunc = options.revertFunction
153 |
154 | var name = nameVersions.name
155 | var versions = nameVersions.versions
156 | verify.string(name, 'expected name string')
157 | verify.array(versions, 'expected versions array')
158 | results = results || []
159 | verify.array(results, 'expected results array')
160 | if (!semver.valid(options.currentVersion)) {
161 | throw new Error('do not have current version for ' + name)
162 | }
163 |
164 | var deferred = q.defer()
165 | var checkPromises = versions.map(function (version) {
166 | return testModuleVersion.bind(null, {
167 | name: name,
168 | version: version,
169 | command: options.command,
170 | color: options.color,
171 | currentVersion: options.currentVersion,
172 | tldr: options.tldr
173 | })
174 | })
175 | var checkAllPromise = checkPromises.reduce(q.when, q())
176 | if (options.keep) {
177 | debug('keep working updates for %s', name)
178 | checkAllPromise = checkAllPromise.then(function (result) {
179 | verify.array(result, 'expected array of results', result)
180 | var lastSuccess = _.last(_.filter(result, { works: true }))
181 | if (lastSuccess) {
182 | console.log('keeping last working version', lastSuccess.name + '@' + lastSuccess.version)
183 | return installModule({
184 | name: lastSuccess.name,
185 | version: lastSuccess.version,
186 | keep: true,
187 | tldr: options.tldr
188 | }, result)
189 | } else {
190 | return restoreVersionFunc().then(function () {
191 | // console.log('returning result after reverting', result);
192 | return q(result)
193 | })
194 | }
195 | })
196 | } else {
197 | checkAllPromise = checkAllPromise
198 | .then(restoreVersionFunc, (err) => {
199 | console.error('Could not check all versions')
200 | console.error(err)
201 | throw err
202 | })
203 | }
204 | checkAllPromise
205 | .then(function (result) {
206 | debug('got result')
207 | debug(result)
208 | check.verify.array(result, 'could not get result array')
209 | results.push(result)
210 | deferred.resolve(results)
211 | }, function (error) {
212 | console.error('could not check', nameVersions, error)
213 | deferred.reject(error)
214 | })
215 |
216 | return deferred.promise
217 | }
218 |
219 | var logLine = (function formLine () {
220 | var n = process.stdout.isTTY ? process.stdout.columns : 40
221 | n = n || 40
222 | verify.positiveNumber(n, 'expected to get terminal width, got ' + n)
223 | var k
224 | var str = ''
225 | for (k = 0; k < n; k += 1) {
226 | str += '-'
227 | }
228 | return function () {
229 | console.log(str)
230 | }
231 | }())
232 |
233 | // checks specific module@version
234 | // returns promise
235 | function testModuleVersion (options, results) {
236 | verify.object(options, 'missing test module options')
237 | verify.string(options.name, 'missing module name')
238 | verify.string(options.version, 'missing version string')
239 | verify.unemptyString(options.currentVersion, 'missing current version')
240 |
241 | if (options.command) {
242 | verify.string(options.command, 'expected command string')
243 | }
244 | // console.log('options', options);
245 |
246 | results = results || []
247 | verify.array(results, 'missing previous results array')
248 |
249 | var nameVersion = options.name + '@' + options.version
250 |
251 | if (!options.tldr) {
252 | console.log('\ntesting', nameVersion)
253 | }
254 |
255 | var result = {
256 | name: options.name,
257 | version: options.version,
258 | from: options.currentVersion,
259 | works: true
260 | }
261 |
262 | var test = testPromise(options, options.command)
263 | console.assert(test, 'could not get test promise for command', options.command)
264 |
265 | var deferred = q.defer()
266 |
267 | var getSuccess = stats.getSuccessStats({
268 | name: options.name,
269 | from: options.currentVersion,
270 | to: options.version
271 | })
272 |
273 | getSuccess
274 | .then(stats.printStats.bind(null, options), function () {
275 | console.log('could not get update stats', options.name)
276 | })
277 | .then(function () {
278 | return installModule({
279 | name: options.name,
280 | version: options.version,
281 | keep: false,
282 | tldr: options.tldr
283 | })
284 | })
285 | .then(test)
286 | .then(function () {
287 | reportSuccess(nameVersion + ' works', options.color)
288 |
289 | stats.sendUpdateResult({
290 | name: options.name,
291 | from: options.currentVersion,
292 | to: options.version,
293 | success: true
294 | })
295 | results.push(result)
296 | deferred.resolve(results)
297 | }, function (error) {
298 | reportFailure(nameVersion + ' tests failed :(', options.color)
299 |
300 | debug('sending stats results')
301 | stats.sendUpdateResult({
302 | name: options.name,
303 | from: options.currentVersion,
304 | to: options.version,
305 | success: false
306 | })
307 |
308 | debug('checking error code', error.code)
309 | verify.number(error.code, 'expected code in error ' +
310 | JSON.stringify(error, null, 2))
311 |
312 | var horizontalLine = options.tldr ? _.noop : logLine
313 |
314 | horizontalLine()
315 | if (!options.tldr) {
316 | console.error('test finished with exit code', error.code)
317 | verify.string(error.errors, 'expected errors string in error ' +
318 | JSON.stringify(error, null, 2))
319 | console.error(error.errors)
320 | }
321 |
322 | horizontalLine()
323 |
324 | result.works = false
325 | results.push(result)
326 | deferred.resolve(results)
327 | })
328 | return deferred.promise
329 | }
330 |
331 | function testPromise (options, command) {
332 | var testFunction = npmTest.bind(null, options)
333 | if (command) {
334 | verify.unemptyString(command, 'expected string command, not ' + command)
335 | testFunction = execTest.bind(null, options, command)
336 | } else {
337 | debug('missing test command')
338 | }
339 | return testFunction
340 | }
341 |
342 | module.exports = {
343 | testModulesVersions: testModulesVersions,
344 | testModuleVersion: testModuleVersion,
345 | testPromise: testPromise
346 | }
347 |
--------------------------------------------------------------------------------
/src/test/all-local-deps.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "foo": "0.0.1"
4 | },
5 | "devDependencies": {
6 | "bar": "~0.2.2"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/exec-test.coffee:
--------------------------------------------------------------------------------
1 | gt.module 'exec test',
2 | setup: ->
3 | @dir = process.cwd()
4 | process.chdir __dirname
5 | console.log 'changed directory to', __dirname
6 | teardown: ->
7 | console.log 'restoring dir', @dir
8 | process.chdir @dir
9 |
10 | test = require '../exec-test'
11 | npmPath = (require '../npm-test').npmPath
12 |
13 | ONE_MINUTE = 60000
14 |
15 | onError = (error) -> throw new Error(error)
16 |
17 | gt.test 'basics', ->
18 | gt.arity test, 2
19 |
20 | gt.async 'using npm test command', ->
21 | gt.string npmPath, 'has npm path'
22 | promise = test {}, npmPath + ' test'
23 | gt.object promise
24 | promise.then ->
25 | gt.ok false, 'there should not be npm test'
26 | .catch ->
27 | console.log 'Failed as expected'
28 | gt.ok true, 'failed as expected'
29 | .finally ->
30 | console.log 'finished test'
31 | gt.start()
32 | , ONE_MINUTE
33 |
34 | gt.async 'auto switch to npm test command', ->
35 | promise = test {}, 'npm test'
36 | gt.object promise
37 | promise.then ->
38 | gt.ok false, 'there should not be npm test'
39 | .catch ->
40 | console.log 'failed as expected'
41 | gt.ok true, 'failed as expected'
42 | .finally ->
43 | console.log 'finished test'
44 | gt.start()
45 | , ONE_MINUTE
46 |
--------------------------------------------------------------------------------
/src/test/get-known-dependencies.coffee:
--------------------------------------------------------------------------------
1 | gt.module 'handling npmrg vs git urls'
2 |
3 | path = require 'path'
4 | getDeps = require '../get-known-dependencies'
5 |
6 | gt.test 'package with all npmrg', ->
7 | deps = getDeps path.join(__dirname, 'all-local-deps.json')
8 | gt.array deps, 'returns an array'
9 | gt.equal deps.length, 2, 'contains two items'
10 |
11 | gt.equal deps[0].name, 'foo', 'first item\'s name'
12 | gt.equal deps[0].version, '0.0.1', 'first item\'s version'
13 | gt.equal deps[0].type, 'prod'
14 |
15 | gt.equal deps[1].name, 'bar', 'second item\'s name'
16 | gt.equal deps[1].version, '0.2.2', 'second item\'s version'
17 |
18 | gt.test 'package with urls', ->
19 | deps = getDeps path.join(__dirname, 'git-url-deps.json')
20 | gt.array deps, 'returns an array'
21 | console.dir deps;
22 | gt.equal deps.length, 1, 'contains single item'
23 | gt.equal deps[0].name, 'bar', 'public item name'
24 | gt.equal deps[0].version, '0.2.2', 'public item version'
25 |
26 |
--------------------------------------------------------------------------------
/src/test/git-url-deps.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "foo": "https://github.com/bahmutov/qunit-promises.git#0.0.7"
4 | },
5 | "devDependencies": {
6 | "bar": "~0.2.2"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/test/local-module-version.coffee:
--------------------------------------------------------------------------------
1 | gt.module 'npm test',
2 | setup: ->
3 | @dir = process.cwd()
4 | process.chdir __dirname
5 | console.log 'changed directory to', __dirname
6 | teardown: ->
7 | console.log 'restoring dir', @dir
8 | process.chdir @dir
9 |
10 | localVersion = require '../local-module-version'
11 |
12 | gt.test 'basics', ->
13 | gt.arity localVersion, 1, 'expects single argument'
14 |
15 | gt.test 'fetch local version', ->
16 | version = localVersion 'sample-module'
17 | console.log version
18 | gt.string version, 'got back a string'
19 | gt.equal version, '1.0.8', 'got back correct value'
--------------------------------------------------------------------------------
/src/test/moduleName.coffee:
--------------------------------------------------------------------------------
1 | moduleName = require '../moduleName'
2 |
3 | gt.module 'module name parsing'
4 |
5 | gt.test 'basic', ->
6 | gt.arity moduleName, 1
7 |
8 | gt.test 'just name', ->
9 | nv = moduleName 'lodash'
10 | gt.object nv, 'returns an object'
11 | gt.equal nv.name, 'lodash'
12 | gt.undefined nv.version, 'has no version'
13 |
14 | gt.test 'name with dashes', ->
15 | nv = moduleName 'lodash-one'
16 | gt.object nv, 'returns an object'
17 | gt.equal nv.name, 'lodash-one'
18 | gt.undefined nv.version, 'has no version'
19 |
20 | gt.test 'name with dots', ->
21 | nv = moduleName 'lodash.one'
22 | gt.object nv, 'returns an object'
23 | gt.equal nv.name, 'lodash.one'
24 | gt.undefined nv.version, 'has no version'
25 |
26 | gt.test 'name@version', ->
27 | nv = moduleName 'lodash@1'
28 | gt.object nv, 'returns an object'
29 | gt.equal nv.name, 'lodash'
30 | gt.equal nv.version, '1', 'has correct version'
31 |
32 | gt.test 'name@version 3 digits', ->
33 | nv = moduleName 'lodash@1.0.1'
34 | gt.object nv, 'returns an object'
35 | gt.equal nv.name, 'lodash'
36 | gt.equal nv.version, '1.0.1', 'has correct version'
37 |
38 | gt.test 'name@version 3 digits + rc', ->
39 | nv = moduleName 'lodash@1.0.1-rc1'
40 | gt.object nv, 'returns an object'
41 | gt.equal nv.name, 'lodash'
42 | gt.equal nv.version, '1.0.1-rc1', 'has correct version'
43 |
44 | gt.test 'scoped name @author/name', ->
45 | nv = moduleName '@bahmutov/csv'
46 | gt.object nv, 'returns an object'
47 | gt.equal nv.name, '@bahmutov/csv'
48 | gt.undefined nv.version, 'has no version'
49 |
50 | gt.test 'scoped name @author/name with version', ->
51 | nv = moduleName '@bahmutov/csv@1.1.0'
52 | gt.object nv, 'returns an object'
53 | gt.equal nv.name, '@bahmutov/csv'
54 | gt.equal nv.version, '1.1.0'
55 |
--------------------------------------------------------------------------------
/src/test/node_modules/sample-module/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sample-module",
3 | "version": "1.0.8"
4 | }
--------------------------------------------------------------------------------
/src/test/npm-test.coffee:
--------------------------------------------------------------------------------
1 | gt.module 'npm test',
2 | setup: ->
3 | @dir = process.cwd()
4 | process.chdir __dirname
5 | console.log 'changed directory to', __dirname
6 | teardown: ->
7 | console.log 'restoring dir', @dir
8 | process.chdir @dir
9 |
10 | npmTest = require '../npm-test'
11 | test = npmTest.test
12 |
13 | ONE_MINUTE = 60000
14 |
15 | onError = (error) ->
16 | throw new Error(error)
17 |
18 | gt.test 'basics', ->
19 | gt.arity test, 1
20 | gt.string npmTest.npmPath, 'has npm path'
21 |
22 | gt.async 'simple npm test', ->
23 | promise = test()
24 | gt.object promise
25 | promise.then ->
26 | gt.ok false, 'there should not be npm test'
27 | .catch ->
28 | gt.ok true, 'failed as expected'
29 | .finally ->
30 | gt.start()
31 | , ONE_MINUTE
32 |
--------------------------------------------------------------------------------
/src/test/registry.js:
--------------------------------------------------------------------------------
1 | /* global gt */
2 | gt.module('registry fetchVersions')
3 |
4 | var registry = require('../registry')
5 | var fetchVersions = registry.fetchVersions
6 | var cleanVersions = registry.cleanVersions
7 | var timeout = 90 * 1000
8 |
9 | function onError (error) {
10 | throw new Error(error)
11 | }
12 |
13 | gt.test('basic of fetch', function () {
14 | gt.func(fetchVersions)
15 | gt.arity(fetchVersions, 2)
16 | })
17 |
18 | gt.async('fetch non existent module', 2, function () {
19 | var promise = fetchVersions({}, {
20 | name: 'this-module-should-not-exist-at-all',
21 | version: '0.2.0'
22 | })
23 | gt.func(promise.then, 'return object has then method')
24 | promise.then(function (results) {
25 | console.log('has results', results)
26 | gt.ok(typeof results === 'object', 'has results')
27 | }).catch(function (error) {
28 | gt.ok(false, 'should not get success, error ' + JSON.stringify(error, null, 2))
29 | }).finally(gt.start)
30 | }, timeout)
31 |
32 | gt.async('fetch gt later versions', function () {
33 | gt.func(fetchVersions)
34 | gt.arity(fetchVersions, 2)
35 | var promise = fetchVersions({}, { name: 'gt', version: '0.5.0' })
36 | gt.func(promise.then, 'return object has then method')
37 | promise.then(function (results) {
38 | gt.object(results, 'returns an object')
39 | gt.equal(results.name, 'gt', 'correct name returned')
40 | gt.array(results.versions, 'has versions array')
41 | gt.ok(results.versions.length > 5, 'a few versions')
42 | }).catch(onError).finally(gt.start)
43 | }, timeout)
44 |
45 | gt.async('fetch module later versions', function () {
46 | gt.func(fetchVersions)
47 | gt.arity(fetchVersions, 2)
48 | var promise = fetchVersions({}, { name: 'lodash', version: '0.7.0' })
49 | gt.func(promise.then, 'return object has then method')
50 | promise.then(function (results) {
51 | gt.object(results, 'returns an object')
52 | gt.equal(results.name, 'lodash', 'correct name returned')
53 | gt.array(results.versions, 'has versions array')
54 | gt.ok(results.versions.length > 5, 'a few versions')
55 | }).catch(onError).finally(gt.start)
56 | }, timeout)
57 |
58 | gt.module('registry nextVersions')
59 |
60 | var nextVersions = require('../registry').nextVersions
61 |
62 | gt.test('next versions basics', function () {
63 | gt.func(nextVersions)
64 | gt.arity(nextVersions, 3)
65 | })
66 |
67 | gt.async('fetch gt, optimist versions', function () {
68 | var promise = nextVersions({}, [{
69 | name: 'gt',
70 | version: '0.5.0'
71 | }, {
72 | name: 'lodash',
73 | version: '1.0.0'
74 | }])
75 | gt.func(promise.then, 'return object has then method')
76 | promise.then(function (results) {
77 | gt.array(results)
78 | gt.equal(results.length, 2, 'two modules')
79 | gt.equal(results[0].name, 'gt')
80 | gt.equal(results[1].name, 'lodash')
81 | gt.equal(results[0].versions[0], '0.5.1')
82 | gt.equal(results[1].versions[0], '1.0.1')
83 | }).catch(onError).finally(gt.start)
84 | }, timeout)
85 |
86 | gt.async('fetch latest version', function () {
87 | var onlyLatest = true
88 | var promise = nextVersions({}, [{
89 | name: 'gt',
90 | version: '0.5.0'
91 | }, {
92 | name: 'lodash',
93 | version: '1.0.0'
94 | }], onlyLatest)
95 | gt.func(promise.then, 'return object has then method')
96 | promise.then(function (results) {
97 | gt.array(results)
98 | gt.equal(results.length, 2, 'two modules')
99 | gt.equal(results[0].name, 'gt')
100 | gt.equal(results[1].name, 'lodash')
101 | gt.equal(results[0].versions.length, 1)
102 | gt.equal(results[1].versions.length, 1)
103 | }).catch(onError).finally(gt.start)
104 | }, timeout)
105 |
106 | gt.async('fetch latest version two digits', function () {
107 | var onlyLatest = true
108 | var promise = nextVersions({}, [{ name: 'mocha', version: '~1.8' }], onlyLatest)
109 | gt.func(promise.then, 'return object has then method')
110 | promise.then(function (results) {
111 | gt.array(results)
112 | gt.equal(results.length, 1, 'single module')
113 | gt.equal(results[0].name, 'mocha')
114 | gt.equal(results[0].versions.length, 1)
115 | }).catch(onError).finally(gt.start)
116 | }, timeout)
117 |
118 | gt.test('clean versions, 2 digits', function () {
119 | gt.arity(cleanVersions, 1)
120 | var cleaned = cleanVersions([{ name: 'mocha', version: '~1.8' }])
121 | gt.array(cleaned)
122 | gt.equal(cleaned.length, 1)
123 | gt.equal(cleaned[0].name, 'mocha', 'correct name')
124 | gt.string(cleaned[0].version, 'version is a string')
125 | })
126 |
127 | gt.test('clean two versions', function () {
128 | var input = [{
129 | name: 'gt',
130 | version: '0.5.0'
131 | }, {
132 | name: 'lodash',
133 | version: '1.0.0'
134 | }]
135 | var cleaned = cleanVersions(input)
136 | gt.array(cleaned)
137 | // console.dir(cleaned);
138 | gt.equal(cleaned.length, 2)
139 | gt.string(cleaned[0].version, 'first module has string version')
140 | gt.string(cleaned[1].version, 'second module has string version')
141 | })
142 |
143 | gt.test('clean latest versions', function () {
144 | var input = [{
145 | name: 'gt',
146 | version: 'latest'
147 | }]
148 | var cleaned = cleanVersions(input)
149 | gt.array(cleaned, 'got back an array')
150 | gt.equal(cleaned.length, 1)
151 | gt.string(cleaned[0].version, 'module has string version')
152 | })
153 |
154 | gt.test('clean version *', function () {
155 | var input = [{
156 | name: 'gt',
157 | version: '*'
158 | }]
159 | var cleaned = cleanVersions(input)
160 | gt.array(cleaned)
161 | gt.equal(cleaned.length, 1)
162 | gt.string(cleaned[0].version, 'module has string version')
163 | })
164 |
--------------------------------------------------------------------------------
/src/test/report-available.coffee:
--------------------------------------------------------------------------------
1 | gt.module 'report-available'
2 |
3 | report = require '../report-available'
4 |
5 | gt.test 'number of arguments', ->
6 | gt.arity report, 3
7 |
8 | gt.test 'expects an array', ->
9 | gt.raises ->
10 | report "info"
11 | , 'needs and array'
12 |
13 | gt.test 'empty array', ->
14 | report []
15 |
16 | gt.test 'good info', ->
17 | report [{name: 'test', versions: [1, 2]}]
18 | report [{name: 'test2', versions: ['1', '2']}]
19 |
20 | gt.test 'missing module name', ->
21 | gt.raises ->
22 | report [{versions: [1,2]}]
23 | , 'needs module name'
24 |
25 | gt.test 'lots of versions', ->
26 | report [{name: 'test', versions: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}]
27 |
--------------------------------------------------------------------------------
/src/test/report-install-command.coffee:
--------------------------------------------------------------------------------
1 | report = require '../report-install-command'
2 | mockery = require 'mockery'
3 |
4 | mockPackageJson = """
5 | {
6 | "name": "next-update",
7 | "version": "0.0.4",
8 | "dependencies": {
9 | "optimist": "~0.6.0",
10 | "lodash": "~2.0.0",
11 | "check-types": "~0.6.4"
12 | },
13 | "devDependencies": {
14 | "grunt": "~0.4.1",
15 | "grunt-contrib-jshint": "~0.6.4",
16 | "grunt-bump": "0.0.11",
17 | "gt": "~0.8.10"
18 | },
19 | "peerDependencies": {
20 | "foo": "0.0.1"
21 | }
22 | }
23 | """
24 |
25 | fsMock =
26 | existsSync: (name) -> name == './package.json'
27 | readFileSync: (name, encoding) ->
28 | console.assert name == './package.json', 'invalid name ' + name
29 | mockPackageJson
30 |
31 | mockery.registerMock 'fs', fsMock
32 |
33 | gt.module 'report-install-command',
34 | setup: -> mockery.enable { useCleanCache : true }
35 | teardown: -> mockery.disable()
36 |
37 |
38 | gt.test 'basics', ->
39 | gt.arity report, 1
40 |
41 | gt.test 'expects an array', ->
42 | gt.raises ->
43 | report {name: 'foo', versions: [1]}
44 | , 'needs an array of objects'
45 |
46 | gt.test 'empty input', ->
47 | cmd = report []
48 | gt.undefined cmd
49 |
50 | gt.test 'nothing works', ->
51 | cmd = report [
52 | [
53 | name: 'foo'
54 | works: false
55 | version: 1
56 | ]
57 | ]
58 | gt.undefined cmd
59 |
60 | gt.test 'single working', ->
61 | cmd = report [
62 | [
63 | name: 'foo'
64 | works: true
65 | version: '0.3.0'
66 | ]
67 | ]
68 | gt.ok /foo@0.3.0/.test(cmd), 'latest version'
69 |
70 | gt.test 'peer dependency command', ->
71 | cmd = report [
72 | [
73 | name: 'foo'
74 | works: true
75 | version: '0.3.0'
76 | ]
77 | ]
78 | gt.ok /--save-peer/.test(cmd), 'save peer dependency'
79 |
80 | gt.test 'normal dependency command', ->
81 | cmd = report [
82 | [
83 | name: 'lodash'
84 | works: true
85 | version: '0.3.0'
86 | ]
87 | ]
88 | gt.ok /--save\ /.test(cmd), 'save normal dependency'
89 |
90 | gt.test 'dev dependency command', ->
91 | cmd = report [
92 | [
93 | name: 'grunt'
94 | works: true
95 | version: '0.3.0'
96 | ]
97 | ]
98 | gt.ok /--save-dev\ /.test(cmd), 'save dev dependency'
99 |
100 | gt.test 'usual input', ->
101 | cmd = report [
102 | [
103 | name: 'foo'
104 | works: true
105 | version: 1
106 | ,
107 | name: 'foo'
108 | works: true
109 | version: 2
110 | ,
111 | name: 'foo'
112 | works: true
113 | version: 3
114 | ]
115 | ]
116 | gt.ok /foo@3/.test(cmd), 'latest version is present'
117 | gt.ok !/foo@2/.test(cmd), 'previous version is not present'
118 |
119 | gt.test 'normal and dev dependency command', ->
120 | cmd = report [
121 | [
122 | name: 'lodash'
123 | works: true
124 | version: '0.3.0'
125 | ],
126 | [
127 | name: 'grunt'
128 | works: true
129 | version: '0.3.0'
130 | ]
131 | ]
132 | console.log cmd
133 | gt.ok /--save --save-exact lodash@0\.3\.0/.test(cmd), 'normal dependency'
134 | gt.ok /--save-dev --save-exact grunt@0\.3\.0/.test(cmd), 'save dev dependency'
135 |
--------------------------------------------------------------------------------
/src/test/report.coffee:
--------------------------------------------------------------------------------
1 | gt.module 'report'
2 |
3 | report = require '../report'
4 |
5 | gt.test 'reportSuccess', ->
6 | rs = report.reportSuccess
7 | gt.arity rs, 2
8 | rs 'sample values'
9 |
10 | gt.test 'reportFailure', ->
11 | rf = report.reportFailure
12 | gt.arity rf, 2
13 | rf 'sample values'
14 |
15 | gt.test 'report basics', ->
16 | r = report.report
17 | gt.ok r.length > 0
18 | data = []
19 | r data
20 |
21 | info = [
22 | [{name: 'a', version: '1.0', from: '0.0.0'}],
23 | [{name: 'a', version: '1.1', from: '0.0.0', works: true}],
24 | [{name: 'b', version: '1.0', from: '0.0.0', works: false}]
25 | ]
26 |
27 | gt.test 'report modules, default colors', 0, ->
28 | report.report info
29 |
30 | gt.test 'report modules, no colors', 0, ->
31 | report.report info, false
32 |
33 | gt.test 'report modules, with colors', 0, ->
34 | report.report info, true
35 |
--------------------------------------------------------------------------------
/src/test/stats.coffee:
--------------------------------------------------------------------------------
1 | gt.module 'stats'
2 |
3 | Q = require 'q'
4 | stats = require '../stats'
5 |
6 | timeout = 35000
7 |
8 | gt.async 'get stats', ->
9 | gt.func stats.getSuccessStats
10 | opts =
11 | name: 'foo-does-not-exist'
12 | from: '1.0.0'
13 | to: '2.0.0'
14 | stats.getSuccessStats(opts)
15 | .then(-> gt.ok false, 'should not find nonexistent module')
16 | .finally(-> gt.start())
17 | , timeout
18 |
19 | gt.async 'bad request for stats', ->
20 | opts =
21 | name: 'foo-does-not-exist'
22 | from: '1.0'
23 | to: '2.0'
24 | stats.getSuccessStats(opts)
25 | .then(-> gt.ok false, 'should not find nonexistent module')
26 | .finally(-> gt.start())
27 | , timeout
28 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | const la = require('lazy-ass')
2 | const is = require('check-more-types')
3 | const R = require('ramda')
4 | const semver = require('semver')
5 | const debug = require('debug')('next-update')
6 |
7 | const name = 'next-update'
8 |
9 | const getConfig = R.memoize(function getConfig (packageFilename) {
10 | la(is.unemptyString(packageFilename),
11 | 'missing package filename', packageFilename)
12 | const pkg = require(packageFilename)
13 | const config = pkg &&
14 | pkg.config &&
15 | pkg.config[name]
16 | return config
17 | })
18 |
19 | function getSkippedModules (packageFilename) {
20 | const config = getConfig(packageFilename)
21 | if (config) {
22 | return config.skip ||
23 | config.skipped ||
24 | config.lock ||
25 | config.locked ||
26 | config.ignore ||
27 | config.ignored ||
28 | []
29 | }
30 | return []
31 | }
32 |
33 | function getTestCommand (packageFilename, moduleName) {
34 | la(is.unemptyString(moduleName), 'missing module name')
35 | const config = getConfig(packageFilename)
36 | if (!config) {
37 | return
38 | }
39 | if (!config.commands) {
40 | return
41 | }
42 | return config.commands[moduleName]
43 | }
44 |
45 | const stringify = (x) => JSON.stringify(x, null, 2)
46 |
47 | const errorObject = (x) => (new Error(stringify(x)))
48 |
49 | function isPrerelease (version) {
50 | la(is.unemptyString(version), 'expected version string', version)
51 | // https://github.com/npm/node-semver#functions
52 | const prereleaseComponents = semver.prerelease(version)
53 | debug('version %s prerelease components %j', version, prereleaseComponents)
54 | return is.array(prereleaseComponents) && is.not.empty(prereleaseComponents)
55 | }
56 |
57 | module.exports = {
58 | name,
59 | getConfig,
60 | getSkippedModules,
61 | getTestCommand,
62 | stringify,
63 | errorObject,
64 | isPrerelease
65 | }
66 |
--------------------------------------------------------------------------------
/test/as-parent-spec.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const chdir = require('chdir-promise')
3 | const nextUpdate = require('..')
4 | const execa = require('execa')
5 | const snapShot = require('snap-shot')
6 | const _ = require('lodash')
7 | const is = require('check-more-types')
8 | const la = require('lazy-ass')
9 | const TWO_MINUTES = 120000
10 |
11 | const testFolder = path.join(__dirname, 'test-next-updater')
12 |
13 | function prune () {
14 | return execa.shell('npm prune')
15 | }
16 |
17 | function install () {
18 | return execa.shell('npm install')
19 | }
20 |
21 | /* global describe, beforeEach, afterEach, it */
22 | describe('testing check-types', () => {
23 | beforeEach(function () {
24 | this.timeout(TWO_MINUTES)
25 | return chdir.to(testFolder).then(prune).then(install)
26 | })
27 |
28 | afterEach(function () {
29 | this.timeout(TWO_MINUTES)
30 | return chdir.to(testFolder).then(prune).then(install).then(chdir.back)
31 | })
32 |
33 | it('skips without module', function () {
34 | this.timeout(TWO_MINUTES)
35 | const opts = {
36 | without: 'check-types',
37 | keep: false
38 | }
39 | return nextUpdate(opts).then(results => {
40 | la(results === undefined, 'no results without a module to check', results)
41 | })
42 | })
43 |
44 | it('checks latest check-types', function () {
45 | this.timeout(TWO_MINUTES)
46 | const opts = {
47 | module: 'check-types',
48 | latest: true,
49 | keep: false
50 | }
51 | const removeVersions = results =>
52 | results.map(r => {
53 | la(is.semver(r.version), 'expected version', r)
54 | r.version = 'valid'
55 | return r
56 | })
57 | return snapShot(nextUpdate(opts).then(_.flatten).then(removeVersions))
58 | })
59 |
60 | it('checks some versions of check-types', function () {
61 | this.timeout(TWO_MINUTES)
62 |
63 | const limit = (name, version) => {
64 | if (name === 'check-types' && version.startsWith('0.7')) {
65 | return true
66 | }
67 | return false
68 | }
69 |
70 | const opts = {
71 | module: 'check-types',
72 | keep: false,
73 | limit
74 | }
75 | return snapShot(nextUpdate(opts))
76 | })
77 | })
78 |
--------------------------------------------------------------------------------
/test/as-parent.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 | q = require 'q'
3 | _ = require 'lodash'
4 | TWO_MINUTES = 120000
5 |
6 | gt.module 'using next-update as module',
7 | setup: ->
8 | process.chdir path.join(__dirname, 'test-next-updater')
9 | teardown: ->
10 | process.chdir __dirname
11 |
12 | nextUpdate = require '..'
13 |
14 | gt.test 'basics', ->
15 | gt.func nextUpdate, 'next update is a function'
16 |
17 | # [ [ { name: 'check-types', version: '1.1.1', works: false } ] ]
18 | # TODO use schem-shot
19 | gt.async 'results format', ->
20 | opts =
21 | module: 'check-types'
22 | latest: true
23 | promise = nextUpdate(opts)
24 | gt.ok q.isPromise(promise), 'next update returns a promise'
25 | promise.then (result) ->
26 | console.log 'Finished with', result
27 | gt.array result, 'result should be an array'
28 | result = _.flatten result
29 | gt.length result, 1, 'single result'
30 | gt.object result[0], 'single result is an object'
31 | gt.equal result[0].name, 'check-types'
32 | gt.string result[0].version, 'has version string'
33 | gt.equal typeof result[0].works, 'boolean', 'has works property'
34 | promise.catch -> gt.ok false, 'promise failed'
35 | promise.finally -> gt.start()
36 | , TWO_MINUTES
37 |
--------------------------------------------------------------------------------
/test/command-spec.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const chdir = require('chdir-promise')
3 | const nextUpdate = require('..')
4 | const execa = require('execa')
5 | const snapShot = require('snap-shot')
6 | const R = require('ramda')
7 | const TWO_MINUTES = 120000
8 |
9 | const testFolder = path.join(__dirname, 'test-command-map')
10 |
11 | function prune () {
12 | return execa.shell('npm prune')
13 | }
14 |
15 | function install () {
16 | return execa.shell('npm install')
17 | }
18 |
19 | const flatVersionSort = R.compose(R.sortBy(R.prop('version')), R.flatten)
20 |
21 | it('sorts by version', () => {
22 | const results = [
23 | [
24 | { name: 'lodash', version: '1.0.1', from: '1.0.0', works: true },
25 | { name: 'lodash', version: '1.1.0', from: '1.0.0', works: false },
26 | { name: 'lodash', version: '1.1.1', from: '1.0.0', works: false },
27 | { name: 'lodash', version: '1.2.0', from: '1.0.0', works: false },
28 | { name: 'lodash', version: '1.2.1', from: '1.0.0', works: false },
29 | { name: 'lodash', version: '1.3.0', from: '1.0.0', works: false },
30 | { name: 'lodash', version: '1.3.1', from: '1.0.0', works: false },
31 | { name: 'lodash', version: '1.0.2', from: '1.0.0', works: true }
32 | ]
33 | ]
34 | snapShot(flatVersionSort(results))
35 | })
36 |
37 | /* global describe, beforeEach, afterEach, it */
38 | describe('per-module configured command', () => {
39 | beforeEach(function () {
40 | this.timeout(TWO_MINUTES)
41 | return chdir.to(testFolder).then(prune).then(install)
42 | })
43 |
44 | afterEach(chdir.back)
45 |
46 | it('finds and uses module own test command', function () {
47 | this.timeout(TWO_MINUTES)
48 | const opts = {
49 | module: 'lodash',
50 | allow: 'minor'
51 | }
52 | return snapShot(nextUpdate(opts).then(flatVersionSort))
53 | })
54 | })
55 |
--------------------------------------------------------------------------------
/test/e2e.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 | index = path.join __dirname, '../bin/next-update.js'
3 | ONE_MINUTE = 60000
4 | TWO_MINUTES = 120000
5 | moduleName = 'check-types'
6 |
7 | gt.module 'end 2 end tests',
8 | setup: ->
9 | process.chdir path.join(__dirname, 'test-next-updater')
10 | teardown: ->
11 | process.chdir __dirname
12 |
13 | # TODO set the final version limit to check
14 | gt.async '--module', ->
15 | gt.exec 'node', [index, '--module', moduleName], 0
16 | , ONE_MINUTE
17 |
18 | gt.async '--available', ->
19 | gt.exec 'node', [index, '--available'], 0
20 | , ONE_MINUTE
21 |
22 | gt.async '--available ' + moduleName, ->
23 | gt.exec 'node', [index, '--available', moduleName], 0
24 | , ONE_MINUTE
25 |
26 | gt.async '--revert ' + moduleName, ->
27 | gt.exec 'node', [index, '--revert', '--module', moduleName], 0
28 | , ONE_MINUTE
29 |
30 | gt.async '--revert', ->
31 | gt.exec 'node', [index, '--revert'], 0
32 | , TWO_MINUTES
33 |
--------------------------------------------------------------------------------
/test/file-deps-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var la = require('lazy-ass')
4 | var is = require('check-more-types')
5 | var pause = 30 * 1000
6 | var chdir = require('chdir-promise')
7 | var join = require('path').join
8 | var testFolder = join(__dirname, 'test-file-deps')
9 |
10 | /* global describe, it */
11 | describe('scoped packages', function () {
12 | var nextUpdate = require('../src/next-update')
13 |
14 | it('is an object', function () {
15 | la(is.object(nextUpdate))
16 | })
17 |
18 | it('has available method', function () {
19 | la(is.fn(nextUpdate.available))
20 | })
21 |
22 | it('handles file: package names', function () {
23 | this.timeout(pause)
24 | return chdir
25 | .to(testFolder)
26 | .then(function () {
27 | return nextUpdate.available()
28 | })
29 | .then(function (available) {
30 | console.log('available', available)
31 | })
32 | .then(chdir.back)
33 | })
34 | })
35 |
--------------------------------------------------------------------------------
/test/revert-spec.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const fromFolder = path.join.bind(null, __dirname)
3 | const chdir = require('chdir-promise')
4 | const execa = require('execa')
5 | const la = require('lazy-ass')
6 | const TWO_MINUTES = 120000
7 |
8 | const index = fromFolder('../bin/next-update.js')
9 | const testFolder = fromFolder('test-next-updater')
10 |
11 | /* global describe, beforeEach, afterEach, it */
12 | describe('--revert', () => {
13 | beforeEach(function () {
14 | return chdir.to(testFolder)
15 | })
16 |
17 | afterEach(chdir.back)
18 |
19 | it('--module check-types', function () {
20 | this.timeout(TWO_MINUTES)
21 | return execa('node', [
22 | index,
23 | '--revert',
24 | '--module',
25 | 'check-types'
26 | ]).then(result => {
27 | la(
28 | result.code === 0,
29 | 'error exit',
30 | result.code,
31 | result.stdout,
32 | result.stderr
33 | )
34 | })
35 | })
36 |
37 | it('--module all', function () {
38 | this.timeout(TWO_MINUTES)
39 | return execa('node', [index, '--revert', '--module']).then(result => {
40 | la(
41 | result.code === 0,
42 | 'error exit',
43 | result.code,
44 | result.stdout,
45 | result.stderr
46 | )
47 | })
48 | })
49 | })
50 |
--------------------------------------------------------------------------------
/test/scoped-packages-spec.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | var la = require('lazy-ass')
4 | var is = require('check-more-types')
5 | var pause = 30 * 1000
6 | var chdir = require('chdir-promise')
7 | var join = require('path').join
8 | var testFolder = join(__dirname, 'test-scoped-names')
9 |
10 | /* global describe, it */
11 | describe('scoped packages', function () {
12 | var nextUpdate = require('../src/next-update')
13 |
14 | it('is an object', function () {
15 | la(is.object(nextUpdate))
16 | })
17 |
18 | it('has available method', function () {
19 | la(is.fn(nextUpdate.available))
20 | })
21 |
22 | it('handles scoped package names', function () {
23 | this.timeout(pause)
24 | return chdir
25 | .to(testFolder)
26 | .then(function () {
27 | return nextUpdate.available('@bahmutov/csv@1.1.0')
28 | })
29 | .then(function (available) {
30 | la(is.array(available), 'returns an array', available)
31 | la(is.not.empty(available), 'there should be a new version', available)
32 | var first = available[0]
33 | la(first.name === '@bahmutov/csv', 'wrong name', first)
34 | var versions = first.versions
35 | la(is.array(versions), 'expected list of new versions', first)
36 | la(versions[0] === '1.2.0', 'first available version', versions)
37 | })
38 | .then(chdir.back)
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/test/single-module-check.coffee:
--------------------------------------------------------------------------------
1 | path = require 'path'
2 | index = path.join __dirname, '../bin/next-update.js'
3 | TWO_MINUTES = 120000
4 |
5 | gt.module 'single module check',
6 | setup: ->
7 | process.chdir path.join(__dirname, 'test-next-updater')
8 | teardown: ->
9 | process.chdir __dirname
10 |
11 | gt.async '--skip -m check-types', ->
12 | gt.exec 'node', [index, '--skip', '--m check-types'], 0
13 | , TWO_MINUTES
14 |
--------------------------------------------------------------------------------
/test/test-command-map/.npmrc:
--------------------------------------------------------------------------------
1 | registry=http://registry.npmjs.org/
2 |
--------------------------------------------------------------------------------
/test/test-command-map/__snapshots__/command-spec.js.snap-shot:
--------------------------------------------------------------------------------
1 | exports['finds and uses module own test command 1'] = [
2 | {
3 | "name": "lodash",
4 | "version": "1.0.1",
5 | "from": "1.0.0",
6 | "works": true
7 | },
8 | {
9 | "name": "lodash",
10 | "version": "1.0.2",
11 | "from": "1.0.0",
12 | "works": true
13 | },
14 | {
15 | "name": "lodash",
16 | "version": "1.1.0",
17 | "from": "1.0.0",
18 | "works": false
19 | },
20 | {
21 | "name": "lodash",
22 | "version": "1.1.1",
23 | "from": "1.0.0",
24 | "works": false
25 | },
26 | {
27 | "name": "lodash",
28 | "version": "1.2.0",
29 | "from": "1.0.0",
30 | "works": false
31 | },
32 | {
33 | "name": "lodash",
34 | "version": "1.2.1",
35 | "from": "1.0.0",
36 | "works": false
37 | },
38 | {
39 | "name": "lodash",
40 | "version": "1.3.0",
41 | "from": "1.0.0",
42 | "works": false
43 | },
44 | {
45 | "name": "lodash",
46 | "version": "1.3.1",
47 | "from": "1.0.0",
48 | "works": false
49 | }
50 | ]
51 |
52 |
--------------------------------------------------------------------------------
/test/test-command-map/lodash-test.js:
--------------------------------------------------------------------------------
1 | const _ = require('lodash')
2 | console.log('lodash test version %s', _.VERSION)
3 | process.exit(_.VERSION.startsWith('1.0') ? 0 : 1)
4 |
--------------------------------------------------------------------------------
/test/test-command-map/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-next-updater",
3 | "version": "0.0.1",
4 | "description": "Simple NPM module with dependencies for testing",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo 'no test'",
8 | "lodash-test": "node lodash-test.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git@github.com:bahmutov/test-next-updater.git"
13 | },
14 | "keywords": [
15 | "test",
16 | "versions",
17 | "updater"
18 | ],
19 | "author": "Gleb Bahmutov ",
20 | "license": "MIT",
21 | "dependencies": {
22 | "lodash": "1.0.0"
23 | },
24 | "config": {
25 | "next-update": {
26 | "commands": {
27 | "lodash": "npm run lodash-test"
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/test/test-file-deps/.npmrc:
--------------------------------------------------------------------------------
1 | registry=http://registry.npmjs.org/
2 |
--------------------------------------------------------------------------------
/test/test-file-deps/index.js:
--------------------------------------------------------------------------------
1 | console.log('do nothing')
2 |
--------------------------------------------------------------------------------
/test/test-file-deps/local-dependency/index.js:
--------------------------------------------------------------------------------
1 | console.log('local dependency')
2 |
--------------------------------------------------------------------------------
/test/test-file-deps/local-dependency/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "local-dependency",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC"
12 | }
13 |
--------------------------------------------------------------------------------
/test/test-file-deps/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-scoped-names",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "node index.js"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "local-dependency": "file:./local-dependency"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test/test-next-updater/.npmrc:
--------------------------------------------------------------------------------
1 | registry=http://registry.npmjs.org/
2 |
--------------------------------------------------------------------------------
/test/test-next-updater/__snapshots__/as-parent-spec.js.snap-shot:
--------------------------------------------------------------------------------
1 | exports['checks some versions of check-types 1'] = [
2 | [
3 | {
4 | "name": "check-types",
5 | "version": "0.7.0",
6 | "from": "0.6.5",
7 | "works": true
8 | },
9 | {
10 | "name": "check-types",
11 | "version": "0.7.1",
12 | "from": "0.6.5",
13 | "works": true
14 | },
15 | {
16 | "name": "check-types",
17 | "version": "0.7.2",
18 | "from": "0.6.5",
19 | "works": true
20 | }
21 | ]
22 | ]
23 |
24 | exports['checks latest check-types 1'] = [
25 | {
26 | "name": "check-types",
27 | "version": "valid",
28 | "from": "0.6.5",
29 | "works": false
30 | }
31 | ]
32 |
33 |
--------------------------------------------------------------------------------
/test/test-next-updater/index.js:
--------------------------------------------------------------------------------
1 | var check = require('check-types');
2 | // using old check verifyString api on purpose
3 | check.verifyString('this is test string, works check-types < 1.0.0');
4 | console.log('works');
5 |
--------------------------------------------------------------------------------
/test/test-next-updater/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-next-updater",
3 | "version": "0.0.1",
4 | "description": "Simple NPM module with dependencies for testing",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "node index.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git@github.com:bahmutov/test-next-updater.git"
12 | },
13 | "keywords": [
14 | "test",
15 | "versions",
16 | "updater"
17 | ],
18 | "author": "Gleb Bahmutov ",
19 | "license": "MIT",
20 | "dependencies": {
21 | "check-types": "0.6.5"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/test/test-scoped-names/.npmrc:
--------------------------------------------------------------------------------
1 | registry=http://registry.npmjs.org/
2 |
--------------------------------------------------------------------------------
/test/test-scoped-names/index.js:
--------------------------------------------------------------------------------
1 | var csv = require('@bahmutov/csv')
2 | console.log('typeof csv', typeof csv)
3 | console.log(Object.keys(csv))
4 |
--------------------------------------------------------------------------------
/test/test-scoped-names/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "test-scoped-names",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "node index.js"
8 | },
9 | "keywords": [],
10 | "author": "",
11 | "license": "ISC",
12 | "dependencies": {
13 | "@bahmutov/csv": "1.1.0"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/test/time-limit-promise.js:
--------------------------------------------------------------------------------
1 | // how to time limit a promise?
2 |
3 | var Q = require('q')
4 | var started = +new Date()
5 |
6 | console.stamp = function stamp () {
7 | var elapsed = +new Date() - started
8 | var messageArgs = ['at %d ms:', elapsed].concat(
9 | Array.prototype.slice.call(arguments, 0)
10 | )
11 | console.log.apply(console, messageArgs)
12 | }
13 |
14 | function onOk (value) {
15 | console.stamp('promise resolved ok', value)
16 | }
17 |
18 | function onError (value) {
19 | console.stamp('promise error with value', value)
20 | }
21 |
22 | // "normal" promise-returning function, knows nothing about the time limit
23 | function longAction () {
24 | var deferred = Q.defer()
25 |
26 | console.stamp('started long-running promise')
27 | setTimeout(function () {
28 | console.stamp('resolving long-running promise with 42 after 1 second')
29 | return deferred.resolve(42)
30 | }, 1000)
31 |
32 | return deferred.promise
33 | }
34 |
35 | longAction().timeout(400, 'timed out').then(onOk, onError).done()
36 |
--------------------------------------------------------------------------------