mirror of
https://github.com/bower/bower.git
synced 2026-01-14 08:47:54 -05:00
386 lines
12 KiB
JavaScript
386 lines
12 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 whether 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;
|