@blamejs/core 0.7.0 → 0.7.4
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.
- package/CHANGELOG.md +8 -0
- package/README.md +2 -2
- package/index.js +2 -0
- package/lib/api-key.js +1 -10
- package/lib/cache-redis.js +1 -11
- package/lib/cache.js +11 -16
- package/lib/file-upload.js +933 -0
- package/lib/framework-error.js +13 -0
- package/lib/log-stream-syslog.js +1 -1
- package/lib/middleware/api-encrypt.js +415 -52
- package/lib/middleware/db-role-for.js +3 -8
- package/lib/notify.js +3 -5
- package/lib/object-store/azure-blob.js +42 -5
- package/lib/object-store/gcs.js +47 -7
- package/lib/object-store/sigv4.js +55 -7
- package/lib/pubsub-redis.js +1 -11
- package/lib/queue-redis.js +1 -8
- package/lib/redis-client.js +30 -0
- package/lib/request-helpers.js +21 -17
- package/lib/seeders.js +5 -17
- package/lib/static.js +699 -114
- package/lib/validate-opts.js +49 -0
- package/lib/webhook.js +3 -6
- package/lib/websocket.js +38 -6
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
|
@@ -36,6 +36,7 @@ var { Readable } = require("stream");
|
|
|
36
36
|
var safeXml = require("../parsers/safe-xml");
|
|
37
37
|
var sharedRequest = require("./http-request");
|
|
38
38
|
var C = require("../constants");
|
|
39
|
+
var requestHelpers = require("../request-helpers");
|
|
39
40
|
var { ObjectStoreError } = require("../framework-error");
|
|
40
41
|
var time = require("../time");
|
|
41
42
|
var safeUrl = require("../safe-url");
|
|
@@ -248,16 +249,51 @@ function create(config) {
|
|
|
248
249
|
});
|
|
249
250
|
}
|
|
250
251
|
|
|
251
|
-
|
|
252
|
+
// get(key, opts?) — opts forwarded to Azure as conditional / range
|
|
253
|
+
// headers (Range / x-ms-range, If-None-Match, If-Match,
|
|
254
|
+
// If-Modified-Since, If-Unmodified-Since). Returns the body buffer;
|
|
255
|
+
// operators wanting status + response headers call getResponse().
|
|
256
|
+
function get(key, opts) {
|
|
257
|
+
return getResponse(key, opts).then(function (r) { return r.body; });
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function getStream(key, opts) { return Readable.from(get(key, opts)); }
|
|
261
|
+
|
|
262
|
+
function getResponse(key, opts) {
|
|
263
|
+
opts = opts || {};
|
|
252
264
|
var url = _blobUrl(key);
|
|
253
|
-
var
|
|
265
|
+
var extraHeaders = {};
|
|
266
|
+
if (opts.range) {
|
|
267
|
+
extraHeaders["x-ms-range"] = "bytes=" + opts.range.start + "-" + opts.range.end;
|
|
268
|
+
}
|
|
269
|
+
if (opts.ifNoneMatch) extraHeaders["If-None-Match"] = opts.ifNoneMatch;
|
|
270
|
+
if (opts.ifMatch) extraHeaders["If-Match"] = opts.ifMatch;
|
|
271
|
+
if (opts.ifModifiedSince) extraHeaders["If-Modified-Since"] = opts.ifModifiedSince;
|
|
272
|
+
if (opts.ifUnmodifiedSince) extraHeaders["If-Unmodified-Since"] = opts.ifUnmodifiedSince;
|
|
273
|
+
var headers = _signed("GET", url, extraHeaders);
|
|
254
274
|
return _httpRequest("GET", url, headers, null, reqOpts).then(function (res) {
|
|
255
|
-
return
|
|
275
|
+
return {
|
|
276
|
+
statusCode: res.statusCode,
|
|
277
|
+
body: res.body,
|
|
278
|
+
etag: res.headers && res.headers.etag,
|
|
279
|
+
lastModified: res.headers && res.headers["last-modified"]
|
|
280
|
+
? Date.parse(res.headers["last-modified"]) : null,
|
|
281
|
+
contentRange: res.headers && res.headers["content-range"] || null,
|
|
282
|
+
size: res.headers && res.headers["content-length"]
|
|
283
|
+
? parseInt(res.headers["content-length"], 10) : null,
|
|
284
|
+
contentType: res.headers && res.headers["content-type"] || null,
|
|
285
|
+
};
|
|
286
|
+
}, function (err) {
|
|
287
|
+
if (err && err.statusCode === requestHelpers.HTTP_STATUS.NOT_MODIFIED) {
|
|
288
|
+
return {
|
|
289
|
+
statusCode: requestHelpers.HTTP_STATUS.NOT_MODIFIED,
|
|
290
|
+
body: null, etag: null, lastModified: null,
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
throw err;
|
|
256
294
|
});
|
|
257
295
|
}
|
|
258
296
|
|
|
259
|
-
function getStream(key) { return Readable.from(get(key)); }
|
|
260
|
-
|
|
261
297
|
function head(key) {
|
|
262
298
|
var url = _blobUrl(key);
|
|
263
299
|
var headers = _signed("HEAD", url, {});
|
|
@@ -434,6 +470,7 @@ function create(config) {
|
|
|
434
470
|
put: put,
|
|
435
471
|
get: get,
|
|
436
472
|
getStream: getStream,
|
|
473
|
+
getResponse: getResponse,
|
|
437
474
|
head: head,
|
|
438
475
|
delete: deleteKey,
|
|
439
476
|
list: list,
|
package/lib/object-store/gcs.js
CHANGED
|
@@ -28,6 +28,7 @@ var { Readable } = require("stream");
|
|
|
28
28
|
var safeJson = require("../safe-json");
|
|
29
29
|
var C = require("../constants");
|
|
30
30
|
var nb = require("../numeric-bounds");
|
|
31
|
+
var requestHelpers = require("../request-helpers");
|
|
31
32
|
var { ObjectStoreError } = require("../framework-error");
|
|
32
33
|
var safeUrl = require("../safe-url");
|
|
33
34
|
var sharedRequest = require("./http-request");
|
|
@@ -223,15 +224,53 @@ function create(config) {
|
|
|
223
224
|
return { size: parseInt(meta.size || buf.length, 10), etag: meta.etag };
|
|
224
225
|
}
|
|
225
226
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
227
|
+
// get(key, opts?) — opts forwarded to GCS as conditional / range
|
|
228
|
+
// headers (Range, If-None-Match, If-Match, If-Modified-Since,
|
|
229
|
+
// If-Unmodified-Since). Returns the body buffer for backwards compat;
|
|
230
|
+
// operators wanting full status + response headers call getResponse().
|
|
231
|
+
async function get(key, opts) {
|
|
232
|
+
var r = await getResponse(key, opts);
|
|
233
|
+
return r.body;
|
|
231
234
|
}
|
|
232
235
|
|
|
233
|
-
function getStream(key) {
|
|
234
|
-
return Readable.from(get(key));
|
|
236
|
+
function getStream(key, opts) {
|
|
237
|
+
return Readable.from(get(key, opts));
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function getResponse(key, opts) {
|
|
241
|
+
opts = opts || {};
|
|
242
|
+
var token = await _ensureToken();
|
|
243
|
+
var url = _objectUrl(key, { alt: "media" });
|
|
244
|
+
var headers = authHeader.bearer(token);
|
|
245
|
+
if (opts.range) {
|
|
246
|
+
headers["Range"] = "bytes=" + opts.range.start + "-" + opts.range.end;
|
|
247
|
+
}
|
|
248
|
+
if (opts.ifNoneMatch) headers["If-None-Match"] = opts.ifNoneMatch;
|
|
249
|
+
if (opts.ifMatch) headers["If-Match"] = opts.ifMatch;
|
|
250
|
+
if (opts.ifModifiedSince) headers["If-Modified-Since"] = opts.ifModifiedSince;
|
|
251
|
+
if (opts.ifUnmodifiedSince) headers["If-Unmodified-Since"] = opts.ifUnmodifiedSince;
|
|
252
|
+
try {
|
|
253
|
+
var res = await _httpRequest("GET", url, headers, null, reqOpts);
|
|
254
|
+
return {
|
|
255
|
+
statusCode: res.statusCode,
|
|
256
|
+
body: res.body,
|
|
257
|
+
etag: res.headers && res.headers.etag,
|
|
258
|
+
lastModified: res.headers && res.headers["last-modified"]
|
|
259
|
+
? Date.parse(res.headers["last-modified"]) : null,
|
|
260
|
+
contentRange: res.headers && res.headers["content-range"] || null,
|
|
261
|
+
size: res.headers && res.headers["content-length"]
|
|
262
|
+
? parseInt(res.headers["content-length"], 10) : null,
|
|
263
|
+
contentType: res.headers && res.headers["content-type"] || null,
|
|
264
|
+
};
|
|
265
|
+
} catch (err) {
|
|
266
|
+
if (err && err.statusCode === requestHelpers.HTTP_STATUS.NOT_MODIFIED) {
|
|
267
|
+
return {
|
|
268
|
+
statusCode: requestHelpers.HTTP_STATUS.NOT_MODIFIED,
|
|
269
|
+
body: null, etag: null, lastModified: null,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
throw err;
|
|
273
|
+
}
|
|
235
274
|
}
|
|
236
275
|
|
|
237
276
|
async function head(key) {
|
|
@@ -457,6 +496,7 @@ function create(config) {
|
|
|
457
496
|
put: put,
|
|
458
497
|
get: get,
|
|
459
498
|
getStream: getStream,
|
|
499
|
+
getResponse: getResponse,
|
|
460
500
|
head: head,
|
|
461
501
|
delete: deleteKey,
|
|
462
502
|
list: list,
|
|
@@ -30,6 +30,7 @@ var safeXml = require("../parsers/safe-xml");
|
|
|
30
30
|
var sharedRequest = require("./http-request");
|
|
31
31
|
var C = require("../constants");
|
|
32
32
|
var nb = require("../numeric-bounds");
|
|
33
|
+
var requestHelpers = require("../request-helpers");
|
|
33
34
|
var { ObjectStoreError } = require("../framework-error");
|
|
34
35
|
var safeUrl = require("../safe-url");
|
|
35
36
|
|
|
@@ -593,18 +594,64 @@ function create(config) {
|
|
|
593
594
|
}
|
|
594
595
|
}
|
|
595
596
|
|
|
596
|
-
|
|
597
|
+
// get(key, opts?) — opts forwarded to the request as conditional /
|
|
598
|
+
// range headers so operator HTTP routes can pass If-None-Match,
|
|
599
|
+
// If-Match, If-Modified-Since, If-Unmodified-Since, and Range from the
|
|
600
|
+
// client request straight through to S3. Returns just the body buffer
|
|
601
|
+
// for backwards compatibility; operators wanting status + response
|
|
602
|
+
// headers (304 vs 206 vs 200) call getResponse() instead.
|
|
603
|
+
function get(key, opts) {
|
|
604
|
+
return getResponse(key, opts).then(function (r) { return r.body; });
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function getStream(key, opts) {
|
|
608
|
+
return Readable.from(get(key, opts));
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// getResponse(key, opts?) — full-fidelity GET. Returns
|
|
612
|
+
// { body, statusCode, etag, lastModified, contentRange, size,
|
|
613
|
+
// contentType }. Throws on non-2xx EXCEPT 304 (returned as
|
|
614
|
+
// { statusCode: 304, etag, lastModified, body: null }) so operator
|
|
615
|
+
// routes can short-circuit conditional GETs without losing the
|
|
616
|
+
// response headers.
|
|
617
|
+
function getResponse(key, opts) {
|
|
618
|
+
opts = opts || {};
|
|
597
619
|
var url = _keyToUrl(key);
|
|
598
620
|
var headers = _makeSigned("GET", url, sha256Hex(Buffer.alloc(0)));
|
|
599
|
-
|
|
600
|
-
|
|
621
|
+
if (opts.range) {
|
|
622
|
+
headers["Range"] = "bytes=" + opts.range.start + "-" + opts.range.end;
|
|
623
|
+
}
|
|
624
|
+
if (opts.ifNoneMatch) headers["If-None-Match"] = opts.ifNoneMatch;
|
|
625
|
+
if (opts.ifMatch) headers["If-Match"] = opts.ifMatch;
|
|
626
|
+
if (opts.ifModifiedSince) headers["If-Modified-Since"] = opts.ifModifiedSince;
|
|
627
|
+
if (opts.ifUnmodifiedSince) headers["If-Unmodified-Since"] = opts.ifUnmodifiedSince;
|
|
628
|
+
var localReqOpts = Object.assign({}, reqOpts, { _resolveOnRedirect: false });
|
|
629
|
+
return _request("GET", url, headers, null, localReqOpts).then(function (res) {
|
|
630
|
+
return {
|
|
631
|
+
statusCode: res.statusCode,
|
|
632
|
+
body: res.body,
|
|
633
|
+
etag: res.headers && res.headers.etag,
|
|
634
|
+
lastModified: res.headers && res.headers["last-modified"]
|
|
635
|
+
? Date.parse(res.headers["last-modified"]) : null,
|
|
636
|
+
contentRange: res.headers && res.headers["content-range"] || null,
|
|
637
|
+
size: res.headers && res.headers["content-length"]
|
|
638
|
+
? parseInt(res.headers["content-length"], 10) : null,
|
|
639
|
+
contentType: res.headers && res.headers["content-type"] || null,
|
|
640
|
+
};
|
|
641
|
+
}, function (err) {
|
|
642
|
+
// 304 surfaces as a "non-2xx error" via httpClient; propagate it
|
|
643
|
+
// as a structured 304 result instead so operator routes get
|
|
644
|
+
// the conditional-GET short-circuit they expect.
|
|
645
|
+
if (err && err.statusCode === requestHelpers.HTTP_STATUS.NOT_MODIFIED) {
|
|
646
|
+
return {
|
|
647
|
+
statusCode: requestHelpers.HTTP_STATUS.NOT_MODIFIED,
|
|
648
|
+
body: null, etag: null, lastModified: null,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
throw err;
|
|
601
652
|
});
|
|
602
653
|
}
|
|
603
654
|
|
|
604
|
-
function getStream(key) {
|
|
605
|
-
return Readable.from(get(key));
|
|
606
|
-
}
|
|
607
|
-
|
|
608
655
|
function head(key) {
|
|
609
656
|
var url = _keyToUrl(key);
|
|
610
657
|
var headers = _makeSigned("HEAD", url, sha256Hex(Buffer.alloc(0)));
|
|
@@ -829,6 +876,7 @@ function create(config) {
|
|
|
829
876
|
put: put,
|
|
830
877
|
get: get,
|
|
831
878
|
getStream: getStream,
|
|
879
|
+
getResponse: getResponse,
|
|
832
880
|
head: head,
|
|
833
881
|
delete: deleteKey,
|
|
834
882
|
list: list,
|
package/lib/pubsub-redis.js
CHANGED
|
@@ -41,17 +41,7 @@ function create(opts) {
|
|
|
41
41
|
// double-fire local handlers).
|
|
42
42
|
var instanceNonce = fwCrypto.generateToken(C.BYTES.bytes(8));
|
|
43
43
|
|
|
44
|
-
var clientOpts =
|
|
45
|
-
url: opts.redisUrl,
|
|
46
|
-
password: opts.redisPassword,
|
|
47
|
-
username: opts.redisUsername,
|
|
48
|
-
tls: opts.redisTls,
|
|
49
|
-
ca: opts.redisCa,
|
|
50
|
-
servername: opts.redisServername,
|
|
51
|
-
connectTimeoutMs: opts.redisConnectTimeoutMs,
|
|
52
|
-
commandTimeoutMs: opts.redisCommandTimeoutMs,
|
|
53
|
-
maxReconnectAttempts: opts.redisMaxReconnectAttempts,
|
|
54
|
-
};
|
|
44
|
+
var clientOpts = redisClient.pickClientOpts(opts, "redis");
|
|
55
45
|
|
|
56
46
|
var subscriberConn = null;
|
|
57
47
|
var publisherConn = null;
|
package/lib/queue-redis.js
CHANGED
|
@@ -254,14 +254,7 @@ function create(opts) {
|
|
|
254
254
|
var prefix = typeof opts.keyPrefix === "string" && opts.keyPrefix.length > 0
|
|
255
255
|
? opts.keyPrefix : DEFAULT_PREFIX;
|
|
256
256
|
|
|
257
|
-
var client = redisClient.create(
|
|
258
|
-
url: opts.url,
|
|
259
|
-
password: opts.password,
|
|
260
|
-
username: opts.username,
|
|
261
|
-
tls: opts.tls,
|
|
262
|
-
connectTimeoutMs: opts.connectTimeoutMs,
|
|
263
|
-
commandTimeoutMs: opts.commandTimeoutMs,
|
|
264
|
-
});
|
|
257
|
+
var client = redisClient.create(redisClient.pickClientOpts(opts));
|
|
265
258
|
|
|
266
259
|
// Lazy connect — defer first connect until the first operation so
|
|
267
260
|
// queue.init({ backends }) doesn't have to be async.
|
package/lib/redis-client.js
CHANGED
|
@@ -479,8 +479,38 @@ function _parseRedisUrl(s) {
|
|
|
479
479
|
};
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
+
// pickClientOpts(cfg, prefix?) — extract the standard redis-client opts
|
|
483
|
+
// from a larger config bag. Lets cache-redis / pubsub-redis / queue-redis
|
|
484
|
+
// / etc. forward to redisClient.create without each repeating the 9-key
|
|
485
|
+
// list. The optional `prefix` lets callers whose operator-facing opts
|
|
486
|
+
// are namespaced (`redisUrl`, `redisPassword`, ...) reuse the same
|
|
487
|
+
// helper by passing prefix="redis" — the helper camel-cases the prefix
|
|
488
|
+
// onto each key.
|
|
489
|
+
//
|
|
490
|
+
// var opts = redisClient.pickClientOpts(cfg); // unprefixed
|
|
491
|
+
// var opts = redisClient.pickClientOpts(operatorOpts, "redis"); // redisUrl etc.
|
|
492
|
+
function pickClientOpts(cfg, prefix) {
|
|
493
|
+
if (!cfg || typeof cfg !== "object") return {};
|
|
494
|
+
function pick(name) {
|
|
495
|
+
if (!prefix) return cfg[name];
|
|
496
|
+
return cfg[prefix + name.charAt(0).toUpperCase() + name.slice(1)];
|
|
497
|
+
}
|
|
498
|
+
return {
|
|
499
|
+
url: pick("url"),
|
|
500
|
+
password: pick("password"),
|
|
501
|
+
username: pick("username"),
|
|
502
|
+
tls: pick("tls"),
|
|
503
|
+
ca: pick("ca"),
|
|
504
|
+
servername: pick("servername"),
|
|
505
|
+
connectTimeoutMs: pick("connectTimeoutMs"),
|
|
506
|
+
commandTimeoutMs: pick("commandTimeoutMs"),
|
|
507
|
+
maxReconnectAttempts: pick("maxReconnectAttempts"),
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
|
|
482
511
|
module.exports = {
|
|
483
512
|
create: create,
|
|
513
|
+
pickClientOpts: pickClientOpts,
|
|
484
514
|
// Exposed for tests / direct callers that already manage their own socket.
|
|
485
515
|
_encodeCommand: _encodeCommand,
|
|
486
516
|
_parseFrame: _parseFrame,
|
package/lib/request-helpers.js
CHANGED
|
@@ -41,23 +41,27 @@
|
|
|
41
41
|
// every consumer reads HTTP_STATUS.<NAME> rather than the underlying
|
|
42
42
|
// integer, so the hex form is purely an internal storage detail.
|
|
43
43
|
var HTTP_STATUS = Object.freeze({
|
|
44
|
-
OK:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
44
|
+
OK: 0xC8,
|
|
45
|
+
PARTIAL_CONTENT: 0xCE,
|
|
46
|
+
NO_CONTENT: 0xCC,
|
|
47
|
+
NOT_MODIFIED: 0x130,
|
|
48
|
+
BAD_REQUEST: 0x190,
|
|
49
|
+
UNAUTHORIZED: 0x191,
|
|
50
|
+
FORBIDDEN: 0x193,
|
|
51
|
+
NOT_FOUND: 0x194,
|
|
52
|
+
METHOD_NOT_ALLOWED: 0x195,
|
|
53
|
+
CONFLICT: 0x199,
|
|
54
|
+
PAYLOAD_TOO_LARGE: 0x19D,
|
|
55
|
+
UNSUPPORTED_MEDIA_TYPE: 0x19F,
|
|
56
|
+
RANGE_NOT_SATISFIABLE: 0x1A0,
|
|
57
|
+
UNPROCESSABLE_CONTENT: 0x1A6,
|
|
58
|
+
PRECONDITION_FAILED: 0x19C,
|
|
59
|
+
TOO_MANY_REQUESTS: 0x1AD,
|
|
60
|
+
UNAVAILABLE_FOR_LEGAL_REASONS: 0x1C3,
|
|
61
|
+
INTERNAL_SERVER_ERROR: 0x1F4,
|
|
62
|
+
BAD_GATEWAY: 0x1F6,
|
|
63
|
+
SERVICE_UNAVAILABLE: 0x1F7,
|
|
64
|
+
GATEWAY_TIMEOUT: 0x1F8,
|
|
61
65
|
});
|
|
62
66
|
|
|
63
67
|
// extractActorContext(req) — pull the 5 W's from a request for audit
|
package/lib/seeders.js
CHANGED
|
@@ -137,11 +137,9 @@ function _validateEnv(name, value) {
|
|
|
137
137
|
function _validateCreateOpts(opts) {
|
|
138
138
|
validateOpts.requireObject(opts, "seeders.create", SeederError);
|
|
139
139
|
validateOpts.requireNonEmptyString(opts.dir, "seeders.create: dir", SeederError, "BAD_OPT");
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
}
|
|
140
|
+
validateOpts.optionalObjectWithMethod(opts.db, "prepare",
|
|
141
|
+
"seeders.create: db", SeederError, "BAD_OPT",
|
|
142
|
+
"must be a SQLite-shaped handle (prepare fn)");
|
|
145
143
|
validateOpts.auditShape(opts.audit, "seeders.create", SeederError);
|
|
146
144
|
validateOpts.optionalBoolean(opts.auditApplied, "seeders.create: auditApplied", SeederError);
|
|
147
145
|
validateOpts.optionalBoolean(opts.auditFailures, "seeders.create: auditFailures", SeederError);
|
|
@@ -205,18 +203,8 @@ function _loadSeed(rootDir, env, file) {
|
|
|
205
203
|
throw _err("BAD_SEED",
|
|
206
204
|
"seed '" + env + "/" + file + "': rerunnable must be a boolean");
|
|
207
205
|
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
throw _err("BAD_SEED",
|
|
211
|
-
"seed '" + env + "/" + file + "': dependsOn must be an array of seed filenames");
|
|
212
|
-
}
|
|
213
|
-
for (var j = 0; j < mod.dependsOn.length; j++) {
|
|
214
|
-
if (typeof mod.dependsOn[j] !== "string" || mod.dependsOn[j].length === 0) {
|
|
215
|
-
throw _err("BAD_SEED",
|
|
216
|
-
"seed '" + env + "/" + file + "': dependsOn[" + j + "] must be a non-empty string");
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
206
|
+
validateOpts.optionalNonEmptyStringArray(mod.dependsOn,
|
|
207
|
+
"seed '" + env + "/" + file + "': dependsOn", SeederError, "BAD_SEED");
|
|
220
208
|
if (mod.description !== undefined && typeof mod.description !== "string") {
|
|
221
209
|
throw _err("BAD_SEED",
|
|
222
210
|
"seed '" + env + "/" + file + "': description must be a string");
|