@blamejs/core 0.6.67 → 0.6.69
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/atomic-file.js +13 -0
- package/lib/csv.js +8 -0
- package/lib/mail-bounce.js +15 -1
- package/lib/numeric-bounds.js +59 -0
- package/lib/safe-buffer.js +30 -15
- package/lib/safe-url.js +13 -3
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.6.x
|
|
10
10
|
|
|
11
|
+
- **0.6.69** (2026-05-03) — `lib/numeric-bounds.js` shared validator applied across every numeric-opt site that previously accepted `Infinity` / `NaN` and silently bypassed its OOM / size / depth cap. Pre-fix the recurring pattern `typeof opts.X === "number" && opts.X > 0` was vulnerable everywhere it shipped: `Infinity > Infinity` is false, `byteLength > NaN` is false, `Number.isFinite()` was missing. Operators with env-var coercions (`Number(process.env.MAX_X || "")` produces `NaN` for missing values, `Infinity` for typo'd ones) silently lost their cap. **The unified fix:** new `lib/numeric-bounds.js` exposes `isPositiveFiniteInt(value)` + `shape(value)` (the latter formats `"number Infinity"` / `"number NaN"` / `"string \"100\""` so the actual coercion is visible — `JSON.stringify` collapses Infinity/NaN to `"null"` and hides the typo). Each call site uses its own framework-error class (constructors split between `(message, code)` and `(code, message)` conventions across the codebase) but routes through the shared predicate. **Sites swept:** `lib/safe-buffer.js` (`boundedChunkCollector`, `toBuffer`, `normalizeText` — the v0.6.57 fix only covered the first), `lib/atomic-file.js` (refactored from v0.6.68's inline check), `lib/csv.js` (parse `maxBytes` was operator-overridable to Infinity → multi-megabyte CSV bodies through unbounded), `lib/safe-url.js` (`maxUrlLength` opt was the v0.6.62 fix's escape hatch — now also bounded), `lib/mail-bounce.js` (`maxBytes` for inbound webhook body — DoS-shape on bounce intake). **Tests** — new `test/layer-0-primitives/numeric-bounds.test.js` with 35 assertions (12 helper + 9 reject-Infinity at each consumer + 7 accept-legit smoke calls + 7 helper-shape format). Smoke 7258 → 7293 / wiki e2e 178 / Linux 86.0s / eslint clean / shellcheck clean.
|
|
12
|
+
|
|
13
|
+
- **0.6.68** (2026-05-03) — `b.atomicFile.read` / `readSync` enforce strict `maxBytes` validation. Same bug class as v0.6.57's `safeBuffer.boundedChunkCollector` fix: pre-fix the size check was `if (stat.size > opts.maxBytes)`, which silently accepted `Infinity` (`stat.size > Infinity` always false → reads any file regardless of size, defeating the OOM cap). Operators with `Number(env.MAX_READ_BYTES || "")` coercion bugs got `NaN` on missing values and unbounded reads on Infinity-typo'd values. New `_validateMaxBytes` runs before the size check and rejects `Infinity` / `NaN` / non-integer / negative / zero / non-number with `atomic-file/bad-opt`. Real-world consumers (vault.initPlaintext, audit-sign, db.loadOrCreateDbKey, etc.) all pass real positive integers via `C.BYTES.*` helpers and are unaffected. **Tests** — 7 new layer-0 boot-validation assertions in `test/00-primitives.js` (Infinity, NaN, 0, -1, 3.5, "100", null all reject). Smoke 7251 → 7258 / wiki e2e 178 / Linux 86.0s / eslint clean / shellcheck clean.
|
|
14
|
+
|
|
11
15
|
- **0.6.67** (2026-05-03) — canonical-JSON walker extracted to `lib/canonical-json.js` and applied to `lib/audit-tools.js` + `lib/config-drift.js`. Same bug class as v0.6.60 (pagination) and v0.6.66 (audit-chain) lived in two more sites: `audit-tools._canonicalize` (used for backup-bundle JSONL serialisation, requires byte-equivalence with audit-chain) and `config-drift._stableStringify` (used to hash framework config for post-boot tamper detection). Both still had the silent-data-loss walk — Date → `{}`, Buffer → `{"0":97,…}`, Map / Set → `{}`, BigInt throws, circular references stack-overflow. New `lib/canonical-json.js` exposes `stringify(value, opts?)` with `opts.bufferAs = "hex" | "reject"` (default `"hex"` for crypto / config / audit; `"reject"` for pagination's strict-cursor policy). All four call sites — audit-chain, audit-tools, config-drift, pagination — now route through it; the four near-identical inline walkers collapse to one. **Stored-row + bundle compatibility** preserved: existing audit rows verify identically (their `metadata` columns are JSON strings); backup bundles produced pre-v0.6.67 round-trip correctly because byte output for plain types is unchanged. **Tests** — 17 layer-0 audit-chain assertions from v0.6.66 cover the unified walker; existing pagination tests cover `bufferAs: "reject"`. Smoke 7251 / wiki e2e 178 / Linux 86.3s / eslint clean / shellcheck clean.
|
|
12
16
|
|
|
13
17
|
- **0.6.66** (2026-05-03) — `b.auditChain.canonicalize` deep-walks values and rejects non-plain types instead of silently round-tripping garbage. Same bug class as the v0.6.60 pagination canonicalize fix: pre-fix the walker did `Object.keys(row).map(...)` and JSON.stringify'd whatever fell through, which silently encoded **Map / Set / RegExp** as `{}`, **Symbol / function** as missing keys, **BigInt** as a thrown `Do not know how to serialize` mid-emit (DoS-shape on any operator routing bigint IDs into audit metadata), and **circular references** as the unwrapped JSON.stringify error message. The post-fix walker: BigInt → decimal string (no data loss for large account IDs / 64-bit counters from external-DB drivers); Date → ISO string (already worked via JSON.stringify but now explicit before the recursive walk); Map / Set / RegExp / Symbol / function reject with a clear constructor-name message; circular refs detected via WeakSet and rejected as a framework error. Walks recursively so nested structures (`{ a: [BigInt(1), BigInt(2)] }`, `{ a: { b: Uint8Array(…) } }`) all serialise correctly. **Stored-row compat** — existing audit rows verify identically because their stored `metadata` columns are already JSON strings; the change only affects EMIT-time serialisation. **Tests** — 17 new layer-0 assertions in `test/00-primitives.js`. Smoke 7234 → 7251 / wiki e2e 178 / Linux 86.1s / eslint clean / shellcheck clean.
|
package/lib/atomic-file.js
CHANGED
|
@@ -37,6 +37,7 @@ var { generateToken, sha3Hash } = require("./crypto");
|
|
|
37
37
|
var safeJson = require("./safe-json");
|
|
38
38
|
var C = require("./constants");
|
|
39
39
|
var safeBuffer = require("./safe-buffer");
|
|
40
|
+
var numericBounds = require("./numeric-bounds");
|
|
40
41
|
var safeAsync = require("./safe-async");
|
|
41
42
|
var retry = require("./retry");
|
|
42
43
|
var { FrameworkError } = require("./framework-error");
|
|
@@ -331,12 +332,24 @@ function readSync(filepath, opts) {
|
|
|
331
332
|
return _readSyncCore(filepath, opts);
|
|
332
333
|
}
|
|
333
334
|
|
|
335
|
+
// maxBytes via shared lib/numeric-bounds — Infinity / NaN bypass the
|
|
336
|
+
// stat.size cap (any-comparison-with-Infinity-or-NaN is false).
|
|
337
|
+
function _validateMaxBytes(maxBytes) {
|
|
338
|
+
if (!numericBounds.isPositiveFiniteInt(maxBytes)) {
|
|
339
|
+
throw new AtomicFileError(
|
|
340
|
+
"atomicFile.read: maxBytes must be a positive finite integer; got " +
|
|
341
|
+
numericBounds.shape(maxBytes),
|
|
342
|
+
"atomic-file/bad-opt");
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
334
346
|
function _readSyncCore(filepath, opts) {
|
|
335
347
|
if (!fs.existsSync(filepath)) {
|
|
336
348
|
var e = new AtomicFileError("file not found: " + filepath, "atomic-file/not-found");
|
|
337
349
|
e.code = "ENOENT";
|
|
338
350
|
throw e;
|
|
339
351
|
}
|
|
352
|
+
_validateMaxBytes(opts.maxBytes);
|
|
340
353
|
var stat = fs.statSync(filepath);
|
|
341
354
|
if (stat.size > opts.maxBytes) {
|
|
342
355
|
throw new AtomicFileError(
|
package/lib/csv.js
CHANGED
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
* Throws CsvError (FrameworkError, permanent) on shape violations.
|
|
43
43
|
*/
|
|
44
44
|
var C = require("./constants");
|
|
45
|
+
var numericBounds = require("./numeric-bounds");
|
|
45
46
|
var { defineClass } = require("./framework-error");
|
|
46
47
|
|
|
47
48
|
var CsvError = defineClass("CsvError", { alwaysPermanent: true });
|
|
@@ -83,6 +84,13 @@ function _validateDelim(name, value) {
|
|
|
83
84
|
|
|
84
85
|
function parse(input, opts) {
|
|
85
86
|
opts = Object.assign({}, DEFAULTS_PARSE, opts || {});
|
|
87
|
+
// maxBytes via shared lib/numeric-bounds — Infinity / NaN bypass the
|
|
88
|
+
// body cap and let a hostile multi-megabyte CSV through unbounded.
|
|
89
|
+
if (!numericBounds.isPositiveFiniteInt(opts.maxBytes)) {
|
|
90
|
+
throw new CsvError("csv/bad-opt",
|
|
91
|
+
"csv.parse: maxBytes must be a positive finite integer; got " +
|
|
92
|
+
numericBounds.shape(opts.maxBytes));
|
|
93
|
+
}
|
|
86
94
|
|
|
87
95
|
var s;
|
|
88
96
|
if (typeof input === "string") s = input;
|
package/lib/mail-bounce.js
CHANGED
|
@@ -64,6 +64,7 @@
|
|
|
64
64
|
*/
|
|
65
65
|
|
|
66
66
|
var lazyRequire = require("./lazy-require");
|
|
67
|
+
var numericBounds = require("./numeric-bounds");
|
|
67
68
|
var audit = lazyRequire(function () { return require("./audit"); });
|
|
68
69
|
var safeJson = require("./safe-json");
|
|
69
70
|
var C = require("./constants");
|
|
@@ -400,7 +401,20 @@ function handler(opts) {
|
|
|
400
401
|
var verify = typeof opts.verify === "function" ? opts.verify : null;
|
|
401
402
|
var onBounce = typeof opts.onBounce === "function" ? opts.onBounce : null;
|
|
402
403
|
var auditOn = opts.audit !== false;
|
|
403
|
-
|
|
404
|
+
// maxBytes must be a positive finite integer (Infinity / NaN /
|
|
405
|
+
// negative / non-integer all bypass the body cap). See
|
|
406
|
+
// lib/numeric-bounds for the rationale shared with every other
|
|
407
|
+
// numeric-opt site swept in v0.6.69.
|
|
408
|
+
var maxBytes;
|
|
409
|
+
if (opts.maxBytes === undefined) {
|
|
410
|
+
maxBytes = MAX_BODY_BYTES;
|
|
411
|
+
} else if (!numericBounds.isPositiveFiniteInt(opts.maxBytes)) {
|
|
412
|
+
throw new MailBounceError("mail-bounce/bad-opt",
|
|
413
|
+
"mailBounce.handler: opts.maxBytes must be a positive finite " +
|
|
414
|
+
"integer; got " + numericBounds.shape(opts.maxBytes), true);
|
|
415
|
+
} else {
|
|
416
|
+
maxBytes = opts.maxBytes;
|
|
417
|
+
}
|
|
404
418
|
|
|
405
419
|
function _emit(event) {
|
|
406
420
|
if (!auditOn) return;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* numeric-bounds — shared validators for operator-tunable numeric opts.
|
|
4
|
+
*
|
|
5
|
+
* Replaces the recurring `typeof opts.X === "number" && opts.X > 0`
|
|
6
|
+
* pattern that ships across the framework. Pre-v0.6.69 each site had
|
|
7
|
+
* its own slightly-different inline check; the bug they all shared was
|
|
8
|
+
* accepting `Infinity` (silently bypassing OOM caps, body size limits,
|
|
9
|
+
* URL caps, depth limits) and `NaN` (also bypassing because every
|
|
10
|
+
* comparison with NaN is false). v0.6.57 fixed boundedChunkCollector,
|
|
11
|
+
* v0.6.68 fixed atomicFile, v0.6.69 sweeps the rest.
|
|
12
|
+
*
|
|
13
|
+
* var nb = require("./numeric-bounds");
|
|
14
|
+
*
|
|
15
|
+
* if (!nb.isPositiveFiniteInt(opts.maxBytes)) {
|
|
16
|
+
* throw new CsvError("csv/bad-opt",
|
|
17
|
+
* "csv.parse: maxBytes must be a positive finite integer; got " +
|
|
18
|
+
* nb.shape(opts.maxBytes));
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* The helper returns a predicate + a shape-formatter rather than
|
|
22
|
+
* throwing itself, because the framework's error classes use two
|
|
23
|
+
* different constructor conventions:
|
|
24
|
+
*
|
|
25
|
+
* AtomicFileError, SafeBufferError, SafeUrlError (message, code)
|
|
26
|
+
* defineClass(...)-built (CsvError, MailBounceError…) (code, message, permanent[, statusCode])
|
|
27
|
+
*
|
|
28
|
+
* Each call site throws its own class, with whatever ctor shape that
|
|
29
|
+
* class wants. The helper just owns the validation rule + the consistent
|
|
30
|
+
* shape format ("number Infinity" / "number NaN" / "string \"100\"") so
|
|
31
|
+
* the typo / coercion is visible in the error message — `JSON.stringify`
|
|
32
|
+
* collapses Infinity / NaN to "null" which obscures what went wrong.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// Shape formatter — typeof + String preserves "number Infinity" /
|
|
36
|
+
// "number NaN" / "string foo". Strings get JSON-quoted so trailing
|
|
37
|
+
// whitespace / control chars are visible.
|
|
38
|
+
function shape(value) {
|
|
39
|
+
if (typeof value === "string") {
|
|
40
|
+
return "string " + JSON.stringify(value);
|
|
41
|
+
}
|
|
42
|
+
return (typeof value) + " " + String(value);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isPositiveFiniteInt(value) {
|
|
46
|
+
return typeof value === "number" && Number.isFinite(value) &&
|
|
47
|
+
Number.isInteger(value) && value > 0;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isNonNegativeFiniteInt(value) {
|
|
51
|
+
return typeof value === "number" && Number.isFinite(value) &&
|
|
52
|
+
Number.isInteger(value) && value >= 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
shape: shape,
|
|
57
|
+
isPositiveFiniteInt: isPositiveFiniteInt,
|
|
58
|
+
isNonNegativeFiniteInt: isNonNegativeFiniteInt,
|
|
59
|
+
};
|
package/lib/safe-buffer.js
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
* if the caller doesn't pass one.
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
|
+
var numericBounds = require("./numeric-bounds");
|
|
40
41
|
var { FrameworkError } = require("./framework-error");
|
|
41
42
|
|
|
42
43
|
class SafeBufferError extends FrameworkError {
|
|
@@ -57,7 +58,18 @@ function _throw(errorClass, message, code) {
|
|
|
57
58
|
|
|
58
59
|
function normalizeText(input, opts) {
|
|
59
60
|
opts = opts || {};
|
|
60
|
-
|
|
61
|
+
// maxBytes optional; positive finite int when set — Infinity / NaN
|
|
62
|
+
// bypass the cap.
|
|
63
|
+
var maxBytes = null;
|
|
64
|
+
if (opts.maxBytes !== undefined && opts.maxBytes !== null) {
|
|
65
|
+
if (!numericBounds.isPositiveFiniteInt(opts.maxBytes)) {
|
|
66
|
+
throw new SafeBufferError(
|
|
67
|
+
"normalizeText: maxBytes must be a positive finite integer; got " +
|
|
68
|
+
numericBounds.shape(opts.maxBytes),
|
|
69
|
+
"buffer/bad-arg");
|
|
70
|
+
}
|
|
71
|
+
maxBytes = opts.maxBytes;
|
|
72
|
+
}
|
|
61
73
|
var stripBom = opts.stripBom !== false; // default true
|
|
62
74
|
var errClass = opts.errorClass;
|
|
63
75
|
var typeCode = opts.typeCode || "buffer/wrong-input-type";
|
|
@@ -83,7 +95,17 @@ function normalizeText(input, opts) {
|
|
|
83
95
|
|
|
84
96
|
function toBuffer(data, opts) {
|
|
85
97
|
opts = opts || {};
|
|
86
|
-
|
|
98
|
+
// maxBytes optional; positive finite int when provided.
|
|
99
|
+
var maxBytes = null;
|
|
100
|
+
if (opts.maxBytes !== undefined && opts.maxBytes !== null) {
|
|
101
|
+
if (!numericBounds.isPositiveFiniteInt(opts.maxBytes)) {
|
|
102
|
+
throw new SafeBufferError(
|
|
103
|
+
"toBuffer: maxBytes must be a positive finite integer; got " +
|
|
104
|
+
numericBounds.shape(opts.maxBytes),
|
|
105
|
+
"buffer/bad-arg");
|
|
106
|
+
}
|
|
107
|
+
maxBytes = opts.maxBytes;
|
|
108
|
+
}
|
|
87
109
|
var errClass = opts.errorClass;
|
|
88
110
|
var typeCode = opts.typeCode || "buffer/wrong-input-type";
|
|
89
111
|
var sizeCode = opts.sizeCode || "buffer/too-large";
|
|
@@ -112,23 +134,16 @@ function toBuffer(data, opts) {
|
|
|
112
134
|
|
|
113
135
|
function boundedChunkCollector(opts) {
|
|
114
136
|
opts = opts || {};
|
|
115
|
-
// maxBytes
|
|
116
|
-
//
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
// chunk.length > maxBytes` arithmetic. NaN, negative, zero, non-
|
|
120
|
-
// numbers all reject with the same `buffer/bad-arg` so operators
|
|
121
|
-
// see one consistent error at boot from a typo or misconfiguration.
|
|
122
|
-
var maxBytes = (typeof opts.maxBytes === "number" &&
|
|
123
|
-
Number.isFinite(opts.maxBytes) &&
|
|
124
|
-
Number.isInteger(opts.maxBytes) &&
|
|
125
|
-
opts.maxBytes > 0) ? opts.maxBytes : null;
|
|
126
|
-
if (maxBytes === null) {
|
|
137
|
+
// maxBytes required, positive finite integer. Accepting Infinity
|
|
138
|
+
// defeats the entire point of the bounded collector (a hostile 10-GB
|
|
139
|
+
// upstream would accumulate fully).
|
|
140
|
+
if (!numericBounds.isPositiveFiniteInt(opts.maxBytes)) {
|
|
127
141
|
throw new SafeBufferError(
|
|
128
142
|
"boundedChunkCollector requires maxBytes (positive finite integer); got " +
|
|
129
|
-
|
|
143
|
+
numericBounds.shape(opts.maxBytes),
|
|
130
144
|
"buffer/bad-arg");
|
|
131
145
|
}
|
|
146
|
+
var maxBytes = opts.maxBytes;
|
|
132
147
|
var errClass = opts.errorClass;
|
|
133
148
|
var sizeCode = opts.sizeCode || "buffer/too-large";
|
|
134
149
|
var sizeMsg = opts.sizeMessage || "stream body exceeds maxBytes";
|
package/lib/safe-url.js
CHANGED
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
* trying and failing weirdly later.
|
|
49
49
|
*/
|
|
50
50
|
|
|
51
|
+
var numericBounds = require("./numeric-bounds");
|
|
51
52
|
var { FrameworkError } = require("./framework-error");
|
|
52
53
|
var { URL } = require("url");
|
|
53
54
|
|
|
@@ -87,9 +88,18 @@ function parse(url, opts) {
|
|
|
87
88
|
? opts.allowedProtocols
|
|
88
89
|
: ALLOW_HTTP_TLS;
|
|
89
90
|
var errClass = opts.errorClass;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
91
|
+
// maxUrlLength via shared lib/numeric-bounds — Infinity / NaN would
|
|
92
|
+
// silently bypass the cap (size > Infinity is always false).
|
|
93
|
+
var maxUrlLength;
|
|
94
|
+
if (opts.maxUrlLength === undefined) {
|
|
95
|
+
maxUrlLength = DEFAULT_MAX_URL_LENGTH;
|
|
96
|
+
} else if (!numericBounds.isPositiveFiniteInt(opts.maxUrlLength)) {
|
|
97
|
+
throw _makeError(errClass, "safe-url/bad-opt",
|
|
98
|
+
"safeUrl.parse: maxUrlLength must be a positive finite integer; got " +
|
|
99
|
+
numericBounds.shape(opts.maxUrlLength));
|
|
100
|
+
} else {
|
|
101
|
+
maxUrlLength = opts.maxUrlLength;
|
|
102
|
+
}
|
|
93
103
|
|
|
94
104
|
if (url == null || url === "") {
|
|
95
105
|
throw _makeError(errClass, "safe-url/missing", "url is required");
|
package/package.json
CHANGED
package/sbom.cyclonedx.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:99ad68c2-f813-40dc-8f9e-0cda46f62feb",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-03T15:21:23.088Z",
|
|
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.6.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.6.69",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.6.
|
|
25
|
+
"version": "0.6.69",
|
|
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.6.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.6.69",
|
|
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.6.
|
|
57
|
+
"ref": "@blamejs/core@0.6.69",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|