UNPKG

9.71 kBJavaScriptView Raw
1/*
2 * Copyright 2014-2016 Guy Bedford (http://guybedford.com)
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16var fs = require('graceful-fs');
17var path = require('path');
18var globalConfig = require('./config/global-config');
19var ui = require('./ui');
20var dextend = require('./common').dextend;
21var HOME = require('./common').HOME;
22var Promise = require('bluebird');
23var Module = require('module');
24
25var base = process.env.jspmConfigPath && path.dirname(process.env.jspmConfigPath) || path.resolve(process.cwd());
26var registryRequireContext = new Module(base);
27
28registryRequireContext.paths = Module._nodeModulePaths(path.resolve(path.dirname(module.id), '..')).concat(Module._nodeModulePaths(base));
29
30/*
31 Registry API
32 See https://github.com/jspm/jspm-cli/wiki/Registry-API for the spec
33*/
34var registryClasses = {
35 local: {
36 constructor: {
37 packageNameFormats: ['*']
38 },
39 lookup: function(pkg) {
40 return Promise.reject('Package `local:' + pkg + '` can only be linked and not installed.');
41 }
42 }
43};
44
45process.on('exit', function() {
46 // dispose all loaded registries
47 // allows for saving cache state using sync fs
48 for (var e in registryClasses) {
49 if (registryClasses[e].dispose)
50 registryClasses[e].dispose();
51 }
52});
53
54var registryHooks = ['locate', 'lookup', 'download', 'getPackageConfig', 'processPackageConfig', 'processPackage', 'getOverride'];
55
56exports.load = function(registry) {
57 if (registryClasses[registry])
58 return registryClasses[registry];
59
60 try {
61 // ensure the tmpDir exists
62 var tmpDir = path.resolve(HOME, '.jspm', registry + '-cache');
63 if (!fs.existsSync(tmpDir))
64 fs.mkdirSync(tmpDir);
65
66 var options = dextend({
67 timeouts: {
68 lookup: 60,
69 download: 300,
70 process: 120
71 },
72 tmpDir: tmpDir,
73 apiVersion: '2.0'
74 }, globalConfig.config.registries[registry] || {});
75
76 options.name = registry;
77 if (globalConfig.config.strictSSL === false || globalConfig.config.strictSSL == 'false')
78 options.strictSSL = false;
79
80 if (!options.handler)
81 throw 'Registry %' + registry + '% not found.';
82
83 var RegistryClass = registryRequireContext.require(options.handler);
84 var registryPackageJSON = registryRequireContext.require(options.handler + '/package.json');
85 var versionString = registryPackageJSON.name + '@' + registryPackageJSON.version.split('.').splice(0, 2).join('.');
86 options.versionString = versionString;
87
88 var registryInstance = registryClasses[registry] = new RegistryClass(options, ui);
89 registryInstance.constructor = RegistryClass;
90
91 var timeoutLookup = options.timeouts.lookup * 1000;
92 var timeoutDownload = options.timeouts.download * 1000;
93 var timeoutProcess = options.timeouts.process * 1000;
94
95 registryInstance.versionString = registryInstance.versionString || versionString;
96
97 var maxRetries = globalConfig.config.maxRetries || 3;
98
99 if (registryInstance.build) {
100 ui.log('warn', 'Registry handler %' + options.handler + '% provides a `build` hook, which has been deprecated for `processPackage`.');
101 registryInstance.processPackage = function(packageConfig, packageName, packageDir) {
102 return this.build(packageConfig, packageDir);
103 };
104 }
105
106 // patch the calls to apply timeout and retry logic
107 registryHooks.forEach(function(hook) {
108 if (!registryInstance[hook])
109 return;
110
111 var runHook = registryInstance[hook];
112 registryInstance[hook] = function() {
113 var self = this;
114 var args = arguments;
115 var retries = 0;
116 var timeout;
117 if (hook == 'download')
118 timeout = timeoutDownload;
119 else if (hook == 'processPackage')
120 timeout = timeoutProcess;
121 else
122 timeout = timeoutLookup;
123
124 return new Promise(function(resolve, reject) {
125
126 function tryHook() {
127 var active = true;
128
129 var timer = setTimeout(function() {
130 active = false;
131 checkRetry();
132 }, timeout);
133
134 // in case registry is being reconfigured, chain on a promise
135 // which delivers the registry to use, when it is ready
136 (self.reconfigPromise_ || Promise.resolve(self))
137 .then(function(endpoint) {
138 self = endpoint;
139 return runHook.apply(self, args);
140 })
141 .then(function(result) {
142 clearTimeout(timer);
143 if (active)
144 resolve(result);
145 }, function(err) {
146 clearTimeout(timer);
147 if (!active)
148 return;
149 active = false;
150 return checkConfigure(err) || checkRetry(err);
151 });
152 }
153
154 /* When err.config is set, that indicates config credentials are somehow the cause.
155 * Call the configure hook and reinstantiate the registry with new config */
156 function checkConfigure(err) {
157 if (err && err.config && !self.triedConfig) {
158 // Place promise chain on existing instance, to block subsequent hooks.
159 // Also print warning for only for first such error, if multiple in a batch
160 if (!self.reconfigPromise_) {
161 ui.log('warn', err.message);
162
163 self.reconfigPromise_ = exports.configure(registry)
164 .then(function() {
165 // replace registered instance
166 delete registryClasses[registry];
167 var instance = exports.load(registry);
168 instance.triedConfig = true;
169 return instance;
170 });
171 }
172
173 tryHook();
174 return true;
175 }
176 }
177
178 function checkRetry(err) {
179 // don't retry process or processPackageConfig
180 if (hook === 'processPackage' || hook === 'processPackageConfig')
181 retries = maxRetries;
182
183 retries++;
184 var retriable = !err || err.retriable;
185 var retry = retriable && retries <= maxRetries;
186
187 var msg = (err ? 'Error' : 'Timed out') + ' on ' + hook +
188 (typeof args[0] === 'string' ? ' for `' + registry + ':' + args[0] + '`' : '') +
189 (retry ? ', retrying (' + retries + ').' : '') +
190
191 (!err ? '\nTo increase the timeout run %jspm config registries.' + registry + '.timeouts.' + (hook == 'download' || hook == 'build' ? hook : 'lookup') + ' ' + timeout / 1000 * 2 + '%' : '') +
192 (err ? '\n' + (!err.hideStack && err.stack || err) : '');
193 if (retry) {
194 ui.log('warn', msg);
195 return tryHook();
196 }
197 else {
198 return reject(msg);
199 }
200 }
201 tryHook();
202 });
203 };
204 });
205
206 return registryInstance;
207 }
208 catch(e) {
209 ui.log('err', !e.hideStack && e.stack || e);
210 throw 'Unable to load registry %' + registry + '%';
211 }
212};
213
214exports.configure = function(registry) {
215 var registryConfig = globalConfig.config.registries[registry] || {},
216 RegistryClass;
217
218 if (!registryConfig.handler)
219 throw 'Registry %' + registry + '% not found.';
220
221 var handler = registryConfig.handler;
222 delete registryConfig.handler;
223
224 try {
225 RegistryClass = registryRequireContext.require(handler);
226 }
227 catch(e) {
228 throw 'Registry handler `' + handler + '` not installed.';
229 }
230
231 registryConfig.name = registry;
232 registryConfig.strictSSL = globalConfig.config.strictSSL;
233
234 return Promise.resolve(RegistryClass.configure && RegistryClass.configure(registryConfig, ui) || registryConfig)
235 .then(function(_config) {
236 delete _config.name;
237 delete _config.strictSSL;
238 _config.handler = handler;
239 globalConfig.config.registries[registry] = _config;
240 })
241 .then(function() {
242 globalConfig.save();
243 });
244};
245
246// jspm registry create mycompany jspm-github
247// creates a custom registry based on the given handler
248exports.create = function(name, handler, override) {
249
250 // handle override prompts etc
251 if (!override && globalConfig.config.registries[name]) {
252 if (globalConfig.config.registries[name].handler === handler)
253 return ui.confirm('Registry %' + name + '% already exists. Do you want to reconfigure it now?')
254 .then(function(configure) {
255 if (configure)
256 return Promise.resolve(exports.configure(name))
257 .then(function() {
258 ui.log('ok', 'Registry %' + name + '% configured successfully.');
259 return false;
260 });
261 else
262 return false;
263 });
264 else
265 return ui.confirm('Registry %' + name + '% already exists, but based on `' + globalConfig.config.registries[name].handler + '`. Are you sure you want to override it?')
266 .then(function(override) {
267 if (override)
268 return Promise.resolve(exports.create(name, handler, true));
269 return false;
270 });
271 }
272
273 var registryConfig = globalConfig.config.registries[name] = globalConfig.config.registries[name] || {};
274 registryConfig.handler = handler;
275
276 // load the registry and configure it
277 return exports.configure(name);
278};