-
Notifications
You must be signed in to change notification settings - Fork 3
/
install.js
221 lines (186 loc) · 5.88 KB
/
install.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
'use strict';
var fs = require('fs');
var path = require('path');
var crypto = require('crypto');
var color = require('colorful');
var mkdirp = require('mkdirp');
var extend = require('extend');
var spmrc = require('spmrc');
var log = require('spm-log');
var gulp = require('gulp');
var gunzip = require('gulp-gunzip');
var untar = require('gulp-untar2');
var pipe = require('multipipe');
var format = require('util').format;
var util = require('./util');
var info = require('./info');
var request = require('request');
var debug = require('debug')('spm-client:install');
var homedir = spmrc.get('user.home');
var defaults = {
base: process.cwd(),
destination: 'spm_modules',
cache: path.join(homedir, '.spm', 'cache')
};
install.installPackage = installPackage;
module.exports = install;
/*
install(args, config)
*args
* name: the package name, can also be name@version
* destination: the directory that install to
* force: force download packages from registry, no cache
* save: save name to package.dependencies
* saveDev: save name to package.devDependencies
* config: see client.config
*/
function* install(args, config) {
args = extend({}, defaults, args, config || {}, require('./config')());
args.destination = path.join(args.base, args.destination);
args.downloadlist = {};
var packages;
// spm install id@version
if (args.name) {
packages = [args.name];
}
// spm install
else {
args.save = false;
args.saveDev = false;
var pkgPath = path.join(args.base, 'package.json');
packages = parseDependencies(pkgPath, true);
}
// no package to be installed
if (!packages.length) return;
debug('install packages %s', packages.join(', '));
yield packages.map(function(id) {
return install.installPackage(id, args, true);
});
}
/* Install a package.
*
* The process of the installation:
*
* 1. Find and download the package from yuan or cache
* 2. Copy the files to `sea-modules/{name}/{version}/{file}
*/
function* installPackage(id, args, saveDeps) {
delete args.name;
var idObj = util.resolveid(id);
var pkgId = idObj.name + '@' + (idObj.version || 'stable');
log.info('install', color.magenta(pkgId));
debug('start install package %s', pkgId);
// The package has downloaded in dest
// always false when version is not empty
if (existInDest(idObj, args)) return;
// The package has been in downloadlist
if (pkgId in args.downloadlist) {
debug('package %s has been in downloadlist', pkgId);
return;
}
var pinfo = yield* info(idObj, args);
pkgId = pinfo.name + '@' + pinfo.version;
args.downloadlist[pkgId] = pinfo;
// save dependencies to package.json
if ((args.save || args.saveDev) && saveDeps) {
save(pinfo, args);
}
// The package has downloaded in dest
if (existInDest(pinfo, args)) return;
var dest = path.join(args.destination, pinfo.name, pinfo.version);
var filename = pinfo.filename || pinfo.name + '-' + pinfo.version + '.tar.gz';
var fileInCache = path.join(args.cache, filename);
var fileInRemote = format('%s/repository/%s/%s/%s',
args.registry, pinfo.name, pinfo.version, filename);
var cacheIsExists = fs.existsSync(fileInCache);
// download from remote when
// 1. force install
// 2. cache not exists
// 3. cache file is old
if (args.force || !(cacheIsExists && md5file(fileInCache) === pinfo.md5)) {
debug('cache exists: %s', cacheIsExists);
yield download(fileInRemote, fileInCache);
}
// extract from cache
yield extract(fileInCache, dest);
log.info('installed', color.green(dest));
debug('end install package %s', pkgId);
var packages = parseDependencies(pinfo);
if (!packages.length) return;
log.info('depends', packages.join(', '));
debug('install dependencies %s of packages(%s)', packages.join(', '), pkgId);
yield packages.map(function(id) {
return installPackage(id, args);
});
}
function existInDest(idObj, args) {
if (!idObj.version) {
return false;
}
var pkgId = format('%s@%s', idObj.name, idObj.version);
var dest = path.join(args.destination, idObj.name, idObj.version);
if (!args.force && fs.existsSync(dest)) {
log.info('found', pkgId);
debug('package %s found in %s', pkgId, dest);
if (!args.downloadlist[pkgId]) args.downloadlist[pkgId] = idObj;
return true;
}
}
function download(urlpath, dest) {
return function(callback) {
log.info('download', urlpath);
debug('download from %s to %s', urlpath, dest);
mkdirp.sync(path.dirname(dest));
request(urlpath)
.once('error', callback)
.once('end', callback)
.once('close', callback)
.pipe(fs.createWriteStream(dest));
};
}
function extract(src, dest) {
return function(callback) {
log.info('extract', src);
debug('extract package from %s to %s', src, dest);
pipe(
gulp.src(src),
gunzip(),
untar(),
gulp.dest(dest)
)
.once('error', callback)
.once('end', callback)
.resume();
};
}
function parseDependencies(pkg, includeDev) {
if (typeof pkg === 'string') {
pkg = readJSON(pkg);
}
var spm = pkg.spm || {};
var deps = extend({},
includeDev ? spm.engines : {},
includeDev ? spm.devDependencies : {},
spm.dependencies);
return Object.keys(deps).map(function(key) {
return key + '@' + deps[key];
});
}
function save(idObj, args) {
var pkgPath = path.join(args.base, 'package.json');
var pkg = readJSON(pkgPath);
var key = args.save ? 'dependencies' : 'devDependencies';
log.info('saved', 'in', key, idObj.name + '@' + idObj.version);
pkg.spm = pkg.spm || {};
pkg.spm[key] = pkg.spm[key] || {};
pkg.spm[key][idObj.name] = idObj.version;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
}
function md5file(fpath) {
var md5 = crypto.createHash('md5');
return md5.update(fs.readFileSync(fpath)).digest('hex');
}
function readJSON(filepath) {
var code = fs.readFileSync(filepath).toString();
return JSON.parse(code);
}