mirror of
https://github.com/bower/bower.git
synced 2026-01-22 20:58:08 -05:00
821 lines
27 KiB
JavaScript
821 lines
27 KiB
JavaScript
// ==========================================
|
|
// BOWER: Package Object Definition
|
|
// ==========================================
|
|
// Copyright 2012 Twitter, Inc
|
|
// Licensed under The MIT License
|
|
// http://opensource.org/licenses/MIT
|
|
// ==========================================
|
|
// Events:
|
|
// - install: fired when package installed
|
|
// - resolve: fired when deps resolved
|
|
// - error: fired on all errors
|
|
// - data: fired when trying to output data
|
|
// ==========================================
|
|
|
|
var fstream = require('fstream');
|
|
var mkdirp = require('mkdirp');
|
|
var events = require('events');
|
|
var rimraf = require('rimraf');
|
|
var semver = require('semver');
|
|
var async = require('async');
|
|
var https = require('https');
|
|
var http = require('http');
|
|
var path = require('path');
|
|
var glob = require('glob');
|
|
var url = require('url');
|
|
var tmp = require('tmp');
|
|
var fs = require('fs');
|
|
var crypto = require('crypto');
|
|
var unzip = require('unzip');
|
|
var tar = require('tar');
|
|
var _ = require('lodash');
|
|
|
|
var config = require('./config');
|
|
var source = require('./source');
|
|
var template = require('../util/template');
|
|
var readJSON = require('../util/read-json');
|
|
var fileExists = require('../util/file-exists');
|
|
var isRepo = require('../util/is-repo');
|
|
var git = require('../util/git-cmd');
|
|
var UnitWork = require('./unit_work');
|
|
|
|
var Package = function (name, endpoint, manager) {
|
|
this.dependencies = {};
|
|
this.json = {};
|
|
this.name = name;
|
|
this.manager = manager;
|
|
this.unitWork = manager ? manager.unitWork : new UnitWork;
|
|
this.opts = manager ? manager.opts : {};
|
|
|
|
if (endpoint) {
|
|
var split;
|
|
|
|
if (/^(.*\.git)$/.exec(endpoint)) {
|
|
this.gitUrl = RegExp.$1.replace(/^git\+/, '');
|
|
this.tag = false;
|
|
|
|
} else if (/^(.*\.git)#(.*)$/.exec(endpoint)) {
|
|
this.tag = RegExp.$2;
|
|
this.gitUrl = RegExp.$1.replace(/^git\+/, '');
|
|
|
|
} else if (/^(?:(git):|git\+(https?):)\/\/([^#]+)#?(.*)$/.exec(endpoint)) {
|
|
this.gitUrl = (RegExp.$1 || RegExp.$2) + '://' + RegExp.$3;
|
|
this.tag = RegExp.$4;
|
|
|
|
} else if (semver.validRange(endpoint)) {
|
|
this.tag = endpoint;
|
|
|
|
} else if (/^[\.\/~]\.?[^.]*\.(js|css)/.test(endpoint) && fs.statSync(endpoint).isFile()) {
|
|
this.path = path.resolve(endpoint);
|
|
this.assetType = path.extname(endpoint);
|
|
|
|
} else if (/^https?:\/\//.exec(endpoint)) {
|
|
this.assetUrl = endpoint;
|
|
this.assetType = path.extname(endpoint);
|
|
|
|
} else if (fileExists.sync((split = endpoint.split('#', 2))[0]) && fs.statSync(split[0]).isDirectory()) {
|
|
this.path = path.resolve(split[0]);
|
|
this.tag = split[1];
|
|
|
|
} else if (/^[\.\/~]/.test(endpoint)) {
|
|
this.path = path.resolve(endpoint);
|
|
|
|
} else if (endpoint.split('/').length === 2) {
|
|
split = endpoint.split('#', 2);
|
|
this.gitUrl = 'git://github.com/' + split[0] + '.git';
|
|
this.tag = split[1];
|
|
} else {
|
|
split = endpoint.split('#', 2);
|
|
this.tag = split[1];
|
|
}
|
|
|
|
// Guess names
|
|
if (!this.name) {
|
|
if (this.gitUrl) this.name = path.basename(endpoint).replace(/(\.git)?(#.*)?$/, '');
|
|
else if (this.path) this.name = path.basename(this.path, this.assetType);
|
|
else if (this.assetUrl) this.name = this.name = path.basename(this.assetUrl, this.assetType);
|
|
else if (split) this.name = split[0];
|
|
}
|
|
|
|
// Store a reference to the original tag & original path
|
|
// This is because the tag & paths can get rewriten later
|
|
if (this.tag) this.originalTag = this.tag;
|
|
if (this.path) this.originalPath = endpoint;
|
|
|
|
// The id is an unique id that describes this package
|
|
this.id = crypto.createHash('md5').update(this.name + '%' + this.tag + '%' + this.gitUrl + '%' + this.path + '%' + this.assetUrl).digest('hex');
|
|
|
|
// Generate a resource id
|
|
if (this.gitUrl) this.generateResourceId();
|
|
}
|
|
|
|
if (this.manager) {
|
|
this.on('data', this.manager.emit.bind(this.manager, 'data'));
|
|
this.on('error', function (err, origin) {
|
|
// Unlock the unit of work automatically on error
|
|
if (!origin && this.unitWork.isLocked(this.name)) this.unitWork.unlock(this.name, this);
|
|
this.manager.emit('error', err, origin || this);
|
|
}.bind(this));
|
|
}
|
|
|
|
// Cache a self bound function
|
|
this.waitUnlock = this.waitUnlock.bind(this);
|
|
|
|
this.setMaxListeners(30); // Increase the number of listeners because a package can have more than the default 10 dependencies
|
|
};
|
|
|
|
Package.prototype = Object.create(events.EventEmitter.prototype);
|
|
|
|
Package.prototype.constructor = Package;
|
|
|
|
Package.prototype.resolve = function () {
|
|
// Ensure that nobody is resolving the same dep at the same time
|
|
// If there is, we wait for the unlock event
|
|
if (this.unitWork.isLocked(this.name)) return this.unitWork.on('unlock', this.waitUnlock);
|
|
|
|
var data = this.unitWork.retrieve(this.name);
|
|
if (data) {
|
|
// Check if this exact package is the last resolved one
|
|
// If so, we copy the resolved result and we don't need to do anything else
|
|
if (data.id === this.id) {
|
|
this.unserialize(data);
|
|
this.emit('resolve');
|
|
return this;
|
|
}
|
|
}
|
|
|
|
// If not, we lock and resolve it
|
|
this.unitWork.lock(this.name, this);
|
|
|
|
if (this.assetUrl) {
|
|
this.download();
|
|
} else if (this.gitUrl) {
|
|
this.clone();
|
|
} else if (this.path) {
|
|
this.copy();
|
|
} else {
|
|
this.once('lookup', this.clone).lookup();
|
|
}
|
|
|
|
return this;
|
|
};
|
|
|
|
Package.prototype.lookup = function () {
|
|
source.lookup(this.name, function (err, url) {
|
|
if (err) return this.emit('error', err);
|
|
this.lookedUp = true;
|
|
this.gitUrl = url;
|
|
this.generateResourceId();
|
|
this.emit('lookup');
|
|
}.bind(this));
|
|
};
|
|
|
|
Package.prototype.install = function () {
|
|
// Only print the installing action if this package has been resolved
|
|
if (this.unitWork.retrieve(this.name)) {
|
|
template('action', { name: 'installing', shizzle: this.name + (this.version ? '#' + this.version : '') })
|
|
.on('data', this.emit.bind(this, 'data'));
|
|
}
|
|
|
|
var localPath = this.localPath;
|
|
|
|
if (path.resolve(this.path) === localPath) {
|
|
this.emit('install');
|
|
return this;
|
|
}
|
|
|
|
// Remove stuff from the local path (if any)
|
|
// Rename path to the local path
|
|
// Beware that if the local path exists and is a git repository, the process is aborted
|
|
isRepo(localPath, function (is) {
|
|
if (is) {
|
|
var err = new Error('Local path is a local repository');
|
|
err.details = 'To avoid losing work, please remove ' + localPath + ' manually.';
|
|
return this.emit('error', err, this);
|
|
}
|
|
|
|
mkdirp(path.dirname(localPath), function (err) {
|
|
if (err) return this.emit('error', err);
|
|
rimraf(localPath, function (err) {
|
|
if (err) return this.emit('error', err);
|
|
return fs.rename(this.path, localPath, function (err) {
|
|
if (!err) return this.cleanUpLocal();
|
|
|
|
var writter = fstream.Writer({
|
|
type: 'Directory',
|
|
path: localPath
|
|
});
|
|
writter
|
|
.on('error', this.emit.bind(this, 'error'))
|
|
.on('end', rimraf.bind(this, this.path, this.cleanUpLocal.bind(this)));
|
|
|
|
fstream.Reader(this.path)
|
|
.on('error', this.emit.bind(this, 'error'))
|
|
.pipe(writter);
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
|
|
return this;
|
|
};
|
|
|
|
Package.prototype.cleanUpLocal = function () {
|
|
this.once('readLocalConfig', function () {
|
|
this.json.name = this.name;
|
|
this.json.version = this.commit ? '0.0.0' : this.version || '0.0.0';
|
|
|
|
// Detect commit and save it in the json for later use
|
|
if (this.commit) this.json.commit = this.commit;
|
|
else delete this.json.commit;
|
|
|
|
if (this.gitUrl) this.json.repository = { type: 'git', url: this.gitUrl };
|
|
else if (this.gitPath) this.json.repository = { type: 'local-repo', path: this.originalPath };
|
|
else if (this.originalPath) this.json.repository = { type: 'local', path: this.originalPath };
|
|
else if (this.assetUrl) this.json = this.generateAssetJSON();
|
|
|
|
var jsonStr = JSON.stringify(this.json, null, 2);
|
|
|
|
fs.writeFile(path.join(this.localPath, this.localConfig.json), jsonStr);
|
|
if (this.gitUrl || this.gitPath) fs.writeFile(path.join(this.gitPath, this.localConfig.json), jsonStr);
|
|
|
|
this.removeLocalPaths();
|
|
}.bind(this)).readLocalConfig();
|
|
};
|
|
|
|
// finish clean up local by removing .git/ and any ignored files
|
|
Package.prototype.removeLocalPaths = function () {
|
|
var removePatterns = ['.git'];
|
|
if (this.json.ignore) {
|
|
removePatterns.push.apply(removePatterns, this.json.ignore);
|
|
}
|
|
|
|
var removePaths = [];
|
|
|
|
// 3: done
|
|
var pathsRemoved = function (err) {
|
|
if (err) return this.emit('error', err);
|
|
this.emit('install');
|
|
}.bind(this);
|
|
|
|
// 2: trigger after paths have been globbed
|
|
var rimrafPaths = function (err) {
|
|
if (err) return this.emit('error', err);
|
|
async.forEach(removePaths, function (removePath, next) {
|
|
// rimraf all the paths
|
|
rimraf(removePath, function (err) {
|
|
if (err) return this.emit('error', err);
|
|
next();
|
|
}.bind(this));
|
|
// finished callback
|
|
}.bind(this), pathsRemoved);
|
|
}.bind(this);
|
|
|
|
// 1: get paths
|
|
var globOpts = { dot: true };
|
|
async.forEach(removePatterns, function (removePattern, next) {
|
|
// glob path for file path pattern matching
|
|
var globPattern = path.join(this.localPath, removePattern);
|
|
glob(globPattern, globOpts, function (err, globPaths) {
|
|
if (err) return this.emit('error', err);
|
|
removePaths = removePaths.concat(globPaths);
|
|
next();
|
|
}.bind(this));
|
|
}.bind(this), rimrafPaths);
|
|
};
|
|
|
|
Package.prototype.generateAssetJSON = function () {
|
|
return {
|
|
name: this.name,
|
|
main: this.assetType !== '.zip' && this.assetType !== '.tar' ? 'index' + this.assetType : '',
|
|
version: '0.0.0',
|
|
repository: { type: 'asset', url: this.assetUrl }
|
|
};
|
|
};
|
|
|
|
Package.prototype.uninstall = function () {
|
|
template('action', { name: 'uninstalling', shizzle: this.path })
|
|
.on('data', this.emit.bind(this, 'data'));
|
|
rimraf(this.path, function (err) {
|
|
if (err) return this.emit('error', err);
|
|
this.emit('uninstall');
|
|
}.bind(this));
|
|
};
|
|
|
|
// Private
|
|
Package.prototype.readLocalConfig = function () {
|
|
if (this.localConfig) return this.emit('readLocalConfig');
|
|
|
|
var checkExistence = function () {
|
|
fileExists(path.join(this.path, this.localConfig.json), function (exists) {
|
|
if (!exists) {
|
|
this.localConfig.json = 'component.json';
|
|
}
|
|
|
|
this.emit('readLocalConfig');
|
|
}.bind(this));
|
|
}.bind(this);
|
|
|
|
fs.readFile(path.join(this.path, '.bowerrc'), function (err, file) {
|
|
// If the local .bowerrc file do not exists then we check if the
|
|
// json specific in the config exists (if not, we fallback to component.json)
|
|
if (err) {
|
|
this.localConfig = { json: config.json };
|
|
checkExistence();
|
|
} else {
|
|
// If the local .bowerrc file exists, we read it and check if a custom json file
|
|
// is defined. If not, we check if the global config json file exists (if not, we fallback to component.json)
|
|
try {
|
|
this.localConfig = JSON.parse(file);
|
|
} catch (e) {
|
|
return this.emit('error', new Error('Unable to parse local .bowerrc file: ' + e.message));
|
|
}
|
|
|
|
if (!this.localConfig.json) {
|
|
this.localConfig.json = config.json;
|
|
return checkExistence();
|
|
}
|
|
|
|
this.emit('readLocalConfig');
|
|
}
|
|
}.bind(this));
|
|
};
|
|
|
|
Package.prototype.loadJSON = function () {
|
|
if (!this.path || this.assetUrl) return this.emit('loadJSON');
|
|
|
|
this.once('readLocalConfig', function () {
|
|
var jsonFile = path.join(this.path, this.localConfig.json);
|
|
fileExists(jsonFile, function (exists) {
|
|
// If the json does not exists, we attempt to get the version
|
|
if (!exists) {
|
|
return this.once('describeTag', function (tag) {
|
|
tag = semver.clean(tag);
|
|
if (!tag) this.version = this.tag;
|
|
else {
|
|
this.version = tag;
|
|
if (!this.tag) this.tag = this.version;
|
|
}
|
|
|
|
this.emit('loadJSON');
|
|
}.bind(this)).describeTag();
|
|
}
|
|
|
|
readJSON(jsonFile, function (err, json) {
|
|
if (err) {
|
|
err.details = 'An error was caught when reading the ' + this.localConfig.json + ':' + err.message;
|
|
return this.emit('error', err);
|
|
}
|
|
|
|
this.json = json;
|
|
this.version = this.commit || json.commit || json.version;
|
|
this.commit = this.commit || json.commit;
|
|
// Only overwrite the name if not already set
|
|
// This is because some packages have different names declared in the registry and the json
|
|
if (!this.name) this.name = json.name;
|
|
|
|
// Read the endpoint from the json to ensure it is set correctly
|
|
this.readEndpoint();
|
|
|
|
// Detect if the tag mismatches the json.version
|
|
// This is very often to happen because developers tag their new releases but forget to update the json accordingly
|
|
var cleanedTag;
|
|
if (this.tag && (cleanedTag = semver.clean(this.tag)) && cleanedTag !== this.version) {
|
|
// Only print the warning once
|
|
if (!this.unitWork.retrieve('mismatch#' + this.name + '_' + cleanedTag)) {
|
|
template('warning-mismatch', { name: this.name, json: this.localConfig.json, tag: cleanedTag, version: this.version || 'N/A' })
|
|
.on('data', this.emit.bind(this, 'data'));
|
|
this.unitWork.store('mismatch#' + this.name + '_' + cleanedTag, true);
|
|
}
|
|
// Assume the tag
|
|
this.version = cleanedTag;
|
|
}
|
|
|
|
this.emit('loadJSON');
|
|
}.bind(this), this);
|
|
}.bind(this));
|
|
}.bind(this)).readLocalConfig();
|
|
};
|
|
|
|
Package.prototype.download = function () {
|
|
template('action', { name: 'downloading', shizzle: this.assetUrl })
|
|
.on('data', this.emit.bind(this, 'data'));
|
|
|
|
var src;
|
|
|
|
if (process.env.HTTP_PROXY) {
|
|
src = url.parse(process.env.HTTP_PROXY);
|
|
src.path = this.assetUrl;
|
|
} else {
|
|
src = url.parse(this.assetUrl);
|
|
}
|
|
|
|
tmp.dir({ prefix: 'bower-' + this.name + '-', mode: parseInt('0777', 8) & (~process.umask()) }, function (err, tmpPath) {
|
|
if (err) return this.emit('error', err);
|
|
|
|
var req = src.protocol === 'https:' ? https : http;
|
|
req.get(src, function (res) {
|
|
// If assetUrl results in a redirect we update the assetUrl to the redirect to url
|
|
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
template('action', { name: 'redirect detected', shizzle: this.assetUrl })
|
|
.on('data', this.emit.bind(this, 'data'));
|
|
this.assetUrl = res.headers.location;
|
|
return this.download();
|
|
}
|
|
|
|
// Detect not OK status codes
|
|
if (res.statusCode < 200 || res.statusCode >= 300) {
|
|
return this.emit('error', new Error(res.statusCode + ' status code for ' + this.assetUrl));
|
|
}
|
|
|
|
var file = fs.createWriteStream(path.join((this.path = tmpPath), 'index' + this.assetType));
|
|
|
|
res.on('data', function (data) {
|
|
file.write(data);
|
|
});
|
|
|
|
res.on('end', function () {
|
|
file.end();
|
|
|
|
var next = function () {
|
|
this.once('loadJSON', this.saveUnit).loadJSON();
|
|
}.bind(this);
|
|
|
|
if (this.assetType === '.zip' || this.assetType === '.tar') this.once('extract', next).extract();
|
|
else next();
|
|
}.bind(this));
|
|
}.bind(this)).on('error', this.emit.bind(this, 'error'));
|
|
}.bind(this));
|
|
};
|
|
|
|
Package.prototype.extract = function () {
|
|
var file = path.join(this.path, 'index' + this.assetType);
|
|
template('action', { name: 'extracting', shizzle: file }).on('data', this.emit.bind(this, 'data'));
|
|
|
|
fs.createReadStream(file).pipe(this.assetType === '.zip' ? unzip.Extract({ path: this.path }) : tar.Extract({ path: this.path }))
|
|
.on('error', this.emit.bind(this, 'error'))
|
|
.on('close', function () {
|
|
// Delete zip
|
|
fs.unlink(file, function (err) {
|
|
if (err) return this.emit('error', err);
|
|
|
|
// If we extracted only a folder, move all the files within it to the original path
|
|
fs.readdir(this.path, function (err, files) {
|
|
if (err) return this.emit('error', err);
|
|
|
|
if (files.length !== 1) return this.emit('extract');
|
|
|
|
var dir = path.join(this.path, files[0]);
|
|
fs.stat(dir, function (err, stat) {
|
|
if (err) return this.emit('error', err);
|
|
if (!stat.isDirectory()) return this.emit('extract');
|
|
|
|
fs.readdir(dir, function (err, files) {
|
|
if (err) return this.emit('error', err);
|
|
|
|
async.forEachSeries(files, function (file, next) {
|
|
fs.rename(path.join(dir, file), path.join(this.path, file), next);
|
|
}.bind(this), function (err) {
|
|
if (err) return this.emit('error');
|
|
|
|
fs.rmdir(dir, function (err) {
|
|
if (err) return this.emit('error');
|
|
this.emit('extract');
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
};
|
|
|
|
Package.prototype.copy = function () {
|
|
template('action', { name: 'copying', shizzle: this.path }).on('data', this.emit.bind(this, 'data'));
|
|
|
|
tmp.dir({ prefix: 'bower-' + this.name + '-' }, function (err, tmpPath) {
|
|
if (err) return this.emit('error', err);
|
|
|
|
fs.stat(this.path, function (err, stats) {
|
|
if (err) return this.emit('error', err);
|
|
|
|
// Copy file permission for directory
|
|
fs.chmod(tmpPath, stats.mode, function (err) {
|
|
if (err) return this.emit('error', err);
|
|
|
|
if (this.assetType) {
|
|
return fs.readFile(this.path, function (err, data) {
|
|
fs.writeFile(path.join((this.path = tmpPath), 'index' + this.assetType), data, function () {
|
|
this.once('loadJSON', this.saveUnit).loadJSON();
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}
|
|
|
|
this.once('loadJSON', function () {
|
|
if (this.gitUrl) return this.saveUnit();
|
|
|
|
// Check if the copied directory is a git repository and is a local endpoint
|
|
// If so, treat it like a repository.
|
|
fileExists(path.join(this.path, '.git'), function (exists) {
|
|
if (!exists) return this.saveUnit();
|
|
|
|
this.gitPath = this.path;
|
|
this.once('loadJSON', this.saveUnit.bind(this)).checkout();
|
|
}.bind(this));
|
|
}.bind(this));
|
|
|
|
var writter = fstream.Writer({
|
|
type: 'Directory',
|
|
path: tmpPath
|
|
})
|
|
.on('error', this.emit.bind(this, 'error'))
|
|
.on('end', this.loadJSON.bind(this));
|
|
|
|
fstream.Reader(this.path)
|
|
.on('error', this.emit.bind(this, 'error'))
|
|
.pipe(writter);
|
|
|
|
this.path = tmpPath;
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
};
|
|
|
|
Package.prototype.getDeepDependencies = function (result) {
|
|
result = result || [];
|
|
for (var name in this.dependencies) {
|
|
result.push(this.dependencies[name]);
|
|
this.dependencies[name].getDeepDependencies(result);
|
|
}
|
|
return result;
|
|
};
|
|
|
|
Package.prototype.saveUnit = function () {
|
|
this.unitWork.store(this.name, this.serialize(), this);
|
|
this.unitWork.unlock(this.name, this);
|
|
this.addDependencies();
|
|
};
|
|
|
|
Package.prototype.addDependencies = function () {
|
|
var dependencies = this.json.dependencies || {};
|
|
var callbacks = Object.keys(dependencies).map(function (name) {
|
|
return function (callback) {
|
|
var endpoint = dependencies[name];
|
|
this.dependencies[name] = new Package(name, endpoint, this);
|
|
this.dependencies[name].once('resolve', callback).resolve();
|
|
}.bind(this);
|
|
}.bind(this));
|
|
async.parallel(callbacks, this.emit.bind(this, 'resolve'));
|
|
};
|
|
|
|
Package.prototype.exists = function (callback) {
|
|
fileExists(this.localPath, callback);
|
|
};
|
|
|
|
Package.prototype.clone = function () {
|
|
template('action', { name: 'cloning', shizzle: this.gitUrl }).on('data', this.emit.bind(this, 'data'));
|
|
this.path = this.gitPath;
|
|
this.once('cache', function () {
|
|
this.once('loadJSON', this.copy.bind(this)).checkout();
|
|
}.bind(this)).cache();
|
|
};
|
|
|
|
Package.prototype.cache = function () {
|
|
// If the force options is true, we need to erase from the cache
|
|
// Be aware that a similar package might already flushed it
|
|
// To prevent that we check the unit of work storage
|
|
if (this.opts.force && !this.unitWork.retrieve('flushed#' + this.name + '_' + this.resourceId)) {
|
|
rimraf(this.path, function (err) {
|
|
if (err) return this.emit('error', err);
|
|
this.unitWork.store('flushed#' + this.name + '_' + this.resourceId, true);
|
|
this.cache();
|
|
}.bind(this));
|
|
return this;
|
|
}
|
|
|
|
mkdirp(config.cache, function (err) {
|
|
if (err) return this.emit('error', err);
|
|
fileExists(this.path, function (exists) {
|
|
if (exists) {
|
|
template('action', { name: 'cached', shizzle: this.gitUrl }).on('data', this.emit.bind(this, 'data'));
|
|
return this.emit('cache');
|
|
}
|
|
template('action', { name: 'caching', shizzle: this.gitUrl }).on('data', this.emit.bind(this, 'data'));
|
|
var url = this.gitUrl;
|
|
if (process.env.HTTP_PROXY) {
|
|
url = url.replace(/^git:/, 'https:');
|
|
}
|
|
|
|
mkdirp(this.path, function (err) {
|
|
if (err) return this.emit('error', err);
|
|
|
|
var cp = git(['clone', url, this.path], null, this);
|
|
cp.on('close', function (code) {
|
|
if (code) return;
|
|
this.emit('cache');
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
};
|
|
|
|
Package.prototype.checkout = function () {
|
|
template('action', { name: 'fetching', shizzle: this.name })
|
|
.on('data', this.emit.bind(this, 'data'));
|
|
|
|
this.once('versions', function (versions) {
|
|
if (!versions.length) {
|
|
this.emit('checkout');
|
|
this.loadJSON();
|
|
}
|
|
|
|
// If tag is specified, try to satisfy it
|
|
if (this.tag) {
|
|
if (!semver.validRange(this.tag)) {
|
|
return this.emit('error', new Error('Tag ' + this.tag + ' is not a valid semver range/version'));
|
|
}
|
|
|
|
versions = versions.filter(function (version) {
|
|
return semver.satisfies(version, this.tag);
|
|
}.bind(this));
|
|
|
|
if (!versions.length) {
|
|
var error = new Error('Could not find tag satisfying: ' + this.name + '#' + this.tag);
|
|
error.details = 'The tag ' + this.tag + ' could not be found within the repository';
|
|
return this.emit('error', error);
|
|
}
|
|
}
|
|
|
|
// Use latest version
|
|
this.tag = versions[0];
|
|
if (!semver.valid(this.tag)) this.commit = this.tag; // If the version is not valid, then its a commit
|
|
|
|
if (this.tag) {
|
|
template('action', {
|
|
name: 'checking out',
|
|
shizzle: this.name + '#' + this.tag
|
|
}).on('data', this.emit.bind(this, 'data'));
|
|
|
|
// Checkout the tag
|
|
git([ 'checkout', this.tag, '-f'], { cwd: this.path }, this).on('close', function (code) {
|
|
if (code) return;
|
|
// Ensure that checkout the tag as it is, removing all untracked files
|
|
git(['clean', '-f', '-d'], { cwd: this.path }, this).on('close', function (code) {
|
|
if (code) return;
|
|
this.emit('checkout');
|
|
this.loadJSON();
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}
|
|
}).versions();
|
|
};
|
|
|
|
Package.prototype.describeTag = function () {
|
|
var cp = git(['describe', '--always', '--tag'], { cwd: this.gitPath || this.path, ignoreCodes: [128] }, this);
|
|
var tag = '';
|
|
|
|
cp.stdout.setEncoding('utf8');
|
|
cp.stdout.on('data', function (data) {
|
|
tag += data;
|
|
});
|
|
|
|
cp.on('close', function (code) {
|
|
if (code === 128) tag = 'unspecified'.grey; // Not a git repo
|
|
this.emit('describeTag', tag.replace(/\n$/, ''));
|
|
}.bind(this));
|
|
};
|
|
|
|
Package.prototype.versions = function () {
|
|
this.once('fetch', function () {
|
|
var cp = git(['tag'], { cwd: this.gitPath }, this);
|
|
|
|
var versions = '';
|
|
|
|
cp.stdout.setEncoding('utf8');
|
|
cp.stdout.on('data', function (data) {
|
|
versions += data;
|
|
});
|
|
|
|
cp.on('close', function (code) {
|
|
if (code) return;
|
|
versions = versions.split('\n');
|
|
versions = versions.filter(function (ver) {
|
|
return semver.valid(ver);
|
|
});
|
|
versions = versions.sort(function (a, b) {
|
|
return semver.gt(a, b) ? -1 : 1;
|
|
});
|
|
|
|
if (versions.length) return this.emit('versions', versions);
|
|
|
|
// If there is no versions tagged in the repo
|
|
// then we grab the hash of the last commit
|
|
versions = '';
|
|
cp = git(['log', '-n', 1, '--format=%H'], { cwd: this.gitPath }, this);
|
|
|
|
cp.stdout.setEncoding('utf8');
|
|
cp.stdout.on('data', function (data) {
|
|
versions += data;
|
|
});
|
|
cp.on('close', function (code) {
|
|
if (code) return;
|
|
versions = _.compact(versions.split('\n'));
|
|
this.emit('versions', versions);
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this)).fetch();
|
|
};
|
|
|
|
Package.prototype.fetch = function () {
|
|
fileExists(this.gitPath, function (exists) {
|
|
if (!exists) return this.emit('error', new Error('Unable to fetch package ' + this.name + ' (if the cache was deleted, run install again)'));
|
|
|
|
var cp = git(['fetch', '--prune'], { cwd: this.gitPath }, this);
|
|
cp.on('close', function (code) {
|
|
if (code) return;
|
|
cp = git(['reset', '--hard', this.gitUrl ? 'origin/HEAD' : 'HEAD'], { cwd: this.gitPath }, this);
|
|
cp.on('close', function (code) {
|
|
if (code) return;
|
|
this.emit('fetch');
|
|
}.bind(this));
|
|
}.bind(this));
|
|
}.bind(this));
|
|
};
|
|
|
|
Package.prototype.readEndpoint = function (replace) {
|
|
if (!this.json.repository) return;
|
|
|
|
if (this.json.repository.type === 'git') {
|
|
if (replace || !this.gitUrl) {
|
|
this.gitUrl = this.json.repository.url;
|
|
this.generateResourceId();
|
|
}
|
|
return { type: 'git', endpoint: this.gitUrl };
|
|
}
|
|
if (this.json.repository.type === 'local-repo') {
|
|
if (replace || !this.gitPath) {
|
|
this.gitPath = path.resolve(this.json.repository.path);
|
|
}
|
|
return { type: 'local', endpoint: this.path };
|
|
}
|
|
if (this.json.repository.type === 'local') {
|
|
if (replace || !this.path) {
|
|
this.path = path.resolve(this.json.repository.path);
|
|
}
|
|
return { type: 'local', endpoint: this.path };
|
|
}
|
|
if (this.json.repository.type === 'asset') {
|
|
if (replace || !this.assetUrl) {
|
|
this.assetUrl = this.json.repository.url;
|
|
this.assetType = path.extname(this.assetUrl);
|
|
}
|
|
return { type: 'asset', endpoint: this.assetUrl };
|
|
}
|
|
};
|
|
|
|
Package.prototype.waitUnlock = function (name) {
|
|
if (this.name === name) {
|
|
this.unitWork.removeListener('unlock', this.waitUnlock);
|
|
this.resolve();
|
|
}
|
|
};
|
|
|
|
Package.prototype.serialize = function () {
|
|
return {
|
|
id: this.id,
|
|
resourceId: this.resourceId,
|
|
path: this.path,
|
|
originalPath: this.originalPath,
|
|
tag: this.tag,
|
|
originalTag: this.originalTag,
|
|
commit: this.commit,
|
|
assetUrl: this.assetUrl,
|
|
assetType: this.assetType,
|
|
lookedUp: this.lookedUp,
|
|
json: this.json,
|
|
gitUrl: this.gitUrl,
|
|
gitPath: this.gitPath,
|
|
dependencies: this.dependencies,
|
|
localConfig: this.localConfig
|
|
};
|
|
};
|
|
|
|
Package.prototype.unserialize = function (obj) {
|
|
for (var key in obj) {
|
|
this[key] = obj[key];
|
|
}
|
|
|
|
this.version = this.tag;
|
|
};
|
|
|
|
Package.prototype.generateResourceId = function () {
|
|
this.resourceId = crypto.createHash('md5').update(this.name + '%' + this.gitUrl).digest('hex');
|
|
this.gitPath = path.join(config.cache, this.name, this.resourceId);
|
|
};
|
|
|
|
Package.prototype.__defineGetter__('localPath', function () {
|
|
return path.join(process.cwd(), config.directory, this.name);
|
|
});
|
|
|
|
module.exports = Package;
|