diff --git a/bin/install.js b/bin/install.js index e9b5224d..c2c4db0f 100755 --- a/bin/install.js +++ b/bin/install.js @@ -46,6 +46,7 @@ const argv = parseArgs(orignalArgv, { 'flatten', 'registry-only', 'cache-strict', + 'fix-bug-versions', ], alias: { // npm install [-S|--save|-D|--save-dev|-O|--save-optional] [-E|--save-exact] [-d|--detail] @@ -102,6 +103,7 @@ Options: --flatten: flatten dependencies by matching ancestors' dependencies --registry-only: make sure all packages install from registry. Any package is installed from remote(e.g.: git, remote url) cause install fail. --cache-strict: use disk cache even on production env. + --fix-bug-versions: auto fix bug version of package. ` ); process.exit(0); @@ -234,6 +236,14 @@ co(function* () { }; } + if (argv['fix-bug-versions']) { + const packageVersionMapping = yield utils.getBugVersions(registry, { proxy }); + config.autoFixVersion = function autoFixVersion(name, version) { + const fixVersions = packageVersionMapping[name]; + return fixVersions && fixVersions[version] || null; + }; + } + // -g install to npm's global prefix if (argv.global) { // support custom prefix for global install diff --git a/lib/download/npm.js b/lib/download/npm.js index b7100cdc..e0e0a43b 100644 --- a/lib/download/npm.js +++ b/lib/download/npm.js @@ -92,6 +92,14 @@ function* resolve(pkg, options) { throw new Error(`[${pkg.displayName}] Can\'t find package ${pkg.name}@${pkg.rawSpec}`); } + if (options.autoFixVersion) { + const fixVersion = options.autoFixVersion(pkg.name, realPkgVersion); + if (fixVersion) { + options.console.warn(`[${pkg.name}@${realPkgVersion}] use ${pkg.name}@${chalk.green(fixVersion.version)} instead, reason: ${chalk.yellow(fixVersion.reason)}`); + realPkgVersion = fixVersion.version; + } + } + const realPkg = packageMeta.versions[realPkgVersion]; if (!realPkg) { throw new Error(`[${pkg.displayName}] Can\'t find package ${pkg.name}\'s version: ${realPkgVersion}`); diff --git a/lib/utils.js b/lib/utils.js index 1df925f5..bf4bb046 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -344,16 +344,16 @@ exports.getTarballStream = function* (url, options) { return result.res; }; -exports.getBinaryMirrors = function* getBinaryMirrors(registry, globalOptions) { +function* getRemotePackage(name, registry, globalOptions) { const registries = [ registry ].concat([ 'https://registry.npm.taobao.org', 'https://r.cnpmjs.org', 'https://registry.npmjs.org', ]); let lastErr; - let binaryMirrors; + let pkg; for (const registry of registries) { - const binaryMirrorUrl = registry + '/binary-mirror-config/latest'; + const binaryMirrorUrl = registry + '/' + name + '/latest'; try { const res = yield get(binaryMirrorUrl, { dataType: 'json', @@ -361,19 +361,28 @@ exports.getBinaryMirrors = function* getBinaryMirrors(registry, globalOptions) { // don't retry retry: 0, }, globalOptions); - binaryMirrors = res.data.mirrors.china; + pkg = res.data; break; } catch (err) { lastErr = err; } } - if (!binaryMirrors) { - console.warn('Get /binary-mirror-config/latest from %s error: %s', registry, lastErr.stack); - binaryMirrors = require('binary-mirror-config/package.json').mirrors.china; + if (!pkg) { + console.warn('Get /%s/latest from %s error: %s', name, registry, lastErr.stack); + pkg = require(name + '/package.json'); } + return pkg; +} + +exports.getBinaryMirrors = function* getBinaryMirrors(registry, globalOptions) { + const pkg = yield getRemotePackage('binary-mirror-config', registry, globalOptions); + return pkg.mirrors.china; +}; - return binaryMirrors; +exports.getBugVersions = function* getBugVersions(registry, globalOptions) { + const pkg = yield getRemotePackage('bug-versions', registry, globalOptions); + return pkg.config['bug-versions']; }; exports.matchPlatform = function(current, osNames) { diff --git a/test/fix-bug-versions.test.js b/test/fix-bug-versions.test.js new file mode 100644 index 00000000..26ecdc04 --- /dev/null +++ b/test/fix-bug-versions.test.js @@ -0,0 +1,85 @@ +'use strict'; + +const coffee = require('coffee'); +const rimraf = require('rimraf'); +const mkdirp = require('mkdirp'); +const path = require('path'); + +describe('fix-bug-versions.test.js', () => { + const tmp = path.join(__dirname, 'fixtures', 'tmp'); + const demo = path.join(__dirname, 'fixtures', 'fix-bug-versions-app'); + const bin = path.join(__dirname, '../bin/install.js'); + const update = path.join(__dirname, '../bin/update.js'); + + function cleanup() { + rimraf.sync(tmp); + rimraf.sync(path.join(demo, 'node_modules')); + } + + beforeEach(() => { + cleanup(); + mkdirp.sync(tmp); + }); + afterEach(cleanup); + + it('should use fix version instead', done => { + coffee.fork(bin, [ + 'is-my-json-valid@2.17.0', + '-d', + '--fix-bug-versions', + '--no-cache', + ], { cwd: tmp }) + .debug() + .expect('code', 0) + .expect('stdout', /is-my-json-valid@2\.17\.1@is-my-json-valid/) + .end(done); + }); + + it('should support on install and update', function* () { + yield coffee.fork(bin, [ + '-d', + '--fix-bug-versions', + '--no-cache', + ], { cwd: demo }) + .debug() + .expect('code', 0) + .expect('stdout', /is-my-json-valid@2\.17\.1@is-my-json-valid/) + .end(); + + yield coffee.fork(update, [ + '-d', + '--fix-bug-versions', + '--no-cache', + ], { cwd: demo }) + .debug() + .expect('code', 0) + .expect('stdout', /is-my-json-valid@2\.17\.1@is-my-json-valid/) + .end(); + }); + + it('should not match version', done => { + coffee.fork(bin, [ + 'is-my-json-valid@2.16.0', + '-d', + '--fix-bug-versions', + '--no-cache', + ], { cwd: tmp }) + .debug() + .expect('code', 0) + .expect('stdout', /is-my-json-valid@2\.16\.0@is-my-json-valid/) + .end(done); + }); + + it('should not match name', done => { + coffee.fork(bin, [ + 'mm', + '-d', + '--fix-bug-versions', + '--no-cache', + ], { cwd: tmp }) + .debug() + .expect('code', 0) + .expect('stdout', /mm@\* installed/) + .end(done); + }); +}); diff --git a/test/fixtures/fix-bug-versions-app/package.json b/test/fixtures/fix-bug-versions-app/package.json new file mode 100644 index 00000000..ef84c147 --- /dev/null +++ b/test/fixtures/fix-bug-versions-app/package.json @@ -0,0 +1,7 @@ +{ + "name": "fix-bug-versions-app", + "version": "1.0.0", + "dependencies": { + "is-my-json-valid": "2.17.0" + } +}