@blamejs/core 0.15.10 → 0.15.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.
- package/CHANGELOG.md +4 -0
- package/lib/acme.js +7 -11
- package/lib/client-hints.js +3 -1
- package/lib/cluster.js +4 -2
- package/lib/crypto-field.js +30 -0
- package/lib/db-declare-row-policy.js +20 -1
- package/lib/db.js +7 -0
- package/lib/guard-csv.js +13 -4
- package/lib/guard-filename.js +6 -2
- package/lib/http-client-cache.js +3 -1
- package/lib/http-message-signature.js +25 -8
- package/lib/log-stream-otlp-grpc.js +12 -1
- package/lib/log-stream-syslog.js +6 -0
- package/lib/log.js +24 -2
- package/lib/mail-bimi.js +16 -3
- package/lib/mail.js +21 -9
- package/lib/mcp.js +28 -6
- package/lib/middleware/body-parser.js +48 -6
- package/lib/middleware/bot-disclose.js +7 -5
- package/lib/middleware/speculation-rules.js +6 -4
- package/lib/network-dns.js +22 -26
- package/lib/network-heartbeat.js +3 -3
- package/lib/network-proxy.js +3 -7
- package/lib/network-tls.js +34 -13
- package/lib/network.js +2 -6
- package/lib/notify.js +7 -12
- package/lib/parsers/safe-env.js +6 -3
- package/lib/parsers/safe-yaml.js +6 -6
- package/lib/safe-buffer.js +69 -1
- package/lib/seeders.js +5 -10
- package/lib/structured-fields.js +38 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/notify.js
CHANGED
|
@@ -408,11 +408,6 @@ function create(opts) {
|
|
|
408
408
|
channels[n] = registry;
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
-
function _emitObs(name, labels) {
|
|
412
|
-
try { observability().event(name, 1, labels || {}); }
|
|
413
|
-
catch (_e) { /* drop-silent — observability sink must not crash send() */ }
|
|
414
|
-
}
|
|
415
|
-
|
|
416
411
|
var _emitAudit = validateOpts.makeAuditEmitter(audit);
|
|
417
412
|
|
|
418
413
|
function _actor(callerOpts) {
|
|
@@ -465,7 +460,7 @@ function create(opts) {
|
|
|
465
460
|
// mechanism — no double-retry inside the breaker or transport).
|
|
466
461
|
async function _oneAttempt(attemptIdx) {
|
|
467
462
|
attemptCount = attemptIdx;
|
|
468
|
-
|
|
463
|
+
observability().safeEvent("notify.send.attempt", 1, { channel: channel, attempt: attemptIdx });
|
|
469
464
|
var sendPromise = entry.transport.send(message, input.sendOpts || null);
|
|
470
465
|
// withTimeout from b.safeAsync — never re-implement timer races.
|
|
471
466
|
var timed = (perCallTimeoutMs > 0)
|
|
@@ -478,7 +473,7 @@ function create(opts) {
|
|
|
478
473
|
// classifies it correctly (operators can still opt OUT by setting
|
|
479
474
|
// err.permanent in their transport).
|
|
480
475
|
if (e && e.code === "async/timeout") {
|
|
481
|
-
|
|
476
|
+
observability().safeEvent("notify.send.timeout", 1, { channel: channel });
|
|
482
477
|
var te = _err("TIMEOUT",
|
|
483
478
|
"notify.send: '" + channel + "' transport timed out after " + perCallTimeoutMs + "ms");
|
|
484
479
|
// Mark transient via a NETWORK-style code so b.retry.isRetryable
|
|
@@ -497,7 +492,7 @@ function create(opts) {
|
|
|
497
492
|
return await entry.breaker.wrap(function () { return _oneAttempt(attemptIdx); });
|
|
498
493
|
} catch (e) {
|
|
499
494
|
if (e && e.code === "CIRCUIT_OPEN") {
|
|
500
|
-
|
|
495
|
+
observability().safeEvent("notify.send.breaker.open", 1, { channel: channel });
|
|
501
496
|
}
|
|
502
497
|
throw e;
|
|
503
498
|
}
|
|
@@ -524,7 +519,7 @@ function create(opts) {
|
|
|
524
519
|
}, perCallRetry);
|
|
525
520
|
|
|
526
521
|
var durationMs = clock() - startedAt;
|
|
527
|
-
|
|
522
|
+
observability().safeEvent("notify.send.success", 1, { channel: channel, durationMs: durationMs });
|
|
528
523
|
if (auditSuccess) {
|
|
529
524
|
_emitAudit("notify.send.success", {
|
|
530
525
|
actor: _actor(input),
|
|
@@ -543,7 +538,7 @@ function create(opts) {
|
|
|
543
538
|
durationMs: durationMs,
|
|
544
539
|
});
|
|
545
540
|
} catch (e) {
|
|
546
|
-
|
|
541
|
+
observability().safeEvent("notify.send.failure", 1, {
|
|
547
542
|
channel: channel,
|
|
548
543
|
reason: (e && e.code) || "unknown",
|
|
549
544
|
});
|
|
@@ -594,7 +589,7 @@ function create(opts) {
|
|
|
594
589
|
if (results[i] && results[i].isNotifyError) failed++;
|
|
595
590
|
else ok++;
|
|
596
591
|
}
|
|
597
|
-
|
|
592
|
+
observability().safeEvent("notify.batch", 1, { size: inputs.length, ok: ok, failed: failed });
|
|
598
593
|
return results;
|
|
599
594
|
}
|
|
600
595
|
|
|
@@ -626,7 +621,7 @@ function create(opts) {
|
|
|
626
621
|
} catch (_e) { /* operator may register their own handler */ }
|
|
627
622
|
}
|
|
628
623
|
var jobId = await q.enqueue(queueName, input);
|
|
629
|
-
|
|
624
|
+
observability().safeEvent("notify.queue.enqueued", 1, { channel: input.channel, queueName: queueName });
|
|
630
625
|
return { jobId: jobId };
|
|
631
626
|
}
|
|
632
627
|
|
package/lib/parsers/safe-env.js
CHANGED
|
@@ -147,7 +147,7 @@ function parse(input, opts) {
|
|
|
147
147
|
if (eqIdx < 0) {
|
|
148
148
|
throw new SafeEnvError("missing '=' separator", "env/bad-line", lineNumber);
|
|
149
149
|
}
|
|
150
|
-
var key = trimmed.substring(0, eqIdx)
|
|
150
|
+
var key = safeBuffer.stripTrailingHspace(trimmed.substring(0, eqIdx));
|
|
151
151
|
var rest = trimmed.substring(eqIdx + 1);
|
|
152
152
|
|
|
153
153
|
if (key.length === 0) {
|
|
@@ -186,10 +186,13 @@ function parse(input, opts) {
|
|
|
186
186
|
// The comment marker MUST be preceded by whitespace to count
|
|
187
187
|
// (so a value like `KEY=color#red` keeps the literal `#`).
|
|
188
188
|
var commentMatch = rest.match(/^([^\s#]*(?:[ \t]+[^#\s]+)*)\s+#.*$/);
|
|
189
|
+
// stripTrailingHspace is a linear char-scan; .replace(/[ \t]+$/) is O(n^2)
|
|
190
|
+
// in V8 and the env parser only caps TOTAL bytes, not per-line, so a
|
|
191
|
+
// single huge-whitespace value line would otherwise hang the parser.
|
|
189
192
|
if (commentMatch) {
|
|
190
|
-
value = commentMatch[1]
|
|
193
|
+
value = safeBuffer.stripTrailingHspace(commentMatch[1]);
|
|
191
194
|
} else {
|
|
192
|
-
value =
|
|
195
|
+
value = safeBuffer.stripTrailingHspace(rest);
|
|
193
196
|
}
|
|
194
197
|
// Reject `$VAR` style references — explicit error so operators
|
|
195
198
|
// see the policy rather than silently getting unexpanded text.
|
package/lib/parsers/safe-yaml.js
CHANGED
|
@@ -185,7 +185,7 @@ function parse(input, opts) {
|
|
|
185
185
|
var raw = rawLines[i];
|
|
186
186
|
// Strip trailing whitespace from the line for analysis (block-scalar
|
|
187
187
|
// content paths preserve their own internal spacing separately).
|
|
188
|
-
var trimmed =
|
|
188
|
+
var trimmed = safeBuffer.stripTrailingHspace(raw);
|
|
189
189
|
var indent = 0;
|
|
190
190
|
while (indent < raw.length && raw.charAt(indent) === " ") indent += 1;
|
|
191
191
|
if (indent < raw.length && raw.charAt(indent) === "\t") {
|
|
@@ -332,7 +332,7 @@ function parse(input, opts) {
|
|
|
332
332
|
if (raw.charAt(0) === "'") {
|
|
333
333
|
return _decodeSingleQuoted(raw, lineNumber, col);
|
|
334
334
|
}
|
|
335
|
-
var trimmed =
|
|
335
|
+
var trimmed = safeBuffer.stripTrailingHspace(raw);
|
|
336
336
|
if (POISONED_KEYS.has(trimmed)) {
|
|
337
337
|
throw new SafeYamlError("forbidden key '" + trimmed + "'",
|
|
338
338
|
"yaml/poisoned-key", lineNumber, col);
|
|
@@ -506,7 +506,7 @@ function parse(input, opts) {
|
|
|
506
506
|
return sq;
|
|
507
507
|
}
|
|
508
508
|
// Plain scalar — strip trailing space, resolve type.
|
|
509
|
-
return _resolveScalar(
|
|
509
|
+
return _resolveScalar(safeBuffer.stripTrailingHspace(t));
|
|
510
510
|
}
|
|
511
511
|
|
|
512
512
|
// For quoted-string termination check on a single-line value.
|
|
@@ -622,7 +622,7 @@ function parse(input, opts) {
|
|
|
622
622
|
if (c === "," || c === "}" || c === "]") break;
|
|
623
623
|
p += 1;
|
|
624
624
|
}
|
|
625
|
-
var raw = text.substring(start, p)
|
|
625
|
+
var raw = safeBuffer.stripTrailingHspace(text.substring(start, p));
|
|
626
626
|
return { value: _resolveScalar(raw), nextPos: p };
|
|
627
627
|
}
|
|
628
628
|
|
|
@@ -645,7 +645,7 @@ function parse(input, opts) {
|
|
|
645
645
|
if (c === ":" || c === "," || c === "}" || c === "]") break;
|
|
646
646
|
p += 1;
|
|
647
647
|
}
|
|
648
|
-
return { key: text.substring(start, p)
|
|
648
|
+
return { key: safeBuffer.stripTrailingHspace(text.substring(start, p)), nextPos: p };
|
|
649
649
|
}
|
|
650
650
|
|
|
651
651
|
function _findMatchingBracket(text, start, open, close, lineNumber, col) {
|
|
@@ -772,7 +772,7 @@ function parse(input, opts) {
|
|
|
772
772
|
function _stripEolComment(text) {
|
|
773
773
|
// Strip ` #...` comments (must be preceded by whitespace) at end of line.
|
|
774
774
|
var match = text.match(/^(.*?)(\s+#.*)?$/);
|
|
775
|
-
return (match && match[1] != null ? match[1] : text)
|
|
775
|
+
return safeBuffer.stripTrailingHspace(match && match[1] != null ? match[1] : text);
|
|
776
776
|
}
|
|
777
777
|
|
|
778
778
|
// ---- Block scalars (| literal, > folded) ----
|
package/lib/safe-buffer.js
CHANGED
|
@@ -494,7 +494,74 @@ var TRAILING_HSPACE_RE = /[ \t]+$/;
|
|
|
494
494
|
*/
|
|
495
495
|
function stripTrailingHspace(s) {
|
|
496
496
|
if (typeof s !== "string") return s;
|
|
497
|
-
|
|
497
|
+
// Linear backward scan over trailing spaces/tabs — NOT s.replace(/[ \t]+$/).
|
|
498
|
+
// The `$`-after-greedy-`[ \t]+` regex is O(n^2) in V8 on adversarial input
|
|
499
|
+
// (a long run of spaces followed by a non-space: the engine retries the
|
|
500
|
+
// greedy match from every offset). normalizeText callers cap TOTAL bytes but
|
|
501
|
+
// not per-line, so a single ~500K-space value hangs (~85s). The char-scan is
|
|
502
|
+
// O(trailing-whitespace) and byte-identical to the regex on every input
|
|
503
|
+
// (JS `$` without /m matches only the absolute end, so a trailing \n is not
|
|
504
|
+
// stripped — the scan stops at it too).
|
|
505
|
+
var e = s.length;
|
|
506
|
+
while (e > 0) {
|
|
507
|
+
var c = s.charCodeAt(e - 1);
|
|
508
|
+
if (c === 0x20 || c === 0x09) { e -= 1; } else { break; }
|
|
509
|
+
}
|
|
510
|
+
return e === s.length ? s : s.slice(0, e);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* @primitive b.safeBuffer.indexAfterOpenTag
|
|
515
|
+
* @signature b.safeBuffer.indexAfterOpenTag(html, tagName)
|
|
516
|
+
* @since 0.15.11
|
|
517
|
+
* @related b.safeBuffer.stripTrailingHspace
|
|
518
|
+
*
|
|
519
|
+
* Find the offset in `html` just past the first `<tagName ...>` opening
|
|
520
|
+
* tag (case-insensitive), or `-1` when the tag is absent or unterminated.
|
|
521
|
+
* The insertion point a response rewriter uses to splice content right
|
|
522
|
+
* after `<body>` / `<head>` without a regex.
|
|
523
|
+
*
|
|
524
|
+
* This replaces the `html.match(/<body[^>]*>/i)` shape, which is O(n^2)
|
|
525
|
+
* in V8: a body carrying many `<body` starts with no closing `>` (e.g.
|
|
526
|
+
* rendered user content) makes the engine retry the greedy `[^>]*` from
|
|
527
|
+
* every offset — a `<body`-repeated 200K-char body benchmarks in
|
|
528
|
+
* seconds. This is a single forward `indexOf` walk: linear in the input,
|
|
529
|
+
* and stricter than the regex — it requires a real tag boundary after
|
|
530
|
+
* the name (whitespace, `>`, or `/`), so `<bodyfoo>` is not mistaken for
|
|
531
|
+
* `<body>`. Non-string input returns `-1`.
|
|
532
|
+
*
|
|
533
|
+
* @example
|
|
534
|
+
* var b = require("blamejs");
|
|
535
|
+
* b.safeBuffer.indexAfterOpenTag("<html><body class=x>hi", "body");
|
|
536
|
+
* // → 19 (just past the '>' of <body class=x>)
|
|
537
|
+
*
|
|
538
|
+
* b.safeBuffer.indexAfterOpenTag("<p>no body here</p>", "body");
|
|
539
|
+
* // → -1
|
|
540
|
+
*/
|
|
541
|
+
function indexAfterOpenTag(html, tagName) {
|
|
542
|
+
if (typeof html !== "string" || typeof tagName !== "string" || tagName.length === 0) return -1;
|
|
543
|
+
var needle = "<" + tagName.toLowerCase();
|
|
544
|
+
var nlen = needle.length;
|
|
545
|
+
// One O(n) lowercase pass keeps the case-insensitive search linear; the
|
|
546
|
+
// forward indexOf walk below never re-scans a region it has passed.
|
|
547
|
+
var lower = html.toLowerCase();
|
|
548
|
+
var from = 0;
|
|
549
|
+
for (;;) {
|
|
550
|
+
var lt = lower.indexOf(needle, from);
|
|
551
|
+
if (lt === -1) return -1;
|
|
552
|
+
var after = lt + nlen;
|
|
553
|
+
var boundary = after < html.length ? html.charCodeAt(after) : -1;
|
|
554
|
+
// The char after "<tag" must end the tag name — '>' (0x3e), '/' (0x2f),
|
|
555
|
+
// or ASCII whitespace — else this is a longer name like "<bodyfoo".
|
|
556
|
+
if (boundary === 0x3e || boundary === 0x2f ||
|
|
557
|
+
boundary === 0x20 || boundary === 0x09 ||
|
|
558
|
+
boundary === 0x0a || boundary === 0x0d || boundary === 0x0c) {
|
|
559
|
+
var gt = html.indexOf(">", after);
|
|
560
|
+
if (gt === -1) return -1; // unterminated opening tag — no insertion point
|
|
561
|
+
return gt + 1;
|
|
562
|
+
}
|
|
563
|
+
from = lt + 1;
|
|
564
|
+
}
|
|
498
565
|
}
|
|
499
566
|
|
|
500
567
|
/**
|
|
@@ -612,6 +679,7 @@ module.exports = {
|
|
|
612
679
|
hasCrlf: hasCrlf,
|
|
613
680
|
stripCrlf: stripCrlf,
|
|
614
681
|
stripTrailingHspace: stripTrailingHspace,
|
|
682
|
+
indexAfterOpenTag: indexAfterOpenTag,
|
|
615
683
|
HEX_RE: HEX_RE,
|
|
616
684
|
BASE64URL_RE: BASE64URL_RE,
|
|
617
685
|
BASE64_RE: BASE64_RE,
|
package/lib/seeders.js
CHANGED
|
@@ -438,11 +438,6 @@ function create(opts) {
|
|
|
438
438
|
var audit = opts.audit || null;
|
|
439
439
|
var clock = opts.clock || function () { return Date.now(); };
|
|
440
440
|
|
|
441
|
-
function _emitObs(name, labels) {
|
|
442
|
-
try { observability().event(name, 1, labels || {}); }
|
|
443
|
-
catch (_e) { /* drop-silent — observability sink must not crash seeders */ }
|
|
444
|
-
}
|
|
445
|
-
|
|
446
441
|
var _emitAudit = validateOpts.makeAuditEmitter(audit);
|
|
447
442
|
|
|
448
443
|
function _actor(callerOpts) {
|
|
@@ -512,7 +507,7 @@ function create(opts) {
|
|
|
512
507
|
}
|
|
513
508
|
|
|
514
509
|
var startedAt = clock();
|
|
515
|
-
|
|
510
|
+
observability().safeEvent("seeders.run.start", 1, { env: env, count: loaded.ordered.length });
|
|
516
511
|
|
|
517
512
|
var holder = _acquireLock(db, lockStaleAfterMs, clock);
|
|
518
513
|
try {
|
|
@@ -538,7 +533,7 @@ function create(opts) {
|
|
|
538
533
|
|
|
539
534
|
if (!shouldRun) {
|
|
540
535
|
skipped.push(name);
|
|
541
|
-
|
|
536
|
+
observability().safeEvent("seeders.skipped", 1, { env: env, name: name, reason: "already-applied" });
|
|
542
537
|
continue;
|
|
543
538
|
}
|
|
544
539
|
|
|
@@ -585,7 +580,7 @@ function create(opts) {
|
|
|
585
580
|
|
|
586
581
|
var auditAction = (alreadyApplied && force) ? "seeders.force_applied" : "seeders.applied";
|
|
587
582
|
var auditEvt = { env: env, name: name };
|
|
588
|
-
|
|
583
|
+
observability().safeEvent(auditAction, 1, auditEvt);
|
|
589
584
|
if (auditApplied) {
|
|
590
585
|
_emitAudit(auditAction, {
|
|
591
586
|
actor: _actor(callerOpts),
|
|
@@ -598,7 +593,7 @@ function create(opts) {
|
|
|
598
593
|
failed = name;
|
|
599
594
|
var msg = (e && e.message) || String(e);
|
|
600
595
|
var code = (e && e.code) || "RUN_FAILED";
|
|
601
|
-
|
|
596
|
+
observability().safeEvent("seeders.failed", 1, { env: env, name: name });
|
|
602
597
|
if (auditFailures) {
|
|
603
598
|
_emitAudit("seeders.failed", {
|
|
604
599
|
actor: _actor(callerOpts),
|
|
@@ -622,7 +617,7 @@ function create(opts) {
|
|
|
622
617
|
failed: failed,
|
|
623
618
|
durationMs: clock() - startedAt,
|
|
624
619
|
};
|
|
625
|
-
|
|
620
|
+
observability().safeEvent("seeders.run.completed", 1, {
|
|
626
621
|
env: env,
|
|
627
622
|
applied: applied.length,
|
|
628
623
|
skipped: skipped.length,
|
package/lib/structured-fields.js
CHANGED
|
@@ -204,7 +204,43 @@ function unquoteSfString(s) {
|
|
|
204
204
|
if (t.length === 0) return "";
|
|
205
205
|
if (t.charAt(0) !== "\"") return t;
|
|
206
206
|
if (t.length < 2 || t.charAt(t.length - 1) !== "\"") return null;
|
|
207
|
-
return t.slice(1, -1)
|
|
207
|
+
return unescapeSfStringBody(t.slice(1, -1));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @primitive b.structuredFields.unescapeSfStringBody
|
|
212
|
+
* @signature b.structuredFields.unescapeSfStringBody(body)
|
|
213
|
+
* @since 0.15.12
|
|
214
|
+
* @related b.structuredFields.unquoteSfString
|
|
215
|
+
*
|
|
216
|
+
* Undo the RFC 8941 §3.3.3 quoted-string backslash-escapes from the BODY of
|
|
217
|
+
* an sf-string (the bytes BETWEEN the surrounding double quotes). Only `\\\\`
|
|
218
|
+
* and `\\"` are legal escapes; every other backslash is literal.
|
|
219
|
+
*
|
|
220
|
+
* This is a single left-to-right scan, NOT two chained `.replace()` passes.
|
|
221
|
+
* The two-pass form (`.replace(/\\\\/g,"\\").replace(/\\"/g,'"')`, in either
|
|
222
|
+
* order) is not equivalent to a single decode: whichever pass runs first can
|
|
223
|
+
* rewrite a backslash the other escape sequence legitimately owns, so a lone
|
|
224
|
+
* escaped backslash (`\\\\`) decodes to two backslashes instead of one. The
|
|
225
|
+
* single pass consumes each escape exactly once. Non-string input passes
|
|
226
|
+
* through unchanged.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* b.structuredFields.unescapeSfStringBody('a\\"b\\\\c');
|
|
230
|
+
* // → 'a"b\c'
|
|
231
|
+
*/
|
|
232
|
+
function unescapeSfStringBody(body) {
|
|
233
|
+
if (typeof body !== "string") return body;
|
|
234
|
+
var out = "";
|
|
235
|
+
for (var i = 0; i < body.length; i++) {
|
|
236
|
+
var c = body.charAt(i);
|
|
237
|
+
if (c === "\\" && i + 1 < body.length) {
|
|
238
|
+
var n = body.charAt(i + 1);
|
|
239
|
+
if (n === "\\" || n === "\"") { out += n; i += 1; continue; }
|
|
240
|
+
}
|
|
241
|
+
out += c;
|
|
242
|
+
}
|
|
243
|
+
return out;
|
|
208
244
|
}
|
|
209
245
|
|
|
210
246
|
/**
|
|
@@ -674,6 +710,7 @@ module.exports = {
|
|
|
674
710
|
refuseControlBytes: refuseControlBytes,
|
|
675
711
|
containsControlBytes: containsControlBytes,
|
|
676
712
|
unquoteSfString: unquoteSfString,
|
|
713
|
+
unescapeSfStringBody: unescapeSfStringBody,
|
|
677
714
|
parse: parse,
|
|
678
715
|
serialize: serialize,
|
|
679
716
|
Token: SfToken,
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:caf34b84-1358-4d28-a517-e360819dc35a",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-06-
|
|
8
|
+
"timestamp": "2026-06-14T16:52:10.733Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/core@0.15.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.15.12",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.15.
|
|
25
|
+
"version": "0.15.12",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "The Node framework that owns its stack.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/core@0.15.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.15.12",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/core@0.15.
|
|
57
|
+
"ref": "@blamejs/core@0.15.12",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|