mirror of
https://github.com/bower/bower.git
synced 2026-01-14 16:57:57 -05:00
324 lines
10 KiB
JavaScript
324 lines
10 KiB
JavaScript
var Q = require('q');
|
|
var path = require('path');
|
|
var fs = require('../../util/fs');
|
|
var object = require('mout/object');
|
|
|
|
var semver = require('../../util/semver');
|
|
var createError = require('../../util/createError');
|
|
var readJson = require('../../util/readJson');
|
|
var removeIgnores = require('../../util/removeIgnores');
|
|
|
|
function pluginResolverFactory(resolverFactory, bower) {
|
|
bower = bower || {};
|
|
|
|
if (typeof resolverFactory !== 'function') {
|
|
throw createError('Resolver has "' + typeof resolverFactory + '" type instead of "function" type.', 'ERESOLERAPI');
|
|
}
|
|
|
|
var resolver = resolverFactory(bower);
|
|
|
|
function maxSatisfyingVersion(versions, target) {
|
|
var versionsArr, index;
|
|
|
|
versionsArr = versions.map(function (obj) { return obj.version; });
|
|
|
|
// Find a satisfying version, enabling strict match so that pre-releases
|
|
// have lower priority over normal ones when target is *
|
|
index = semver.maxSatisfyingIndex(versionsArr, target, true);
|
|
|
|
if (index !== -1) {
|
|
return versions[index];
|
|
}
|
|
}
|
|
|
|
function PluginResolver(decEndpoint) {
|
|
this._decEndpoint = decEndpoint;
|
|
}
|
|
|
|
// @private
|
|
PluginResolver.prototype.getEndpoint = function () {
|
|
return object.merge(this._decEndpoint, {
|
|
name: this.getName(),
|
|
source: this.getSource(),
|
|
target: this.getTarget()
|
|
});
|
|
};
|
|
|
|
PluginResolver.prototype.getSource = function () {
|
|
return this._decEndpoint.source;
|
|
};
|
|
|
|
PluginResolver.prototype.getTarget = function () {
|
|
return this._decEndpoint.target || '*';
|
|
};
|
|
|
|
PluginResolver.prototype.getName = function () {
|
|
if (!this._decEndpoint.name && typeof resolver.getName === 'function') {
|
|
return resolver.getName.call(resolver, this.getSource());
|
|
} else if (!this._decEndpoint.name) {
|
|
return path.basename(this.getSource());
|
|
} else {
|
|
return this._decEndpoint.name;
|
|
}
|
|
};
|
|
|
|
PluginResolver.prototype.getPkgMeta = function () {
|
|
return this._pkgMeta;
|
|
};
|
|
|
|
// -----------------
|
|
|
|
// Plugin Resolver is always considered potentially cacheable
|
|
// The "resolve" method decides whether to use cached or fetch new version.
|
|
PluginResolver.prototype.isCacheable = function () {
|
|
return true;
|
|
};
|
|
|
|
// Not only it's always potentially cacheable, but also always potenially new.
|
|
// The "resolve" handles logic of re-downloading target if needed.
|
|
PluginResolver.prototype.hasNew = function (pkgMeta) {
|
|
if (this.hasNewPromise) {
|
|
return this.hasNewPromise;
|
|
}
|
|
|
|
this._pkgMeta = pkgMeta;
|
|
|
|
return this.hasNewPromise = this.resolve().then(function (result) {
|
|
return result !== undefined;
|
|
});
|
|
};
|
|
|
|
PluginResolver.prototype.resolve = function () {
|
|
if (this.resolvePromise) {
|
|
return this.resolvePromise;
|
|
}
|
|
|
|
var that = this;
|
|
|
|
return this.resolvePromise = Q.fcall(function () {
|
|
var target = that.getTarget();
|
|
|
|
// It means that we can accept ranges as targets
|
|
if (that.constructor.isTargetable()) {
|
|
that._release = target;
|
|
|
|
if (semver.validRange(target)) {
|
|
return Q.fcall(resolver.releases.bind(resolver), that.getSource())
|
|
.then(function (result) {
|
|
if (!result) {
|
|
throw createError('Resolver did not provide releases of package.');
|
|
}
|
|
|
|
var releases = that._releases = result;
|
|
|
|
var versions = releases.filter(function (target) {
|
|
return semver.clean(target.version);
|
|
});
|
|
|
|
var maxRelease = maxSatisfyingVersion(versions, target);
|
|
|
|
if (maxRelease) {
|
|
that._version = maxRelease.version;
|
|
that._release = that._decEndpoint.target = maxRelease.target;
|
|
} else {
|
|
throw createError('No version found that was able to satisfy ' + target, 'ENORESTARGET', {
|
|
details: !versions.length ?
|
|
'No versions found in ' + that.getSource() :
|
|
'Available versions: ' + versions.map(function (version) { return version.version; }).join(', ')
|
|
});
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
if (semver.validRange(target) && target !== '*') {
|
|
return Q.reject(createError('Resolver does not accept version ranges (' + target + ')'));
|
|
}
|
|
}
|
|
})
|
|
.then(function () {
|
|
|
|
// We pass old _resolution (if hasNew has been called before contents).
|
|
// So resolver can decide wheter use cached version of contents new one.
|
|
if (typeof resolver.fetch !== 'function') {
|
|
throw createError('Resolver does not implement the "fetch" method.');
|
|
}
|
|
|
|
var cached = {};
|
|
|
|
if (that._releases) {
|
|
cached.releases = that._releases;
|
|
}
|
|
|
|
if (that._pkgMeta) {
|
|
cached.endpoint = {
|
|
name: that._pkgMeta.name,
|
|
source: that._pkgMeta._source,
|
|
target: that._pkgMeta._target
|
|
};
|
|
|
|
cached.release = that._pkgMeta._release;
|
|
|
|
cached.version = that._pkgMeta.version;
|
|
|
|
cached.resolution = that._pkgMeta._resolution || {};
|
|
}
|
|
|
|
return Q.fcall(resolver.fetch.bind(resolver), that.getEndpoint(), cached);
|
|
})
|
|
.then(function (result) {
|
|
// Empty result means to re-use existing resolution
|
|
if (!result) {
|
|
return;
|
|
} else {
|
|
if (!result.tempPath) {
|
|
throw createError('Resolver did not provide path to extracted contents of package.');
|
|
}
|
|
|
|
that._tempDir = result.tempPath;
|
|
|
|
return that._readJson(that._tempDir).then(function (meta) {
|
|
return that._applyPkgMeta(meta, result)
|
|
.then(that._savePkgMeta.bind(that, meta, result))
|
|
.then(function () {
|
|
return that._tempDir;
|
|
});
|
|
});
|
|
}
|
|
});
|
|
};
|
|
|
|
PluginResolver.prototype._readJson = function (dir) {
|
|
var that = this;
|
|
|
|
return readJson(dir, {
|
|
assume: { name: that.getName() },
|
|
logger: bower.logger
|
|
})
|
|
.spread(function (json, deprecated) {
|
|
if (deprecated) {
|
|
bower.logger.warn('deprecated', 'Package ' + that.getName() + ' is using the deprecated ' + deprecated);
|
|
}
|
|
|
|
return json;
|
|
});
|
|
};
|
|
|
|
PluginResolver.prototype._applyPkgMeta = function (meta, result) {
|
|
// Check if name defined in the json is different
|
|
// If so and if the name was "guessed", assume the json name
|
|
if (meta.name !== this._name) {
|
|
this._name = meta.name;
|
|
}
|
|
|
|
// Handle ignore property, deleting all files from the temporary directory
|
|
// If no ignores were specified, simply resolve
|
|
if (result.removeIgnores === false || !meta.ignore || !meta.ignore.length) {
|
|
return Q.resolve(meta);
|
|
}
|
|
|
|
// Otherwise remove them from the temp dir
|
|
return removeIgnores(this._tempDir, meta).then(function () {
|
|
return meta;
|
|
});
|
|
};
|
|
|
|
PluginResolver.prototype._savePkgMeta = function (meta, result) {
|
|
var that = this;
|
|
|
|
meta._source = that.getSource();
|
|
meta._target = that.getTarget();
|
|
|
|
if (result.resolution) {
|
|
meta._resolution = result.resolution;
|
|
}
|
|
|
|
if (that._release) {
|
|
meta._release = that._release;
|
|
}
|
|
|
|
if (that._version) {
|
|
meta.version = that._version;
|
|
} else {
|
|
delete meta.version;
|
|
}
|
|
|
|
// Stringify contents
|
|
var contents = JSON.stringify(meta, null, 2);
|
|
|
|
return Q.nfcall(fs.writeFile, path.join(this._tempDir, '.bower.json'), contents)
|
|
.then(function () {
|
|
return that._pkgMeta = meta;
|
|
});
|
|
};
|
|
|
|
// It is used only by "bower info". It returns all semver versions.
|
|
PluginResolver.versions = function (source) {
|
|
return Q.fcall(resolver.releases.bind(resolver), source).then(function (result) {
|
|
if (!result) {
|
|
throw createError('Resolver did not provide releases of package.');
|
|
}
|
|
|
|
var releases = this._releases = result;
|
|
|
|
var versions = releases.map(function (version) {
|
|
return semver.clean(version.version);
|
|
});
|
|
|
|
versions = versions.filter(function (version) {
|
|
return version;
|
|
});
|
|
|
|
versions.sort(function (a, b) {
|
|
return semver.rcompare(a, b);
|
|
});
|
|
|
|
return versions;
|
|
});
|
|
};
|
|
|
|
PluginResolver.isTargetable = function () {
|
|
// If resolver doesn't define versions function, it's not targetable..
|
|
return typeof resolver.releases === 'function';
|
|
};
|
|
|
|
PluginResolver.clearRuntimeCache = function () {
|
|
resolver = resolverFactory(bower);
|
|
};
|
|
|
|
PluginResolver.match = function (source) {
|
|
if (typeof resolver.match !== 'function') {
|
|
throw createError('Resolver is missing "match" method.', 'ERESOLVERAPI');
|
|
}
|
|
|
|
var match = resolver.match.bind(resolver);
|
|
|
|
return Q.fcall(match, source).then(function (result) {
|
|
if (typeof result !== 'boolean') {
|
|
throw createError('Resolver\'s "match" method should return a boolean', 'ERESOLVERAPI');
|
|
}
|
|
|
|
return result;
|
|
});
|
|
};
|
|
|
|
PluginResolver.locate = function (source) {
|
|
if (typeof resolver.locate !== 'function') {
|
|
return source;
|
|
}
|
|
|
|
return Q.fcall(resolver.locate.bind(resolver), source).then(function (result) {
|
|
if (typeof result !== 'string') {
|
|
throw createError('Resolver\'s "locate" method should return a string', 'ERESOLVERAPI');
|
|
}
|
|
|
|
return result;
|
|
});
|
|
};
|
|
|
|
return PluginResolver;
|
|
}
|
|
|
|
|
|
module.exports = pluginResolverFactory;
|
|
|