roster-server 2.2.9 → 2.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/index.js +2 -2
  2. package/package.json +12 -3
  3. package/tasks/lessons.md +2 -1
  4. package/test/roster-server.test.js +5 -6
  5. package/vendor/greenlock/.prettierrc +8 -0
  6. package/vendor/greenlock/LICENSE +312 -0
  7. package/vendor/greenlock/MIGRATION_GUIDE.md +403 -0
  8. package/vendor/greenlock/README.md +667 -0
  9. package/vendor/greenlock/accounts.js +218 -0
  10. package/vendor/greenlock/bin/add.js +72 -0
  11. package/vendor/greenlock/bin/certonly.js +368 -0
  12. package/vendor/greenlock/bin/config.js +77 -0
  13. package/vendor/greenlock/bin/defaults.js +58 -0
  14. package/vendor/greenlock/bin/greenlock.js +26 -0
  15. package/vendor/greenlock/bin/init.js +159 -0
  16. package/vendor/greenlock/bin/lib/cli.js +230 -0
  17. package/vendor/greenlock/bin/lib/flags.js +385 -0
  18. package/vendor/greenlock/bin/remove.js +46 -0
  19. package/vendor/greenlock/bin/tmpl/app.tmpl.js +9 -0
  20. package/vendor/greenlock/bin/tmpl/cluster.tmpl.js +30 -0
  21. package/vendor/greenlock/bin/tmpl/greenlock.tmpl.js +13 -0
  22. package/vendor/greenlock/bin/tmpl/server.tmpl.js +20 -0
  23. package/vendor/greenlock/bin/update.js +62 -0
  24. package/vendor/greenlock/certificates.js +324 -0
  25. package/vendor/greenlock/errors.js +58 -0
  26. package/vendor/greenlock/greenlock.js +621 -0
  27. package/vendor/greenlock/greenlockrc.js +169 -0
  28. package/vendor/greenlock/lib/challenges-wrapper.js +88 -0
  29. package/vendor/greenlock/lib/directory-url.js +44 -0
  30. package/vendor/greenlock/lib/init.js +191 -0
  31. package/vendor/greenlock/lib/manager-wrapper.js +625 -0
  32. package/vendor/greenlock/lib/rc.js +70 -0
  33. package/vendor/greenlock/logo/beaker-browser-301x112.png +0 -0
  34. package/vendor/greenlock/logo/from-not-secure-to-secure-url-bar.png +0 -0
  35. package/vendor/greenlock/logo/greenlock-1063x250.png +0 -0
  36. package/vendor/greenlock/logo/greenlock-850x200.png +0 -0
  37. package/vendor/greenlock/logo/ibm-301x112.png +0 -0
  38. package/vendor/greenlock/logo/telebit-301x112.png +0 -0
  39. package/vendor/greenlock/order.js +63 -0
  40. package/vendor/greenlock/package-lock.json +140 -0
  41. package/vendor/greenlock/package.json +56 -0
  42. package/vendor/greenlock/plugins.js +270 -0
  43. package/vendor/greenlock/tests/cli.sh +31 -0
  44. package/vendor/greenlock/tests/index.js +53 -0
  45. package/vendor/greenlock/user-events.js +7 -0
  46. package/vendor/greenlock/utils.js +281 -0
  47. package/vendor/greenlock-express/greenlock-shim.js +3 -1
  48. package/vendor/greenlock-express/package.json +0 -1
@@ -0,0 +1,621 @@
1
+ 'use strict';
2
+
3
+ var pkg = require('./package.json');
4
+ var log = require('lemonlog')('greenlock');
5
+
6
+ var ACME = require('@root/acme');
7
+ var Greenlock = module.exports;
8
+ var request = require('@root/request');
9
+ var process = require('process');
10
+
11
+ var G = Greenlock;
12
+ var U = require('./utils.js');
13
+ var E = require('./errors.js');
14
+ var P = require('./plugins.js');
15
+ var A = require('./accounts.js');
16
+ var C = require('./certificates.js');
17
+
18
+ var DIR = require('./lib/directory-url.js');
19
+ var ChWrapper = require('./lib/challenges-wrapper.js');
20
+ var MngWrapper = require('./lib/manager-wrapper.js');
21
+
22
+ var UserEvents = require('./user-events.js');
23
+ var Init = require('./lib/init.js');
24
+
25
+ var caches = {};
26
+
27
+ // { maintainerEmail, directoryUrl, subscriberEmail, store, challenges }
28
+ G.create = function(gconf) {
29
+ var greenlock = {};
30
+ var gdefaults = {};
31
+ if (!gconf) {
32
+ gconf = {};
33
+ }
34
+
35
+ greenlock._create = function() {
36
+ if (!gconf._bin_mode) {
37
+ if (!gconf.maintainerEmail) {
38
+ throw E.NO_MAINTAINER('create');
39
+ }
40
+
41
+ // TODO send welcome message with benefit info
42
+ U._validMx(gconf.maintainerEmail).catch(function() {
43
+ log.error('Invalid maintainer contact:', gconf.maintainerEmail);
44
+
45
+ // maybe move this to init and don't exit the process, just in case
46
+ process.exit(1);
47
+ });
48
+ }
49
+
50
+ if ('function' === typeof gconf.notify) {
51
+ gdefaults.notify = gconf.notify;
52
+ } else {
53
+ gdefaults.notify = _notify;
54
+ }
55
+
56
+ gconf = Init._init(gconf);
57
+
58
+ // OK: /path/to/blah
59
+ // OK: npm-name-blah
60
+ // NOT OK: ./rel/path/to/blah
61
+ // Error: .blah
62
+ if ('.' === (gconf.manager.module || '')[0]) {
63
+ if (!gconf.packageRoot) {
64
+ gconf.packageRoot = process.cwd();
65
+ log.warn('packageRoot not set; using', gconf.packageRoot);
66
+ }
67
+ gconf.manager.module =
68
+ gconf.packageRoot + '/' + gconf.manager.module.slice(2);
69
+ }
70
+
71
+ // Wraps each of the following with appropriate error checking
72
+ // greenlock.manager.defaults
73
+ // greenlock.sites.add
74
+ // greenlock.sites.update
75
+ // greenlock.sites.remove
76
+ // greenlock.sites.find
77
+ // greenlock.sites.get
78
+ MngWrapper.wrap(greenlock, gconf);
79
+ // The goal here is to reduce boilerplate, such as error checking
80
+ // and duration parsing, that a manager must implement
81
+ greenlock.sites.add = greenlock.add = greenlock.manager.add;
82
+ greenlock.sites.update = greenlock.update = greenlock.manager.update;
83
+ greenlock.sites.remove = greenlock.remove = greenlock.manager.remove;
84
+
85
+ // Exports challenges.get for Greenlock Express HTTP-01,
86
+ // and whatever odd use case pops up, I suppose
87
+ // greenlock.challenges.get
88
+ ChWrapper.wrap(greenlock);
89
+
90
+ DIR._getDefaultDirectoryUrl('', gconf.staging, '');
91
+ if (gconf.directoryUrl) {
92
+ gdefaults.directoryUrl = gconf.directoryUrl;
93
+ }
94
+
95
+ greenlock._defaults = gdefaults;
96
+ greenlock._defaults.debug = gconf.debug;
97
+
98
+ if (!gconf._bin_mode && false !== gconf.renew) {
99
+ // renew every 90-ish minutes (random for staggering)
100
+ // the weak setTimeout (unref) means that when run as a CLI process this
101
+ // will still finish as expected, and not wait on the timeout
102
+ (function renew() {
103
+ setTimeout(function() {
104
+ greenlock.renew({});
105
+ renew();
106
+ }, Math.PI * 30 * 60 * 1000).unref();
107
+ })();
108
+ }
109
+ };
110
+
111
+ // The purpose of init is to make MCONF the source of truth
112
+ greenlock._init = function() {
113
+ var p;
114
+ greenlock._init = function() {
115
+ return p;
116
+ };
117
+
118
+ p = greenlock.manager
119
+ .init({
120
+ request: request
121
+ //punycode: require('punycode')
122
+ })
123
+ .then(async function() {
124
+ var MCONF = await greenlock.manager._defaults();
125
+ mergeDefaults(MCONF, gconf);
126
+ if (true === MCONF.agreeToTerms) {
127
+ gdefaults.agreeToTerms = function(tos) {
128
+ return Promise.resolve(tos);
129
+ };
130
+ }
131
+
132
+ return greenlock.manager._defaults(MCONF);
133
+ })
134
+ .catch(function(err) {
135
+ if ('load_plugin' !== err.context) {
136
+ log.error('Fatal error during greenlock init:', err.message);
137
+ }
138
+ if (!gconf._bin_mode) {
139
+ process.exit(1);
140
+ }
141
+ });
142
+ return p;
143
+ };
144
+
145
+ greenlock.notify = greenlock._notify = function(ev, params) {
146
+ var mng = greenlock.manager;
147
+
148
+ if ('_' === String(ev)[0]) {
149
+ if ('_cert_issue' === ev) {
150
+ try {
151
+ mng.update({
152
+ subject: params.subject,
153
+ renewAt: params.renewAt
154
+ }).catch(function(e) {
155
+ e.context = '_cert_issue';
156
+ greenlock._notify('error', e);
157
+ });
158
+ } catch (e) {
159
+ e.context = '_cert_issue';
160
+ greenlock._notify('error', e);
161
+ }
162
+ }
163
+ // trap internal events internally
164
+ return;
165
+ }
166
+
167
+ try {
168
+ var p = greenlock._defaults.notify(ev, params);
169
+ if (p && p.catch) {
170
+ p.catch(function(e) {
171
+ log.error("Promise rejection on event '" + ev + "':", e);
172
+ });
173
+ }
174
+ } catch (e) {
175
+ log.error("Exception in notify '" + ev + "':", e, params);
176
+ }
177
+
178
+ if (-1 !== ['cert_issue', 'cert_renewal'].indexOf(ev)) {
179
+ // We will notify all greenlock users of mandatory and security updates
180
+ // We'll keep track of versions and os so we can make sure things work well
181
+ // { name, version, email, domains, action, communityMember, telemetry }
182
+ // TODO look at the other one
183
+ UserEvents.notify({
184
+ /*
185
+ // maintainer should be only on pre-publish, or maybe install, I think
186
+ maintainerEmail: greenlock._defaults._maintainerEmail,
187
+ name: greenlock._defaults._packageAgent,
188
+ version: greenlock._defaults._maintainerPackageVersion,
189
+ //action: params.pems._type,
190
+ domains: params.altnames,
191
+ subscriberEmail: greenlock._defaults._subscriberEmail,
192
+ // TODO enable for Greenlock Pro
193
+ //customerEmail: args.customerEmail
194
+ telemetry: greenlock._defaults.telemetry
195
+ */
196
+ });
197
+ }
198
+ };
199
+
200
+ // certs.get
201
+ greenlock.get = async function(args) {
202
+ greenlock._single(args);
203
+ args._includePems = true;
204
+ var results = await greenlock.renew(args);
205
+ if (!results || !results.length) {
206
+ // TODO throw an error here?
207
+ return null;
208
+ }
209
+
210
+ // just get the first one
211
+ var result = results[0];
212
+
213
+ // (there should be only one, ideally)
214
+ if (results.length > 1) {
215
+ var err = new Error(
216
+ "a search for '" +
217
+ args.servername +
218
+ "' returned multiple certificates"
219
+ );
220
+ err.context = 'duplicate_certs';
221
+ err.servername = args.servername;
222
+ err.subjects = results.map(function(r) {
223
+ return (r.site || {}).subject || 'N/A';
224
+ });
225
+
226
+ greenlock._notify('warning', err);
227
+ }
228
+
229
+ if (result.error) {
230
+ return Promise.reject(result.error);
231
+ }
232
+
233
+ // site for plugin options, such as http-01 challenge
234
+ // pems for the obvious reasons
235
+ return result;
236
+ };
237
+
238
+ // TODO remove async here, it doesn't matter
239
+ greenlock._single = async function(args) {
240
+ if ('string' !== typeof args.servername) {
241
+ throw new Error('no `servername` given');
242
+ }
243
+ // www.example.com => *.example.com
244
+ args.wildname =
245
+ '*.' +
246
+ args.servername
247
+ .split('.')
248
+ .slice(1)
249
+ .join('.');
250
+ if (args.wildname.split('.').length < 3) {
251
+ // No '*.com'
252
+ args.wildname = '';
253
+ }
254
+ if (
255
+ args.servernames ||
256
+ //TODO I think we need to block altnames as well, but I don't want to break anything
257
+ //args.altnames ||
258
+ args.subject ||
259
+ args.renewBefore ||
260
+ args.issueBefore ||
261
+ args.expiresBefore
262
+ ) {
263
+ throw new Error(
264
+ 'bad arguments, did you mean to call greenlock.renew()?'
265
+ );
266
+ }
267
+ // duplicate, force, and others still allowed
268
+ return args;
269
+ };
270
+
271
+ greenlock._config = async function(args) {
272
+ greenlock._single(args);
273
+ var sites = await greenlock._configAll(args);
274
+ return sites[0];
275
+ };
276
+ greenlock._configAll = async function(args) {
277
+ var sites = await greenlock._find(args);
278
+ if (!sites || !sites.length) {
279
+ return [];
280
+ }
281
+ sites = JSON.parse(JSON.stringify(sites));
282
+ var mconf = await greenlock.manager._defaults();
283
+ return sites.map(function(site) {
284
+ if (site.store && site.challenges) {
285
+ return site;
286
+ }
287
+
288
+ var dconf = site;
289
+ // TODO make cli and api mode the same
290
+ if (gconf._bin_mode) {
291
+ dconf = site.defaults = {};
292
+ }
293
+ if (!site.store) {
294
+ dconf.store = mconf.store;
295
+ }
296
+ if (!site.challenges) {
297
+ dconf.challenges = mconf.challenges;
298
+ }
299
+ return site;
300
+ });
301
+ };
302
+
303
+ // needs to get info about the renewal, such as which store and challenge(s) to use
304
+ greenlock.renew = async function(args) {
305
+ await greenlock._init();
306
+ var mconf = await greenlock.manager._defaults();
307
+ return greenlock._renew(mconf, args);
308
+ };
309
+ greenlock._renew = async function(mconf, args) {
310
+ if (!args) {
311
+ args = {};
312
+ }
313
+
314
+ var renewedOrFailed = [];
315
+ //console.log('greenlock._renew find', args);
316
+ var sites = await greenlock._find(args);
317
+ // Note: the manager must guaranteed that these are mutable copies
318
+ //console.log('greenlock._renew found', sites);;
319
+
320
+ if (!Array.isArray(sites)) {
321
+ throw new Error(
322
+ 'Developer Error: not an array of sites returned from find: ' +
323
+ JSON.stringify(sites)
324
+ );
325
+ }
326
+
327
+ await (async function next() {
328
+ var site = sites.shift();
329
+ if (!site) {
330
+ return null;
331
+ }
332
+
333
+ var order = { site: site };
334
+ renewedOrFailed.push(order);
335
+ // TODO merge args + result?
336
+ return greenlock
337
+ ._order(mconf, site)
338
+ .then(function(pems) {
339
+ if (args._includePems) {
340
+ order.pems = pems;
341
+ }
342
+ })
343
+ .catch(function(err) {
344
+ order.error = err;
345
+
346
+ // For greenlock express serialization
347
+ err.toJSON = errorToJSON;
348
+ err.context = err.context || 'cert_order';
349
+ err.subject = site.subject;
350
+ if (args.servername) {
351
+ err.servername = args.servername;
352
+ }
353
+ // for debugging, but not to be relied on
354
+ err._site = site;
355
+ // TODO err.context = err.context || 'renew_certificate'
356
+ greenlock._notify('error', err);
357
+ })
358
+ .then(function() {
359
+ return next();
360
+ });
361
+ })();
362
+
363
+ return renewedOrFailed;
364
+ };
365
+
366
+ greenlock._acme = async function(mconf, args, dirUrl) {
367
+ var packageAgent = gconf.packageAgent || '';
368
+ // because Greenlock_Express/v3.x Greenlock/v3 is redundant
369
+ if (!/greenlock/i.test(packageAgent)) {
370
+ packageAgent = (packageAgent + ' Greenlock/' + pkg.version).trim();
371
+ }
372
+ var acme = ACME.create({
373
+ maintainerEmail: gconf.maintainerEmail,
374
+ packageAgent: packageAgent,
375
+ notify: greenlock._notify,
376
+ debug: greenlock._defaults.debug || args.debug
377
+ });
378
+
379
+ var dir = caches[dirUrl];
380
+ // don't cache more than an hour
381
+ if (dir && Date.now() - dir.ts < 1 * 60 * 60 * 1000) {
382
+ return dir.promise;
383
+ }
384
+
385
+ await acme.init(dirUrl).catch(function(err) {
386
+ log.error(
387
+ "ACME init failed (directory may be down or directoryUrl wrong):",
388
+ err.message
389
+ );
390
+ throw err;
391
+ });
392
+
393
+ caches[dirUrl] = {
394
+ promise: Promise.resolve(acme),
395
+ ts: Date.now()
396
+ };
397
+ return acme;
398
+ };
399
+
400
+ greenlock.order = async function(siteConf) {
401
+ await greenlock._init();
402
+ var mconf = await greenlock.manager._defaults();
403
+ return greenlock._order(mconf, siteConf);
404
+ };
405
+ greenlock._order = async function(mconf, siteConf) {
406
+ // packageAgent, maintainerEmail
407
+
408
+ var dirUrl = DIR._getDirectoryUrl(
409
+ siteConf.directoryUrl || mconf.directoryUrl,
410
+ siteConf.subject
411
+ );
412
+
413
+ var acme = await greenlock._acme(mconf, siteConf, dirUrl);
414
+ var storeConf = siteConf.store || mconf.store;
415
+ storeConf = JSON.parse(JSON.stringify(storeConf));
416
+ storeConf.packageRoot = gconf.packageRoot;
417
+
418
+ if (!storeConf.basePath) {
419
+ storeConf.basePath = gconf.configDir;
420
+ }
421
+
422
+ if ('.' === (storeConf.basePath || '')[0]) {
423
+ if (!gconf.packageRoot) {
424
+ gconf.packageRoot = process.cwd();
425
+ log.warn('packageRoot not set; using', gconf.packageRoot);
426
+ }
427
+ storeConf.basePath = require('path').resolve(
428
+ gconf.packageRoot || '',
429
+ storeConf.basePath
430
+ );
431
+ }
432
+
433
+ storeConf.directoryUrl = dirUrl;
434
+ var store = await P._loadStore(storeConf);
435
+ var account = await A._getOrCreate(
436
+ greenlock,
437
+ mconf,
438
+ store.accounts,
439
+ acme,
440
+ siteConf
441
+ );
442
+ var challengeConfs = siteConf.challenges || mconf.challenges;
443
+ var challenges = {};
444
+ var arr = await Promise.all(
445
+ Object.keys(challengeConfs).map(function(typ01) {
446
+ return P._loadChallenge(challengeConfs, typ01);
447
+ })
448
+ );
449
+ arr.forEach(function(el) {
450
+ challenges[el._type] = el;
451
+ });
452
+
453
+ var pems = await C._getOrOrder(
454
+ greenlock,
455
+ mconf,
456
+ store.certificates,
457
+ acme,
458
+ challenges,
459
+ account,
460
+ siteConf
461
+ );
462
+ if (!pems) {
463
+ throw new Error('no order result');
464
+ }
465
+ if (!pems.privkey) {
466
+ throw new Error('missing private key, which is kinda important');
467
+ }
468
+
469
+ return pems;
470
+ };
471
+
472
+ greenlock._create();
473
+
474
+ return greenlock;
475
+ };
476
+
477
+ G._loadChallenge = P._loadChallenge;
478
+
479
+ function errorToJSON(e) {
480
+ var error = {};
481
+ Object.getOwnPropertyNames(e).forEach(function(k) {
482
+ error[k] = e[k];
483
+ });
484
+ return error;
485
+ }
486
+
487
+ function mergeDefaults(MCONF, gconf) {
488
+ if (
489
+ gconf.agreeToTerms === true ||
490
+ MCONF.agreeToTerms === true ||
491
+ // TODO deprecate
492
+ gconf.agreeTos === true ||
493
+ MCONF.agreeTos === true
494
+ ) {
495
+ MCONF.agreeToTerms = true;
496
+ }
497
+
498
+ if (!MCONF.subscriberEmail && gconf.subscriberEmail) {
499
+ MCONF.subscriberEmail = gconf.subscriberEmail;
500
+ }
501
+
502
+ // Load the default store module
503
+ if (!MCONF.store) {
504
+ if (gconf.store) {
505
+ MCONF.store = gconf.store;
506
+ } else {
507
+ MCONF.store = {
508
+ module: 'greenlock-store-fs'
509
+ };
510
+ log.info('Default store:', MCONF.store.module);
511
+ }
512
+ }
513
+
514
+ /*
515
+ if ('greenlock-store-fs' === MCONF.store.module && !MCONF.store.basePath) {
516
+ //homedir = require('os').homedir();
517
+ if (gconf.configFile) {
518
+ MCONF.store.basePath = gconf.configFile.replace(/\.json$/i, '.d');
519
+ } else {
520
+ MCONF.store.basePath = './greenlock.d';
521
+ }
522
+ }
523
+ */
524
+
525
+ // just to test that it loads
526
+ P._loadSync(MCONF.store.module);
527
+
528
+ // Load the default challenge modules
529
+ var challenges = MCONF.challenges || gconf.challenges;
530
+ if (!challenges) {
531
+ challenges = {};
532
+ }
533
+ if (!challenges['http-01'] && !challenges['dns-01']) {
534
+ challenges['http-01'] = { module: 'acme-http-01-standalone' };
535
+ log.info('Default http-01 challenge:', challenges['http-01'].module);
536
+ }
537
+ if (challenges['http-01']) {
538
+ if ('string' !== typeof challenges['http-01'].module) {
539
+ throw new Error(
540
+ 'bad challenge http-01 module config:' +
541
+ JSON.stringify(challenges['http-01'])
542
+ );
543
+ }
544
+ P._loadSync(challenges['http-01'].module);
545
+ }
546
+ if (challenges['dns-01']) {
547
+ if ('string' !== typeof challenges['dns-01'].module) {
548
+ throw new Error(
549
+ 'bad challenge dns-01 module config' +
550
+ JSON.stringify(challenges['dns-01'])
551
+ );
552
+ }
553
+ P._loadSync(challenges['dns-01'].module);
554
+ }
555
+ MCONF.challenges = challenges;
556
+
557
+ if (!MCONF.renewOffset) {
558
+ MCONF.renewOffset = gconf.renewOffset || '-45d';
559
+ log.info('Default renewOffset:', MCONF.renewOffset);
560
+ }
561
+ if (!MCONF.renewStagger) {
562
+ MCONF.renewStagger = gconf.renewStagger || '3d';
563
+ log.info('Default renewStagger:', MCONF.renewStagger);
564
+ }
565
+
566
+ var vers = process.versions.node.split('.');
567
+ var defaultKeyType = 'EC-P256';
568
+ if (vers[0] < 10 || (vers[0] === '10' && vers[1] < '12')) {
569
+ defaultKeyType = 'RSA-2048';
570
+ }
571
+ if (!MCONF.accountKeyType) {
572
+ MCONF.accountKeyType = gconf.accountKeyType || defaultKeyType;
573
+ log.info('Default accountKeyType:', MCONF.accountKeyType);
574
+ }
575
+ if (!MCONF.serverKeyType) {
576
+ MCONF.serverKeyType = gconf.serverKeyType || 'RSA-2048';
577
+ log.info('Default serverKeyType:', MCONF.serverKeyType);
578
+ }
579
+
580
+ if (!MCONF.subscriberEmail && false !== MCONF.subscriberEmail) {
581
+ MCONF.subscriberEmail =
582
+ gconf.subscriberEmail || gconf.maintainerEmail || undefined;
583
+ MCONF.agreeToTerms = gconf.agreeToTerms || undefined;
584
+ log.info(
585
+ 'Defaults: subscriberEmail=%s, agreeToTerms=%s',
586
+ MCONF.subscriberEmail,
587
+ MCONF.agreeToTerms || gconf.agreeToTerms || '(show notice on use)'
588
+ );
589
+ }
590
+ }
591
+
592
+ function _notify(ev, args) {
593
+ if (!args) {
594
+ args = ev;
595
+ ev = args.event;
596
+ delete args.event;
597
+ }
598
+
599
+ if (!_notify._notice) {
600
+ log.info('Set greenlockOptions.notify to override the default logger');
601
+ _notify._notice = true;
602
+ }
603
+ switch (ev) {
604
+ case 'error':
605
+ case 'warning': {
606
+ var msg = (args.context ? args.context + ': ' : '') + (args.message || '');
607
+ log[ev === 'error' ? 'error' : 'warn'](msg);
608
+ if (args.description) log[ev === 'error' ? 'error' : 'warn'](args.description);
609
+ if (args.code) log[ev === 'error' ? 'error' : 'warn']('code:', args.code);
610
+ if (args.stack) log.error(args.stack);
611
+ break;
612
+ }
613
+ default:
614
+ if (/status/.test(ev)) {
615
+ log.info(ev, args.altname || args.subject || '', args.status || '');
616
+ if (!args.status) log.info(args);
617
+ } else {
618
+ log.info(ev, '(details:', Object.keys(args).join(' ') + ')');
619
+ }
620
+ }
621
+ }