├── .editorconfig
├── .eslintrc
├── .gitignore
├── .nvmrc
├── .travis.yml
├── .vscode
└── launch.json
├── CHANGELOG.md
├── LICENSE
├── README.md
├── appveyor.yml
├── assets
├── updtr.gif
├── updtr.jpg
└── updtr.psd
├── babel.config.js
├── bin
└── updtr
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
├── Updtr.js
├── bin
│ ├── argv.js
│ └── index.js
├── constants
│ ├── config.js
│ └── filterReasons.js
├── errors.js
├── exec
│ ├── cmds.js
│ ├── exec.js
│ └── parse.js
├── index.js
├── reporters
│ ├── basic.js
│ ├── dense.js
│ ├── index.js
│ ├── none.js
│ └── util
│ │ ├── Indicator.js
│ │ ├── Message.js
│ │ ├── Projector.js
│ │ ├── Spinner.js
│ │ ├── Terminal.js
│ │ ├── configList.js
│ │ ├── customConfigToLines.js
│ │ ├── handleError.js
│ │ ├── msToString.js
│ │ └── pluralize.js
├── run.js
├── tasks
│ ├── batchUpdate.js
│ ├── finish.js
│ ├── init.js
│ ├── sequentialUpdate.js
│ ├── updatePackageJson.js
│ └── util
│ │ ├── Sequence.js
│ │ ├── createUpdateResult.js
│ │ ├── createUpdateTask.js
│ │ ├── createUpdatedPackageJson.js
│ │ ├── filterUpdateResults.js
│ │ ├── filterUpdateTask.js
│ │ ├── rollbackTo.js
│ │ ├── splitUpdateTasks.js
│ │ ├── updateTo.js
│ │ └── updateVersionRange.js
└── util
│ └── fs.js
└── test
├── .eslintrc
├── Updtr.test.js
├── __snapshots__
├── Updtr.test.js.snap
├── index.test.js.snap
└── run.test.js.snap
├── bin
├── __snapshots__
│ └── index.test.js.snap
└── index.test.js
├── exec
├── __snapshots__
│ ├── cmds.test.js.snap
│ ├── exec.test.js.snap
│ └── parse.test.js.snap
├── cmds.test.js
├── exec.test.js
└── parse.test.js
├── fixtures
├── events.js
├── execResults.js
├── outdateds.js
├── packageJsons.js
├── updateResults.js
└── versionRanges.js
├── helpers
├── ExecError.js
├── FakeUpdtr.js
├── binMocks.js
├── cleanupFixtures.js
├── pickEventNames.js
├── pickEvents.js
├── readFixtures.js
├── replayTerminalSnapshots.js
├── runBinMock.js
├── setupFixtures.js
└── temp.js
├── index.test.js
├── integration
├── __snapshots__
│ ├── noOutdated.test.js.snap
│ └── outdated.test.js.snap
├── helpers
│ ├── constants.js
│ ├── fs.js
│ └── getInstalledVersions.js
├── noOutdated.test.js
└── outdated.test.js
├── manual
├── package.json
├── test
└── yarn.lock
├── reporters
├── __snapshots__
│ ├── basic.test.js.snap
│ ├── dense.test.js.snap
│ └── index.test.js.snap
├── basic.test.js
├── dense.test.js
├── index.test.js
└── util
│ ├── __snapshots__
│ └── configList.test.js.snap
│ ├── configList.test.js
│ └── handleError.test.js
├── run.test.js
└── tasks
├── __snapshots__
├── batchUpdate.test.js.snap
├── finish.test.js.snap
├── init.test.js.snap
├── sequentialUpdate.test.js.snap
└── updatePackageJson.test.js.snap
├── batchUpdate.test.js
├── finish.test.js
├── init.test.js
├── sequentialUpdate.test.js
├── updatePackageJson.test.js
└── util
├── Sequence.test.js
├── __snapshots__
├── createUpdateTask.test.js.snap
├── createUpdatedPackageJson.test.js.snap
└── updateVersionRange.test.js.snap
├── createUpdateTask.test.js
├── createUpdatedPackageJson.test.js
├── filterUpdateTask.test.js
├── splitUpdateTasks.test.js
└── updateVersionRange.test.js
/.editorconfig:
--------------------------------------------------------------------------------
1 | # This file is for unifying the coding style for different editors and IDEs.
2 | # More information at http://EditorConfig.org
3 |
4 | # No .editorconfig files above the root directory
5 | root = true
6 |
7 | [*]
8 | indent_style = space
9 | indent_size = 4
10 | charset = utf-8
11 | trim_trailing_whitespace = true
12 | insert_final_newline = true
13 |
14 | [package.json]
15 | indent_size = 2
16 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "peerigon"
4 | ],
5 | "env": {
6 | "node": true
7 | },
8 | "rules": {
9 | "import/extensions": "off"
10 | },
11 | "root": true
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 |
5 | # Runtime data
6 | pids
7 | *.pid
8 | *.seed
9 |
10 | # Directory for instrumented libs generated by jscoverage/JSCover
11 | lib-cov
12 |
13 | # Coverage directory used by tools like istanbul
14 | coverage
15 |
16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
17 | .grunt
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (http://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directory
26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
27 | node_modules
28 |
29 | # IDE stuff
30 | .idea
31 |
32 | # Will be generated by npm run ensure-fixtures
33 | test/fixtures/*/**
34 |
35 | # Rollup output
36 | dist
37 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | v12.20.1
2 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | os:
4 | - linux
5 | node_js:
6 | - 16
7 | - 14
8 | - 12
9 |
10 | script:
11 | - npm install
12 | - npm install -g yarn
13 | - npm test
14 |
15 | after_success:
16 | - bash <(curl -s https://codecov.io/bash)
17 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible Node.js debug attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "type": "node",
9 | "request": "launch",
10 | "name": "Bin",
11 | "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/babel-node",
12 | "runtimeArgs": [
13 | "--harmony_async_await"
14 | ],
15 | "program": "${workspaceRoot}/src/bin/index.js"
16 | },
17 | {
18 | "name": "Current test",
19 | "type": "node",
20 | "request": "launch",
21 | "program": "${workspaceRoot}/node_modules/jest/bin/jest.js",
22 | "stopOnEntry": false,
23 | "args": [
24 | "${file}",
25 | "--runInBand"
26 | ],
27 | "cwd": "${workspaceRoot}",
28 | "preLaunchTask": null,
29 | "runtimeExecutable": null,
30 | "runtimeArgs": [
31 | "--harmony_async_await",
32 | "--nolazy"
33 | ],
34 | "env": {
35 | "NODE_ENV": "test"
36 | },
37 | "externalConsole": false,
38 | "sourceMaps": true,
39 | "outDir": null
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4 |
5 | ## [4.1.0](https://github.com/peerigon/updtr/compare/v4.0.0...v4.1.0) (2024-07-03)
6 |
7 |
8 | ### Features
9 |
10 | * Introduce interactive update ([d3975e2](https://github.com/peerigon/updtr/commit/d3975e27787fac1199ef751cb947f3675ccec70c))
11 |
12 | ## [4.0.0](https://github.com/peerigon/updtr/compare/v3.1.0...v4.0.0) (2021-08-20)
13 |
14 |
15 | ### ⚠ BREAKING CHANGES
16 |
17 | * No longer supporting node versions 6-11
18 |
19 | ### Features
20 |
21 | * update dependencies and remove vulnerabilities ([#86](https://github.com/peerigon/updtr/issues/86)) ([eb7a1ef](https://github.com/peerigon/updtr/commit/eb7a1ef4281714405c64ef633ac2963b87510887))
22 |
23 |
24 | * upgrade node version to v12 ([2e29ef0](https://github.com/peerigon/updtr/commit/2e29ef0c605a7889ca5c50c7da86e65444c0acec))
25 |
26 |
27 | # [3.1.0](https://github.com/peerigon/updtr/compare/v3.0.0...v3.1.0) (2018-10-04)
28 |
29 |
30 | ### Features
31 |
32 | * Add basic reporter ([#78](https://github.com/peerigon/updtr/issues/78)) ([59885d3](https://github.com/peerigon/updtr/commit/59885d3))
33 |
34 |
35 |
36 |
37 | # [3.0.0](https://github.com/peerigon/updtr/compare/v2.0.0...v3.0.0) (2018-10-03)
38 |
39 |
40 | ### Bug Fixes
41 |
42 | * Respect package.json indentation ([#77](https://github.com/peerigon/updtr/issues/77)) ([e766fb0](https://github.com/peerigon/updtr/commit/e766fb0)), closes [#68](https://github.com/peerigon/updtr/issues/68)
43 |
44 |
45 | ### Fix
46 |
47 | * Stdout output parsing from yarn ([ff56fbb](https://github.com/peerigon/updtr/commit/ff56fbb))
48 |
49 |
50 | ### BREAKING CHANGES
51 |
52 | * Removed official Node 4 support. It may still work, but now you're on your own.
53 |
54 |
55 |
56 |
57 | # [2.0.0](https://github.com/peerigon/updtr/compare/v1.0.0...v2.0.0) (2017-06-20)
58 |
59 |
60 | ### Features
61 |
62 | * Rewrite ([7ffc10a](https://github.com/peerigon/updtr/commit/7ffc10a)), closes [#14](https://github.com/peerigon/updtr/issues/14) [#47](https://github.com/peerigon/updtr/issues/47) [#46](https://github.com/peerigon/updtr/issues/46) [#13](https://github.com/peerigon/updtr/issues/13) [#51](https://github.com/peerigon/updtr/issues/51) [#58](https://github.com/peerigon/updtr/issues/58)
63 |
64 |
65 | ### BREAKING CHANGES
66 |
67 | * New and changed CLI options
68 |
69 | ```
70 | --use, -u Specify the package manager to use [choices: "npm", "yarn"] [default: "npm"]
71 | --exclude, --ex Space separated list of module names that should not be updated [array]
72 | --update-to, --to Specify which updates you want to install [choices: "latest", "non-breaking", "wanted"] [default: "latest"]
73 | --save, -s Specify how updated versions should be saved to the package.json [choices: "smart", "caret", "exact"] [default: "smart"]
74 | --reporter, -r Choose a reporter for the console output [choices: "dense", "none"] [default: "dense"]
75 | --test, -t Specify a custom test command. Surround with quotes.
76 | --test-stdout, --out Show test stdout if the update fails [boolean]
77 | --registry, --reg Specify a custom registry to use
78 | --version Show version number [boolean]
79 | --help Show help [boolean]
80 | ```
81 |
82 |
83 |
84 |
85 | # [1.0.0](https://github.com/peerigon/updtr/compare/v0.2.1...v1.0.0) (2017-04-01)
86 |
87 |
88 | ### Bug Fixes
89 |
90 | * **npm:** ignore error status 1 on npm outdated ([ec4057c](https://github.com/peerigon/updtr/commit/ec4057c))
91 |
92 |
93 | ### Features
94 |
95 | * Use yarn package manager when available ([bcca7ce](https://github.com/peerigon/updtr/commit/bcca7ce))
96 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
26 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # updtr
4 |
5 | **Update outdated npm modules with zero pain™**
6 |
7 | [](https://travis-ci.org/peerigon/updtr)
8 | [](https://www.npmjs.com/package/updtr)
9 | [](https://www.npmjs.com/package/updtr)
10 | [](https://coveralls.io/github/peerigon/updtr?branch=master)
11 |
12 | Based on `npm outdated`, **updtr** installs the latest version and runs `npm test` for each dependency. In case the test succeeds, **updtr** saves the new version number to your `package.json`. Otherwise, **updtr** rolls back the conflicting update.
13 |
14 | Additionally, it will use `yarn` instead of `npm` when a `yarn.lock` file is present in your project.
15 |
16 | Made by [Peerigon](https://peerigon.com/?pk_campaign=gh-os&pk_kwd=updtr).
17 |
18 | 
19 |
20 | ## Installation
21 |
22 | ```
23 | npm install -g updtr
24 | ```
25 |
26 | ## Options
27 |
28 | ### `--use` `-u`
29 |
30 | Specify the package manager to use:
31 |
32 | - `npm`
33 | - `yarn`
34 |
35 | Updtr tries to guess the package manager by looking for a `yarn.lock` file. If there is one in `process.cwd()`, it will use yarn. Setting this option overrides that default.
36 |
37 | ### `--exclude` `--ex`
38 |
39 | Space separated list of module names that should not be updated.
40 |
41 | ### `--update-to` `--to`
42 |
43 | - `latest` *(default)*: update all packages to the latest version number
44 | - `non-breaking`: update all packages to the latest version number that does not conflict with the installed version number
45 | - `wanted`: update all packages to the latest version number that does not conflict with the version number as specified in the `package.json`
46 |
47 | ### `--save` `-s`
48 |
49 | Specify how updated versions should be saved to the `package.json`:
50 |
51 | - `smart` *(default)*: tries to preserve the current style. Falls back to `caret` if the style cannot be preserved.
52 | - `caret`: saves `^x.y.z`
53 | - `exact`: saves `x.y.z`
54 |
55 | ### `--reporter` `-r`
56 |
57 | Choose a reporter for the console output:
58 |
59 | - `dense` *(default*): See screenshot
60 | - `basic`: Uses `console.log` for output, no need for a TTY (e.g when running on CI)
61 | - `none`: No console output
62 |
63 | ### `--test` `-t`
64 |
65 | Specify a custom test command. Surround with quotes:
66 |
67 | `updtr -t "mocha -R spec"`
68 |
69 | ### `--test-stdout` `--out`
70 |
71 | Show test stdout if the update fails.
72 |
73 | ### `--registry` `--reg`
74 |
75 | Specify a custom registry to use.
76 |
77 | **Please note:** yarn does not support to set a custom registry via command line. Use a `.npmrc` file to achieve this. See also [yarnpkg/yarn#606](https://github.com/yarnpkg/yarn/issues/606).
78 |
79 | ### `--version`
80 |
81 | Show the current updtr version.
82 |
83 | ### `--help`
84 |
85 | Show all commands.
86 |
87 | ## License
88 |
89 | Unlicense
90 |
91 | ## Sponsors
92 |
93 | [
](https://peerigon.com)
94 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | environment:
2 | matrix:
3 | - nodejs_version: 14
4 | - nodejs_version: 12
5 |
6 | platform:
7 | - x86
8 |
9 | install:
10 | - ps: Install-Product node $env:nodejs_version
11 | - npm install
12 |
13 | test_script:
14 | - npm install -g yarn
15 | - node --version
16 | - npm --version
17 | - npm test
18 |
19 | build: off
20 |
--------------------------------------------------------------------------------
/assets/updtr.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peerigon/updtr/bfc57515ae71ca9d5ad0ad0a53a3363518bcd56c/assets/updtr.gif
--------------------------------------------------------------------------------
/assets/updtr.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peerigon/updtr/bfc57515ae71ca9d5ad0ad0a53a3363518bcd56c/assets/updtr.jpg
--------------------------------------------------------------------------------
/assets/updtr.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peerigon/updtr/bfc57515ae71ca9d5ad0ad0a53a3363518bcd56c/assets/updtr.psd
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/unambiguous, strict */
2 | "use strict";
3 |
4 | module.exports = function (api) {
5 | api.cache(() => process.env.NODE_ENV);
6 |
7 | return {
8 | presets: [
9 | [
10 | "@babel/preset-env",
11 | {
12 | targets: {
13 | node: 12,
14 | },
15 | },
16 | ],
17 | ],
18 | plugins: [
19 | "@babel/plugin-transform-runtime",
20 | ],
21 | sourceMaps: "inline",
22 | retainLines: true,
23 | };
24 | };
25 |
26 |
--------------------------------------------------------------------------------
/bin/updtr:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 |
3 | require("../dist");
4 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "updtr",
3 | "version": "4.1.0",
4 | "description": "Update outdated npm modules with zero pain™",
5 | "main": "dist",
6 | "scripts": {
7 | "pretest": "npm run fixtures:ensure",
8 | "test": "cross-env FORCE_COLOR=1 jest --coverage",
9 | "test:watch": "jest --watch",
10 | "posttest": "npm run lint",
11 | "lint": "eslint --ignore-path ./.gitignore src test",
12 | "build": "cross-env NODE_ENV=production rollup -c",
13 | "prepublishOnly": "npm run build && node ./bin/updtr --help",
14 | "release": "standard-version",
15 | "fixtures:ensure": "babel-node ./test/helpers/setupFixtures.js",
16 | "fixtures:rebuild": "babel-node ./test/helpers/cleanupFixtures.js && npm run fixtures:ensure"
17 | },
18 | "author": "developers@peerigon.com",
19 | "license": "Unlicense",
20 | "bin": {
21 | "updtr": "./bin/updtr"
22 | },
23 | "dependencies": {
24 | "@babel/runtime": "^7.15.3",
25 | "ansi-escapes": "^4.3.0",
26 | "chalk": "^4.1.2",
27 | "cli-cursor": "^3.1.0",
28 | "cli-spinners": "^2.6.0",
29 | "detect-indent": "^6.0.0",
30 | "es6-error": "^4.0.2",
31 | "inquirer": "7.0.0",
32 | "pify": "^4.0.1",
33 | "semver": "^7.3.5",
34 | "string-width": "^4.2.0",
35 | "unicons": "0.0.3",
36 | "yargs": "^15.1.0"
37 | },
38 | "devDependencies": {
39 | "@babel/core": "^7.15.0",
40 | "@babel/node": "^7.14.9",
41 | "@babel/plugin-transform-runtime": "^7.15.0",
42 | "@babel/preset-env": "^7.15.0",
43 | "@babel/register": "^7.15.3",
44 | "babel-core": "^7.0.0-bridge.0",
45 | "babel-jest": "^27.0.6",
46 | "clone": "^2.1.1",
47 | "cross-env": "^7.0.3",
48 | "eslint": "^7.32.0",
49 | "eslint-config-peerigon": "^25.3.1",
50 | "eslint-plugin-jest": "^23.6.0",
51 | "jest": "^25.1.0",
52 | "rimraf": "^3.0.2",
53 | "rollup": "^1.31.0",
54 | "rollup-plugin-babel": "^4.4.0",
55 | "rollup-plugin-node-resolve": "^5.2.0",
56 | "sinon": "^8.1.1",
57 | "standard-version": "^9.3.1",
58 | "stream-buffers": "^3.0.1",
59 | "temp": "^0.9.4",
60 | "wrap-ansi": "^6.2.0"
61 | },
62 | "engines": {
63 | "node": ">= 4.0.0",
64 | "npm": ">= 2.5.0"
65 | },
66 | "repository": {
67 | "type": "git",
68 | "url": "https://github.com/peerigon/updtr.git"
69 | },
70 | "files": [
71 | "bin",
72 | "dist",
73 | "README.md",
74 | "LICENSE"
75 | ],
76 | "jest": {
77 | "coveragePathIgnorePatterns": [
78 | "/node_modules/",
79 | "/test/"
80 | ],
81 | "coverageThreshold": {
82 | "global": {
83 | "branches": 94,
84 | "functions": 94,
85 | "lines": 94,
86 | "statements": 94
87 | }
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import babel from "rollup-plugin-babel";
3 | import resolve from "rollup-plugin-node-resolve";
4 |
5 | const rollupConfig = {
6 | input: "src/bin/index.js",
7 | plugins: [
8 | resolve({
9 | jail: path.resolve(__dirname, "src"),
10 | }),
11 | babel({
12 | runtimeHelpers: true,
13 | exclude: "node_modules/**", // only transpile our source code
14 | }),
15 | ],
16 | output: {
17 | file: "dist/index.js",
18 | sourceMap: true,
19 | format: "cjs",
20 | },
21 | };
22 |
23 | export default rollupConfig;
24 |
--------------------------------------------------------------------------------
/src/Updtr.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsdoc/no-undefined-types */
2 | import EventEmitter from "events";
3 | import path from "path";
4 | import fs from "./util/fs";
5 | import {
6 | USE_OPTIONS,
7 | UPDATE_TO_OPTIONS,
8 | SAVE_OPTIONS,
9 | } from "./constants/config";
10 | import exec from "./exec/exec";
11 | import cmds from "./exec/cmds";
12 | import parse from "./exec/parse";
13 | import {
14 | RequiredOptionMissingError,
15 | OptionValueNotSupportedError,
16 | YarnWithCustomRegistryError,
17 | } from "./errors";
18 |
19 | // node v4 has no dedicated constants object.
20 | // Remove this if node v4 is not supported anymore.
21 | const FS_CONSTANTS = fs.constants === undefined ? fs : fs.constants;
22 |
23 | function checkCwd(cwd) {
24 | if (typeof cwd !== "string") {
25 | throw new RequiredOptionMissingError("cwd", cwd);
26 | }
27 | }
28 |
29 | function checkUse(use) {
30 | if (USE_OPTIONS.indexOf(use) === -1) {
31 | throw new OptionValueNotSupportedError("use", use);
32 | }
33 | }
34 |
35 | function checkUpdateTo(updateTo) {
36 | if (UPDATE_TO_OPTIONS.indexOf(updateTo) === -1) {
37 | throw new OptionValueNotSupportedError("updateTo", updateTo);
38 | }
39 | }
40 |
41 | function checkSave(save) {
42 | if (SAVE_OPTIONS.indexOf(save) === -1) {
43 | throw new OptionValueNotSupportedError("save", save);
44 | }
45 | }
46 |
47 | function checkForYarnWithCustomReg(packageManager, registry) {
48 | if (packageManager === "yarn" && registry !== undefined) {
49 | throw new YarnWithCustomRegistryError();
50 | }
51 | }
52 |
53 | export default class Updtr extends EventEmitter {
54 | // TODO: Add typings for UpdtrConfig
55 | // eslint-disable-next-line
56 | /**
57 | * The config passed-in here should look identically to the CLI config.
58 | * Dash-cased properties should be renamed to camelCased.
59 | * The goal is to replicate the API of the CLI as close as possible so users don't
60 | * have to guess the options.
61 | *
62 | * @param {UpdtrConfig} config
63 | */
64 | constructor(config) {
65 | super();
66 |
67 | const cwd = config.cwd;
68 | const registry = config.registry;
69 |
70 | const packageManager = config.use === undefined ?
71 | USE_OPTIONS[0] :
72 | config.use;
73 |
74 | const updateTo = config.updateTo === undefined ?
75 | UPDATE_TO_OPTIONS[0] :
76 | config.updateTo;
77 |
78 | const exclude = Array.isArray(config.exclude) === true ?
79 | config.exclude :
80 | [];
81 |
82 | const save = config.save === undefined ? SAVE_OPTIONS[0] : config.save;
83 |
84 | checkCwd(cwd);
85 | checkUse(packageManager);
86 | checkForYarnWithCustomReg(packageManager, registry);
87 | checkUpdateTo(updateTo);
88 | checkSave(save);
89 |
90 | this.config = {
91 | cwd,
92 | use: packageManager,
93 | exclude,
94 | test: config.test,
95 | registry,
96 | updateTo,
97 | save,
98 | interactive: config.interactive,
99 | };
100 | this.cmds = cmds[packageManager];
101 | this.parse = parse[packageManager];
102 |
103 | if (typeof config.test === "string") {
104 | this.cmds = {
105 | ...this.cmds,
106 | test: () => config.test,
107 | };
108 | }
109 | }
110 |
111 | async canAccessPackageJson() {
112 | let result = true;
113 |
114 | try {
115 | await fs.access(
116 | path.join(this.config.cwd, "package.json"),
117 | FS_CONSTANTS.R_OK | FS_CONSTANTS.W_OK // eslint-disable-line no-bitwise
118 | );
119 | } catch (err) {
120 | result = false;
121 | }
122 |
123 | return result;
124 | }
125 |
126 | exec(cmd) {
127 | return exec(this.config.cwd, cmd);
128 | }
129 |
130 | readFile(filenameInCwd) {
131 | return fs.readFile(path.join(this.config.cwd, filenameInCwd), "utf8");
132 | }
133 |
134 | writeFile(filenameInCwd, contents) {
135 | return fs.writeFile(
136 | path.join(this.config.cwd, filenameInCwd),
137 | contents
138 | );
139 | }
140 |
141 | dispose() {
142 | this.removeAllListeners();
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/bin/argv.js:
--------------------------------------------------------------------------------
1 | import {EOL} from "os";
2 | import fs from "fs";
3 | import path from "path";
4 | import yargs from "yargs";
5 | import chalk from "chalk";
6 | import reporters from "../reporters";
7 | import {
8 | USE_OPTIONS,
9 | USE_NPM,
10 | USE_YARN,
11 | UPDATE_TO_OPTIONS,
12 | SAVE_OPTIONS,
13 | } from "../constants/config";
14 |
15 | const reporterNames = Object.keys(reporters);
16 | const pathToYarnLock = path.join(process.cwd(), "yarn.lock");
17 | const useDefault = fs.existsSync(pathToYarnLock) === true ? USE_YARN : USE_NPM;
18 |
19 | export default yargs
20 | .usage(
21 | [
22 | "",
23 | chalk.bold.cyan("Update outdated npm modules with zero pain™"),
24 | `${chalk.bold("Usage:")} $0 ${chalk.dim("[options]")}`,
25 | ].join(EOL)
26 | )
27 | .option("use", {
28 | describe: "Specify the package manager to use",
29 | choices: USE_OPTIONS,
30 | default: useDefault,
31 | alias: "u",
32 | })
33 | .option("exclude", {
34 | describe: "Space separated list of module names that should not be updated",
35 | array: true,
36 | alias: "ex",
37 | })
38 | .option("interactive", {
39 | describe: "Let you select the packages you want to update",
40 | alias: "i",
41 | boolean: true,
42 | default: false,
43 | })
44 | .option("update-to", {
45 | describe: "Specify which updates you want to install",
46 | choices: UPDATE_TO_OPTIONS,
47 | default: UPDATE_TO_OPTIONS[0],
48 | alias: "to",
49 | })
50 | .option("save", {
51 | describe: "Specify how updated versions should be saved to the package.json",
52 | choices: SAVE_OPTIONS,
53 | default: SAVE_OPTIONS[0],
54 | alias: "s",
55 | })
56 | .option("reporter", {
57 | describe: "Choose a reporter for the console output",
58 | choices: reporterNames,
59 | default: reporterNames[0],
60 | alias: "r",
61 | })
62 | .option("test", {
63 | describe: "Specify a custom test command. Surround with quotes.",
64 | alias: "t",
65 | })
66 | .option("test-stdout", {
67 | describe: "Show test stdout if the update fails",
68 | boolean: true,
69 | default: false,
70 | alias: "out",
71 | })
72 | .option("registry", {
73 | describe: "Specify a custom registry to use",
74 | alias: "reg",
75 | })
76 | .version()
77 | .wrap(null)
78 | .help().argv;
79 |
--------------------------------------------------------------------------------
/src/bin/index.js:
--------------------------------------------------------------------------------
1 | import reporters from "../reporters";
2 | import argv from "./argv";
3 | import {create, run} from "..";
4 |
5 | async function start() {
6 | const cwd = process.cwd();
7 | const config = {...argv};
8 |
9 | const reporterConfig = {
10 | stream: process.stdout,
11 | testStdout: argv.testStdout,
12 | };
13 |
14 | const reporter = reporters[argv.reporter];
15 |
16 | config.cwd = cwd;
17 |
18 | const updtr = create(config);
19 |
20 | reporter(updtr, reporterConfig);
21 | try {
22 | await run(updtr);
23 | } catch (err) {
24 | updtr.emit("error", err);
25 | }
26 | }
27 |
28 | start();
29 |
--------------------------------------------------------------------------------
/src/constants/config.js:
--------------------------------------------------------------------------------
1 | // The first value in arrays is the default value
2 |
3 | export const USE_NPM = "npm";
4 | export const USE_YARN = "yarn";
5 | export const USE_OPTIONS = [USE_NPM, USE_YARN];
6 | export const UPDATE_TO_LATEST = "latest";
7 | export const UPDATE_TO_NON_BREAKING = "non-breaking";
8 | export const UPDATE_TO_WANTED = "wanted";
9 | export const UPDATE_TO_OPTIONS = [
10 | UPDATE_TO_LATEST,
11 | UPDATE_TO_NON_BREAKING,
12 | UPDATE_TO_WANTED,
13 | ];
14 | export const SAVE_SMART = "smart";
15 | export const SAVE_CARET = "caret";
16 | export const SAVE_EXACT = "exact";
17 | export const SAVE_OPTIONS = [SAVE_SMART, SAVE_CARET, SAVE_EXACT];
18 |
--------------------------------------------------------------------------------
/src/constants/filterReasons.js:
--------------------------------------------------------------------------------
1 | export const GIT = "git";
2 | export const EXOTIC = "exotic";
3 | export const UNSTABLE = "unstable";
4 | export const EXCLUDED = "excluded";
5 | export const NOT_WANTED = "not-wanted";
6 |
--------------------------------------------------------------------------------
/src/errors.js:
--------------------------------------------------------------------------------
1 | // Errors are not extendable in node v4.
2 | // Remove this if node v4 is not supported anymore.
3 | import Error from "es6-error";
4 |
5 | export class PackageJsonNoAccessError extends Error {
6 | constructor(dir) {
7 | super(`Cannot access package.json in ${dir}`);
8 | }
9 | }
10 |
11 | export class RequiredOptionMissingError extends Error {
12 | constructor(optionName, optionValue) {
13 | super(
14 | `Required option ${optionName} is missing. Instead received ${optionValue}`
15 | );
16 | }
17 | }
18 |
19 | export class OptionValueNotSupportedError extends Error {
20 | constructor(optionName, unsupportedValue) {
21 | super(`Unsupported value ${unsupportedValue} for option ${optionName}`);
22 | }
23 | }
24 |
25 | export class YarnWithCustomRegistryError extends Error {
26 | constructor() {
27 | super(
28 | "yarn does not support custom registries yet. Please use a .npmrc file to achieve this"
29 | );
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/exec/cmds.js:
--------------------------------------------------------------------------------
1 | function installFn(baseCmd) {
2 | return ({registry, modules} = {}) =>
3 | [baseCmd, stringifyRegistry(registry), stringifyModules(modules)].join(
4 | ""
5 | );
6 | }
7 |
8 | function stringifyModules(modules) {
9 | return Array.isArray(modules) === true ?
10 | " " +
11 | modules
12 | // We need to wrap this in double-quotes because some semver
13 | // characters like the caret symbol are reserved characters on Windows.
14 | .map(({name, version}) => `"${name}@${version}"`)
15 | .join(" ") :
16 | "";
17 | }
18 |
19 | function stringifyRegistry(registry) {
20 | return registry === undefined ? "" : ` --registry "${registry}"`;
21 | }
22 |
23 | const cmds = {
24 | npm: {
25 | outdated: () => "npm outdated --json --depth=0",
26 | installMissing: installFn("npm install"),
27 | install: installFn("npm install"),
28 | // remove: ({ name }) => ["npm remove ", name].join(""),
29 | test: () => "npm test",
30 | list: ({modules} = {}) => [
31 | "npm ls --json --depth=0",
32 | Array.isArray(modules) === true ? " " + modules.join(" ") : "",
33 | ].join(""),
34 | },
35 | // yarn does not support custom registries yet.
36 | // However, these renderers accept them anyway.
37 | yarn: {
38 | outdated: () => "yarn outdated --json --flat",
39 | installMissing: installFn("yarn"),
40 | install: installFn("yarn add"),
41 | // remove: ({ name }) => ["yarn remove ", name].join(""),
42 | test: () => "yarn test",
43 | list: ({modules} = {}) => [
44 | "yarn list --json --depth=0",
45 | Array.isArray(modules) === true ? " " + modules.join(" ") : "",
46 | ].join(""),
47 | },
48 | };
49 |
50 | export default cmds;
51 |
--------------------------------------------------------------------------------
/src/exec/exec.js:
--------------------------------------------------------------------------------
1 | import childProcess from "child_process";
2 |
3 | function promiseExec(cwd, cmd) {
4 | return new Promise(resolve => {
5 | childProcess.exec(
6 | cmd,
7 | {maxBuffer: Infinity, encoding: "utf8", cwd},
8 | // We need to use the callback API here
9 | (err, stdout, stderr) => void resolve({err, stdout, stderr}) // eslint-disable-line promise/prefer-await-to-callbacks
10 | );
11 | });
12 | }
13 |
14 | export default (async function exec(cwd, cmd) {
15 | const {err, stdout, stderr} = await promiseExec(cwd, cmd);
16 |
17 | if (err !== null) {
18 | err.stdout = stdout;
19 | err.stderr = stderr;
20 |
21 | throw err;
22 | }
23 |
24 | return {stdout, stderr};
25 | });
26 |
--------------------------------------------------------------------------------
/src/exec/parse.js:
--------------------------------------------------------------------------------
1 | const STRING_PROPERTIES = ["name", "current", "wanted", "latest"];
2 |
3 | function isNotEmptyString(value) {
4 | return typeof value === "string" && value.length > 0;
5 | }
6 |
7 | function returnIfValid(result) {
8 | STRING_PROPERTIES.forEach(prop => {
9 | if (isNotEmptyString(result[prop]) === false) {
10 | throw new Error("Unexpected output format of package manager");
11 | }
12 | });
13 |
14 | return result;
15 | }
16 |
17 | function tryParse(parser) {
18 | return (stdout, cmd) => {
19 | try {
20 | return parser(stdout);
21 | } catch (err) {
22 | err.message = `Error when trying to parse stdout from command '${cmd}': ${err.message}`;
23 | throw err;
24 | }
25 | };
26 | }
27 |
28 | function arrToObj(arr, keys) {
29 | return keys.reduce((obj, key, i) => {
30 | obj[key] = arr[i];
31 |
32 | return obj;
33 | }, {});
34 | }
35 |
36 | // By sorting the parsed data, we get deterministic results across different npm and yarn versions.
37 | // As a nice side-effect, a package like eslint will always be updated before eslint-config-peerigon
38 | // which might have a peer dependency on eslint
39 | // See https://github.com/peerigon/updtr/issues/48
40 | function sortByName(o1, o2) {
41 | return o1.name > o2.name;
42 | }
43 |
44 | function npmParser(stdout) {
45 | const trimmed = stdout.trim();
46 |
47 | if (trimmed.length === 0) {
48 | return null;
49 | }
50 |
51 | return JSON.parse(trimmed);
52 | }
53 |
54 | export function splitYarnLines(stdout) {
55 | // Yarn is using \n on all platforms now in their stdout
56 | return stdout.split("\n");
57 | }
58 |
59 | function yarnParser(stdout, wantedTypeProperty) {
60 | try {
61 | return npmParser(stdout);
62 | } catch (error) {
63 | /* in some cases (e.g. when printing the outdated result), yarn prints for each line a separate JSON object */
64 | /* in that case, we need to look for a { type: "table" } object which holds the interesting data to display */
65 | }
66 | const dataLine = splitYarnLines(stdout)
67 | .map(line => line.trim())
68 | .filter(line => line !== "")
69 | .find(line => {
70 | try {
71 | console.log(line);
72 | const parsedLine = JSON.parse(line);
73 |
74 | return parsedLine.type === wantedTypeProperty;
75 | } catch (error) {
76 | console.log(error);
77 |
78 | return false;
79 | }
80 | });
81 |
82 | if (dataLine === undefined) {
83 | throw new Error(`Could not find object with type === ${wantedTypeProperty}`);
84 | }
85 |
86 | return JSON.parse(dataLine);
87 | }
88 |
89 | const parse = {
90 | npm: {
91 | outdated: tryParse(stdout => {
92 | const parsed = npmParser(stdout);
93 |
94 | if (parsed === null) {
95 | return [];
96 | }
97 |
98 | const names = Object.keys(parsed);
99 |
100 | return names
101 | .map(name => parsed[name])
102 | .map((dep, index) =>
103 | returnIfValid({
104 | name: names[index],
105 | current: dep.current,
106 | wanted: dep.wanted,
107 | latest: dep.latest,
108 | })
109 | )
110 | .sort(sortByName);
111 | }),
112 | list: tryParse(stdout => {
113 | const parsed = npmParser(stdout);
114 |
115 | return (parsed.dependencies === undefined ?
116 | [] :
117 | Object.keys(parsed.dependencies)
118 | .map(name => ({
119 | name,
120 | version: parsed.dependencies[name].version,
121 | }))
122 | .sort(sortByName));
123 | }),
124 | },
125 | yarn: {
126 | outdated: tryParse(
127 | stdout => {
128 | const parsed = yarnParser(stdout, "table");
129 |
130 | if (parsed === null) {
131 | return [];
132 | }
133 |
134 | return parsed.data.body
135 | .map(row => arrToObj(row, parsed.data.head))
136 | .map(dep =>
137 | returnIfValid({
138 | name: dep.Package,
139 | current: dep.Current,
140 | wanted: dep.Wanted,
141 | latest: dep.Latest,
142 | })
143 | )
144 | .sort(sortByName);
145 | }
146 | ),
147 | list: tryParse(stdout => {
148 | const parsed = yarnParser(stdout, "tree");
149 |
150 | if (parsed.data.trees.length === 0) {
151 | return [];
152 | }
153 |
154 | return parsed.data.trees
155 | .map(dependency => {
156 | const [name, version] = dependency.name.split("@");
157 |
158 | if (isNotEmptyString(name) === false || isNotEmptyString(version) === false) {
159 | throw new Error(`Could not parse dependency name "${dependency.name}"`);
160 | }
161 |
162 | return {
163 | name,
164 | version,
165 | };
166 | })
167 | .sort(sortByName);
168 | }),
169 | },
170 | };
171 |
172 | export default parse;
173 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import run from "./run";
2 | import Updtr from "./Updtr";
3 | import * as errors from "./errors";
4 |
5 | export function create(config) {
6 | return new Updtr(config);
7 | }
8 |
9 | export {errors, run};
10 |
--------------------------------------------------------------------------------
/src/reporters/basic.js:
--------------------------------------------------------------------------------
1 | import ansiEscapes from "ansi-escapes";
2 | import chalk from "chalk";
3 | import unicons from "unicons";
4 | import {
5 | filterSuccessfulUpdates,
6 | filterFailedUpdates,
7 | } from "../tasks/util/filterUpdateResults";
8 | import Message from "./util/Message";
9 | import Indicator, {
10 | INDICATOR_NEUTRAL,
11 | INDICATOR_PENDING,
12 | INDICATOR_OK,
13 | INDICATOR_FAIL,
14 | } from "./util/Indicator";
15 | import customConfigToLines from "./util/customConfigToLines";
16 | import pluralize from "./util/pluralize";
17 | import handleError from "./util/handleError";
18 | import msToString from "./util/msToString";
19 |
20 | function updatingLine(updateTask) {
21 | return [
22 | new Indicator(INDICATOR_PENDING),
23 | chalk.bold(updateTask.name),
24 | chalk.grey("updating"),
25 | updateTask.rollbackTo,
26 | chalk.grey(unicons.arrowRight),
27 | updateTask.updateTo + chalk.grey("..."),
28 | ].join(" ");
29 | }
30 |
31 | function testingLine(updateTask) {
32 | return [
33 | new Indicator(INDICATOR_PENDING),
34 | chalk.bold(updateTask.name),
35 | chalk.grey("testing..."),
36 | ].join(" ");
37 | }
38 |
39 | function rollbackLine(updateTask) {
40 | return [
41 | new Indicator(INDICATOR_FAIL),
42 | chalk.bold.red(updateTask.name),
43 | chalk.grey("rolling back"),
44 | updateTask.updateTo,
45 | chalk.grey(unicons.arrowRight),
46 | updateTask.rollbackTo + chalk.grey("..."),
47 | ].join(" ");
48 | }
49 |
50 | function successLine(updateTask) {
51 | return [
52 | new Indicator(INDICATOR_OK),
53 | chalk.bold(updateTask.name),
54 | updateTask.updateTo,
55 | chalk.grey("success"),
56 | ].join(" ");
57 | }
58 |
59 | function failLine(updateTask) {
60 | return [
61 | new Indicator(INDICATOR_FAIL),
62 | chalk.bold.red(updateTask.name),
63 | updateTask.updateTo,
64 | chalk.grey("failed"),
65 | ].join(" ");
66 | }
67 |
68 | function excludedLine(excluded) {
69 | return [
70 | new Indicator(INDICATOR_NEUTRAL),
71 | chalk.bold(excluded.name),
72 | chalk.grey(excluded.reason),
73 | ].join(" ");
74 | }
75 |
76 | function cmdToLines(description, cmd) {
77 | const lines = Array.isArray(description) === true ?
78 | description :
79 | [description];
80 |
81 | return lines.concat([chalk.grey(`> ${cmd} `)]);
82 | }
83 |
84 | function writeLinesToConsole(lines) {
85 | if (lines.length === 0) {
86 | return;
87 | }
88 | console.log(ansiEscapes.eraseDown + lines.join("\n"));
89 | }
90 |
91 | export default function basic(updtr, reporterConfig) {
92 | const startTime = Date.now();
93 | let excludedModules;
94 |
95 | updtr.on("start", ({config}) => {
96 | writeLinesToConsole(customConfigToLines(config));
97 | });
98 | updtr.on("init/install-missing", ({cmd}) => {
99 | writeLinesToConsole(
100 | cmdToLines(
101 | "Installing missing dependencies" + chalk.grey("..."),
102 | cmd
103 | )
104 | );
105 | });
106 | updtr.on("init/collect", ({cmd}) => {
107 | writeLinesToConsole(
108 | cmdToLines("Looking for outdated modules" + chalk.grey("..."), cmd)
109 | );
110 | });
111 | updtr.on("init/end", ({updateTasks, excluded}) => {
112 | excludedModules = excluded;
113 | if (updateTasks.length === 0 && excluded.length === 0) {
114 | writeLinesToConsole(["Everything " + chalk.bold("up-to-date")]);
115 | } else if (updateTasks.length === 0) {
116 | writeLinesToConsole([
117 | chalk.bold("No updates available") +
118 | " for the given modules and version range",
119 | ]);
120 | } else {
121 | writeLinesToConsole([
122 | new Message("Found " + chalk.bold("%s update%s") + ".", [
123 | updateTasks.length,
124 | pluralize(updateTasks.length),
125 | ]),
126 | "",
127 | ]);
128 | }
129 | });
130 | updtr.on("batch-update/updating", event => {
131 | writeLinesToConsole(
132 | cmdToLines(event.updateTasks.map(updatingLine), event.cmd)
133 | );
134 | });
135 | updtr.on("batch-update/testing", event => {
136 | writeLinesToConsole(
137 | cmdToLines(event.updateTasks.map(testingLine), event.cmd)
138 | );
139 | });
140 | updtr.on("batch-update/rollback", event => {
141 | writeLinesToConsole(
142 | cmdToLines(event.updateTasks.map(rollbackLine), event.cmd)
143 | );
144 | });
145 | updtr.on("batch-update/result", event => {
146 | if (event.success === true) {
147 | writeLinesToConsole(
148 | event.updateTasks.map(event.success ? successLine : failLine)
149 | );
150 | }
151 | // Not showing the test stdout here when there was an error because
152 | // we will proceed with the sequential update.
153 | });
154 | updtr.on("sequential-update/updating", event => {
155 | writeLinesToConsole(cmdToLines(updatingLine(event), event.cmd));
156 | });
157 | updtr.on("sequential-update/testing", event => {
158 | writeLinesToConsole(cmdToLines(testingLine(event), event.cmd));
159 | });
160 | updtr.on("sequential-update/rollback", event => {
161 | writeLinesToConsole(cmdToLines(rollbackLine(event), event.cmd));
162 | });
163 | updtr.on("sequential-update/result", event => {
164 | writeLinesToConsole([(event.success ? successLine : failLine)(event)]);
165 | if (reporterConfig.testStdout === true && event.success === false) {
166 | writeLinesToConsole([event.stdout]);
167 | }
168 | });
169 | updtr.on("end", ({results}) => {
170 | const duration = msToString(Date.now() - startTime);
171 | const successful = filterSuccessfulUpdates(results);
172 | const failed = filterFailedUpdates(results);
173 |
174 | writeLinesToConsole([""]);
175 |
176 | if (successful.length > 0) {
177 | writeLinesToConsole([
178 | new Message(chalk.bold("%s successful") + " update%s.", [
179 | successful.length,
180 | pluralize(successful.length),
181 | ]),
182 | ]);
183 | }
184 | if (failed.length > 0) {
185 | writeLinesToConsole([
186 | new Message(chalk.bold("%s failed") + " update%s.", [
187 | failed.length,
188 | pluralize(failed.length),
189 | ]),
190 | ]);
191 | }
192 | if (excludedModules.length > 0) {
193 | const list = excludedModules.map(excludedLine);
194 |
195 | if (successful.length > 0 || failed.length > 0) {
196 | writeLinesToConsole([""]);
197 | }
198 | writeLinesToConsole(
199 | [
200 | new Message(chalk.bold("%s skipped") + " module%s:", [
201 | excludedModules.length,
202 | pluralize(excludedModules.length),
203 | ]),
204 | "",
205 | ].concat(list)
206 | );
207 | }
208 |
209 | writeLinesToConsole(["", new Message("Finished after %s.", [duration])]);
210 | });
211 | updtr.on("error", err => void handleError(err));
212 | }
213 |
--------------------------------------------------------------------------------
/src/reporters/dense.js:
--------------------------------------------------------------------------------
1 | import chalk from "chalk";
2 | import unicons from "unicons";
3 | import {
4 | filterSuccessfulUpdates,
5 | filterFailedUpdates,
6 | } from "../tasks/util/filterUpdateResults";
7 | import Projector from "./util/Projector";
8 | import Terminal from "./util/Terminal";
9 | import Message from "./util/Message";
10 | import Spinner from "./util/Spinner";
11 | import Indicator, {
12 | INDICATOR_NEUTRAL,
13 | INDICATOR_PENDING,
14 | INDICATOR_OK,
15 | INDICATOR_FAIL,
16 | } from "./util/Indicator";
17 | import customConfigToLines from "./util/customConfigToLines";
18 | import pluralize from "./util/pluralize";
19 | import handleError from "./util/handleError";
20 | import msToString from "./util/msToString";
21 |
22 | const spinner = new Spinner("dots");
23 |
24 | function updatingLine(updateTask) {
25 | return [
26 | new Indicator(INDICATOR_PENDING),
27 | chalk.bold(updateTask.name),
28 | chalk.grey("updating"),
29 | updateTask.rollbackTo,
30 | chalk.grey(unicons.arrowRight),
31 | updateTask.updateTo + chalk.grey("..."),
32 | ].join(" ");
33 | }
34 |
35 | function testingLine(updateTask) {
36 | return [
37 | new Indicator(INDICATOR_PENDING),
38 | chalk.bold(updateTask.name),
39 | chalk.grey("testing..."),
40 | ].join(" ");
41 | }
42 |
43 | function rollbackLine(updateTask) {
44 | return [
45 | new Indicator(INDICATOR_FAIL),
46 | chalk.bold.red(updateTask.name),
47 | chalk.grey("rolling back"),
48 | updateTask.updateTo,
49 | chalk.grey(unicons.arrowRight),
50 | updateTask.rollbackTo + chalk.grey("..."),
51 | ].join(" ");
52 | }
53 |
54 | function successLine(updateTask) {
55 | return [
56 | new Indicator(INDICATOR_OK),
57 | chalk.bold(updateTask.name),
58 | updateTask.updateTo,
59 | chalk.grey("success"),
60 | ].join(" ");
61 | }
62 |
63 | function failLine(updateTask) {
64 | return [
65 | new Indicator(INDICATOR_FAIL),
66 | chalk.bold.red(updateTask.name),
67 | updateTask.updateTo,
68 | chalk.grey("failed"),
69 | ].join(" ");
70 | }
71 |
72 | function excludedLine(excluded) {
73 | return [
74 | new Indicator(INDICATOR_NEUTRAL),
75 | chalk.bold(excluded.name),
76 | chalk.grey(excluded.reason),
77 | ].join(" ");
78 | }
79 |
80 | function cmdToLines(description, cmd) {
81 | const lines = Array.isArray(description) === true ?
82 | description :
83 | [description];
84 |
85 | return lines.concat([chalk.grey(`> ${cmd} `), spinner]);
86 | }
87 |
88 | function dense(updtr, reporterConfig) {
89 | const terminal = new Terminal(reporterConfig.stream);
90 | const projector = new Projector(terminal);
91 | const startTime = Date.now();
92 | let excludedModules;
93 |
94 | updtr.on("start", ({config}) => {
95 | terminal.append(customConfigToLines(config));
96 | });
97 | updtr.on("init/install-missing", ({cmd}) => {
98 | projector.display(
99 | cmdToLines(
100 | "Installing missing dependencies" + chalk.grey("..."),
101 | cmd
102 | )
103 | );
104 | });
105 | updtr.on("init/collect", ({cmd}) => {
106 | projector.display(
107 | cmdToLines("Looking for outdated modules" + chalk.grey("..."), cmd)
108 | );
109 | });
110 | updtr.on("init/end", ({updateTasks, excluded}) => {
111 | excludedModules = excluded;
112 | projector.stop();
113 | if (updateTasks.length === 0 && excluded.length === 0) {
114 | terminal.append(["Everything " + chalk.bold("up-to-date")]);
115 | } else if (updateTasks.length === 0) {
116 | terminal.append([
117 | chalk.bold("No updates available") +
118 | " for the given modules and version range",
119 | ]);
120 | } else {
121 | terminal.append([
122 | new Message("Found " + chalk.bold("%s update%s") + ".", [
123 | updateTasks.length,
124 | pluralize(updateTasks.length),
125 | ]),
126 | "",
127 | ]);
128 | }
129 | });
130 | updtr.on("batch-update/updating", event => {
131 | projector.display(
132 | cmdToLines(event.updateTasks.map(updatingLine), event.cmd)
133 | );
134 | });
135 | updtr.on("batch-update/testing", event => {
136 | projector.display(
137 | cmdToLines(event.updateTasks.map(testingLine), event.cmd)
138 | );
139 | });
140 | updtr.on("batch-update/rollback", event => {
141 | projector.display(
142 | cmdToLines(event.updateTasks.map(rollbackLine), event.cmd)
143 | );
144 | });
145 | updtr.on("batch-update/result", event => {
146 | projector.stop();
147 | if (event.success === true) {
148 | terminal.append(
149 | event.updateTasks.map(event.success ? successLine : failLine)
150 | );
151 | }
152 | // Not showing the test stdout here when there was an error because
153 | // we will proceed with the sequential update.
154 | });
155 | updtr.on("sequential-update/updating", event => {
156 | projector.display(cmdToLines(updatingLine(event), event.cmd));
157 | });
158 | updtr.on("sequential-update/testing", event => {
159 | projector.display(cmdToLines(testingLine(event), event.cmd));
160 | });
161 | updtr.on("sequential-update/rollback", event => {
162 | projector.display(cmdToLines(rollbackLine(event), event.cmd));
163 | });
164 | updtr.on("sequential-update/result", event => {
165 | projector.stop();
166 | terminal.append([(event.success ? successLine : failLine)(event)]);
167 | if (reporterConfig.testStdout === true && event.success === false) {
168 | terminal.append([event.stdout]);
169 | }
170 | });
171 | updtr.on("end", ({results}) => {
172 | const duration = msToString(Date.now() - startTime);
173 | const successful = filterSuccessfulUpdates(results);
174 | const failed = filterFailedUpdates(results);
175 |
176 | terminal.append([""]);
177 |
178 | if (successful.length > 0) {
179 | terminal.append([
180 | new Message(chalk.bold("%s successful") + " update%s.", [
181 | successful.length,
182 | pluralize(successful.length),
183 | ]),
184 | ]);
185 | }
186 | if (failed.length > 0) {
187 | terminal.append([
188 | new Message(chalk.bold("%s failed") + " update%s.", [
189 | failed.length,
190 | pluralize(failed.length),
191 | ]),
192 | ]);
193 | }
194 | if (excludedModules.length > 0) {
195 | const list = excludedModules.map(excludedLine);
196 |
197 | if (successful.length > 0 || failed.length > 0) {
198 | terminal.append([""]);
199 | }
200 | terminal.append(
201 | [
202 | new Message(chalk.bold("%s skipped") + " module%s:", [
203 | excludedModules.length,
204 | pluralize(excludedModules.length),
205 | ]),
206 | "",
207 | ].concat(list)
208 | );
209 | }
210 |
211 | terminal.append(["", new Message("Finished after %s.", [duration])]);
212 | });
213 | updtr.on("error", err => void handleError(err));
214 | }
215 |
216 | export default dense;
217 |
--------------------------------------------------------------------------------
/src/reporters/index.js:
--------------------------------------------------------------------------------
1 | import basic from "./basic";
2 | import dense from "./dense";
3 | import none from "./none";
4 |
5 | // The first property here is the default reporter
6 | const reporters = {dense, basic, none};
7 |
8 | export default reporters;
9 |
--------------------------------------------------------------------------------
/src/reporters/none.js:
--------------------------------------------------------------------------------
1 | export default Function.prototype;
2 |
--------------------------------------------------------------------------------
/src/reporters/util/Indicator.js:
--------------------------------------------------------------------------------
1 | import unicons from "unicons";
2 | import chalk from "chalk";
3 |
4 | export const INDICATOR_NEUTRAL = 0;
5 | export const INDICATOR_FAIL = 1;
6 | export const INDICATOR_PENDING = 2;
7 | export const INDICATOR_OK = 3;
8 |
9 | const COLORS = [chalk.grey, chalk.red, chalk.yellow, chalk.green];
10 |
11 | export default class Indicator {
12 | constructor(initialState) {
13 | this.state = initialState;
14 | }
15 |
16 | valueOf() {
17 | return COLORS[this.state](unicons.cli("circle"));
18 | }
19 |
20 | toString() {
21 | return this.valueOf();
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/reporters/util/Message.js:
--------------------------------------------------------------------------------
1 | export default class Message {
2 | constructor(template, variables) {
3 | this.template = template;
4 | this.variables = variables;
5 | }
6 |
7 | valueOf() {
8 | const split = this.template.split(/%s/g);
9 |
10 | return split.reduce(
11 | (str, part, i) => str + part + (this.variables[i] || ""),
12 | ""
13 | );
14 | }
15 |
16 | toString() {
17 | return this.valueOf();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/reporters/util/Projector.js:
--------------------------------------------------------------------------------
1 | function lineToString(line) {
2 | if (Array.isArray(line) === true) {
3 | return line.join("");
4 | }
5 |
6 | return String(line);
7 | }
8 |
9 | export default class Projector {
10 | constructor(terminal, frameRate = 10) {
11 | this.terminal = terminal;
12 | this.delay = Math.floor(1000 / frameRate);
13 | this.timeoutId = null;
14 | }
15 |
16 | display(frame) {
17 | if (this.timeoutId !== null) {
18 | this.stop();
19 | }
20 | this.terminal.append(frame.map(lineToString));
21 | this.timeoutId = setTimeout(() => {
22 | this.display(frame);
23 | }, this.delay);
24 | }
25 |
26 | stop() {
27 | if (this.timeoutId === null) {
28 | return;
29 | }
30 | this.terminal.rewind();
31 | clearTimeout(this.timeoutId);
32 | this.timeoutId = null;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/reporters/util/Spinner.js:
--------------------------------------------------------------------------------
1 | import cliSpinners from "cli-spinners";
2 |
3 | const isWin = process.platform === "win32";
4 | const winFallback = "simpleDotsScrolling";
5 |
6 | export default class Spinner {
7 | constructor(spinnerName) {
8 | const spinner = cliSpinners[isWin === true ? winFallback : spinnerName];
9 |
10 | this.frames = spinner.frames;
11 | this.interval = spinner.interval;
12 | this.length = Math.max(...this.frames.map(frame => frame.length));
13 | }
14 |
15 | valueOf() {
16 | const currentInterval = Math.floor(Date.now() / this.interval);
17 | const currentFrame = currentInterval % this.frames.length;
18 |
19 | return this.frames[currentFrame];
20 | }
21 |
22 | toString() {
23 | return this.valueOf();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/reporters/util/Terminal.js:
--------------------------------------------------------------------------------
1 | import ansiEscapes from "ansi-escapes";
2 | import cliCursor from "cli-cursor";
3 | import stringWidth from "string-width";
4 |
5 | function calcNumOfRows(lines, columns) {
6 | return lines
7 | .map(lineContent => Math.ceil(stringWidth(lineContent) / columns))
8 | .reduce((allRows, rows) => allRows + rows, 0);
9 | }
10 |
11 | // Solves some issues where stdout output is truncated
12 | // See https://github.com/nodejs/node/issues/6456
13 | function setBlocking(stream) {
14 | if (stream._handle && typeof stream._handle.setBlocking === "function") {
15 | stream._handle.setBlocking(true);
16 | }
17 | }
18 |
19 | export default class Terminal {
20 | constructor(stream) {
21 | if (stream.isTTY !== true) {
22 | throw new Error("Given stream is not a TTY stream");
23 | }
24 | setBlocking(stream);
25 | cliCursor.hide(stream);
26 | this.stream = stream;
27 | this.lines = [];
28 | this.hasBeenResized = false;
29 | this.stream.on("resize", () => {
30 | this.hasBeenResized = true;
31 | });
32 | }
33 |
34 | append(lines) {
35 | if (lines.length === 0) {
36 | return;
37 | }
38 | this.lines.push(lines);
39 |
40 | const content = this.hasBeenResized === true ?
41 | ansiEscapes.clearScreen +
42 | this.lines.map(lines => lines.join("\n")).join("\n") :
43 | ansiEscapes.eraseDown + lines.join("\n");
44 |
45 | this.stream.write(content + "\n");
46 | this.hasBeenResized = false;
47 | }
48 |
49 | rewind() {
50 | const removedLines = this.lines.pop();
51 | const rows = calcNumOfRows(removedLines, this.stream.columns);
52 |
53 | this.stream.write(ansiEscapes.cursorUp(rows));
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/reporters/util/configList.js:
--------------------------------------------------------------------------------
1 | import {
2 | USE_OPTIONS,
3 | UPDATE_TO_OPTIONS,
4 | SAVE_OPTIONS,
5 | } from "../../constants/config";
6 |
7 | const configNames = {
8 | use: "use",
9 | exclude: "exclude",
10 | test: "test command",
11 | registry: "registry",
12 | updateTo: "update to",
13 | save: "save",
14 | };
15 |
16 | const configValues = {
17 | exclude: list => list.join(", "),
18 | };
19 |
20 | const configFilter = {
21 | cwd: () => false,
22 | use: option => option !== USE_OPTIONS[0],
23 | exclude: list => list.length > 0,
24 | test: cmd => cmd !== undefined,
25 | registry: reg => reg !== undefined,
26 | updateTo: option => option !== UPDATE_TO_OPTIONS[0],
27 | save: option => option !== SAVE_OPTIONS[0],
28 | };
29 |
30 | export default function configList(config) {
31 | return Object.keys(config)
32 | .filter(key => {
33 | const filter = configFilter[key];
34 | const name = configNames[key];
35 |
36 | return (
37 | name !== undefined &&
38 | (filter === undefined || filter(config[key]) === true)
39 | );
40 | })
41 | .map(key => {
42 | const toString = configValues[key] || String;
43 |
44 | return `${configNames[key]}: ${toString(config[key])}`;
45 | });
46 | }
47 |
--------------------------------------------------------------------------------
/src/reporters/util/customConfigToLines.js:
--------------------------------------------------------------------------------
1 | import unicons from "unicons";
2 | import configList from "./configList";
3 |
4 | export default function customConfigToLines(config) {
5 | const list = configList(config);
6 | const lines = [];
7 |
8 | if (list.length > 0) {
9 | lines.push(
10 | "Running updtr with custom configuration:",
11 | "",
12 | ...list.map(item => unicons.cli("circle") + " " + item),
13 | ""
14 | );
15 | }
16 |
17 | return lines;
18 | }
19 |
--------------------------------------------------------------------------------
/src/reporters/util/handleError.js:
--------------------------------------------------------------------------------
1 | import chalk from "chalk";
2 | import {PackageJsonNoAccessError} from "../../errors";
3 |
4 | const ERROR = chalk.bgRed.bold(" ERROR ");
5 |
6 | export default function handleError(err) {
7 | const lines = [""];
8 |
9 | switch (err.constructor) {
10 | case PackageJsonNoAccessError:
11 | lines.push(
12 | ERROR + " Cannot find package.json in current directory."
13 | );
14 | break;
15 | default: {
16 | // The stack does only contain \n, also on windows
17 | const stack = err.stack.split("\n");
18 |
19 | stack.shift();
20 | lines.push(ERROR + " " + err.message);
21 | lines.push(...stack.map(line => chalk.grey(line)));
22 | }
23 | }
24 | lines.push("");
25 |
26 | console.error(lines.join("\n"));
27 | process.exit(1); // eslint-disable-line no-process-exit
28 | }
29 |
--------------------------------------------------------------------------------
/src/reporters/util/msToString.js:
--------------------------------------------------------------------------------
1 | function msToString(milliseconds) {
2 | return Math.floor(milliseconds / 1000).toFixed(1) + "s";
3 | }
4 |
5 | export default msToString;
6 |
--------------------------------------------------------------------------------
/src/reporters/util/pluralize.js:
--------------------------------------------------------------------------------
1 | export default function pluralize(num) {
2 | return num === 1 ? "" : "s";
3 | }
4 |
--------------------------------------------------------------------------------
/src/run.js:
--------------------------------------------------------------------------------
1 | import inquirer from "inquirer";
2 | import chalk from "chalk";
3 | import init from "./tasks/init";
4 | import sequentialUpdate from "./tasks/sequentialUpdate";
5 | import splitUpdateTasks from "./tasks/util/splitUpdateTasks";
6 | import batchUpdate from "./tasks/batchUpdate";
7 | import createUpdateResult from "./tasks/util/createUpdateResult";
8 | import finish from "./tasks/finish";
9 | import updatePackageJson from "./tasks/updatePackageJson";
10 |
11 | async function runUpdateTasks(updtr, updateTasks) {
12 | const results = [];
13 | const {breaking, nonBreaking} = splitUpdateTasks(updateTasks);
14 | const sequentialUpdateTasks = breaking.slice();
15 | let batchSuccess; // can be undefined, true or false
16 | let batchUpdateFailure;
17 |
18 | // Run batch update if we have more than one non-breaking update
19 | // If the batch update fails, it will roll back all modules except the first one.
20 | // This way we can skip one install command since we will run the sequential update for it anyway.
21 | if (nonBreaking.length > 1) {
22 | batchSuccess = await batchUpdate(updtr, nonBreaking);
23 | }
24 |
25 | if (batchSuccess === true) {
26 | results.push(
27 | ...nonBreaking.map(updateTask =>
28 | createUpdateResult(updateTask, true)
29 | )
30 | );
31 | } else {
32 | sequentialUpdateTasks.unshift(...nonBreaking);
33 | // If batchSuccess is false, we have actually executed the batch update and it returned false
34 | if (batchSuccess === false) {
35 | batchUpdateFailure = createUpdateResult(nonBreaking[0], false);
36 | }
37 | }
38 |
39 | // Run sequential update for all breaking updates and non-breaking batch updates that failed
40 | results.push(
41 | ...(await sequentialUpdate(
42 | updtr,
43 | sequentialUpdateTasks,
44 | batchUpdateFailure
45 | ))
46 | );
47 |
48 | return finish(updtr, results);
49 | }
50 |
51 | function generateChoices(tasks) {
52 | let dims = [
53 | "name".length,
54 | "from".length,
55 | // "to".length,
56 | ];
57 |
58 | dims = tasks.reduce((acc, {name, rollbackTo, updateTo}) => ([
59 | name.length > acc[0] ? name.length : acc[0],
60 | rollbackTo.length > acc[1] ? rollbackTo.length : acc[1],
61 | // updateTo.length > acc[2] ? updateTo.length : acc[2],
62 | ]), dims);
63 |
64 | let choices = [
65 | new inquirer.Separator(" "),
66 | new inquirer.Separator(chalk` {green.bold.underline name}${"".padEnd((dims[0] + 3) - "name".length, " ")}{green.bold.underline from}${"".padEnd((dims[1] + 5) - "from".length, " ")}{green.bold.underline to}`),
67 | ];
68 |
69 | choices = choices.concat(tasks.map(task => ({
70 | name: chalk`{green ${task.name.padEnd(dims[0] + 3)}}{blue ${task.rollbackTo.padEnd(dims[1])}} ❯ {blue ${task.updateTo}}`,
71 | value: task,
72 | short: `${task.name}@${task.updateTo}`,
73 | })));
74 |
75 | return choices;
76 | }
77 |
78 | async function selectUpdateTasks(tasks) {
79 | const prompt = inquirer.createPromptModule();
80 | const choices = generateChoices(tasks);
81 |
82 | const {packagesToUpdate} = await prompt([
83 | {
84 | name: "packagesToUpdate",
85 | type: "checkbox",
86 | message: chalk`{white.bold Choose which packages to update.}`,
87 | choices,
88 | pageSize: choices.length,
89 | validate: answer => Boolean(answer.length) || "You must choose at least one package.",
90 | },
91 | ]);
92 |
93 | return packagesToUpdate;
94 | }
95 |
96 | export default (async function run(updtr) {
97 | const results = [];
98 |
99 | updtr.emit("start", {
100 | config: updtr.config,
101 | });
102 |
103 | let {updateTasks} = await init(updtr);
104 |
105 | if (updtr.config.interactive === true) {
106 | updateTasks = await selectUpdateTasks(updateTasks);
107 | }
108 |
109 | if (updateTasks.length > 0) {
110 | results.push(...(await runUpdateTasks(updtr, updateTasks)));
111 | await updatePackageJson(updtr, results);
112 | }
113 |
114 | updtr.emit("end", {
115 | config: updtr.config,
116 | results,
117 | });
118 |
119 | return results;
120 | });
121 |
122 |
--------------------------------------------------------------------------------
/src/tasks/batchUpdate.js:
--------------------------------------------------------------------------------
1 | import Sequence from "./util/Sequence";
2 | import updateTo from "./util/updateTo";
3 | import rollbackTo from "./util/rollbackTo";
4 |
5 | function renderUpdate(updtr, updateTask) {
6 | return updtr.cmds.install({
7 | registry: updtr.config.registry,
8 | modules: updateTask.map(updateTo),
9 | });
10 | }
11 |
12 | function renderTest(updtr) {
13 | return updtr.cmds.test();
14 | }
15 |
16 | function renderRollback(updtr, failedUpdateTasks) {
17 | return updtr.cmds.install({
18 | registry: updtr.config.registry,
19 | modules: failedUpdateTasks.map(rollbackTo),
20 | });
21 | }
22 |
23 | async function update(sequence, updateTasks) {
24 | const updtr = sequence.updtr;
25 | let success;
26 | let testResult;
27 |
28 | await sequence.exec("updating", renderUpdate(updtr, updateTasks));
29 | try {
30 | testResult = await sequence.exec("testing", renderTest(updtr));
31 | success = true;
32 | } catch (err) {
33 | // Remember: instanceof Error might not work in Jest as expected
34 | // https://github.com/facebook/jest/issues/2549
35 | testResult = err;
36 | success = false;
37 | }
38 |
39 | // eslint-disable-next-line require-atomic-updates
40 | sequence.baseEvent.success = success;
41 |
42 | if (success === false && updateTasks.length > 1) {
43 | // If the update was a failure, we roll back every update except the first.
44 | // The first update will be tested with the sequential-update. This way we skip one unnecessary install cycle.
45 | await sequence.exec(
46 | "rollback",
47 | renderRollback(updtr, updateTasks.slice(1))
48 | );
49 | }
50 |
51 | sequence.emit("result", {
52 | stdout: testResult.stdout,
53 | });
54 |
55 | return success;
56 | }
57 |
58 | export default (async function batchUpdate(updtr, updateTasks) {
59 | const sequence = new Sequence("batch-update", updtr, {
60 | updateTasks,
61 | });
62 |
63 | let success = true;
64 |
65 | if (updateTasks.length > 0) {
66 | sequence.start();
67 | success = await update(sequence, updateTasks);
68 | sequence.baseEvent = {
69 | success,
70 | };
71 | sequence.end();
72 | }
73 |
74 | return success;
75 | });
76 |
--------------------------------------------------------------------------------
/src/tasks/finish.js:
--------------------------------------------------------------------------------
1 | import Sequence from "./util/Sequence";
2 | import {isUpdateToNonBreaking} from "./util/createUpdateTask";
3 |
4 | function isIncompleteResult(result) {
5 | return result.success === true && isUpdateToNonBreaking(result) === true;
6 | }
7 |
8 | async function finishIncomplete(sequence, incomplete, allResults) {
9 | const updtr = sequence.updtr;
10 | const modulesToCheck = incomplete.map(result => result.name);
11 | const listCmd = updtr.cmds.list({modules: modulesToCheck});
12 | let stdout;
13 |
14 | try {
15 | stdout = (await sequence.exec("list-incomplete", listCmd)).stdout;
16 | } catch (err) {
17 | // npm may exit with zero code 1 complaining about invalid installed versions
18 | if (err.code > 1) {
19 | throw err;
20 | }
21 | stdout = err.stdout;
22 | }
23 | const moduleVersions = updtr.parse.list(stdout, listCmd);
24 |
25 | return (
26 | allResults
27 | .map(result => {
28 | if (isIncompleteResult(result) === false) {
29 | return result;
30 | }
31 |
32 | const version = moduleVersions.find(
33 | module => module.name === result.name
34 | ).version;
35 |
36 | return {
37 | ...result,
38 | updateTo: version,
39 | };
40 | })
41 | // Remove results where no actual update did happen.
42 | // These results can happen if the updateTo option was set to non-breaking
43 | // and the module did not have a new version for the rollbackTo version range.
44 | .filter(result => result.rollbackTo !== result.updateTo)
45 | );
46 | }
47 |
48 | export default (async function finish(updtr, results) {
49 | const incomplete = results.filter(isIncompleteResult);
50 | const sequence = new Sequence("finish", updtr);
51 | let finishedResults = results;
52 |
53 | if (incomplete.length > 0) {
54 | sequence.start();
55 | sequence.emit("incomplete", {incomplete});
56 | finishedResults = await finishIncomplete(sequence, incomplete, results);
57 | sequence.end({results: finishedResults});
58 | }
59 |
60 | return finishedResults;
61 | });
62 |
--------------------------------------------------------------------------------
/src/tasks/init.js:
--------------------------------------------------------------------------------
1 | import {PackageJsonNoAccessError} from "../errors";
2 | import Sequence from "./util/Sequence";
3 | import createUpdateTask from "./util/createUpdateTask";
4 | import filterUpdateTask from "./util/filterUpdateTask";
5 |
6 | function getUpdateTasksFromStdout(updtr, outdatedCmd, stdout) {
7 | if (stdout.length === 0) {
8 | // When there is not stdout, there is nothing to update
9 | return [];
10 | }
11 |
12 | return updtr.parse
13 | .outdated(stdout, outdatedCmd)
14 | .map(outdated => createUpdateTask(outdated, updtr.config));
15 | }
16 |
17 | export default (async function init(updtr) {
18 | const baseEvent = {config: updtr.config};
19 | const outdatedCmd = updtr.cmds.outdated();
20 | const sequence = new Sequence("init", updtr, baseEvent);
21 | let stdout;
22 |
23 | sequence.start();
24 |
25 | if ((await updtr.canAccessPackageJson()) === false) {
26 | throw new PackageJsonNoAccessError(updtr.config.cwd);
27 | }
28 |
29 | await sequence.exec(
30 | "install-missing",
31 | updtr.cmds.installMissing({
32 | registry: updtr.config.registry,
33 | })
34 | );
35 |
36 | try {
37 | stdout = (await sequence.exec("collect", outdatedCmd)).stdout;
38 | } catch (err) {
39 | // npm exits with zero code 1 when there are outdated dependencies
40 | // We don't check for the package manager here because yarn might change their
41 | // behavior in the future to be npm-compatible.
42 | if (err.code > 1) {
43 | throw err;
44 | }
45 |
46 | stdout = err.stdout;
47 | }
48 |
49 | const allUpdateTasks = getUpdateTasksFromStdout(
50 | updtr,
51 | outdatedCmd,
52 | stdout.trim()
53 | );
54 |
55 | const filterResults = allUpdateTasks.map(updateTask =>
56 | filterUpdateTask(updateTask, updtr.config));
57 |
58 | const result = {
59 | updateTasks: allUpdateTasks.filter(
60 | (updateTask, index) => filterResults[index] === null
61 | ),
62 | excluded: allUpdateTasks.reduce(
63 | (excluded, updateTask, index) => {
64 | const reason = filterResults[index];
65 |
66 | if (reason === null) {
67 | return excluded;
68 | }
69 |
70 | return excluded.concat({
71 | ...updateTask,
72 | reason,
73 | });
74 | },
75 | []
76 | ),
77 | };
78 |
79 | sequence.end(result);
80 |
81 | return result;
82 | });
83 |
--------------------------------------------------------------------------------
/src/tasks/sequentialUpdate.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable require-atomic-updates */
2 | import Sequence from "./util/Sequence";
3 | import createUpdateResult from "./util/createUpdateResult";
4 | import updateTo from "./util/updateTo";
5 | import rollbackTo from "./util/rollbackTo";
6 |
7 | function renderUpdate(updtr, updateTask) {
8 | return updtr.cmds.install({
9 | registry: updtr.config.registry,
10 | modules: [updateTo(updateTask)],
11 | });
12 | }
13 |
14 | function renderTest(updtr) {
15 | return updtr.cmds.test();
16 | }
17 |
18 | function renderRollback(updtr, failedUpdateTask, nextUpdateTask) {
19 | const modules = [rollbackTo(failedUpdateTask)];
20 |
21 | if (nextUpdateTask !== undefined) {
22 | modules.push(updateTo(nextUpdateTask));
23 | }
24 |
25 | return updtr.cmds.install({
26 | registry: updtr.config.registry,
27 | modules,
28 | });
29 | }
30 |
31 | async function runUpdateTask(sequence, updateTasks, i, previousUpdateResults) {
32 | const updateResults = await previousUpdateResults;
33 | const previousUpdateResult = updateResults[updateResults.length - 1];
34 | const updateTask = updateTasks[i];
35 |
36 | // If the previous update was a failure, we don't need to update now because
37 | // during the rollback, the next update is also installed in parallel
38 | const updateNecessary = previousUpdateResult === undefined ?
39 | true :
40 | previousUpdateResult.success === true;
41 |
42 | sequence.baseEvent = {
43 | updateTasks: {
44 | current: i + 1,
45 | total: updateTasks.length,
46 | },
47 | ...updateTask,
48 | };
49 | const updtr = sequence.updtr;
50 | let testResult;
51 | let success;
52 |
53 | if (updateNecessary === true) {
54 | await sequence.exec("updating", renderUpdate(updtr, updateTask));
55 | }
56 |
57 | try {
58 | testResult = await sequence.exec("testing", renderTest(updtr));
59 | success = true;
60 | } catch (err) {
61 | // Remember: instanceof Error might not work in Jest as expected
62 | // https://github.com/facebook/jest/issues/2549
63 | testResult = err;
64 | success = false;
65 | }
66 |
67 | sequence.baseEvent.success = success;
68 |
69 | if (success === false) {
70 | const nextUpdateTask = i + 1 < updateTasks.length ?
71 | updateTasks[i + 1] :
72 | undefined;
73 |
74 | await sequence.exec(
75 | "rollback",
76 | renderRollback(updtr, updateTask, nextUpdateTask)
77 | );
78 | }
79 |
80 | sequence.emit("result", {
81 | stdout: testResult.stdout,
82 | });
83 |
84 | return updateResults.concat(createUpdateResult(updateTask, success));
85 | }
86 |
87 | export default (async function sequentialUpdate(
88 | updtr,
89 | updateTasks,
90 | previousUpdateResult
91 | ) {
92 | const sequence = new Sequence("sequential-update", updtr, {
93 | updateTasks,
94 | });
95 |
96 | if (updateTasks.length === 0) {
97 | return [];
98 | }
99 |
100 | sequence.start();
101 |
102 | const updateResults = await updateTasks.reduce(
103 | (updateResults, updateTask, i) =>
104 | runUpdateTask(sequence, updateTasks, i, updateResults),
105 | previousUpdateResult === undefined ? [] : [previousUpdateResult]
106 | );
107 |
108 | if (previousUpdateResult !== undefined) {
109 | // The previousUpdateResult is the first element in the updateResults array, so let's remove it.
110 | updateResults.shift();
111 | }
112 |
113 | sequence.baseEvent = {
114 | updateResults,
115 | };
116 |
117 | sequence.end();
118 |
119 | return updateResults;
120 | });
121 |
--------------------------------------------------------------------------------
/src/tasks/updatePackageJson.js:
--------------------------------------------------------------------------------
1 | import detectIndent from "detect-indent";
2 | import createUpdatedPackageJson from "./util/createUpdatedPackageJson";
3 | import Sequence from "./util/Sequence";
4 |
5 | function lastChar(str) {
6 | return str.charAt(str.length - 1);
7 | }
8 |
9 | function stringify(newPackageJson, oldPackageJsonStr) {
10 | const indent = detectIndent(oldPackageJsonStr).indent || " ";
11 | let newPackageJsonStr = JSON.stringify(newPackageJson, null, indent);
12 | const lastCharFromOldPackageJson = lastChar(oldPackageJsonStr);
13 |
14 | // Preserve the new line character at the end if there was one
15 | if (lastCharFromOldPackageJson !== lastChar(newPackageJsonStr)) {
16 | newPackageJsonStr += lastCharFromOldPackageJson;
17 | }
18 |
19 | return newPackageJsonStr;
20 | }
21 |
22 | async function enhanceErrorMessage(fn, enhancedMessage) {
23 | try {
24 | return await fn();
25 | } catch (err) {
26 | err.message = enhancedMessage + err.message;
27 | throw err;
28 | }
29 | }
30 |
31 | export default (async function updatePackageJson(updtr, updateResults) {
32 | const sequence = new Sequence("update-package-json", updtr);
33 | let oldPackageJsonStr;
34 |
35 | sequence.start();
36 |
37 | const oldPackageJson = await enhanceErrorMessage(
38 | async () => {
39 | oldPackageJsonStr = await updtr.readFile("package.json");
40 |
41 | return JSON.parse(oldPackageJsonStr);
42 | },
43 | "Error while trying to read the package.json: "
44 | );
45 |
46 | const newPackageJson = createUpdatedPackageJson(
47 | oldPackageJson,
48 | updateResults,
49 | updtr.config
50 | );
51 |
52 | await enhanceErrorMessage(
53 | () =>
54 | updtr.writeFile(
55 | "package.json",
56 | stringify(newPackageJson, oldPackageJsonStr)
57 | ),
58 | "Error while trying to write the package.json: "
59 | );
60 |
61 | sequence.end({
62 | packageJson: newPackageJson,
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/src/tasks/util/Sequence.js:
--------------------------------------------------------------------------------
1 | export default class Sequence {
2 | constructor(name, updtr, baseEvent = {}) {
3 | this.name = name;
4 | this.updtr = updtr;
5 | this.baseEvent = baseEvent;
6 | this.isRunning = false;
7 | }
8 |
9 | start() {
10 | this.isRunning = true;
11 | this.emit("start");
12 | }
13 |
14 | emit(eventName, event = {}) {
15 | const fullEventName = this.name + "/" + eventName;
16 |
17 | if (this.isRunning === false) {
18 | throw new Error(
19 | `Cannot emit event ${fullEventName}: sequence is not running`
20 | );
21 | }
22 | this.updtr.emit(fullEventName, {
23 | ...this.baseEvent,
24 | ...event,
25 | });
26 | }
27 |
28 | exec(step, cmd) {
29 | this.emit(step, {cmd});
30 |
31 | return this.updtr.exec(cmd);
32 | }
33 |
34 | end(result) {
35 | this.emit("end", result);
36 | this.isRunning = false;
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/tasks/util/createUpdateResult.js:
--------------------------------------------------------------------------------
1 | export default function createUpdateResult(updateTask, success) {
2 | return {
3 | name: updateTask.name,
4 | updateTo: updateTask.updateTo,
5 | rollbackTo: updateTask.rollbackTo,
6 | success,
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/tasks/util/createUpdateTask.js:
--------------------------------------------------------------------------------
1 | import {
2 | UPDATE_TO_LATEST,
3 | UPDATE_TO_NON_BREAKING,
4 | UPDATE_TO_WANTED,
5 | } from "../../constants/config";
6 |
7 | function determineUpdateToVersion({current, wanted, latest}, {updateTo}) {
8 | switch (updateTo) {
9 | case UPDATE_TO_LATEST:
10 | return latest;
11 | case UPDATE_TO_WANTED:
12 | return wanted;
13 | case UPDATE_TO_NON_BREAKING:
14 | default:
15 | return "^" + current;
16 | }
17 | }
18 |
19 | export function isUpdateToNonBreaking(updateTask) {
20 | return updateTask.updateTo === "^" + updateTask.rollbackTo;
21 | }
22 |
23 | export default function createUpdateTask(outdated, updtrConfig) {
24 | return {
25 | name: outdated.name,
26 | updateTo: determineUpdateToVersion(outdated, updtrConfig),
27 | rollbackTo: outdated.current,
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/src/tasks/util/createUpdatedPackageJson.js:
--------------------------------------------------------------------------------
1 | import {SAVE_CARET, SAVE_EXACT, SAVE_SMART} from "../../constants/config";
2 | import updateVersionRange from "./updateVersionRange";
3 | import {filterSuccessfulUpdates} from "./filterUpdateResults";
4 |
5 | const dependencyTypes = [
6 | "dependencies",
7 | "devDependencies",
8 | "optionalDependencies",
9 | ];
10 |
11 | function newVersionRange(updtrConfig, oldVersionRange, update) {
12 | switch (updtrConfig.save) {
13 | case SAVE_CARET:
14 | return "^" + update.updateTo;
15 | case SAVE_EXACT:
16 | return update.updateTo;
17 | case SAVE_SMART:
18 | }
19 |
20 | return updateVersionRange(oldVersionRange, update.updateTo);
21 | }
22 |
23 | export default function createUpdatedPackageJson(
24 | oldPackageJson,
25 | updateResults,
26 | updtrConfig
27 | ) {
28 | const newPackageJson = {...oldPackageJson};
29 | const successfulUpdates = filterSuccessfulUpdates(updateResults);
30 | let dependenciesToSave = successfulUpdates;
31 |
32 | dependencyTypes
33 | .filter(type => oldPackageJson[type] !== undefined)
34 | .forEach(type => {
35 | const dependencies = oldPackageJson[type];
36 | const newDependencies = {};
37 |
38 | Object.keys(dependencies).forEach(moduleName => {
39 | const update = successfulUpdates.find(
40 | ({name}) => name === moduleName
41 | );
42 |
43 | const oldVersionRange = dependencies[moduleName];
44 |
45 | newDependencies[moduleName] = update === undefined ?
46 | oldVersionRange :
47 | newVersionRange(updtrConfig, oldVersionRange, update);
48 |
49 | dependenciesToSave = dependenciesToSave.filter(
50 | ({name}) => name !== moduleName
51 | );
52 | });
53 |
54 | newPackageJson[type] = newDependencies;
55 | });
56 |
57 | if (dependenciesToSave.length > 0) {
58 | const dependencies = newPackageJson.dependencies || {};
59 |
60 | dependenciesToSave.forEach(update => {
61 | dependencies[update.name] = update.updateTo;
62 | });
63 |
64 | newPackageJson.dependencies = dependencies;
65 | }
66 |
67 | return newPackageJson;
68 | }
69 |
--------------------------------------------------------------------------------
/src/tasks/util/filterUpdateResults.js:
--------------------------------------------------------------------------------
1 | export function filterSuccessfulUpdates(results) {
2 | return results.filter(result => result.success === true);
3 | }
4 |
5 | export function filterFailedUpdates(results) {
6 | return results.filter(result => result.success === false);
7 | }
8 |
--------------------------------------------------------------------------------
/src/tasks/util/filterUpdateTask.js:
--------------------------------------------------------------------------------
1 | import semver from "semver";
2 | import {
3 | GIT,
4 | UNSTABLE,
5 | NOT_WANTED,
6 | EXCLUDED,
7 | EXOTIC,
8 | } from "../../constants/filterReasons";
9 | import {isUpdateToNonBreaking} from "./createUpdateTask";
10 |
11 | const prePattern = /^pre/;
12 |
13 | const reasonTests = [
14 | {
15 | name: EXCLUDED,
16 | test: (updateTask, {exclude}) =>
17 | exclude.some(name => updateTask.name === name) === true,
18 | },
19 | {
20 | name: GIT,
21 | test: updateTask => updateTask.updateTo === "git",
22 | },
23 | {
24 | name: EXOTIC,
25 | test: updateTask => updateTask.updateTo === "exotic",
26 | },
27 | {
28 | name: NOT_WANTED,
29 | test: updateTask =>
30 | isUpdateToNonBreaking(updateTask) === false &&
31 | semver.lte(updateTask.updateTo, updateTask.rollbackTo) === true,
32 | },
33 | {
34 | name: UNSTABLE,
35 | test(updateTask) {
36 | if (isUpdateToNonBreaking(updateTask) === true) {
37 | return null;
38 | }
39 |
40 | const diff = semver.diff(
41 | updateTask.rollbackTo,
42 | updateTask.updateTo
43 | );
44 |
45 | const unstableTest = diff !== null &&
46 | prePattern.test(diff) === true &&
47 | diff !== "prerelease";
48 |
49 | return unstableTest === true;
50 | },
51 | },
52 | ];
53 |
54 | const reasons = reasonTests.map(test => test.name);
55 |
56 | export default function filterUpdateTask(updateTask, updtrConfig) {
57 | const reasonIndex = reasonTests.findIndex(
58 | reasonTest => reasonTest.test(updateTask, updtrConfig) === true
59 | );
60 |
61 | return reasonIndex === -1 ? null : reasons[reasonIndex];
62 | }
63 |
--------------------------------------------------------------------------------
/src/tasks/util/rollbackTo.js:
--------------------------------------------------------------------------------
1 | export default function rollbackTo(updateTask) {
2 | return {
3 | name: updateTask.name,
4 | version: updateTask.rollbackTo,
5 | };
6 | }
7 |
--------------------------------------------------------------------------------
/src/tasks/util/splitUpdateTasks.js:
--------------------------------------------------------------------------------
1 | import semver from "semver";
2 | import {isUpdateToNonBreaking} from "./createUpdateTask";
3 |
4 | function isBreaking(updateTask) {
5 | return isUpdateToNonBreaking(updateTask) === false &&
6 | semver.satisfies(updateTask.updateTo, "^" + updateTask.rollbackTo) ===
7 | false;
8 | }
9 |
10 | export default function splitUpdateTask(updateTasks) {
11 | const breaking = [];
12 | const nonBreaking = [];
13 |
14 | for (const updateTask of updateTasks) {
15 | (isBreaking(updateTask) === true ? breaking : nonBreaking).push(
16 | updateTask
17 | );
18 | }
19 |
20 | return {
21 | breaking,
22 | nonBreaking,
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/src/tasks/util/updateTo.js:
--------------------------------------------------------------------------------
1 | export default function updateTo(updateTask) {
2 | return {
3 | name: updateTask.name,
4 | version: updateTask.updateTo,
5 | };
6 | }
7 |
--------------------------------------------------------------------------------
/src/tasks/util/updateVersionRange.js:
--------------------------------------------------------------------------------
1 | import semver from "semver";
2 |
3 | // Matches a semver version range that can be transformed to the new version in a safe manner
4 | // eslint-disable-next-line optimize-regex/optimize-regex
5 | const expectedSemverPattern = /^(\^|~|>=|)(\d+)\.(\d+|x|\*)\.(\d+|x|\*)(-[a-z][\d\-.a-z]+|)$/i;
6 | const numberPattern = /^\d+$/;
7 |
8 | function parse(semverString) {
9 | const match = semverString.match(expectedSemverPattern);
10 |
11 | return match === null ?
12 | null :
13 | {
14 | operator: match[1],
15 | major: match[2],
16 | minor: match[3],
17 | patch: match[4],
18 | release: match[5],
19 | };
20 | }
21 |
22 | function isPinned({operator, major, minor, patch}) {
23 | return operator === "" &&
24 | [major, minor, patch].every(
25 | version => numberPattern.test(version) === true
26 | );
27 | }
28 |
29 | function tryVersionRangeUpdate(parsedOldRange, parsedNewVersion) {
30 | const {minor, patch, operator} = parsedOldRange;
31 | let newMinor = parsedNewVersion.minor;
32 | let newPatch = parsedNewVersion.patch;
33 |
34 | if (numberPattern.test(minor) === false) {
35 | newMinor = minor;
36 | newPatch = numberPattern.test(patch) === true ? minor : patch;
37 | } else if (numberPattern.test(patch) === false) {
38 | newPatch = patch;
39 | }
40 |
41 | return operator +
42 | parsedNewVersion.major +
43 | "." +
44 | newMinor +
45 | "." +
46 | newPatch +
47 | parsedNewVersion.release;
48 | }
49 |
50 | function isExpectedNewVersion(parsedNewVersion) {
51 | return parsedNewVersion !== null && parsedNewVersion.operator === "";
52 | }
53 |
54 | function fallbackRange(newVersion) {
55 | return "^" + newVersion;
56 | }
57 |
58 | /**
59 | * Tries to apply the newVersion while maintaining the range (see https://github.com/peerigon/updtr/issues/47)
60 | * This is kind of risky because there are tons of semver possibilities. That's why this
61 | * function is very conservative in accepting semver ranges. If the range is not easily updatable,
62 | * we opt-out to npm's default caret operator.
63 | *
64 | * @param {string} oldRange
65 | * @param {string} newVersion
66 | * @returns {string}
67 | */
68 | export default function updateVersionRange(oldRange, newVersion) {
69 | const parsedOldRange = parse(oldRange.trim());
70 |
71 | if (parsedOldRange !== null) {
72 | if (isPinned(parsedOldRange) === true) {
73 | // The old version was pinned, so the new should also be pinned
74 | return newVersion;
75 | }
76 |
77 | const parsedNewVersion = parse(newVersion);
78 |
79 | if (isExpectedNewVersion(parsedNewVersion) === false) {
80 | return newVersion;
81 | }
82 |
83 | const newVersionRange = tryVersionRangeUpdate(
84 | parsedOldRange,
85 | parsedNewVersion
86 | );
87 |
88 | // All this is kind of error prone so let's do a sanity check if everything's ok
89 | if (semver.satisfies(newVersion, newVersionRange) === true) {
90 | return newVersionRange;
91 | }
92 | }
93 |
94 | return fallbackRange(newVersion);
95 | }
96 |
--------------------------------------------------------------------------------
/src/util/fs.js:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 | import pify from "pify";
3 |
4 | export default pify(fs);
5 |
--------------------------------------------------------------------------------
/test/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "peerigon/tests",
4 | "plugin:jest/recommended"
5 | ],
6 | "plugins": [
7 | "jest"
8 | ],
9 | "env": {
10 | "jest/globals": true
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/test/__snapshots__/Updtr.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`new Updtr() .config should match the default shape 1`] = `
4 | Object {
5 | "cwd": "/updtr/test/cwd",
6 | "exclude": Array [],
7 | "interactive": undefined,
8 | "registry": undefined,
9 | "save": "smart",
10 | "test": undefined,
11 | "updateTo": "latest",
12 | "use": "npm",
13 | }
14 | `;
15 |
--------------------------------------------------------------------------------
/test/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`index .errors should be an object with expected shape 1`] = `
4 | Object {
5 | "OptionValueNotSupportedError": [Function],
6 | "PackageJsonNoAccessError": [Function],
7 | "RequiredOptionMissingError": [Function],
8 | "YarnWithCustomRegistryError": [Function],
9 | }
10 | `;
11 |
--------------------------------------------------------------------------------
/test/__snapshots__/run.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`run() should emit a start event of expected shape: start event 1`] = `
4 | Array [
5 | "start",
6 | Object {
7 | "config": Object {
8 | "cwd": "/updtr/test/cwd",
9 | "exclude": Array [],
10 | "interactive": undefined,
11 | "registry": undefined,
12 | "save": "smart",
13 | "test": undefined,
14 | "updateTo": "latest",
15 | "use": "npm",
16 | },
17 | },
18 | ]
19 | `;
20 |
21 | exports[`run() when there are just breaking updates should emit an end event of expected shape and return the results: breaking updates > end event 1`] = `
22 | Array [
23 | "end",
24 | Object {
25 | "config": Object {
26 | "cwd": "/updtr/test/cwd",
27 | "exclude": Array [],
28 | "interactive": undefined,
29 | "registry": undefined,
30 | "save": "smart",
31 | "test": undefined,
32 | "updateTo": "latest",
33 | "use": "npm",
34 | },
35 | "results": Array [
36 | Object {
37 | "name": "updtr-test-module-1",
38 | "rollbackTo": "1.0.0",
39 | "success": true,
40 | "updateTo": "2.0.0",
41 | },
42 | Object {
43 | "name": "updtr-test-module-2",
44 | "rollbackTo": "1.0.0",
45 | "success": false,
46 | "updateTo": "2.0.0",
47 | },
48 | ],
49 | },
50 | ]
51 | `;
52 |
53 | exports[`run() when there are no outdated dependencies should emit an end event of expected shape and return the results: no-outdated > end event 1`] = `
54 | Array [
55 | "end",
56 | Object {
57 | "config": Object {
58 | "cwd": "/updtr/test/cwd",
59 | "exclude": Array [],
60 | "interactive": undefined,
61 | "registry": undefined,
62 | "save": "smart",
63 | "test": undefined,
64 | "updateTo": "latest",
65 | "use": "npm",
66 | },
67 | "results": Array [],
68 | },
69 | ]
70 | `;
71 |
72 | exports[`run() when there are two non-breaking updates and the update is not ok should emit an end event of expected shape and return the results: two non-breaking and update not ok > end event 1`] = `
73 | Array [
74 | "end",
75 | Object {
76 | "config": Object {
77 | "cwd": "/updtr/test/cwd",
78 | "exclude": Array [],
79 | "interactive": undefined,
80 | "registry": undefined,
81 | "save": "smart",
82 | "test": undefined,
83 | "updateTo": "latest",
84 | "use": "npm",
85 | },
86 | "results": Array [
87 | Object {
88 | "name": "updtr-test-module-1",
89 | "rollbackTo": "2.0.0",
90 | "success": false,
91 | "updateTo": "2.1.1",
92 | },
93 | Object {
94 | "name": "updtr-test-module-2",
95 | "rollbackTo": "2.0.0",
96 | "success": true,
97 | "updateTo": "2.1.1",
98 | },
99 | ],
100 | },
101 | ]
102 | `;
103 |
104 | exports[`run() when there are two non-breaking updates and the update is ok should emit an end event of expected shape and return the results: two non-breaking and update ok > end event 1`] = `
105 | Array [
106 | "end",
107 | Object {
108 | "config": Object {
109 | "cwd": "/updtr/test/cwd",
110 | "exclude": Array [],
111 | "interactive": undefined,
112 | "registry": undefined,
113 | "save": "smart",
114 | "test": undefined,
115 | "updateTo": "latest",
116 | "use": "npm",
117 | },
118 | "results": Array [
119 | Object {
120 | "name": "updtr-test-module-1",
121 | "rollbackTo": "2.0.0",
122 | "success": true,
123 | "updateTo": "2.1.1",
124 | },
125 | Object {
126 | "name": "updtr-test-module-2",
127 | "rollbackTo": "2.0.0",
128 | "success": true,
129 | "updateTo": "2.1.1",
130 | },
131 | ],
132 | },
133 | ]
134 | `;
135 |
136 | exports[`run() when there is just one non-breaking update should emit an end event of expected shape and return the results: one-breaking > end event 1`] = `
137 | Array [
138 | "end",
139 | Object {
140 | "config": Object {
141 | "cwd": "/updtr/test/cwd",
142 | "exclude": Array [],
143 | "interactive": undefined,
144 | "registry": undefined,
145 | "save": "smart",
146 | "test": undefined,
147 | "updateTo": "latest",
148 | "use": "npm",
149 | },
150 | "results": Array [
151 | Object {
152 | "name": "updtr-test-module-1",
153 | "rollbackTo": "2.0.0",
154 | "success": true,
155 | "updateTo": "2.1.1",
156 | },
157 | ],
158 | },
159 | ]
160 | `;
161 |
162 | exports[`run() when updateTo is "non-breaking" should finish incomplete results: updateTo non-breaking > end event 1`] = `
163 | Array [
164 | Object {
165 | "name": "updtr-test-module-1",
166 | "rollbackTo": "1.0.0",
167 | "success": true,
168 | "updateTo": "2.0.0",
169 | },
170 | ]
171 | `;
172 |
--------------------------------------------------------------------------------
/test/bin/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`bin when all arguments have been specified should run updtr with the expected config 1`] = `
4 | Object {
5 | "reporter": "dense",
6 | "reporterConfig": Object {
7 | "stream": null,
8 | "testStdout": true,
9 | },
10 | "updtrConfig": Object {
11 | "cwd": "/test/bin",
12 | "exclude": Array [
13 | "a",
14 | "b",
15 | "c",
16 | ],
17 | "interactive": false,
18 | "save": "exact",
19 | "test": "jest -b",
20 | "updateTo": "non-breaking",
21 | "use": "yarn",
22 | },
23 | }
24 | `;
25 |
26 | exports[`bin when no arguments have been specified should run updtr with the default config 1`] = `
27 | Object {
28 | "reporter": "dense",
29 | "reporterConfig": Object {
30 | "stream": null,
31 | "testStdout": false,
32 | },
33 | "updtrConfig": Object {
34 | "cwd": "/test/bin",
35 | "exclude": Array [],
36 | "interactive": false,
37 | "save": "smart",
38 | "updateTo": "latest",
39 | "use": "npm",
40 | },
41 | }
42 | `;
43 |
44 | exports[`bin when there is an error should emit an error event on the updtr instance 1`] = `"Cannot access package.json in /test/bin"`;
45 |
--------------------------------------------------------------------------------
/test/bin/index.test.js:
--------------------------------------------------------------------------------
1 | import {execFile} from "child_process";
2 | import path from "path";
3 | import {USE_YARN} from "../../src/constants/config";
4 |
5 | const projectPath = path.resolve(__dirname, "..", "..").replace(/\\/g, "/");
6 | const pathToRunBin = require.resolve("../helpers/runBinMock");
7 |
8 | // These tests may take longer on travis
9 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 10 * 60 * 1000;
10 |
11 | async function execRunBinMock(
12 | {cwd = __dirname, args = [], runMock = ""} = {}
13 | ) {
14 | const stdout = await new Promise((resolve, reject) => {
15 | execFile(
16 | "node",
17 | [pathToRunBin, ...args],
18 | {
19 | cwd,
20 | env: {
21 | ...process.env,
22 | RUN_MOCK: runMock,
23 | },
24 | },
25 | (error, stdout, stderr) => {
26 | if (error || stderr) {
27 | reject(error || stderr);
28 |
29 | return;
30 | }
31 | resolve(stdout);
32 | }
33 | );
34 | });
35 |
36 | const stdoutWithoutBasePath = stdout
37 | // Replace double-backslashes with one forward slash
38 | .replace(/\\\\/g, "/")
39 | // Remove project path
40 | .split(projectPath)
41 | .join("");
42 |
43 | return JSON.parse(stdoutWithoutBasePath);
44 | }
45 |
46 | describe("bin", () => {
47 | describe("when no arguments have been specified", () => {
48 | it("should run updtr with the default config", async () => {
49 | const configs = await execRunBinMock();
50 |
51 | expect(configs).toMatchSnapshot();
52 | });
53 | });
54 | describe("when the binary is executed in a directory with a yarn.lock file", () => {
55 | it("should use yarn", async () => {
56 | const cwd = path.resolve(__dirname, "..", "fixtures", "empty");
57 | const configs = await execRunBinMock({cwd});
58 |
59 | expect(configs.updtrConfig.use).toBe(USE_YARN);
60 | });
61 | });
62 | describe("when all arguments have been specified", () => {
63 | it("should run updtr with the expected config", async () => {
64 | const args = [
65 | "--reporter",
66 | "dense",
67 | "--use",
68 | "yarn",
69 | "--exclude",
70 | "a",
71 | "b",
72 | "c",
73 | "--test",
74 | "jest -b",
75 | // yarn does not support custom registries
76 | // We don't test for this option here and assume that it'll work if all the other options did also work
77 | // "--registry http://example.com",
78 | "--update-to",
79 | "non-breaking",
80 | "--test-stdout",
81 | "--save",
82 | "exact",
83 | ];
84 |
85 | const configs = await execRunBinMock({args});
86 |
87 | expect(configs).toMatchSnapshot();
88 | });
89 | });
90 | describe("when there is an error", () => {
91 | it("should emit an error event on the updtr instance", async () => {
92 | const error = await execRunBinMock({
93 | runMock: "rejectWithAccessError",
94 | args: ["--reporter", "error"],
95 | });
96 |
97 | expect(error).toMatchSnapshot();
98 | });
99 | });
100 | });
101 |
--------------------------------------------------------------------------------
/test/exec/__snapshots__/cmds.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`cmds .npm .install() multiple modules custom registry should match snapshot 1`] = `"npm install --registry \\"http://example.com/registry\\" \\"a@1.0.0\\" \\"b@1.0.0\\""`;
4 |
5 | exports[`cmds .npm .install() multiple modules should match snapshot 1`] = `"npm install \\"a@1.0.0\\" \\"b@1.0.0\\""`;
6 |
7 | exports[`cmds .npm .install() one module custom registry should match snapshot 1`] = `"npm install --registry \\"http://example.com/registry\\" \\"a@1.0.0\\""`;
8 |
9 | exports[`cmds .npm .install() one module should match snapshot 1`] = `"npm install \\"a@1.0.0\\""`;
10 |
11 | exports[`cmds .npm .installMissing() custom registry should match snapshot 1`] = `"npm install --registry \\"http://example.com\\""`;
12 |
13 | exports[`cmds .npm .installMissing() should match snapshot 1`] = `"npm install"`;
14 |
15 | exports[`cmds .npm .list() no arguments should match snapshot 1`] = `"npm ls --json --depth=0"`;
16 |
17 | exports[`cmds .npm .list() with modules should match snapshot 1`] = `"npm ls --json --depth=0 a b c"`;
18 |
19 | exports[`cmds .npm .outdated() should match snapshot 1`] = `"npm outdated --json --depth=0"`;
20 |
21 | exports[`cmds .npm .test() should match snapshot 1`] = `"npm test"`;
22 |
23 | exports[`cmds .yarn .install() multiple modules custom registry should match snapshot 1`] = `"yarn add --registry \\"http://example.com/registry\\" \\"a@1.0.0\\" \\"b@1.0.0\\""`;
24 |
25 | exports[`cmds .yarn .install() multiple modules should match snapshot 1`] = `"yarn add \\"a@1.0.0\\" \\"b@1.0.0\\""`;
26 |
27 | exports[`cmds .yarn .install() one module custom registry should match snapshot 1`] = `"yarn add --registry \\"http://example.com/registry\\" \\"some-module@1.0.0\\""`;
28 |
29 | exports[`cmds .yarn .install() one module should match snapshot 1`] = `"yarn add \\"some-module@1.0.0\\""`;
30 |
31 | exports[`cmds .yarn .installMissing() custom registry should match snapshot 1`] = `"yarn --registry \\"http://example.com\\""`;
32 |
33 | exports[`cmds .yarn .installMissing() should match snapshot 1`] = `"yarn"`;
34 |
35 | exports[`cmds .yarn .list() should match snapshot 1`] = `"yarn list --json --depth=0"`;
36 |
37 | exports[`cmds .yarn .outdated() should match snapshot 1`] = `"yarn outdated --json --flat"`;
38 |
39 | exports[`cmds .yarn .test() should match snapshot 1`] = `"yarn test"`;
40 |
--------------------------------------------------------------------------------
/test/exec/__snapshots__/exec.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`exec() should return a promise that resolves to an object of expected shape 1`] = `
4 | Object {
5 | "stderr": "",
6 | "stdout": "",
7 | }
8 | `;
9 |
--------------------------------------------------------------------------------
/test/exec/__snapshots__/parse.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`parse .npm .list() no-outdated fixture should return an array with installed versions 1`] = `
4 | Array [
5 | Object {
6 | "name": "updtr-test-module-1",
7 | "version": "2.0.0",
8 | },
9 | Object {
10 | "name": "updtr-test-module-2",
11 | "version": "2.1.1",
12 | },
13 | ]
14 | `;
15 |
16 | exports[`parse .npm .list() no-outdated-dev fixture should return an array with installed versions 1`] = `
17 | Array [
18 | Object {
19 | "name": "updtr-test-module-1",
20 | "version": "2.0.0",
21 | },
22 | Object {
23 | "name": "updtr-test-module-2",
24 | "version": "2.1.1",
25 | },
26 | ]
27 | `;
28 |
29 | exports[`parse .npm .list() outdated fixture should return an array with installed versions 1`] = `
30 | Array [
31 | Object {
32 | "name": "updtr-test-module-1",
33 | "version": "1.0.0",
34 | },
35 | Object {
36 | "name": "updtr-test-module-2",
37 | "version": "2.0.0",
38 | },
39 | ]
40 | `;
41 |
42 | exports[`parse .npm .list() outdated-dev fixture should return an array with installed versions 1`] = `
43 | Array [
44 | Object {
45 | "name": "updtr-test-module-1",
46 | "version": "1.0.0",
47 | },
48 | Object {
49 | "name": "updtr-test-module-2",
50 | "version": "2.0.0",
51 | },
52 | ]
53 | `;
54 |
55 | exports[`parse .npm .outdated() outdated fixture should return an array with normalized outdated data 1`] = `
56 | Array [
57 | Object {
58 | "current": "1.0.0",
59 | "latest": "2.0.0",
60 | "name": "updtr-test-module-1",
61 | "wanted": "1.1.1",
62 | },
63 | Object {
64 | "current": "2.0.0",
65 | "latest": "2.1.1",
66 | "name": "updtr-test-module-2",
67 | "wanted": "2.1.1",
68 | },
69 | ]
70 | `;
71 |
72 | exports[`parse .npm .outdated() outdated-dev fixture should return an array with normalized outdated data 1`] = `
73 | Array [
74 | Object {
75 | "current": "1.0.0",
76 | "latest": "2.0.0",
77 | "name": "updtr-test-module-1",
78 | "wanted": "1.1.1",
79 | },
80 | Object {
81 | "current": "2.0.0",
82 | "latest": "2.1.1",
83 | "name": "updtr-test-module-2",
84 | "wanted": "2.1.1",
85 | },
86 | ]
87 | `;
88 |
89 | exports[`parse .yarn .list() malformed dependency name should throw a helpful error message 1`] = `[Error: Error when trying to parse stdout from command 'undefined': Could not parse dependency name "updtr-test-module-1"]`;
90 |
91 | exports[`parse .yarn .list() no-outdated fixture should return an array with installed versions 1`] = `
92 | Array [
93 | Object {
94 | "name": "updtr-test-module-1",
95 | "version": "2.0.0",
96 | },
97 | Object {
98 | "name": "updtr-test-module-2",
99 | "version": "2.1.1",
100 | },
101 | Object {
102 | "name": "batch-replace",
103 | "version": "1.1.3",
104 | },
105 | ]
106 | `;
107 |
108 | exports[`parse .yarn .list() no-outdated-dev fixture should return an array with installed versions 1`] = `
109 | Array [
110 | Object {
111 | "name": "updtr-test-module-1",
112 | "version": "2.0.0",
113 | },
114 | Object {
115 | "name": "updtr-test-module-2",
116 | "version": "2.1.1",
117 | },
118 | Object {
119 | "name": "batch-replace",
120 | "version": "1.1.3",
121 | },
122 | ]
123 | `;
124 |
125 | exports[`parse .yarn .list() outdated fixture should return an array with installed versions 1`] = `
126 | Array [
127 | Object {
128 | "name": "updtr-test-module-1",
129 | "version": "1.1.1",
130 | },
131 | Object {
132 | "name": "updtr-test-module-2",
133 | "version": "2.1.1",
134 | },
135 | Object {
136 | "name": "batch-replace",
137 | "version": "1.1.3",
138 | },
139 | ]
140 | `;
141 |
142 | exports[`parse .yarn .list() outdated-dev fixture should return an array with installed versions 1`] = `
143 | Array [
144 | Object {
145 | "name": "updtr-test-module-1",
146 | "version": "1.1.1",
147 | },
148 | Object {
149 | "name": "updtr-test-module-2",
150 | "version": "2.1.1",
151 | },
152 | Object {
153 | "name": "batch-replace",
154 | "version": "1.1.3",
155 | },
156 | ]
157 | `;
158 |
159 | exports[`parse .yarn .outdated() outdated fixture should return an array with normalized outdated data 1`] = `
160 | Array [
161 | Object {
162 | "current": "1.0.0",
163 | "latest": "2.0.0",
164 | "name": "updtr-test-module-1",
165 | "wanted": "1.0.0",
166 | },
167 | Object {
168 | "current": "2.0.0",
169 | "latest": "2.1.1",
170 | "name": "updtr-test-module-2",
171 | "wanted": "2.0.0",
172 | },
173 | ]
174 | `;
175 |
176 | exports[`parse .yarn .outdated() outdated-dev fixture should return an array with normalized outdated data 1`] = `
177 | Array [
178 | Object {
179 | "current": "1.0.0",
180 | "latest": "2.0.0",
181 | "name": "updtr-test-module-1",
182 | "wanted": "1.0.0",
183 | },
184 | Object {
185 | "current": "2.0.0",
186 | "latest": "2.1.1",
187 | "name": "updtr-test-module-2",
188 | "wanted": "2.0.0",
189 | },
190 | ]
191 | `;
192 |
--------------------------------------------------------------------------------
/test/exec/exec.test.js:
--------------------------------------------------------------------------------
1 | import exec from "../../src/exec/exec";
2 |
3 | const logMemoryCode = (() => {
4 | const DEFAULT_STDOUT_BUFFER_SIZE = 200 * 1024;
5 | const size = DEFAULT_STDOUT_BUFFER_SIZE + 1;
6 | const memory = Buffer.allocUnsafe(size);
7 |
8 | console.log(memory.toString("utf8"));
9 | })
10 | .toString()
11 | .replace(/"/g, '\\"')
12 | .replace(/\r?\n/g, "");
13 |
14 | const logOkCmd = 'node -e "console.log(\\"ok\\")"';
15 | const noopCmd = 'node -e ""';
16 | const throwCmd = "node -e \"throw 'fail'\"";
17 | const logMemoryCmd = `node -e "(${logMemoryCode})()"`;
18 | const cwd = __dirname;
19 |
20 | describe("exec()", () => {
21 | it("should return a promise that resolves to an object of expected shape", async () => {
22 | const result = await exec(cwd, noopCmd);
23 |
24 | expect(result).toMatchSnapshot();
25 | });
26 | it("should return a promise that resolves to a value that contains the stdout", async () => {
27 | const result = await exec(cwd, logOkCmd);
28 |
29 | expect(result.stdout).toBe("ok\n");
30 | });
31 | it("should not fail with 'stdout maxBuffer exceeded' if stdout is pretty big", async () => {
32 | await exec(cwd, logMemoryCmd);
33 | });
34 | describe("when the command fails", () => {
35 | it("should reject the promise with an error of expected shape", async () => {
36 | let givenErr;
37 |
38 | try {
39 | await exec(cwd, throwCmd);
40 | } catch (err) {
41 | givenErr = err;
42 | }
43 |
44 | expect(givenErr.message).toContain(throwCmd);
45 | expect(givenErr).toHaveProperty("stdout");
46 | expect(givenErr).toHaveProperty("stderr");
47 | expect(givenErr.stderr).toMatch(/throw 'fail'/);
48 | });
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/test/fixtures/execResults.js:
--------------------------------------------------------------------------------
1 | import readFixtures from "../helpers/readFixtures";
2 | import ExecError from "../helpers/ExecError";
3 |
4 | // Using exitCode 2 because this way we can re-use this error also for npm outdated
5 | export const execError = new ExecError({
6 | message: "Oops",
7 | exitCode: 2,
8 | stdout: "Oh noez",
9 | });
10 |
11 | export const npmNoOutdated = [];
12 | export const npmOutdated = [];
13 | export const yarnOutdated = [];
14 | export const update = [];
15 | export const testPass = [];
16 | export const testFail = [];
17 | export const testFailWithRollback = [];
18 | export const npmList = [];
19 | export const yarnList = [];
20 | export const errorExecInstallMissing = [];
21 | export const errorExecOutdated = [];
22 | export const errorParseOutdated = [];
23 | export const errorExecUpdate = [];
24 | export const errorExecRollback = [];
25 | export const errorExecList = [];
26 |
27 | beforeAll(async () => {
28 | const stdoutLogs = await readFixtures([
29 | "no-outdated/outdated.npm.log",
30 | "no-outdated/outdated.yarn.log",
31 | "no-outdated/list.npm.log",
32 | "no-outdated/list.yarn.log",
33 | "outdated/outdated.npm.log",
34 | "outdated/outdated.yarn.log",
35 | ]);
36 |
37 | npmNoOutdated.push(
38 | {stdout: ""}, // installMissing
39 | {
40 | stdout: stdoutLogs.get("no-outdated/outdated.npm.log"),
41 | } // outdated
42 | );
43 |
44 | npmOutdated.push(
45 | {stdout: ""}, // installMissing
46 | // npm exits with exit code 1 when there are outdated dependencies
47 | new ExecError({
48 | stdout: stdoutLogs.get("outdated/outdated.npm.log"),
49 | exitCode: 1,
50 | }) // outdated
51 | );
52 | yarnOutdated.push(
53 | {stdout: ""}, // installMissing
54 | {
55 | stdout: stdoutLogs.get("outdated/outdated.yarn.log"),
56 | } // outdated
57 | );
58 |
59 | update.push(
60 | {stdout: ""} // update
61 | );
62 |
63 | testPass.push(
64 | {stdout: "Everything ok"} // test
65 | );
66 | testFail.push(
67 | execError // test
68 | );
69 | testFailWithRollback.push(
70 | execError, // test
71 | {stdout: ""} // rollback
72 | );
73 |
74 | // npm exits with exit code 1 when there are outdated dependencies
75 | npmList.push(
76 | new ExecError({
77 | stdout: stdoutLogs.get("no-outdated/list.npm.log"),
78 | exitCode: 1,
79 | })
80 | );
81 | yarnList.push({
82 | stdout: stdoutLogs.get("no-outdated/list.yarn.log"),
83 | });
84 |
85 | errorExecInstallMissing.push(
86 | execError // installMissing
87 | );
88 | errorExecOutdated.push(
89 | {stdout: ""}, // installMissing
90 | execError // outdated
91 | );
92 | errorParseOutdated.push(
93 | {stdout: ""}, // installMissing
94 | {stdout: "Nonsense"} // outdated
95 | );
96 | errorExecUpdate.push(
97 | execError // update
98 | );
99 | errorExecRollback.push(
100 | {stdout: ""}, // update
101 | execError, // test
102 | execError // rollback
103 | );
104 | errorExecList.push(
105 | execError // list
106 | );
107 | });
108 |
--------------------------------------------------------------------------------
/test/fixtures/outdateds.js:
--------------------------------------------------------------------------------
1 | // location property missing on purpose because we don't care for now.
2 | // Maybe someday if we add support for linked modules?
3 | // See https://github.com/peerigon/updtr/issues/52
4 |
5 | const name = "module";
6 |
7 | const outdateds = [
8 | {
9 | name,
10 | current: "0.0.0",
11 | wanted: "0.0.0",
12 | latest: "0.0.1",
13 | },
14 | {
15 | name,
16 | current: "0.0.0",
17 | wanted: "0.0.0",
18 | latest: "0.1.0",
19 | },
20 | {
21 | name,
22 | current: "0.0.0",
23 | wanted: "0.0.0",
24 | latest: "1.0.0",
25 | },
26 | {
27 | name,
28 | current: "0.0.0",
29 | wanted: "0.0.1",
30 | latest: "0.0.1",
31 | },
32 | {
33 | name,
34 | current: "0.0.0",
35 | wanted: "0.0.1",
36 | latest: "0.1.1",
37 | },
38 | {
39 | name,
40 | current: "0.0.0",
41 | wanted: "0.1.1",
42 | latest: "0.1.1",
43 | },
44 | {
45 | name,
46 | current: "0.0.0",
47 | wanted: "0.1.1",
48 | latest: "1.1.1",
49 | },
50 | {
51 | name,
52 | current: "0.0.0",
53 | wanted: "1.1.1",
54 | latest: "1.1.1",
55 | },
56 |
57 | {
58 | name,
59 | current: "1.0.0",
60 | wanted: "1.0.0",
61 | latest: "1.0.1",
62 | },
63 | {
64 | name,
65 | current: "1.0.0",
66 | wanted: "1.0.0",
67 | latest: "1.1.0",
68 | },
69 | {
70 | name,
71 | current: "1.0.0",
72 | wanted: "1.0.0",
73 | latest: "2.0.0",
74 | },
75 | {
76 | name,
77 | current: "1.0.0",
78 | wanted: "1.0.1",
79 | latest: "1.0.1",
80 | },
81 | {
82 | name,
83 | current: "1.0.0",
84 | wanted: "1.0.1",
85 | latest: "1.1.1",
86 | },
87 | {
88 | name,
89 | current: "1.0.0",
90 | wanted: "1.1.1",
91 | latest: "1.1.1",
92 | },
93 | {
94 | name,
95 | current: "1.0.0",
96 | wanted: "1.0.0",
97 | latest: "2.0.0",
98 | },
99 | {
100 | name,
101 | current: "1.0.0",
102 | wanted: "1.0.1",
103 | latest: "2.0.0",
104 | },
105 | {
106 | name,
107 | current: "1.0.0",
108 | wanted: "1.1.0",
109 | latest: "2.0.0",
110 | },
111 | {
112 | name,
113 | current: "1.0.0",
114 | wanted: "2.0.0",
115 | latest: "2.0.0",
116 | },
117 | {
118 | name,
119 | current: "1.0.0-beta.1",
120 | wanted: "1.0.0-beta.1",
121 | latest: "1.0.0-beta.1",
122 | },
123 | {
124 | name,
125 | current: "1.0.0-beta.1",
126 | wanted: "1.0.0-beta.1",
127 | latest: "1.0.0-beta.2",
128 | },
129 | {
130 | name,
131 | current: "1.0.0-beta.1",
132 | wanted: "1.0.0-beta.2",
133 | latest: "1.0.0-beta.2",
134 | },
135 | {
136 | name,
137 | current: "1.0.0-beta.1",
138 | wanted: "1.0.0-beta.1",
139 | latest: "2.0.0-beta.1",
140 | },
141 | {
142 | name,
143 | current: "1.0.0-beta.1",
144 | wanted: "1.0.0-beta.2",
145 | latest: "2.0.0-beta.1",
146 | },
147 | ];
148 |
149 | export default outdateds;
150 |
--------------------------------------------------------------------------------
/test/fixtures/packageJsons.js:
--------------------------------------------------------------------------------
1 | import readFixtures from "../helpers/readFixtures";
2 |
3 | export let noOutdatedRegular;
4 | export let outdatedRegular;
5 | export let outdatedDev;
6 |
7 | beforeAll(async () => {
8 | const packageJsons = await readFixtures([
9 | "no-outdated/package.json",
10 | // Currently not used anywhere
11 | // "no-outdated-dev/package.json",
12 | "outdated/package.json",
13 | "outdated-dev/package.json",
14 | ]);
15 |
16 | noOutdatedRegular = packageJsons.get("no-outdated/package.json");
17 | outdatedRegular = packageJsons.get("outdated/package.json");
18 | outdatedDev = packageJsons.get("outdated-dev/package.json");
19 | });
20 |
--------------------------------------------------------------------------------
/test/fixtures/updateResults.js:
--------------------------------------------------------------------------------
1 | import {
2 | UPDATE_TO_LATEST,
3 | UPDATE_TO_NON_BREAKING,
4 | } from "../../src/constants/config";
5 | import parse from "../../src/exec/parse";
6 | import createUpdateTask from "../../src/tasks/util/createUpdateTask";
7 | import createUpdateResult from "../../src/tasks/util/createUpdateResult";
8 | import {readFixture} from "../helpers/readFixtures";
9 | import FakeUpdtr from "../helpers/FakeUpdtr";
10 |
11 | export let module1ToLatestSuccess;
12 | export let module1ToNonBreakingSuccess;
13 | export let module2ToLatestSuccess;
14 | export let module2ToNonBreakingSuccess;
15 | export let module1ToLatestFail;
16 | export let module1ToNonBreakingFail;
17 | export let module2ToLatestFail;
18 | export let module2ToNonBreakingFail;
19 |
20 | function outdatedToUpdateResult(outdated, updateTo, success) {
21 | return createUpdateResult(
22 | createUpdateTask(outdated, {
23 | ...FakeUpdtr.baseConfig,
24 | updateTo,
25 | }),
26 | success
27 | );
28 | }
29 |
30 | beforeAll(async () => {
31 | const stdoutLog = await readFixture("outdated/outdated.npm.log");
32 |
33 | const [outdatedTestModule1, outdatedTestModule2] = parse.npm.outdated(
34 | stdoutLog
35 | );
36 |
37 | module1ToLatestSuccess = outdatedToUpdateResult(
38 | outdatedTestModule1,
39 | UPDATE_TO_LATEST,
40 | true
41 | );
42 | module1ToNonBreakingSuccess = outdatedToUpdateResult(
43 | outdatedTestModule1,
44 | UPDATE_TO_NON_BREAKING,
45 | true
46 | );
47 | module1ToLatestFail = outdatedToUpdateResult(
48 | outdatedTestModule1,
49 | UPDATE_TO_LATEST,
50 | false
51 | );
52 | module1ToNonBreakingFail = outdatedToUpdateResult(
53 | outdatedTestModule1,
54 | UPDATE_TO_NON_BREAKING,
55 | false
56 | );
57 | module2ToLatestSuccess = outdatedToUpdateResult(
58 | outdatedTestModule2,
59 | UPDATE_TO_LATEST,
60 | true
61 | );
62 | module2ToNonBreakingSuccess = outdatedToUpdateResult(
63 | outdatedTestModule2,
64 | UPDATE_TO_NON_BREAKING,
65 | true
66 | );
67 | module2ToLatestFail = outdatedToUpdateResult(
68 | outdatedTestModule2,
69 | UPDATE_TO_LATEST,
70 | false
71 | );
72 | module2ToNonBreakingFail = outdatedToUpdateResult(
73 | outdatedTestModule2,
74 | UPDATE_TO_NON_BREAKING,
75 | false
76 | );
77 | });
78 |
--------------------------------------------------------------------------------
/test/fixtures/versionRanges.js:
--------------------------------------------------------------------------------
1 | // Copied from https://github.com/npm/node-semver
2 | const versionRanges = [
3 | "~0.6.1-1",
4 | "1.0.0 - 2.0.0",
5 | "1.0.0-2.0.0",
6 | "1.0.0",
7 | " 1.0.0 ",
8 | "1.0.0-beta.1",
9 | ">=1.0.0-beta.1",
10 | "1.0.0-BETA.1",
11 | "1.0.0-alpha-1",
12 | "1.0.0 alpha", // Nonsense value
13 | "~1.0.0",
14 | " ~1.0.0 ",
15 | "~1.0.x",
16 | ">=*",
17 | "",
18 | "*",
19 | ">=1.0.0",
20 | " >=1.0.0 ",
21 | ">1.0.0",
22 | " >1.0.0 ",
23 | "<=2.0.0",
24 | "<2.0.0",
25 | ">= 1.0.0",
26 | "> 1.0.0",
27 | "<= 2.0.0",
28 | "< 2.0.0",
29 | "<\t2.0.0",
30 | ">=0.1.97",
31 | "0.1.20 || 1.2.4",
32 | ">=0.2.3 || <0.0.1",
33 | "||",
34 | "2.x.x",
35 | "1.2.x",
36 | "1.2.x || 2.x",
37 | "x",
38 | "2.*.*",
39 | "1.2.*",
40 | "1.2.* || 2.*",
41 | "2",
42 | "2.3",
43 | "~2.4",
44 | "~>3.2.1",
45 | "~1",
46 | "~>1",
47 | "~> 1",
48 | "~1.0",
49 | "~ 1.0",
50 | ">=1",
51 | ">= 1",
52 | "<1.2",
53 | "< 1.2",
54 | "1",
55 | "~v0.5.4-pre",
56 | "~v0.5.4-pre",
57 | "=0.7.x",
58 | ">=0.7.x",
59 | ">=0.7.X",
60 | "=0.7.x",
61 | ">=0.7.x",
62 | "<=0.7.x",
63 | ">0.2.3 >0.2.4 <=0.2.5",
64 | ">=0.2.3 <=0.2.4",
65 | "1.0.0 - 2.0.0",
66 | "^1",
67 | "^3.0.0",
68 | "^1.0.0 || ~2.0.1",
69 | "^0.1.0 || ~3.0.1 || 5.0.0",
70 | "^0.1.0 || ~3.0.1 || >4 <=5.0.0",
71 | // Nonsense values
72 | "*.1.2",
73 | "x.1.2",
74 | "1.x.2",
75 | "1.*.2",
76 | ];
77 |
78 | export default versionRanges;
79 |
--------------------------------------------------------------------------------
/test/helpers/ExecError.js:
--------------------------------------------------------------------------------
1 | export default class ExecError extends Error {
2 | constructor({message, stdout = "", stderr = "", exitCode}) {
3 | super(message);
4 | this.stdout = stdout;
5 | this.stderr = stderr;
6 | this.code = exitCode;
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/test/helpers/FakeUpdtr.js:
--------------------------------------------------------------------------------
1 | import {stub} from "sinon";
2 | import Updtr from "../../src/Updtr";
3 |
4 | export default class FakeUpdtr extends Updtr {
5 | constructor(updtrConfig = {}) {
6 | super({
7 | ...FakeUpdtr.baseConfig,
8 | ...updtrConfig,
9 | });
10 | this.canAccessPackageJson = stub();
11 | this.readFile = stub();
12 | this.writeFile = stub();
13 | this.emit = stub();
14 | this._exec = stub();
15 | this.exec.args = this._exec.args;
16 |
17 | this.canAccessPackageJson.resolves(true);
18 | }
19 |
20 | set execResults(execResults) {
21 | execResults.forEach((execResult, index) => {
22 | const call = this._exec.onCall(index);
23 |
24 | if (execResult instanceof Error) {
25 | call.rejects(execResult);
26 | } else {
27 | call.resolves(execResult);
28 | }
29 | });
30 | }
31 |
32 | async exec(...args) {
33 | const result = await this._exec(...args);
34 |
35 | if (result === undefined) {
36 | throw new Error("Not enough execResults for FakeUpdtr");
37 | }
38 |
39 | return result;
40 | }
41 | }
42 |
43 | FakeUpdtr.baseConfig = {
44 | cwd: "/updtr/test/cwd",
45 | };
46 |
--------------------------------------------------------------------------------
/test/helpers/binMocks.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/unambiguous */
2 |
3 | // We're using require() instead of import here because we need to replace some exports with mocks
4 | const runExports = require("../../src/run");
5 | const reportersExports = require("../../src/reporters");
6 | const errorsExports = require("../../src/errors");
7 |
8 | const runMock = process.env.RUN_MOCK || "logConfigAndExit";
9 |
10 | const runMocks = {
11 | logConfigAndExit(updtr) {
12 | console.log(
13 | JSON.stringify({
14 | updtrConfig: updtr.config,
15 | reporter,
16 | reporterConfig,
17 | })
18 | );
19 | process.exit(0); // eslint-disable-line no-process-exit
20 | },
21 | rejectWithAccessError() {
22 | return Promise.reject(
23 | new errorsExports.PackageJsonNoAccessError(process.cwd())
24 | );
25 | },
26 | };
27 |
28 | let reporterConfig;
29 | let reporter;
30 |
31 | // Replace run default exports with mock
32 | runExports.default = runMocks[runMock];
33 | reportersExports.default.dense = function (updtr, config) {
34 | // Removing the stream property because we can't JSON stringify that
35 | reporterConfig = {...config, stream: null};
36 | reporter = "dense";
37 | };
38 | reportersExports.default.error = function (updtr) {
39 | updtr.on("error", err => {
40 | console.log(JSON.stringify(err.message));
41 | process.exit(0); // eslint-disable-line no-process-exit
42 | });
43 | };
44 |
45 | require("../../src/bin"); // eslint-disable-line import/no-unassigned-import
46 |
--------------------------------------------------------------------------------
/test/helpers/cleanupFixtures.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import pify from "pify";
3 | import rimraf from "rimraf";
4 | import {fixtureSetups} from "./setupFixtures";
5 |
6 | const pathToFixtures = path.resolve(__dirname, "..", "fixtures");
7 | const fixtures = Object.keys(fixtureSetups);
8 | const promiseRimraf = pify(rimraf);
9 |
10 | function remove(fixture) {
11 | return promiseRimraf(
12 | path.join(pathToFixtures, fixture),
13 | );
14 | }
15 |
16 | export default function cleanupFixtures() {
17 | return Promise.all(fixtures.map(remove));
18 | }
19 |
20 | if (!module.parent) {
21 | process.on("unhandledRejection", error => {
22 | console.error(error.stack);
23 | process.exit(1); // eslint-disable-line no-process-exit
24 | });
25 | cleanupFixtures();
26 | }
27 |
--------------------------------------------------------------------------------
/test/helpers/pickEventNames.js:
--------------------------------------------------------------------------------
1 | export default function pickEventNames(eventNames, emitArgs) {
2 | return emitArgs
3 | .filter(([eventName]) => eventNames.indexOf(eventName) > -1)
4 | .map(([eventName]) => eventName);
5 | }
6 |
--------------------------------------------------------------------------------
/test/helpers/pickEvents.js:
--------------------------------------------------------------------------------
1 | export default function pickEvents(eventName, emitArgs) {
2 | return emitArgs.filter(([name]) => name === eventName).map(args => args[1]);
3 | }
4 |
--------------------------------------------------------------------------------
/test/helpers/readFixtures.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import fs from "../../src/util/fs";
3 |
4 | const pathToFixtures = path.resolve(__dirname, "..", "fixtures");
5 | const cache = new Map();
6 |
7 | export async function readFixture(fixture) {
8 | const filename = path.join(pathToFixtures, fixture);
9 | const cached = cache.get(filename);
10 |
11 | if (cached !== undefined) {
12 | return cached;
13 | }
14 |
15 | const contents = await fs.readFile(filename, "utf8");
16 |
17 | if (cache.has(filename) === false) {
18 | cache.set(filename, contents);
19 | }
20 |
21 | return contents;
22 | }
23 |
24 | export default (async function readFixtures(fixtures) {
25 | const fixturesMap = new Map();
26 | const contents = await Promise.all(fixtures.map(readFixture));
27 |
28 | contents.forEach((content, index) => {
29 | fixturesMap.set(fixtures[index], content);
30 | });
31 |
32 | return fixturesMap;
33 | });
34 |
--------------------------------------------------------------------------------
/test/helpers/replayTerminalSnapshots.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import ansiEscapes from "ansi-escapes";
3 | import wrapAnsi from "wrap-ansi";
4 |
5 | function timeout(ms) {
6 | return new Promise(resolve => setTimeout(resolve, ms));
7 | }
8 |
9 | async function replayTerminalSnapshots(pathToSnapshots) {
10 | const snapshots = require(path.join(process.cwd(), pathToSnapshots)); // eslint-disable-line import/no-dynamic-require
11 |
12 | for (const snapshotTitle of Object.keys(snapshots)) {
13 | const stdout = snapshots[snapshotTitle]
14 | // Removing double quotes
15 | .replace(/^.*?"|".*?$/gm, "");
16 |
17 | // Restoring ansi-escape-sequences
18 | // .replace(/(\[\d)/g, "\u001B$1");
19 | const frames = stdout.split(ansiEscapes.eraseDown);
20 | const line = new Array(snapshotTitle.length + 1).join("-");
21 |
22 | process.stdout.write(ansiEscapes.clearScreen);
23 |
24 | console.log(line);
25 | console.log(snapshotTitle);
26 | console.log(line);
27 |
28 | await timeout(3000); // eslint-disable-line no-await-in-loop
29 |
30 | for (let i = 0; i < frames.length; i++) {
31 | if (i !== 0) {
32 | process.stdout.write(ansiEscapes.eraseDown);
33 | }
34 | process.stdout.write(wrapAnsi(frames[i], 80, {hard: true}));
35 | await timeout(1000); // eslint-disable-line no-await-in-loop
36 | }
37 | await timeout(3000); // eslint-disable-line no-await-in-loop
38 | }
39 | }
40 |
41 | if (!module.parent) {
42 | process.on("unhandledRejection", error => {
43 | console.error(error.stack);
44 | process.exit(1); // eslint-disable-line no-process-exit
45 | });
46 | replayTerminalSnapshots(process.argv[2]);
47 | }
48 |
49 | export default replayTerminalSnapshots;
50 |
--------------------------------------------------------------------------------
/test/helpers/runBinMock.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/unambiguous */
2 |
3 | const path = require("path");
4 |
5 | // babel-node would use the current process.cwd() for all the config resolving logic
6 | // That's why we use @babel/register where we can set all the options
7 | require("@babel/register")({
8 | cwd: path.resolve(__dirname, "..", ".."),
9 | });
10 |
11 | require("./binMocks"); // eslint-disable-line import/no-unassigned-import
12 |
--------------------------------------------------------------------------------
/test/helpers/temp.js:
--------------------------------------------------------------------------------
1 | import pify from "pify";
2 | import temp from "temp";
3 |
4 | temp.track();
5 |
6 | process.on("exit", () => {
7 | temp.cleanupSync();
8 | });
9 |
10 | export default pify(temp);
11 |
--------------------------------------------------------------------------------
/test/index.test.js:
--------------------------------------------------------------------------------
1 | import Updtr from "../src/Updtr";
2 | import FakeUpdtr from "./helpers/FakeUpdtr";
3 | const index = require("../src");
4 |
5 | describe("index", () => {
6 | describe(".create", () => {
7 | it("should be a function", () => {
8 | expect(typeof index.create).toBe("function");
9 | });
10 | it("should return an instance of Updtr", () => {
11 | expect(index.create(FakeUpdtr.baseConfig)).toBeInstanceOf(Updtr);
12 | });
13 | });
14 | describe(".run", () => {
15 | it("should be a function", () => {
16 | expect(typeof index.run).toBe("function");
17 | });
18 | });
19 | describe(".errors", () => {
20 | it("should be an object with expected shape", () => {
21 | expect(index.errors).toMatchSnapshot();
22 | });
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/test/integration/__snapshots__/noOutdated.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`integration test: when there are no outdated dependencies npm should not change the package.json, change the installed dependencies and resolve with an empty array as results: npm > installed versions 1`] = `
4 | Object {
5 | "updtr-test-module-1": "2.0.0",
6 | "updtr-test-module-2": "2.1.1",
7 | }
8 | `;
9 |
10 | exports[`integration test: when there are no outdated dependencies yarn should not change the package.json and yarn.lock and resolve with an empty array as results: npm > installed versions 1`] = `
11 | Object {
12 | "updtr-test-module-1": "2.0.0",
13 | "updtr-test-module-2": "2.1.1",
14 | }
15 | `;
16 |
--------------------------------------------------------------------------------
/test/integration/__snapshots__/outdated.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`integration test: when there are outdated dependencies advanced configuration npm should install all updates, update the package.json and return the results: advanced configuration > npm > installed versions 1`] = `
4 | Object {
5 | "updtr-test-module-1": "1.1.1",
6 | "updtr-test-module-2": "2.0.0",
7 | }
8 | `;
9 |
10 | exports[`integration test: when there are outdated dependencies advanced configuration npm should install all updates, update the package.json and return the results: advanced configuration > npm > package.json dependencies 1`] = `
11 | Object {
12 | "updtr-test-module-1": "1.1.1",
13 | "updtr-test-module-2": "^2.0.0",
14 | }
15 | `;
16 |
17 | exports[`integration test: when there are outdated dependencies advanced configuration npm should install all updates, update the package.json and return the results: advanced configuration > npm > results 1`] = `
18 | Array [
19 | Object {
20 | "name": "updtr-test-module-1",
21 | "rollbackTo": "1.0.0",
22 | "success": true,
23 | "updateTo": "1.1.1",
24 | },
25 | ]
26 | `;
27 |
28 | exports[`integration test: when there are outdated dependencies advanced configuration yarn should install all updates, update the package.json and return the results: advanced configuration > yarn > installed versions 1`] = `
29 | Object {
30 | "updtr-test-module-1": "1.1.1",
31 | "updtr-test-module-2": "2.0.0",
32 | }
33 | `;
34 |
35 | exports[`integration test: when there are outdated dependencies advanced configuration yarn should install all updates, update the package.json and return the results: advanced configuration > yarn > package.json dependencies 1`] = `
36 | Object {
37 | "updtr-test-module-1": "^1.1.1",
38 | "updtr-test-module-2": "2.0.0",
39 | }
40 | `;
41 |
42 | exports[`integration test: when there are outdated dependencies advanced configuration yarn should install all updates, update the package.json and return the results: advanced configuration > yarn > results 1`] = `
43 | Array [
44 | Object {
45 | "name": "updtr-test-module-1",
46 | "rollbackTo": "1.0.0",
47 | "success": true,
48 | "updateTo": "1.1.1",
49 | },
50 | ]
51 | `;
52 |
53 | exports[`integration test: when there are outdated dependencies default configuration npm should install all updates, update the package.json and return the results: default configuration > npm > installed versions 1`] = `
54 | Object {
55 | "updtr-test-module-1": "2.0.0",
56 | "updtr-test-module-2": "2.1.1",
57 | }
58 | `;
59 |
60 | exports[`integration test: when there are outdated dependencies default configuration npm should install all updates, update the package.json and return the results: default configuration > npm > package.json dependencies 1`] = `
61 | Object {
62 | "updtr-test-module-1": "^2.0.0",
63 | "updtr-test-module-2": "^2.0.0",
64 | }
65 | `;
66 |
67 | exports[`integration test: when there are outdated dependencies default configuration npm should install all updates, update the package.json and return the results: default configuration > npm > results 1`] = `
68 | Array [
69 | Object {
70 | "name": "updtr-test-module-1",
71 | "rollbackTo": "1.1.1",
72 | "success": true,
73 | "updateTo": "2.0.0",
74 | },
75 | ]
76 | `;
77 |
78 | exports[`integration test: when there are outdated dependencies default configuration yarn should install all updates, update the package.json and return the results: default configuration > yarn > installed versions 1`] = `
79 | Object {
80 | "updtr-test-module-1": "2.0.0",
81 | "updtr-test-module-2": "2.1.1",
82 | }
83 | `;
84 |
85 | exports[`integration test: when there are outdated dependencies default configuration yarn should install all updates, update the package.json and return the results: default configuration > yarn > package.json dependencies 1`] = `
86 | Object {
87 | "updtr-test-module-1": "2.0.0",
88 | "updtr-test-module-2": "^2.0.0",
89 | }
90 | `;
91 |
92 | exports[`integration test: when there are outdated dependencies default configuration yarn should install all updates, update the package.json and return the results: default configuration > yarn > results 1`] = `
93 | Array [
94 | Object {
95 | "name": "updtr-test-module-1",
96 | "rollbackTo": "1.1.1",
97 | "success": true,
98 | "updateTo": "2.0.0",
99 | },
100 | ]
101 | `;
102 |
--------------------------------------------------------------------------------
/test/integration/helpers/constants.js:
--------------------------------------------------------------------------------
1 | export const PACKAGE_JSON = "package.json";
2 | export const YARN_LOCK = "yarn.lock";
3 | export const FIXTURE_NO_OUTDATED = "no-outdated";
4 | export const FIXTURE_OUTDATED = "outdated";
5 |
--------------------------------------------------------------------------------
/test/integration/helpers/fs.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import temp from "../../helpers/temp";
3 | import fs from "../../../src/util/fs";
4 |
5 | const pathToFixtures = path.resolve(__dirname, "..", "..", "fixtures");
6 | let testCounter = 0;
7 |
8 | export function createTempDir() {
9 | return temp.mkdir("updtr-integration-test-" + testCounter++);
10 | }
11 |
12 | export function read(dir, file) {
13 | return fs.readFile(
14 | // If dir is an absolute path, pathToFixtures will just be ignored
15 | path.resolve(pathToFixtures, dir, file),
16 | "utf8"
17 | );
18 | }
19 |
20 | export function write(dir, file, contents) {
21 | return fs.writeFile(
22 | // If dir is an absolute path, pathToFixtures will just be ignored
23 | path.resolve(dir, file),
24 | contents
25 | );
26 | }
27 |
--------------------------------------------------------------------------------
/test/integration/helpers/getInstalledVersions.js:
--------------------------------------------------------------------------------
1 | import path from "path";
2 | import fs from "../../../src/util/fs";
3 |
4 | export const filterUpdtrTestModule = /^updtr-test-module-\d+/;
5 |
6 | export default (async function (dir, filterPattern) {
7 | const pathToNodeModules = path.resolve(dir, "node_modules");
8 | const modules = await fs.readdir(pathToNodeModules);
9 | const errors = [];
10 | const versions = Object.create(null);
11 |
12 | await Promise.all(
13 | modules
14 | .filter(
15 | moduleName =>
16 | filterPattern === undefined ?
17 | true :
18 | filterPattern.test(moduleName) === true
19 | )
20 | .map(async moduleName => {
21 | const pathToPackageJson = path.resolve(
22 | pathToNodeModules,
23 | moduleName,
24 | "package.json"
25 | );
26 |
27 | try {
28 | const packageJson = JSON.parse(
29 | await fs.readFile(pathToPackageJson, "utf8")
30 | );
31 |
32 | versions[moduleName] = packageJson.version;
33 | } catch (error) {
34 | errors.push({
35 | error,
36 | moduleName,
37 | });
38 | }
39 | })
40 | );
41 |
42 | return {
43 | versions,
44 | errors,
45 | };
46 | });
47 |
--------------------------------------------------------------------------------
/test/integration/noOutdated.test.js:
--------------------------------------------------------------------------------
1 | import {USE_YARN} from "../../src/constants/config";
2 | import {
3 | noOutdatedRegular as noOutdatedPackageContents,
4 | } from "../fixtures/packageJsons";
5 | import {create, run} from "../../src";
6 | import getInstalledVersions, {
7 | filterUpdtrTestModule,
8 | } from "./helpers/getInstalledVersions";
9 | import {
10 | PACKAGE_JSON,
11 | YARN_LOCK,
12 | FIXTURE_NO_OUTDATED,
13 | } from "./helpers/constants";
14 | import {createTempDir, read, write} from "./helpers/fs";
15 |
16 | // These tests may take longer on travis
17 | jasmine.DEFAULT_TIMEOUT_INTERVAL = 10 * 60 * 1000;
18 |
19 | describe("integration test: when there are no outdated dependencies", () => {
20 | describe("npm", () => {
21 | it("should not change the package.json, change the installed dependencies and resolve with an empty array as results", async () => {
22 | const tempDir = await createTempDir();
23 |
24 | await write(tempDir, PACKAGE_JSON, noOutdatedPackageContents);
25 |
26 | const updtr = create({
27 | cwd: tempDir,
28 | });
29 |
30 | const results = await run(updtr);
31 |
32 | const [
33 | tempDirPackageContents,
34 | installedVersions,
35 | ] = await Promise.all([
36 | read(tempDir, PACKAGE_JSON),
37 | getInstalledVersions(tempDir, filterUpdtrTestModule),
38 | ]);
39 |
40 | expect(results).toEqual([]);
41 | expect(tempDirPackageContents).toBe(noOutdatedPackageContents);
42 | expect(installedVersions.versions).toMatchSnapshot(
43 | "npm > installed versions"
44 | );
45 | });
46 | });
47 | describe("yarn", () => {
48 | it("should not change the package.json and yarn.lock and resolve with an empty array as results", async () => {
49 | const [tempDir, yarnLockContents] = await Promise.all([
50 | createTempDir(),
51 | read(FIXTURE_NO_OUTDATED, YARN_LOCK),
52 | ]);
53 |
54 | await write(tempDir, PACKAGE_JSON, noOutdatedPackageContents);
55 |
56 | const updtr = create({
57 | cwd: tempDir,
58 | use: USE_YARN,
59 | });
60 |
61 | const results = await run(updtr);
62 |
63 | const [
64 | tempDirPackageContents,
65 | installedVersions,
66 | tempDirYarnLockContents,
67 | ] = await Promise.all([
68 | read(tempDir, PACKAGE_JSON),
69 | getInstalledVersions(tempDir, filterUpdtrTestModule),
70 | read(tempDir, YARN_LOCK),
71 | ]);
72 |
73 | expect(results).toEqual([]);
74 | expect(tempDirPackageContents).toBe(noOutdatedPackageContents);
75 | expect(installedVersions.versions).toMatchSnapshot(
76 | "npm > installed versions"
77 | );
78 | expect(tempDirYarnLockContents).toBe(yarnLockContents);
79 | });
80 | });
81 | });
82 |
--------------------------------------------------------------------------------
/test/manual/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "manual-test",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "node ../../node_modules/babel-cli/bin/babel-node ../../src/bin",
8 | "test": "node test"
9 | },
10 | "keywords": [],
11 | "author": "",
12 | "license": "ISC",
13 | "dependencies": {
14 | "async": "1.0.0",
15 | "batch-replace": "1.0.0",
16 | "less": "2.0.0",
17 | "node-sass": "4.0.0",
18 | "updtr-test-module-1": "1.0.0",
19 | "updtr-test-module-2": "2.0.0"
20 | },
21 | "devDependencies": {
22 | "express": "3.0.0",
23 | "mongoose": "4.0.0",
24 | "nodemon": "1.0.0",
25 | "underscore": "1.0.3"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/test/manual/test:
--------------------------------------------------------------------------------
1 | setTimeout(() => {
2 | const exitCode = Math.floor(Math.random() * 10) % 2;
3 |
4 | if (exitCode === 0) {
5 | console.log("Everything ok");
6 | } else {
7 | console.trace("Not ok");
8 | }
9 | process.exit(exitCode);
10 | }, 2000);
11 |
--------------------------------------------------------------------------------
/test/manual/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | batch-replace@^1.1.3:
6 | version "1.1.3"
7 | resolved "https://registry.yarnpkg.com/batch-replace/-/batch-replace-1.1.3.tgz#d178467380d1df93efac7bb873274fa274d2d293"
8 |
9 | updtr-test-module-1@1.0.0:
10 | version "1.0.0"
11 | resolved "https://registry.yarnpkg.com/updtr-test-module-1/-/updtr-test-module-1-1.0.0.tgz#bbbe9d48f45a6c04952bb64f506ca7d5d2659c36"
12 | dependencies:
13 | batch-replace "^1.1.3"
14 |
15 | updtr-test-module-2@2.0.0:
16 | version "2.0.0"
17 | resolved "https://registry.yarnpkg.com/updtr-test-module-2/-/updtr-test-module-2-2.0.0.tgz#1c5ca3c2cc40e4e7004819074bb620dc76c6156d"
18 |
--------------------------------------------------------------------------------
/test/reporters/__snapshots__/basic.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`basic() batch-update fail and show test stdout should print the expected lines: batch-update fail and show test stdout 1`] = `
4 | "[JInstalling missing dependencies[90m...[39m
5 | [90m> npm install [39m
6 | [JLooking for outdated modules[90m...[39m
7 | [90m> npm outdated [39m
8 | [JFound [1m4 updates[22m.
9 |
10 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
11 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.1.0[90m...[39m
12 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 1.0.0[90m...[39m
13 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
14 | [90m> npm install [39m
15 | [J[33m-[39m [1mmodule[22m [90mtesting...[39m
16 | [33m-[39m [1mmodule[22m [90mtesting...[39m
17 | [33m-[39m [1mmodule[22m [90mtesting...[39m
18 | [33m-[39m [1mmodule[22m [90mtesting...[39m
19 | [90m> npm test [39m
20 | [J[31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 0.0.1 [90m→[39m 0.0.0[90m...[39m
21 | [31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 0.1.0 [90m→[39m 0.0.0[90m...[39m
22 | [31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 1.0.0 [90m→[39m 0.0.0[90m...[39m
23 | [31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 0.0.1 [90m→[39m 0.0.0[90m...[39m
24 | [90m> npm install [39m
25 | [J
26 | [J[1m2 successful[22m updates.
27 | [J[1m1 failed[22m update.
28 | [J
29 | Finished after 1.0s."
30 | `;
31 |
32 | exports[`basic() batch-update success and show test stdout should print the expected lines: batch-update success and show test stdout 1`] = `
33 | "[JInstalling missing dependencies[90m...[39m
34 | [90m> npm install [39m
35 | [JLooking for outdated modules[90m...[39m
36 | [90m> npm outdated [39m
37 | [JFound [1m4 updates[22m.
38 |
39 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
40 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.1.0[90m...[39m
41 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 1.0.0[90m...[39m
42 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
43 | [90m> npm install [39m
44 | [J[33m-[39m [1mmodule[22m [90mtesting...[39m
45 | [33m-[39m [1mmodule[22m [90mtesting...[39m
46 | [33m-[39m [1mmodule[22m [90mtesting...[39m
47 | [33m-[39m [1mmodule[22m [90mtesting...[39m
48 | [90m> npm test [39m
49 | [J[32m-[39m [1mmodule[22m 0.0.1 [90msuccess[39m
50 | [32m-[39m [1mmodule[22m 0.1.0 [90msuccess[39m
51 | [32m-[39m [1mmodule[22m 1.0.0 [90msuccess[39m
52 | [32m-[39m [1mmodule[22m 0.0.1 [90msuccess[39m
53 | [J
54 | [J[1m3 successful[22m updates.
55 | [J
56 | Finished after 1.0s."
57 | `;
58 |
59 | exports[`basic() custom config and only excluded modules should print the expected lines: custom config and only excluded modules 1`] = `
60 | "[JRunning updtr with custom configuration:
61 |
62 | - exclude: b, c
63 |
64 | [JInstalling missing dependencies[90m...[39m
65 | [90m> npm install [39m
66 | [JLooking for outdated modules[90m...[39m
67 | [90m> npm outdated [39m
68 | [J[1mNo updates available[22m for the given modules and version range
69 | [J
70 | [J[1m3 skipped[22m modules:
71 |
72 | [90m-[39m [1ma[22m [90mgit[39m
73 | [90m-[39m [1mb[22m [90mexcluded[39m
74 | [90m-[39m [1mc[22m [90mexcluded[39m
75 | [J
76 | Finished after 1.0s."
77 | `;
78 |
79 | exports[`basic() custom config and sequential-update with mixed success and show test stdout should print the expected lines: custom config and sequential-update with mixed success and show test stdout 1`] = `
80 | "[JRunning updtr with custom configuration:
81 |
82 | - exclude: b, c
83 |
84 | [JInstalling missing dependencies[90m...[39m
85 | [90m> npm install [39m
86 | [JLooking for outdated modules[90m...[39m
87 | [90m> npm outdated [39m
88 | [JFound [1m4 updates[22m.
89 |
90 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
91 | [90m> npm install [39m
92 | [J[33m-[39m [1mmodule[22m [90mtesting...[39m
93 | [90m> npm test [39m
94 | [J[32m-[39m [1mmodule[22m 0.0.1 [90msuccess[39m
95 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.1.0[90m...[39m
96 | [90m> npm install [39m
97 | [J[33m-[39m [1mmodule[22m [90mtesting...[39m
98 | [90m> npm test [39m
99 | [J[31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 0.1.0 [90m→[39m 0.0.0[90m...[39m
100 | [90m> npm install [39m
101 | [J[31m-[39m [1m[31mmodule[39m[22m 0.1.0 [90mfailed[39m
102 | [JThis is the test stdout
103 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 1.0.0[90m...[39m
104 | [90m> npm install [39m
105 | [J[33m-[39m [1mmodule[22m [90mtesting...[39m
106 | [90m> npm test [39m
107 | [J[32m-[39m [1mmodule[22m 1.0.0 [90msuccess[39m
108 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
109 | [90m> npm install [39m
110 | [J[33m-[39m [1mmodule[22m [90mtesting...[39m
111 | [90m> npm test [39m
112 | [J[31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 0.0.1 [90m→[39m 0.0.0[90m...[39m
113 | [90m> npm install [39m
114 | [J[31m-[39m [1m[31mmodule[39m[22m 0.0.1 [90mfailed[39m
115 | [JThis is the test stdout
116 | [J
117 | [J[1m2 successful[22m updates.
118 | [J[1m2 failed[22m updates.
119 | [J
120 | Finished after 1.0s."
121 | `;
122 |
123 | exports[`basic() no outdated modules should print the expected lines: no outdated modules 1`] = `
124 | "[JInstalling missing dependencies[90m...[39m
125 | [90m> npm install [39m
126 | [JLooking for outdated modules[90m...[39m
127 | [90m> npm outdated [39m
128 | [JEverything [1mup-to-date[22m
129 | [J
130 | [J
131 | Finished after 1.0s."
132 | `;
133 |
--------------------------------------------------------------------------------
/test/reporters/__snapshots__/dense.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`dense() batch-update fail and show test stdout should print the expected lines: batch-update fail and show test stdout 1`] = `
4 | "[?25l[JInstalling missing dependencies[90m...[39m
5 | [90m> npm install [39m
6 | ...
7 | [3A[JLooking for outdated modules[90m...[39m
8 | [90m> npm outdated [39m
9 | ...
10 | [3A[JFound [1m4 updates[22m.
11 |
12 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
13 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.1.0[90m...[39m
14 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 1.0.0[90m...[39m
15 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
16 | [90m> npm install [39m
17 | ...
18 | [6A[J[33m-[39m [1mmodule[22m [90mtesting...[39m
19 | [33m-[39m [1mmodule[22m [90mtesting...[39m
20 | [33m-[39m [1mmodule[22m [90mtesting...[39m
21 | [33m-[39m [1mmodule[22m [90mtesting...[39m
22 | [90m> npm test [39m
23 | ...
24 | [6A[J[31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 0.0.1 [90m→[39m 0.0.0[90m...[39m
25 | [31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 0.1.0 [90m→[39m 0.0.0[90m...[39m
26 | [31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 1.0.0 [90m→[39m 0.0.0[90m...[39m
27 | [31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 0.0.1 [90m→[39m 0.0.0[90m...[39m
28 | [90m> npm install [39m
29 | ...
30 | [6A[J
31 | [J[1m2 successful[22m updates.
32 | [J[1m1 failed[22m update.
33 | [J
34 | Finished after 1.0s.
35 | "
36 | `;
37 |
38 | exports[`dense() batch-update success and show test stdout should print the expected lines: batch-update success and show test stdout 1`] = `
39 | "[?25l[JInstalling missing dependencies[90m...[39m
40 | [90m> npm install [39m
41 | ...
42 | [3A[JLooking for outdated modules[90m...[39m
43 | [90m> npm outdated [39m
44 | ...
45 | [3A[JFound [1m4 updates[22m.
46 |
47 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
48 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.1.0[90m...[39m
49 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 1.0.0[90m...[39m
50 | [33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
51 | [90m> npm install [39m
52 | ...
53 | [6A[J[33m-[39m [1mmodule[22m [90mtesting...[39m
54 | [33m-[39m [1mmodule[22m [90mtesting...[39m
55 | [33m-[39m [1mmodule[22m [90mtesting...[39m
56 | [33m-[39m [1mmodule[22m [90mtesting...[39m
57 | [90m> npm test [39m
58 | ...
59 | [6A[J[32m-[39m [1mmodule[22m 0.0.1 [90msuccess[39m
60 | [32m-[39m [1mmodule[22m 0.1.0 [90msuccess[39m
61 | [32m-[39m [1mmodule[22m 1.0.0 [90msuccess[39m
62 | [32m-[39m [1mmodule[22m 0.0.1 [90msuccess[39m
63 | [J
64 | [J[1m3 successful[22m updates.
65 | [J
66 | Finished after 1.0s.
67 | "
68 | `;
69 |
70 | exports[`dense() custom config and only excluded modules should print the expected lines: custom config and only excluded modules 1`] = `
71 | "[?25l[JRunning updtr with custom configuration:
72 |
73 | - exclude: b, c
74 |
75 | [JInstalling missing dependencies[90m...[39m
76 | [90m> npm install [39m
77 | ...
78 | [3A[JLooking for outdated modules[90m...[39m
79 | [90m> npm outdated [39m
80 | ...
81 | [3A[J[1mNo updates available[22m for the given modules and version range
82 | [J
83 | [J[1m3 skipped[22m modules:
84 |
85 | [90m-[39m [1ma[22m [90mgit[39m
86 | [90m-[39m [1mb[22m [90mexcluded[39m
87 | [90m-[39m [1mc[22m [90mexcluded[39m
88 | [J
89 | Finished after 1.0s.
90 | "
91 | `;
92 |
93 | exports[`dense() custom config and sequential-update with mixed success and show test stdout should print the expected lines: custom config and sequential-update with mixed success and show test stdout 1`] = `
94 | "[?25l[JRunning updtr with custom configuration:
95 |
96 | - exclude: b, c
97 |
98 | [JInstalling missing dependencies[90m...[39m
99 | [90m> npm install [39m
100 | ...
101 | [3A[JLooking for outdated modules[90m...[39m
102 | [90m> npm outdated [39m
103 | ...
104 | [3A[JFound [1m4 updates[22m.
105 |
106 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
107 | [90m> npm install [39m
108 | ...
109 | [3A[J[33m-[39m [1mmodule[22m [90mtesting...[39m
110 | [90m> npm test [39m
111 | ...
112 | [3A[J[32m-[39m [1mmodule[22m 0.0.1 [90msuccess[39m
113 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.1.0[90m...[39m
114 | [90m> npm install [39m
115 | ...
116 | [3A[J[33m-[39m [1mmodule[22m [90mtesting...[39m
117 | [90m> npm test [39m
118 | ...
119 | [3A[J[31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 0.1.0 [90m→[39m 0.0.0[90m...[39m
120 | [90m> npm install [39m
121 | ...
122 | [3A[J[31m-[39m [1m[31mmodule[39m[22m 0.1.0 [90mfailed[39m
123 | [JThis is the test stdout
124 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 1.0.0[90m...[39m
125 | [90m> npm install [39m
126 | ...
127 | [3A[J[33m-[39m [1mmodule[22m [90mtesting...[39m
128 | [90m> npm test [39m
129 | ...
130 | [3A[J[32m-[39m [1mmodule[22m 1.0.0 [90msuccess[39m
131 | [J[33m-[39m [1mmodule[22m [90mupdating[39m 0.0.0 [90m→[39m 0.0.1[90m...[39m
132 | [90m> npm install [39m
133 | ...
134 | [3A[J[33m-[39m [1mmodule[22m [90mtesting...[39m
135 | [90m> npm test [39m
136 | ...
137 | [3A[J[31m-[39m [1m[31mmodule[39m[22m [90mrolling back[39m 0.0.1 [90m→[39m 0.0.0[90m...[39m
138 | [90m> npm install [39m
139 | ...
140 | [3A[J[31m-[39m [1m[31mmodule[39m[22m 0.0.1 [90mfailed[39m
141 | [JThis is the test stdout
142 | [J
143 | [J[1m2 successful[22m updates.
144 | [J[1m2 failed[22m updates.
145 | [J
146 | Finished after 1.0s.
147 | "
148 | `;
149 |
150 | exports[`dense() no outdated modules should print the expected lines: no outdated modules 1`] = `
151 | "[?25l[JInstalling missing dependencies[90m...[39m
152 | [90m> npm install [39m
153 | ...
154 | [3A[JLooking for outdated modules[90m...[39m
155 | [90m> npm outdated [39m
156 | ...
157 | [3A[JEverything [1mup-to-date[22m
158 | [J
159 | [J
160 | Finished after 1.0s.
161 | "
162 | `;
163 |
--------------------------------------------------------------------------------
/test/reporters/__snapshots__/index.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`reporters should export all available reporters 1`] = `
4 | Array [
5 | "dense",
6 | "basic",
7 | "none",
8 | ]
9 | `;
10 |
--------------------------------------------------------------------------------
/test/reporters/basic.test.js:
--------------------------------------------------------------------------------
1 | import EventEmitter from "events";
2 | import unicons from "unicons";
3 | import {stub} from "sinon";
4 | import basic from "../../src/reporters/basic";
5 | import events from "../fixtures/events";
6 |
7 | let consoleStub;
8 |
9 | function setup(reporterConfig = {}) {
10 | const updtr = new EventEmitter();
11 | const output = [];
12 |
13 | consoleStub.callsFake((...args) => output.push(args));
14 | basic(updtr, reporterConfig);
15 |
16 | return {
17 | updtr,
18 | output,
19 | };
20 | }
21 |
22 | beforeAll(() => {
23 | // We need to replace platform-dependent characters with static counter parts to make snapshot testing
24 | // consistent across platforms.
25 | unicons.cli = sign => {
26 | switch (sign) {
27 | case "circle":
28 | return "-";
29 | default:
30 | return "";
31 | }
32 | };
33 | consoleStub = stub(console, "log");
34 | });
35 |
36 | afterEach(() => {
37 | consoleStub.reset();
38 | });
39 |
40 | afterAll(() => {
41 | consoleStub.restore();
42 | });
43 |
44 | describe("basic()", () => {
45 | Object.keys(events).forEach(caseName => {
46 | describe(caseName, () => {
47 | it("should print the expected lines", async () => {
48 | const testCase = events[caseName];
49 | const {updtr, output} = setup(testCase.reporterConfig);
50 |
51 | await testCase.events.reduce(async (previous, [
52 | eventName,
53 | event,
54 | ]) => {
55 | await previous;
56 | updtr.emit(eventName, event);
57 |
58 | // Faking async events
59 | return Promise.resolve();
60 | }, Promise.resolve());
61 |
62 | expect(
63 | output.join("\n").replace(
64 | // We need to replace the timing because that is non-deterministic
65 | /Finished after \d+\.\ds/,
66 | "Finished after 1.0s"
67 | )
68 | ).toMatchSnapshot(caseName);
69 | });
70 | });
71 | });
72 | });
73 |
--------------------------------------------------------------------------------
/test/reporters/dense.test.js:
--------------------------------------------------------------------------------
1 | import EventEmitter from "events";
2 | import {WritableStreamBuffer} from "stream-buffers";
3 | import unicons from "unicons";
4 | import dense from "../../src/reporters/dense";
5 | import Spinner from "../../src/reporters/util/Spinner";
6 | import events from "../fixtures/events";
7 |
8 | function setup(reporterConfig = {}) {
9 | const updtr = new EventEmitter();
10 | const stdout = new WritableStreamBuffer();
11 |
12 | stdout.isTTY = true;
13 | stdout.columns = 80;
14 |
15 | reporterConfig.stream = stdout;
16 | dense(updtr, reporterConfig);
17 |
18 | return {
19 | updtr,
20 | stdout,
21 | };
22 | }
23 |
24 | beforeAll(() => {
25 | // We need to replace platform-dependent characters with static counter parts to make snapshot testing
26 | // consistent across platforms.
27 | Spinner.prototype.valueOf = () => "...";
28 | unicons.cli = sign => {
29 | switch (sign) {
30 | case "circle":
31 | return "-";
32 | default:
33 | return "";
34 | }
35 | };
36 | });
37 |
38 | describe("dense()", () => {
39 | Object.keys(events).forEach(caseName => {
40 | describe(caseName, () => {
41 | it("should print the expected lines", async () => {
42 | const testCase = events[caseName];
43 | const {updtr, stdout} = setup(testCase.reporterConfig);
44 |
45 | await testCase.events.reduce(async (previous, [
46 | eventName,
47 | event,
48 | ]) => {
49 | await previous;
50 | updtr.emit(eventName, event);
51 |
52 | // Faking async events
53 | return Promise.resolve();
54 | }, Promise.resolve());
55 |
56 | const output = stdout.getContentsAsString("utf8");
57 |
58 | expect(
59 | output.replace(
60 | // We need to replace the timing because that is non-deterministic
61 | /Finished after \d+\.\ds/,
62 | "Finished after 1.0s"
63 | )
64 | ).toMatchSnapshot(caseName);
65 | });
66 | });
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/test/reporters/index.test.js:
--------------------------------------------------------------------------------
1 | import reporters from "../../src/reporters";
2 |
3 | describe("reporters", () => {
4 | it("should export all available reporters", () => {
5 | expect(Object.keys(reporters)).toMatchSnapshot();
6 | });
7 | });
8 |
--------------------------------------------------------------------------------
/test/reporters/util/__snapshots__/configList.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`configList() when config.exclude is an array with module names should return the expected line: exclude 1`] = `"exclude: a, b, c"`;
4 |
5 | exports[`configList() when config.registry is specified should return the expected line: registry 1`] = `"registry: http://example.com"`;
6 |
7 | exports[`configList() when config.save is something else should return the expected line: save 1`] = `"save: exact"`;
8 |
9 | exports[`configList() when config.test is specified should return the expected line: test 1`] = `"test command: custom test command"`;
10 |
11 | exports[`configList() when config.updateTo is something else should return the expected line: updateTo 1`] = `"update to: non-breaking"`;
12 |
13 | exports[`configList() when config.use is something else should return the expected line: use 1`] = `"use: yarn"`;
14 |
--------------------------------------------------------------------------------
/test/reporters/util/configList.test.js:
--------------------------------------------------------------------------------
1 | import configList from "../../../src/reporters/util/configList";
2 | import {
3 | USE_OPTIONS,
4 | UPDATE_TO_OPTIONS,
5 | SAVE_OPTIONS,
6 | } from "../../../src/constants/config";
7 |
8 | describe("configList()", () => {
9 | describe("when an empty object is passed", () => {
10 | it("should return an empty array", () => {
11 | expect(configList({})).toEqual([]);
12 | });
13 | });
14 | describe("when config.cwd is specified", () => {
15 | it("should return an empty array", () => {
16 | expect(
17 | configList({
18 | cwd: "some/path",
19 | })
20 | ).toEqual([]);
21 | });
22 | });
23 | describe("when config.use is", () => {
24 | describe(USE_OPTIONS[0], () => {
25 | it("should return an empty array", () => {
26 | expect(
27 | configList({
28 | use: USE_OPTIONS[0],
29 | })
30 | ).toEqual([]);
31 | });
32 | });
33 | describe("something else", () => {
34 | it("should return the expected line", () => {
35 | expect(
36 | configList({
37 | use: "yarn",
38 | })[0]
39 | ).toMatchSnapshot("use");
40 | });
41 | });
42 | });
43 | describe("when config.exclude is", () => {
44 | describe("an empty array", () => {
45 | it("should return an empty array", () => {
46 | expect(
47 | configList({
48 | exclude: [],
49 | })
50 | ).toEqual([]);
51 | });
52 | });
53 | describe("an array with module names", () => {
54 | it("should return the expected line", () => {
55 | expect(
56 | configList({
57 | exclude: ["a", "b", "c"],
58 | })[0]
59 | ).toMatchSnapshot("exclude");
60 | });
61 | });
62 | });
63 | describe("when config.updateTo is", () => {
64 | describe(UPDATE_TO_OPTIONS[0], () => {
65 | it("should return an empty array", () => {
66 | expect(
67 | configList({
68 | updateTo: UPDATE_TO_OPTIONS[0],
69 | })
70 | ).toEqual([]);
71 | });
72 | });
73 | describe("something else", () => {
74 | it("should return the expected line", () => {
75 | expect(
76 | configList({
77 | updateTo: "non-breaking",
78 | })[0]
79 | ).toMatchSnapshot("updateTo");
80 | });
81 | });
82 | });
83 | describe("when config.save is", () => {
84 | describe(SAVE_OPTIONS[0], () => {
85 | it("should return an empty array", () => {
86 | expect(
87 | configList({
88 | save: SAVE_OPTIONS[0],
89 | })
90 | ).toEqual([]);
91 | });
92 | });
93 | describe("something else", () => {
94 | it("should return the expected line", () => {
95 | expect(
96 | configList({
97 | save: "exact",
98 | })[0]
99 | ).toMatchSnapshot("save");
100 | });
101 | });
102 | });
103 | describe("when config.test is specified", () => {
104 | it("should return the expected line", () => {
105 | expect(
106 | configList({
107 | test: "custom test command",
108 | })[0]
109 | ).toMatchSnapshot("test");
110 | });
111 | });
112 | describe("when config.registry is specified", () => {
113 | it("should return the expected line", () => {
114 | expect(
115 | configList({
116 | registry: "http://example.com",
117 | })[0]
118 | ).toMatchSnapshot("registry");
119 | });
120 | });
121 | describe("when unknown config keys are specified", () => {
122 | it("should ignore them", () => {
123 | expect(
124 | configList({
125 | someOtherOption: true,
126 | })
127 | ).toEqual([]);
128 | });
129 | });
130 | });
131 |
--------------------------------------------------------------------------------
/test/reporters/util/handleError.test.js:
--------------------------------------------------------------------------------
1 | import {spy} from "sinon";
2 | import handleError from "../../../src/reporters/util/handleError";
3 | import {PackageJsonNoAccessError} from "../../../src/errors";
4 |
5 | const processExit = process.exit;
6 | const consoleError = console.error;
7 |
8 | function setupSpies() {
9 | process.exit = spy();
10 | console.error = spy();
11 | }
12 |
13 | describe("handleError()", () => {
14 | describe("when err is a PackageJsonNoAccessError", () => {
15 | it("should console.error a descriptive error message and process.exit(1)", () => {
16 | setupSpies();
17 | handleError(new PackageJsonNoAccessError());
18 |
19 | const errorStr = console.error.firstCall.args[0];
20 |
21 | expect(errorStr).toContain("[41m[1m ERROR [22m[49m");
22 | expect(errorStr).toContain(
23 | "Cannot find package.json in current directory"
24 | );
25 | expect(process.exit.calledWith(1)).toBe(true);
26 | });
27 | });
28 | describe("when err is unknown", () => {
29 | it("should console.error the error message and process.exit(1)", () => {
30 | setupSpies();
31 | handleError(new Error("Unknown error"));
32 |
33 | const errorStr = console.error.firstCall.args[0];
34 |
35 | expect(errorStr).toContain("[41m[1m ERROR [22m[49m");
36 | expect(errorStr).toContain("Unknown error");
37 | // Check for greyed out stack
38 | expect(errorStr).toContain("[90m at");
39 | expect(process.exit.calledWith(1)).toBe(true);
40 | });
41 | });
42 | });
43 |
44 | afterAll(() => {
45 | process.exit = processExit;
46 | console.error = consoleError;
47 | });
48 |
--------------------------------------------------------------------------------
/test/tasks/__snapshots__/finish.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`finish() when there are incomplete results using npm should return the expected results and emit the expected events: incomplete > npm > emit args 1`] = `
4 | Array [
5 | Array [
6 | "finish/start",
7 | Object {},
8 | ],
9 | Array [
10 | "finish/incomplete",
11 | Object {
12 | "incomplete": Array [
13 | Object {
14 | "name": "updtr-test-module-1",
15 | "rollbackTo": "1.0.0",
16 | "success": true,
17 | "updateTo": "^1.0.0",
18 | },
19 | Object {
20 | "name": "updtr-test-module-2",
21 | "rollbackTo": "2.0.0",
22 | "success": true,
23 | "updateTo": "^2.0.0",
24 | },
25 | ],
26 | },
27 | ],
28 | Array [
29 | "finish/list-incomplete",
30 | Object {
31 | "cmd": "npm ls --json --depth=0 updtr-test-module-1 updtr-test-module-2",
32 | },
33 | ],
34 | Array [
35 | "finish/end",
36 | Object {
37 | "results": Array [
38 | Object {
39 | "name": "updtr-test-module-1",
40 | "rollbackTo": "1.0.0",
41 | "success": true,
42 | "updateTo": "2.0.0",
43 | },
44 | Object {
45 | "name": "updtr-test-module-1",
46 | "rollbackTo": "1.0.0",
47 | "success": true,
48 | "updateTo": "2.0.0",
49 | },
50 | Object {
51 | "name": "updtr-test-module-2",
52 | "rollbackTo": "2.0.0",
53 | "success": true,
54 | "updateTo": "2.1.1",
55 | },
56 | Object {
57 | "name": "updtr-test-module-2",
58 | "rollbackTo": "2.0.0",
59 | "success": true,
60 | "updateTo": "2.1.1",
61 | },
62 | ],
63 | },
64 | ],
65 | ]
66 | `;
67 |
68 | exports[`finish() when there are incomplete results using npm should return the expected results and emit the expected events: incomplete > npm > exec args 1`] = `
69 | Array [
70 | Array [
71 | "npm ls --json --depth=0 updtr-test-module-1 updtr-test-module-2",
72 | ],
73 | ]
74 | `;
75 |
76 | exports[`finish() when there are incomplete results using npm should return the expected results and emit the expected events: incomplete > npm > results 1`] = `
77 | Array [
78 | Object {
79 | "name": "updtr-test-module-1",
80 | "rollbackTo": "1.0.0",
81 | "success": true,
82 | "updateTo": "2.0.0",
83 | },
84 | Object {
85 | "name": "updtr-test-module-1",
86 | "rollbackTo": "1.0.0",
87 | "success": true,
88 | "updateTo": "2.0.0",
89 | },
90 | Object {
91 | "name": "updtr-test-module-2",
92 | "rollbackTo": "2.0.0",
93 | "success": true,
94 | "updateTo": "2.1.1",
95 | },
96 | Object {
97 | "name": "updtr-test-module-2",
98 | "rollbackTo": "2.0.0",
99 | "success": true,
100 | "updateTo": "2.1.1",
101 | },
102 | ]
103 | `;
104 |
105 | exports[`finish() when there are incomplete results using yarn should return the expected results and emit the expected events: incomplete > yarn > emit args 1`] = `
106 | Array [
107 | Array [
108 | "finish/start",
109 | Object {},
110 | ],
111 | Array [
112 | "finish/incomplete",
113 | Object {
114 | "incomplete": Array [
115 | Object {
116 | "name": "updtr-test-module-1",
117 | "rollbackTo": "1.0.0",
118 | "success": true,
119 | "updateTo": "^1.0.0",
120 | },
121 | Object {
122 | "name": "updtr-test-module-2",
123 | "rollbackTo": "2.0.0",
124 | "success": true,
125 | "updateTo": "^2.0.0",
126 | },
127 | ],
128 | },
129 | ],
130 | Array [
131 | "finish/list-incomplete",
132 | Object {
133 | "cmd": "yarn list --json --depth=0 updtr-test-module-1 updtr-test-module-2",
134 | },
135 | ],
136 | Array [
137 | "finish/end",
138 | Object {
139 | "results": Array [
140 | Object {
141 | "name": "updtr-test-module-1",
142 | "rollbackTo": "1.0.0",
143 | "success": true,
144 | "updateTo": "2.0.0",
145 | },
146 | Object {
147 | "name": "updtr-test-module-1",
148 | "rollbackTo": "1.0.0",
149 | "success": true,
150 | "updateTo": "2.0.0",
151 | },
152 | Object {
153 | "name": "updtr-test-module-2",
154 | "rollbackTo": "2.0.0",
155 | "success": true,
156 | "updateTo": "2.1.1",
157 | },
158 | Object {
159 | "name": "updtr-test-module-2",
160 | "rollbackTo": "2.0.0",
161 | "success": true,
162 | "updateTo": "2.1.1",
163 | },
164 | ],
165 | },
166 | ],
167 | ]
168 | `;
169 |
170 | exports[`finish() when there are incomplete results using yarn should return the expected results and emit the expected events: incomplete > yarn > exec args 1`] = `
171 | Array [
172 | Array [
173 | "yarn list --json --depth=0 updtr-test-module-1 updtr-test-module-2",
174 | ],
175 | ]
176 | `;
177 |
178 | exports[`finish() when there are incomplete results using yarn should return the expected results and emit the expected events: incomplete > yarn > results 1`] = `
179 | Array [
180 | Object {
181 | "name": "updtr-test-module-1",
182 | "rollbackTo": "1.0.0",
183 | "success": true,
184 | "updateTo": "2.0.0",
185 | },
186 | Object {
187 | "name": "updtr-test-module-1",
188 | "rollbackTo": "1.0.0",
189 | "success": true,
190 | "updateTo": "2.0.0",
191 | },
192 | Object {
193 | "name": "updtr-test-module-2",
194 | "rollbackTo": "2.0.0",
195 | "success": true,
196 | "updateTo": "2.1.1",
197 | },
198 | Object {
199 | "name": "updtr-test-module-2",
200 | "rollbackTo": "2.0.0",
201 | "success": true,
202 | "updateTo": "2.1.1",
203 | },
204 | ]
205 | `;
206 |
--------------------------------------------------------------------------------
/test/tasks/__snapshots__/updatePackageJson.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`updatePackageJson() should save a package.json with expected shape to the cwd 1`] = `
4 | Object {
5 | "dependencies": Object {
6 | "updtr-test-module-1": "2.0.0",
7 | "updtr-test-module-2": "1.0.x",
8 | },
9 | "devDependencies": Object {
10 | "updtr-test-module-1": "~2.0.x",
11 | "updtr-test-module-2": "1.0.x",
12 | },
13 | }
14 | `;
15 |
--------------------------------------------------------------------------------
/test/tasks/finish.test.js:
--------------------------------------------------------------------------------
1 | import {USE_YARN} from "../../src/constants/config";
2 | import finish from "../../src/tasks/finish";
3 | import FakeUpdtr from "../helpers/FakeUpdtr";
4 | import {
5 | execError,
6 | npmList,
7 | yarnList,
8 | errorExecList,
9 | } from "../fixtures/execResults";
10 | import {
11 | module1ToLatestSuccess,
12 | module1ToNonBreakingSuccess,
13 | module2ToLatestSuccess,
14 | module2ToNonBreakingSuccess,
15 | module1ToLatestFail,
16 | module1ToNonBreakingFail,
17 | module2ToLatestFail,
18 | module2ToNonBreakingFail,
19 | } from "../fixtures/updateResults";
20 |
21 | describe("finish()", () => {
22 | describe("when the given results array is empty", () => {
23 | it("should resolve immediately with an empty array without emitting events", async () => {
24 | const updtr = new FakeUpdtr();
25 |
26 | expect(await finish(updtr, [])).toEqual([]);
27 | expect(updtr.exec.args).toEqual([]);
28 | expect(updtr.emit.args).toEqual([]);
29 | });
30 | });
31 | describe("when the given results are complete", () => {
32 | it("should resolve immediately with the given results without emitting events", async () => {
33 | const updtr = new FakeUpdtr();
34 |
35 | const results = [
36 | module1ToLatestSuccess,
37 | module1ToLatestFail,
38 | module1ToNonBreakingFail,
39 | module2ToLatestSuccess,
40 | module2ToLatestFail,
41 | module2ToNonBreakingFail,
42 | ];
43 |
44 | expect(await finish(updtr, results)).toEqual(results);
45 | expect(updtr.exec.args).toEqual([]);
46 | expect(updtr.emit.args).toEqual([]);
47 | });
48 | });
49 | describe("when there are incomplete results", () => {
50 | it("should filter tasks where rollbackTo and updateTo is the same value", async () => {
51 | const updtr = new FakeUpdtr();
52 |
53 | const results = [
54 | {
55 | ...module1ToLatestSuccess,
56 | updateTo: "^" + module1ToLatestSuccess.updateTo,
57 | rollbackTo: module1ToLatestSuccess.updateTo,
58 | },
59 | ];
60 |
61 | updtr.execResults = npmList;
62 |
63 | expect(await finish(updtr, results)).toEqual([]);
64 | });
65 | describe("using npm", () => {
66 | it("should return the expected results and emit the expected events", async () => {
67 | const updtr = new FakeUpdtr();
68 |
69 | const results = [
70 | module1ToLatestSuccess,
71 | module1ToNonBreakingSuccess,
72 | module2ToLatestSuccess,
73 | module2ToNonBreakingSuccess,
74 | ];
75 |
76 | updtr.execResults = npmList;
77 |
78 | expect(await finish(updtr, results)).toMatchSnapshot(
79 | "incomplete > npm > results"
80 | );
81 | expect(updtr.exec.args).toMatchSnapshot(
82 | "incomplete > npm > exec args"
83 | );
84 | expect(updtr.emit.args).toMatchSnapshot(
85 | "incomplete > npm > emit args"
86 | );
87 | });
88 | });
89 | describe("using yarn", () => {
90 | it("should return the expected results and emit the expected events", async () => {
91 | const updtr = new FakeUpdtr({
92 | use: USE_YARN,
93 | });
94 |
95 | const results = [
96 | module1ToLatestSuccess,
97 | module1ToNonBreakingSuccess,
98 | module2ToLatestSuccess,
99 | module2ToNonBreakingSuccess,
100 | ];
101 |
102 | updtr.execResults = yarnList;
103 |
104 | expect(await finish(updtr, results)).toMatchSnapshot(
105 | "incomplete > yarn > results"
106 | );
107 | expect(updtr.exec.args).toMatchSnapshot(
108 | "incomplete > yarn > exec args"
109 | );
110 | expect(updtr.emit.args).toMatchSnapshot(
111 | "incomplete > yarn > emit args"
112 | );
113 | });
114 | });
115 | });
116 | describe("unexpected errors", () => {
117 | it("should bail out completely", async () => {
118 | const updtr = new FakeUpdtr();
119 | const results = [module1ToNonBreakingSuccess];
120 | let givenErr;
121 |
122 | updtr.execResults = errorExecList;
123 |
124 | try {
125 | await finish(updtr, results);
126 | } catch (err) {
127 | givenErr = err;
128 | }
129 |
130 | expect(givenErr).toBe(execError);
131 | });
132 | });
133 | });
134 |
--------------------------------------------------------------------------------
/test/tasks/init.test.js:
--------------------------------------------------------------------------------
1 | import init from "../../src/tasks/init";
2 | import FakeUpdtr from "../helpers/FakeUpdtr";
3 | import {
4 | execError,
5 | npmNoOutdated,
6 | npmOutdated,
7 | yarnOutdated,
8 | errorExecInstallMissing,
9 | errorExecOutdated,
10 | errorParseOutdated,
11 | } from "../fixtures/execResults";
12 | import {PackageJsonNoAccessError} from "../../src/errors";
13 |
14 | describe("init()", () => {
15 | describe("when there are no outdated dependencies", () => {
16 | it("should emit expected events and execute expected commands", async () => {
17 | const updtr = new FakeUpdtr();
18 |
19 | updtr.execResults = npmNoOutdated;
20 |
21 | await init(updtr);
22 |
23 | expect(updtr.exec.args).toMatchSnapshot();
24 | expect(updtr.emit.args).toMatchSnapshot();
25 | });
26 | it("should return the results as emitted with the 'init/end' event", async () => {
27 | const updtr = new FakeUpdtr();
28 |
29 | updtr.execResults = npmNoOutdated;
30 |
31 | const returnedResults = await init(updtr);
32 | const [name, endEvent] = updtr.emit.args.pop();
33 |
34 | expect(name).toBe("init/end");
35 | // The emitted event has additional properties like the config
36 | expect(endEvent).toEqual(expect.objectContaining(returnedResults));
37 | });
38 | });
39 | describe("when there are outdated dependencies", () => {
40 | describe("using npm", () => {
41 | it("should emit expected events and execute expected commands", async () => {
42 | const updtr = new FakeUpdtr();
43 |
44 | updtr.execResults = npmOutdated;
45 |
46 | await init(updtr);
47 |
48 | expect(updtr.exec.args).toMatchSnapshot();
49 | expect(updtr.emit.args).toMatchSnapshot();
50 | });
51 | it("should return the results as emitted with the 'init/end' event", async () => {
52 | const updtr = new FakeUpdtr();
53 |
54 | updtr.execResults = npmOutdated;
55 |
56 | const returnedResults = await init(updtr);
57 | const [name, endEvent] = updtr.emit.args.pop();
58 |
59 | expect(name).toBe("init/end");
60 | // The emitted event has additional properties like the config
61 | expect(endEvent).toEqual(
62 | expect.objectContaining(returnedResults)
63 | );
64 | });
65 | });
66 | describe("using yarn", () => {
67 | it("should emit expected events and execute expected commands", async () => {
68 | const updtr = new FakeUpdtr({
69 | use: "yarn",
70 | });
71 |
72 | updtr.execResults = yarnOutdated;
73 |
74 | await init(updtr);
75 |
76 | expect(updtr.exec.args).toMatchSnapshot();
77 | expect(updtr.emit.args).toMatchSnapshot();
78 | });
79 | // We don't test for everything here because we assume that the rest works the same as with npm
80 | });
81 | });
82 | describe("when there are excluded dependencies", () => {
83 | it("should emit expected events and execute expected commands", async () => {
84 | const updtr = new FakeUpdtr({
85 | exclude: ["updtr-test-module-1", "updtr-test-module-2"],
86 | });
87 |
88 | updtr.execResults = npmOutdated;
89 |
90 | await init(updtr);
91 |
92 | expect(updtr.exec.args).toMatchSnapshot();
93 | expect(updtr.emit.args).toMatchSnapshot();
94 | });
95 | });
96 | describe("when there is no package.json in the cwd", () => {
97 | it("should throw a PackageJsonNoAccessError", async () => {
98 | const updtr = new FakeUpdtr();
99 | let givenErr;
100 |
101 | updtr.canAccessPackageJson.resolves(false);
102 |
103 | try {
104 | await init(updtr);
105 | } catch (err) {
106 | givenErr = err;
107 | }
108 | expect(givenErr).toBeInstanceOf(PackageJsonNoAccessError);
109 | });
110 | });
111 | describe("unexpected errors", () => {
112 | it("should fail when installMissing cmd exits with a non-zero exit code", async () => {
113 | const updtr = new FakeUpdtr();
114 | let givenErr;
115 |
116 | updtr.execResults = errorExecInstallMissing;
117 |
118 | try {
119 | await init(updtr);
120 | } catch (err) {
121 | givenErr = err;
122 | }
123 |
124 | expect(givenErr).toBe(execError);
125 | });
126 | it("should fail when the outdated cmd exits with an exit code above 1", async () => {
127 | const updtr = new FakeUpdtr();
128 | let givenErr;
129 |
130 | updtr.execResults = errorExecOutdated;
131 |
132 | try {
133 | await init(updtr);
134 | } catch (err) {
135 | givenErr = err;
136 | }
137 |
138 | expect(givenErr).toBe(execError);
139 | });
140 | it("should fail with a parse error when the stdout could not be parsed", async () => {
141 | const updtr = new FakeUpdtr();
142 | let givenErr;
143 |
144 | updtr.execResults = errorParseOutdated;
145 |
146 | try {
147 | await init(updtr);
148 | } catch (err) {
149 | givenErr = err;
150 | }
151 |
152 | expect(givenErr).toBeInstanceOf(SyntaxError);
153 | expect(givenErr.message).toMatch(
154 | /Error when trying to parse stdout from command 'npm outdated --json --depth=0': Unexpected token N/
155 | );
156 | });
157 | });
158 | });
159 |
--------------------------------------------------------------------------------
/test/tasks/updatePackageJson.test.js:
--------------------------------------------------------------------------------
1 | import updatePackageJson from "../../src/tasks/updatePackageJson";
2 | import FakeUpdtr from "../helpers/FakeUpdtr";
3 | import pickEventNames from "../helpers/pickEventNames";
4 | import {
5 | module1ToLatestSuccess,
6 | module2ToLatestFail,
7 | } from "../fixtures/updateResults";
8 | import {outdatedRegular} from "../fixtures/packageJsons";
9 |
10 | describe("updatePackageJson()", () => {
11 | it("should read and write the package json", async () => {
12 | const updtr = new FakeUpdtr();
13 | const updateResults = [];
14 |
15 | updtr.readFile.resolves(JSON.stringify({}));
16 | updtr.writeFile.resolves();
17 |
18 | await updatePackageJson(updtr, updateResults);
19 |
20 | expect(updtr.readFile.calledWith("package.json")).toBe(true);
21 | expect(updtr.writeFile.calledWith("package.json")).toBe(true);
22 | });
23 | it("should save a package.json with expected shape to the cwd", async () => {
24 | const updtr = new FakeUpdtr();
25 | const updateResults = [module1ToLatestSuccess, module2ToLatestFail];
26 |
27 | updtr.readFile.resolves(
28 | JSON.stringify({
29 | dependencies: {
30 | [module1ToLatestSuccess.name]: "1.0.0",
31 | [module2ToLatestFail.name]: "1.0.x",
32 | },
33 | devDependencies: {
34 | [module1ToLatestSuccess.name]: "~1.0.x",
35 | [module2ToLatestFail.name]: "1.0.x",
36 | },
37 | })
38 | );
39 | updtr.writeFile.resolves();
40 |
41 | await updatePackageJson(updtr, updateResults);
42 |
43 | expect(updtr.readFile.calledWith("package.json")).toBe(true);
44 | expect(updtr.writeFile.calledWith("package.json")).toBe(true);
45 |
46 | const packageJson = JSON.parse(updtr.writeFile.getCall(0).args[1]);
47 |
48 | expect(packageJson).toMatchSnapshot();
49 | });
50 | it("should not alter the formatting", async () => {
51 | const updtr = new FakeUpdtr();
52 | const updateResults = [module1ToLatestSuccess];
53 | const oldPackageJson = outdatedRegular;
54 |
55 | updtr.readFile.resolves(oldPackageJson);
56 | updtr.writeFile.resolves();
57 |
58 | await updatePackageJson(updtr, updateResults);
59 |
60 | const newPackageJson = updtr.writeFile.getCall(0).args[1];
61 |
62 | expect(newPackageJson).toBe(
63 | oldPackageJson.replace(
64 | /\^1\.0\.0/,
65 | "^" + module1ToLatestSuccess.updateTo
66 | )
67 | );
68 | });
69 | it("should respect indentation", async () => {
70 | const updtr = new FakeUpdtr();
71 | const updateResults = [];
72 | const fourSpacedPackageJson = outdatedRegular.replace(/ {2}/g, " ");
73 | const tabbedPackageJson = outdatedRegular.replace(/ {2}/g, "\t");
74 |
75 | updtr.writeFile.resolves();
76 |
77 | updtr.readFile.resolves(fourSpacedPackageJson);
78 |
79 | await updatePackageJson(updtr, updateResults);
80 |
81 | updtr.readFile.resolves(tabbedPackageJson);
82 |
83 | await updatePackageJson(updtr, updateResults);
84 |
85 | expect(updtr.writeFile.getCall(0).args[1]).toBe(fourSpacedPackageJson);
86 | expect(updtr.writeFile.getCall(1).args[1]).toBe(tabbedPackageJson);
87 | });
88 | it("should emit the expected events", async () => {
89 | const updtr = new FakeUpdtr();
90 | const updateResults = [module1ToLatestSuccess];
91 |
92 | const eventNames = [
93 | "update-package-json/start",
94 | "update-package-json/end",
95 | ];
96 |
97 | updtr.readFile.resolves(JSON.stringify({}));
98 | updtr.writeFile.resolves();
99 |
100 | await updatePackageJson(updtr, updateResults);
101 |
102 | expect(pickEventNames(eventNames, updtr.emit.args)).toEqual(eventNames);
103 | });
104 | describe("errors", () => {
105 | it("should enhance the error message in case the package json could not been read", async () => {
106 | const updtr = new FakeUpdtr();
107 | const updateResults = [];
108 | let givenErr;
109 |
110 | updtr.readFile.rejects(new Error("Oops"));
111 |
112 | try {
113 | await updatePackageJson(updtr, updateResults);
114 | } catch (err) {
115 | givenErr = err;
116 | }
117 |
118 | expect(givenErr).toBeInstanceOf(Error);
119 | expect(givenErr.message).toMatch(
120 | /Error while trying to read the package\.json: Oops/
121 | );
122 | });
123 | it("should enhance the error message in case the package json could not been parsed", async () => {
124 | const updtr = new FakeUpdtr();
125 | const updateResults = [];
126 | let givenErr;
127 |
128 | updtr.readFile.resolves("Invalid JSON");
129 |
130 | try {
131 | await updatePackageJson(updtr, updateResults);
132 | } catch (err) {
133 | givenErr = err;
134 | }
135 |
136 | expect(givenErr).toBeInstanceOf(SyntaxError);
137 | expect(givenErr.message).toMatch(
138 | /Error while trying to read the package\.json: Unexpected token I/
139 | );
140 | });
141 | it("should enhance the error message in case the package json could not been written", async () => {
142 | const updtr = new FakeUpdtr();
143 | const updateResults = [];
144 | let givenErr;
145 |
146 | updtr.readFile.resolves(JSON.stringify({}));
147 | updtr.writeFile.rejects(new Error("Oops"));
148 |
149 | try {
150 | await updatePackageJson(updtr, updateResults);
151 | } catch (err) {
152 | givenErr = err;
153 | }
154 |
155 | expect(givenErr).toBeInstanceOf(Error);
156 | expect(givenErr.message).toMatch(
157 | /Error while trying to write the package\.json: Oops/
158 | );
159 | });
160 | });
161 | });
162 |
--------------------------------------------------------------------------------
/test/tasks/util/__snapshots__/createUpdateTask.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`createUpdateTask() should return a valid update task 1`] = `
4 | Object {
5 | "name": "module",
6 | "rollbackTo": "0.0.0",
7 | "updateTo": "0.0.1",
8 | }
9 | `;
10 |
--------------------------------------------------------------------------------
/test/tasks/util/__snapshots__/createUpdatedPackageJson.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`createUpdatedPackageJson() dependencies should write the versions of the successful updates to the package json: dependencies > only successful updates 1`] = `
4 | Object {
5 | "dependencies": Object {
6 | "dep-1": "1.0.0",
7 | "dep-2": "1.0.0",
8 | "dep-3": "1.0.0",
9 | "updtr-test-module-1": "2.0.0",
10 | "updtr-test-module-2": "2.1.1",
11 | "updtr-test-module-3": "1.0.0",
12 | "updtr-test-module-5": "2.0.0",
13 | },
14 | }
15 | `;
16 |
17 | exports[`createUpdatedPackageJson() devDependencies should write the versions of the successful updates to the package json: devDependencies > only successful updates 1`] = `
18 | Object {
19 | "devDependencies": Object {
20 | "dep-1": "1.0.0",
21 | "dep-2": "1.0.0",
22 | "dep-3": "1.0.0",
23 | "updtr-test-module-1": "2.0.0",
24 | "updtr-test-module-2": "2.1.1",
25 | "updtr-test-module-3": "1.0.0",
26 | },
27 | }
28 | `;
29 |
30 | exports[`createUpdatedPackageJson() optionalDependencies should write the versions of the successful updates to the package json: optionalDependencies > only successful updates 1`] = `
31 | Object {
32 | "optionalDependencies": Object {
33 | "dep-1": "1.0.0",
34 | "dep-2": "1.0.0",
35 | "dep-3": "1.0.0",
36 | "updtr-test-module-1": "2.0.0",
37 | "updtr-test-module-2": "2.1.1",
38 | "updtr-test-module-3": "1.0.0",
39 | },
40 | }
41 | `;
42 |
43 | exports[`createUpdatedPackageJson() when a module is listed in more than one dependency type should update all dependency occurrences: more than one dependency type 1`] = `
44 | Object {
45 | "dependencies": Object {
46 | "updtr-test-module-1": "2.0.0",
47 | },
48 | "devDependencies": Object {
49 | "updtr-test-module-1": "^2.0.0",
50 | },
51 | "optionalDependencies": Object {
52 | "updtr-test-module-1": "2.x.x",
53 | },
54 | }
55 | `;
56 |
57 | exports[`createUpdatedPackageJson() when the updated modules are not listed as any dependency type should save the modules as regular dependency: not listed as any dependency type 1`] = `
58 | Object {
59 | "dependencies": Object {
60 | "updtr-test-module-1": "2.0.0",
61 | "updtr-test-module-2": "2.1.1",
62 | },
63 | }
64 | `;
65 |
--------------------------------------------------------------------------------
/test/tasks/util/__snapshots__/updateVersionRange.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`updateVersionRange() should match the expected new version range 1`] = `
4 | Array [
5 | "~0.6.1-1 becomes ^5.0.0",
6 | "1.0.0 - 2.0.0 becomes ^5.0.0",
7 | "1.0.0-2.0.0 becomes ^5.0.0",
8 | "1.0.0 becomes 5.0.0",
9 | " 1.0.0 becomes 5.0.0",
10 | "1.0.0-beta.1 becomes 5.0.0",
11 | ">=1.0.0-beta.1 becomes >=5.0.0",
12 | "1.0.0-BETA.1 becomes 5.0.0",
13 | "1.0.0-alpha-1 becomes 5.0.0",
14 | "1.0.0 alpha becomes ^5.0.0",
15 | "~1.0.0 becomes ~5.0.0",
16 | " ~1.0.0 becomes ~5.0.0",
17 | "~1.0.x becomes ~5.0.x",
18 | ">=* becomes ^5.0.0",
19 | " becomes ^5.0.0",
20 | "* becomes ^5.0.0",
21 | ">=1.0.0 becomes >=5.0.0",
22 | " >=1.0.0 becomes >=5.0.0",
23 | ">1.0.0 becomes ^5.0.0",
24 | " >1.0.0 becomes ^5.0.0",
25 | "<=2.0.0 becomes ^5.0.0",
26 | "<2.0.0 becomes ^5.0.0",
27 | ">= 1.0.0 becomes ^5.0.0",
28 | "> 1.0.0 becomes ^5.0.0",
29 | "<= 2.0.0 becomes ^5.0.0",
30 | "< 2.0.0 becomes ^5.0.0",
31 | "< 2.0.0 becomes ^5.0.0",
32 | ">=0.1.97 becomes >=5.0.0",
33 | "0.1.20 || 1.2.4 becomes ^5.0.0",
34 | ">=0.2.3 || <0.0.1 becomes ^5.0.0",
35 | "|| becomes ^5.0.0",
36 | "2.x.x becomes 5.x.x",
37 | "1.2.x becomes 5.0.x",
38 | "1.2.x || 2.x becomes ^5.0.0",
39 | "x becomes ^5.0.0",
40 | "2.*.* becomes 5.*.*",
41 | "1.2.* becomes 5.0.*",
42 | "1.2.* || 2.* becomes ^5.0.0",
43 | "2 becomes ^5.0.0",
44 | "2.3 becomes ^5.0.0",
45 | "~2.4 becomes ^5.0.0",
46 | "~>3.2.1 becomes ^5.0.0",
47 | "~1 becomes ^5.0.0",
48 | "~>1 becomes ^5.0.0",
49 | "~> 1 becomes ^5.0.0",
50 | "~1.0 becomes ^5.0.0",
51 | "~ 1.0 becomes ^5.0.0",
52 | ">=1 becomes ^5.0.0",
53 | ">= 1 becomes ^5.0.0",
54 | "<1.2 becomes ^5.0.0",
55 | "< 1.2 becomes ^5.0.0",
56 | "1 becomes ^5.0.0",
57 | "~v0.5.4-pre becomes ^5.0.0",
58 | "~v0.5.4-pre becomes ^5.0.0",
59 | "=0.7.x becomes ^5.0.0",
60 | ">=0.7.x becomes >=5.0.x",
61 | ">=0.7.X becomes >=5.0.X",
62 | "=0.7.x becomes ^5.0.0",
63 | ">=0.7.x becomes >=5.0.x",
64 | "<=0.7.x becomes ^5.0.0",
65 | ">0.2.3 >0.2.4 <=0.2.5 becomes ^5.0.0",
66 | ">=0.2.3 <=0.2.4 becomes ^5.0.0",
67 | "1.0.0 - 2.0.0 becomes ^5.0.0",
68 | "^1 becomes ^5.0.0",
69 | "^3.0.0 becomes ^5.0.0",
70 | "^1.0.0 || ~2.0.1 becomes ^5.0.0",
71 | "^0.1.0 || ~3.0.1 || 5.0.0 becomes ^5.0.0",
72 | "^0.1.0 || ~3.0.1 || >4 <=5.0.0 becomes ^5.0.0",
73 | "*.1.2 becomes ^5.0.0",
74 | "x.1.2 becomes ^5.0.0",
75 | "1.x.2 becomes 5.x.x",
76 | "1.*.2 becomes 5.*.*",
77 | ]
78 | `;
79 |
80 | exports[`updateVersionRange() when the new version contains a release tag should match the expected new version range 1`] = `
81 | Array [
82 | "~0.6.1-1 becomes ^5.0.0-beta.1",
83 | "1.0.0 - 2.0.0 becomes ^5.0.0-beta.1",
84 | "1.0.0-2.0.0 becomes ^5.0.0-beta.1",
85 | "1.0.0 becomes 5.0.0-beta.1",
86 | " 1.0.0 becomes 5.0.0-beta.1",
87 | "1.0.0-beta.1 becomes 5.0.0-beta.1",
88 | ">=1.0.0-beta.1 becomes >=5.0.0-beta.1",
89 | "1.0.0-BETA.1 becomes 5.0.0-beta.1",
90 | "1.0.0-alpha-1 becomes 5.0.0-beta.1",
91 | "1.0.0 alpha becomes ^5.0.0-beta.1",
92 | "~1.0.0 becomes ~5.0.0-beta.1",
93 | " ~1.0.0 becomes ~5.0.0-beta.1",
94 | "~1.0.x becomes ^5.0.0-beta.1",
95 | ">=* becomes ^5.0.0-beta.1",
96 | " becomes ^5.0.0-beta.1",
97 | "* becomes ^5.0.0-beta.1",
98 | ">=1.0.0 becomes >=5.0.0-beta.1",
99 | " >=1.0.0 becomes >=5.0.0-beta.1",
100 | ">1.0.0 becomes ^5.0.0-beta.1",
101 | " >1.0.0 becomes ^5.0.0-beta.1",
102 | "<=2.0.0 becomes ^5.0.0-beta.1",
103 | "<2.0.0 becomes ^5.0.0-beta.1",
104 | ">= 1.0.0 becomes ^5.0.0-beta.1",
105 | "> 1.0.0 becomes ^5.0.0-beta.1",
106 | "<= 2.0.0 becomes ^5.0.0-beta.1",
107 | "< 2.0.0 becomes ^5.0.0-beta.1",
108 | "< 2.0.0 becomes ^5.0.0-beta.1",
109 | ">=0.1.97 becomes >=5.0.0-beta.1",
110 | "0.1.20 || 1.2.4 becomes ^5.0.0-beta.1",
111 | ">=0.2.3 || <0.0.1 becomes ^5.0.0-beta.1",
112 | "|| becomes ^5.0.0-beta.1",
113 | "2.x.x becomes ^5.0.0-beta.1",
114 | "1.2.x becomes ^5.0.0-beta.1",
115 | "1.2.x || 2.x becomes ^5.0.0-beta.1",
116 | "x becomes ^5.0.0-beta.1",
117 | "2.*.* becomes ^5.0.0-beta.1",
118 | "1.2.* becomes ^5.0.0-beta.1",
119 | "1.2.* || 2.* becomes ^5.0.0-beta.1",
120 | "2 becomes ^5.0.0-beta.1",
121 | "2.3 becomes ^5.0.0-beta.1",
122 | "~2.4 becomes ^5.0.0-beta.1",
123 | "~>3.2.1 becomes ^5.0.0-beta.1",
124 | "~1 becomes ^5.0.0-beta.1",
125 | "~>1 becomes ^5.0.0-beta.1",
126 | "~> 1 becomes ^5.0.0-beta.1",
127 | "~1.0 becomes ^5.0.0-beta.1",
128 | "~ 1.0 becomes ^5.0.0-beta.1",
129 | ">=1 becomes ^5.0.0-beta.1",
130 | ">= 1 becomes ^5.0.0-beta.1",
131 | "<1.2 becomes ^5.0.0-beta.1",
132 | "< 1.2 becomes ^5.0.0-beta.1",
133 | "1 becomes ^5.0.0-beta.1",
134 | "~v0.5.4-pre becomes ^5.0.0-beta.1",
135 | "~v0.5.4-pre becomes ^5.0.0-beta.1",
136 | "=0.7.x becomes ^5.0.0-beta.1",
137 | ">=0.7.x becomes ^5.0.0-beta.1",
138 | ">=0.7.X becomes ^5.0.0-beta.1",
139 | "=0.7.x becomes ^5.0.0-beta.1",
140 | ">=0.7.x becomes ^5.0.0-beta.1",
141 | "<=0.7.x becomes ^5.0.0-beta.1",
142 | ">0.2.3 >0.2.4 <=0.2.5 becomes ^5.0.0-beta.1",
143 | ">=0.2.3 <=0.2.4 becomes ^5.0.0-beta.1",
144 | "1.0.0 - 2.0.0 becomes ^5.0.0-beta.1",
145 | "^1 becomes ^5.0.0-beta.1",
146 | "^3.0.0 becomes ^5.0.0-beta.1",
147 | "^1.0.0 || ~2.0.1 becomes ^5.0.0-beta.1",
148 | "^0.1.0 || ~3.0.1 || 5.0.0 becomes ^5.0.0-beta.1",
149 | "^0.1.0 || ~3.0.1 || >4 <=5.0.0 becomes ^5.0.0-beta.1",
150 | "*.1.2 becomes ^5.0.0-beta.1",
151 | "x.1.2 becomes ^5.0.0-beta.1",
152 | "1.x.2 becomes ^5.0.0-beta.1",
153 | "1.*.2 becomes ^5.0.0-beta.1",
154 | ]
155 | `;
156 |
--------------------------------------------------------------------------------
/test/tasks/util/createUpdateTask.test.js:
--------------------------------------------------------------------------------
1 | import createUpdateTask from "../../../src/tasks/util/createUpdateTask";
2 | import {
3 | UPDATE_TO_LATEST,
4 | UPDATE_TO_NON_BREAKING,
5 | UPDATE_TO_WANTED,
6 | } from "../../../src/constants/config";
7 | import FakeUpdtr from "../../helpers/FakeUpdtr";
8 | import outdateds from "../../fixtures/outdateds";
9 |
10 | function stringify(outdated) {
11 | return [
12 | "current " + outdated.current,
13 | "wanted " + outdated.wanted,
14 | "latest " + outdated.latest,
15 | ].join(", ");
16 | }
17 |
18 | describe("createUpdateTask()", () => {
19 | it("should return a valid update task", () => {
20 | expect(
21 | createUpdateTask(outdateds[0], {
22 | ...FakeUpdtr.baseConfig,
23 | updateTo: UPDATE_TO_LATEST,
24 | })
25 | ).toMatchSnapshot();
26 | });
27 | describe(`when updateTo is "${UPDATE_TO_LATEST}"`, () => {
28 | outdateds.forEach(outdated => {
29 | it(`when given ${stringify(outdated)} should update to ${outdated.latest} and rollback to ${outdated.current}`, () => {
30 | const updateTask = createUpdateTask(outdated, {
31 | ...FakeUpdtr.baseConfig,
32 | updateTo: UPDATE_TO_LATEST,
33 | });
34 |
35 | expect(updateTask.updateTo).toBe(outdated.latest);
36 | expect(updateTask.rollbackTo).toBe(outdated.current);
37 | });
38 | });
39 | });
40 | describe(`when updateTo is "${UPDATE_TO_NON_BREAKING}"`, () => {
41 | outdateds.forEach(outdated => {
42 | const range = "^" + outdated.current;
43 |
44 | it(`when given ${stringify(outdated)} should update to ${range} and rollback to ${outdated.current}`, () => {
45 | const updateTask = createUpdateTask(outdated, {
46 | ...FakeUpdtr.baseConfig,
47 | updateTo: UPDATE_TO_NON_BREAKING,
48 | });
49 |
50 | expect(updateTask.updateTo).toBe(range);
51 | expect(updateTask.rollbackTo).toBe(outdated.current);
52 | });
53 | });
54 | });
55 | describe(`when updateTo is "${UPDATE_TO_WANTED}"`, () => {
56 | outdateds.forEach(outdated => {
57 | it(`when given ${stringify(outdated)} should update to ${outdated.wanted} and rollback to ${outdated.current}`, () => {
58 | const updateTask = createUpdateTask(outdated, {
59 | ...FakeUpdtr.baseConfig,
60 | updateTo: UPDATE_TO_WANTED,
61 | });
62 |
63 | expect(updateTask.updateTo).toBe(outdated.wanted);
64 | expect(updateTask.rollbackTo).toBe(outdated.current);
65 | });
66 | });
67 | });
68 | });
69 |
--------------------------------------------------------------------------------
/test/tasks/util/filterUpdateTask.test.js:
--------------------------------------------------------------------------------
1 | import filterUpdateTask from "../../../src/tasks/util/filterUpdateTask";
2 | import {
3 | GIT,
4 | UNSTABLE,
5 | NOT_WANTED,
6 | EXCLUDED,
7 | EXOTIC,
8 | } from "../../../src/constants/filterReasons";
9 | import {
10 | isUpdateToNonBreaking,
11 | } from "../../../src/tasks/util/createUpdateTask";
12 |
13 | const baseUpdateTask = {
14 | name: "some-module",
15 | updateTo: "2.0.0",
16 | rollbackTo: "1.0.0",
17 | };
18 |
19 | const baseUpdtrConfig = {
20 | exclude: [],
21 | };
22 |
23 | describe("filterUpdateTask()", () => {
24 | it("should not filter a regular update task", () => {
25 | expect(filterUpdateTask(baseUpdateTask, baseUpdtrConfig)).toBe(null);
26 | });
27 | it("should not filter an update task that satisfies the isUpdateToNonBreaking test", () => {
28 | const updateTask = {...baseUpdateTask, updateTo: "^1.0.0"};
29 |
30 | expect(isUpdateToNonBreaking(updateTask)).toBe(true); // sanity check
31 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(null);
32 | });
33 | describe("excluded dependencies", () => {
34 | it("should honor the given exclude filter", () => {
35 | const updtrConfig = {...baseUpdateTask};
36 |
37 | updtrConfig.exclude = [baseUpdateTask.name];
38 | expect(filterUpdateTask(baseUpdateTask, updtrConfig)).toBe(
39 | EXCLUDED
40 | );
41 | });
42 | });
43 | describe("git dependencies", () => {
44 | it("should filter git dependencies", () => {
45 | const updateTask = {...baseUpdateTask};
46 |
47 | updateTask.updateTo = "git";
48 |
49 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(GIT);
50 | });
51 | });
52 | describe("exotic dependencies", () => {
53 | it("should filter exotic dependencies", () => {
54 | const updateTask = {...baseUpdateTask};
55 |
56 | updateTask.updateTo = "exotic";
57 |
58 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(EXOTIC);
59 | });
60 | });
61 | describe("not wanted", () => {
62 | it("should filter if rollbackTo is the same as updateTo", () => {
63 | const updateTask = {...baseUpdateTask};
64 |
65 | updateTask.rollbackTo = "2.0.0";
66 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
67 | NOT_WANTED
68 | );
69 | updateTask.rollbackTo = "2.0.0-beta.1";
70 | updateTask.updateTo = "2.0.0-beta.1";
71 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
72 | NOT_WANTED
73 | );
74 | });
75 | it("should filter if rollbackTo is greater than updateTo", () => {
76 | const updateTask = {...baseUpdateTask};
77 |
78 | updateTask.rollbackTo = "3.0.0";
79 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
80 | NOT_WANTED
81 | );
82 | updateTask.rollbackTo = "3.0.0-beta.1";
83 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
84 | NOT_WANTED
85 | );
86 | });
87 | });
88 | describe("unstable dependencies", () => {
89 | describe("when the current version is not a pre-release", () => {
90 | it("should filter pre-releases", () => {
91 | const updateTask = {...baseUpdateTask};
92 |
93 | updateTask.updateTo = "2.0.0-alpha.1";
94 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
95 | UNSTABLE
96 | );
97 | updateTask.updateTo = "2.0.0-beta.1";
98 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
99 | UNSTABLE
100 | );
101 | updateTask.updateTo = "2.0.0-some-other-cryptic-thing";
102 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
103 | UNSTABLE
104 | );
105 | });
106 | });
107 | describe("when the current version is a pre-release within the same version range", () => {
108 | it("should not filter pre-releases", () => {
109 | const updateTask = {
110 | ...baseUpdateTask,
111 | rollbackTo: "1.0.0-alpha.1",
112 | };
113 |
114 | updateTask.updateTo = "1.0.0-alpha.2";
115 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
116 | null
117 | );
118 | updateTask.updateTo = "1.0.0-beta.2";
119 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
120 | null
121 | );
122 | });
123 | });
124 | describe("when the current version is a pre-release not in the same version range", () => {
125 | it("should filter pre-releases", () => {
126 | const updateTask = {
127 | ...baseUpdateTask,
128 | rollbackTo: "1.0.0-beta.1",
129 | };
130 |
131 | updateTask.updateTo = "1.1.0-alpha.1";
132 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
133 | UNSTABLE
134 | );
135 | updateTask.updateTo = "2.0.0-beta.1";
136 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
137 | UNSTABLE
138 | );
139 | updateTask.updateTo = "2.0.0-some-other-cryptic-thing";
140 | expect(filterUpdateTask(updateTask, baseUpdtrConfig)).toBe(
141 | UNSTABLE
142 | );
143 | });
144 | });
145 | });
146 | });
147 |
--------------------------------------------------------------------------------
/test/tasks/util/splitUpdateTasks.test.js:
--------------------------------------------------------------------------------
1 | import splitUpdateTasks from "../../../src/tasks/util/splitUpdateTasks";
2 |
3 | const breakingUpdateTasks = [
4 | {
5 | updateTo: "2.0.0",
6 | rollbackTo: "1.0.1",
7 | },
8 | {
9 | updateTo: "2.0.0",
10 | rollbackTo: "1.1.0",
11 | },
12 | {
13 | updateTo: "0.2.0",
14 | rollbackTo: "0.1.0",
15 | },
16 | {
17 | updateTo: "0.0.2",
18 | rollbackTo: "0.0.1",
19 | },
20 | ];
21 |
22 | const nonBreakingUpdateTasks = [
23 | {
24 | // Does not occur in the real-world since these modules are not outdated.
25 | // However, we still test it here for consistency.
26 | updateTo: "1.0.0",
27 | rollbackTo: "1.0.0",
28 | },
29 | {
30 | updateTo: "^1.0.0",
31 | rollbackTo: "1.0.0",
32 | },
33 | {
34 | updateTo: "1.0.1",
35 | rollbackTo: "1.0.0",
36 | },
37 | {
38 | updateTo: "^1.0.1",
39 | rollbackTo: "1.0.1",
40 | },
41 | {
42 | updateTo: "1.1.0",
43 | rollbackTo: "1.0.0",
44 | },
45 | {
46 | updateTo: "^1.1.0",
47 | rollbackTo: "1.1.0",
48 | },
49 | {
50 | updateTo: "^0.1.0",
51 | rollbackTo: "0.1.0",
52 | },
53 | {
54 | updateTo: "^0.0.1",
55 | rollbackTo: "0.0.1",
56 | },
57 | ];
58 |
59 | const nonBreakingPreVersionUpdateTasks = [
60 | {
61 | updateTo: "^0.0.1-beta.1",
62 | rollbackTo: "0.0.1-beta.1",
63 | },
64 | {
65 | updateTo: "^0.1.0-beta.1",
66 | rollbackTo: "0.1.0-beta.1",
67 | },
68 | {
69 | updateTo: "^1.0.0-beta.1",
70 | rollbackTo: "1.0.0-beta.1",
71 | },
72 | {
73 | updateTo: "^1.1.0-beta.1",
74 | rollbackTo: "1.1.0-beta.1",
75 | },
76 | {
77 | updateTo: "^1.1.1-beta.1",
78 | rollbackTo: "1.1.1-beta.1",
79 | },
80 | {
81 | updateTo: "1.0.1-beta.2",
82 | rollbackTo: "1.0.1-beta.1",
83 | },
84 | {
85 | updateTo: "1.0.1-beta.1",
86 | rollbackTo: "1.0.1-alpha.1",
87 | },
88 | ];
89 |
90 | const breakingPreVersionUpdateTasks = [
91 | {
92 | updateTo: "1.0.1-beta.1",
93 | rollbackTo: "1.0.0",
94 | },
95 | // diff = prepatch
96 | {
97 | updateTo: "1.0.2-beta.2",
98 | rollbackTo: "1.0.1-beta.1",
99 | },
100 | // diff = preminor
101 | {
102 | updateTo: "1.1.0-beta.2",
103 | rollbackTo: "1.0.1-beta.1",
104 | },
105 | // diff = premajor
106 | {
107 | updateTo: "2.0.0-beta.2",
108 | rollbackTo: "1.0.1-beta.1",
109 | },
110 | ];
111 |
112 | describe("splitUpdateTasks()", () => {
113 | describe(".breaking", () => {
114 | it("should be empty by default", () => {
115 | expect(splitUpdateTasks([]).breaking).toEqual([]);
116 | });
117 | it("should be empty if there are just non-breaking updates", () => {
118 | expect(splitUpdateTasks(nonBreakingUpdateTasks).breaking).toEqual(
119 | []
120 | );
121 | });
122 | it("should be an array with all breaking updates", () => {
123 | const updateTasks = nonBreakingUpdateTasks.concat(
124 | breakingUpdateTasks
125 | );
126 |
127 | expect(splitUpdateTasks(updateTasks).breaking).toEqual(
128 | breakingUpdateTasks
129 | );
130 | });
131 | it("should contain all breaking pre-version updates", () => {
132 | expect(
133 | splitUpdateTasks(breakingPreVersionUpdateTasks).breaking
134 | ).toEqual(breakingPreVersionUpdateTasks);
135 | });
136 | it("should not contain non-breaking pre-version updates", () => {
137 | expect(
138 | splitUpdateTasks(nonBreakingPreVersionUpdateTasks).breaking
139 | ).toEqual([]);
140 | });
141 | });
142 | describe(".nonBreaking", () => {
143 | it("should be empty by default", () => {
144 | expect(splitUpdateTasks([]).nonBreaking).toEqual([]);
145 | });
146 | it("should be empty if there are just breaking updates", () => {
147 | expect(splitUpdateTasks(breakingUpdateTasks).nonBreaking).toEqual(
148 | []
149 | );
150 | });
151 | it("should be an array with all non-breaking updates", () => {
152 | const updateTasks = breakingUpdateTasks.concat(
153 | nonBreakingUpdateTasks
154 | );
155 |
156 | expect(splitUpdateTasks(updateTasks).nonBreaking).toEqual(
157 | nonBreakingUpdateTasks
158 | );
159 | });
160 | it("should contain non-breaking pre-version updates", () => {
161 | expect(
162 | splitUpdateTasks(nonBreakingPreVersionUpdateTasks).nonBreaking
163 | ).toEqual(nonBreakingPreVersionUpdateTasks);
164 | });
165 | it("should not contain breaking pre-version updates since they are considered to be unstable", () => {
166 | expect(
167 | splitUpdateTasks(breakingPreVersionUpdateTasks).nonBreaking
168 | ).toEqual([]);
169 | });
170 | });
171 | });
172 |
--------------------------------------------------------------------------------
/test/tasks/util/updateVersionRange.test.js:
--------------------------------------------------------------------------------
1 | import updateVersionRange from "../../../src/tasks/util/updateVersionRange";
2 | import versionRanges from "../../fixtures/versionRanges";
3 |
4 | describe("updateVersionRange()", () => {
5 | it("should match the expected new version range", () => {
6 | const newVersionRanges = versionRanges.map(oldVersionRange =>
7 | updateVersionRange(oldVersionRange, "5.0.0")
8 | );
9 |
10 | const comparisons = versionRanges.map(
11 | (oldVersionRange, index) =>
12 | oldVersionRange + " becomes " + newVersionRanges[index]
13 | );
14 |
15 | expect(comparisons).toMatchSnapshot();
16 | });
17 | describe("when the new version contains a release tag", () => {
18 | it("should match the expected new version range", () => {
19 | const newVersionRanges = versionRanges.map(oldVersionRange =>
20 | updateVersionRange(oldVersionRange, "5.0.0-beta.1")
21 | );
22 |
23 | const comparisons = versionRanges.map(
24 | (oldVersionRange, index) =>
25 | oldVersionRange +
26 | " becomes " +
27 | newVersionRanges[index]
28 | );
29 |
30 | expect(comparisons).toMatchSnapshot();
31 | });
32 | });
33 | describe("unexpected input", () => {
34 | describe("when the new version is a parseable range", () => {
35 | it("should just return the range", () => {
36 | expect(updateVersionRange("^1.0.0", "^5.0.0")).toBe("^5.0.0");
37 | });
38 | });
39 | describe("when the new version is not parseable", () => {
40 | it("should just return the new range", () => {
41 | expect(updateVersionRange("^1.0.0", "5.x")).toBe("5.x");
42 | });
43 | });
44 | });
45 | });
46 |
--------------------------------------------------------------------------------