@blamejs/core 0.7.84 → 0.7.85
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 +2 -0
- package/index.js +1 -0
- package/lib/auth/status-list.js +271 -0
- package/package.json +1 -1
- package/sbom.cyclonedx.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,8 @@ upgrading across more than a few patches at a time.
|
|
|
8
8
|
|
|
9
9
|
## v0.7.x
|
|
10
10
|
|
|
11
|
+
- **0.7.85** (2026-05-06) — `b.auth.statusList` — OAuth Token Status List (draft-ietf-oauth-status-list-20). The canonical credential-revocation mechanism for SD-JWT VC and OpenID for Verifiable Credentials. An issuer publishes a JWT-wrapped bitstring at a URL; relying parties fetch + check the bit at index N to determine if the credential whose `status_list` claim points at that URL+index is valid / invalid / suspended / application-specific. **`b.auth.statusList.create({ size, bits?, fill? })`** allocates a bit-packed buffer (`bits` ∈ {1, 2, 4, 8} per draft §6.1.1, default 1). The returned object exposes `.set(idx, status)` / `.get(idx)` / `.snapshot()` / `.toJwt({ issuer, subject, privateKey, algorithm, expiresInSec?, ... })`. **`b.auth.statusList.fromJwt(token, { publicKey | keyResolver, algorithms?, expectedIssuer?, ... })`** verifies the JWT through `b.auth.jwt.verify` and returns `{ list, claims }` — the list exposes `.get(idx)` to check status of an individual credential without decompressing into a separate Buffer. The bitstring is zlib-deflated (RFC 1951 raw deflate per draft §6.1.4) before base64url encoding so a million-entry list collapses to ~125 KB on the wire when most bits are zero. Caps the compressed payload at 1 MiB; operators publishing larger lists shard. Status constants exported as `b.auth.statusList.STATUS_{VALID,INVALID,SUSPENDED,APPLICATION_SPECIFIC}`. Foundational for the v0.7.58 EU AI Act + eIDAS 2.0 wallet slice.
|
|
12
|
+
|
|
11
13
|
- **0.7.84** (2026-05-06) — `b.crypto.sri(content, { algorithm? })` — Subresource Integrity hash builder per W3C SRI 1.0. Operators emit `<script integrity="sha384-...">` and `<link integrity="sha384-...">` to defend against CDN compromise + ISP MITM injection — the browser refuses to load a resource whose actual hash diverges from the integrity attribute. Default algorithm is `sha384` (W3C §3.2 — collision margin without sha512's 64-byte overhead); `sha256` and `sha512` also accepted, anything else refused. Accepts `Buffer` / `Uint8Array` / `string` / array of those — array inputs emit multiple space-separated integrity tokens per W3C §3.3 multi-integrity (browser picks the strongest it recognizes). Returns the standard `sha###-<base64>` format ready to paste into the `integrity=""` attribute.
|
|
12
14
|
|
|
13
15
|
- **0.7.83** (2026-05-06) — `b.auth.oauth.endSessionUrl()` (OpenID Connect RP-Initiated Logout) + `b.auth.oauth.pushAuthorizationRequest()` (RFC 9126 PAR). **`endSessionUrl({ idTokenHint?, postLogoutRedirectUri?, state?, logoutHint?, uiLocales?, clientId?, extraParams? })`** builds the URL the operator's `/logout` route redirects the user-agent to so the IdP terminates the session and bounces back to the operator's app. The IdP's `end_session_endpoint` is read from the OIDC discovery document or operator-supplied at `create({ endSessionEndpoint })`. **`pushAuthorizationRequest({ state?, nonce?, prompt?, loginHint?, maxAge?, extraParams? })`** POSTs the authorization-request parameters directly to the IdP's PAR endpoint (mTLS or client-secret authenticated) and returns `{ url, state, nonce, verifier, challenge, requestUri, expiresIn }` — the browser-side redirect URL is `<authorizationEndpoint>?client_id=...&request_uri=...`. Defends against authorization-request parameter tampering by an MITM at the user-agent + against URL-length overflow on long authorization requests (request_uri reference is short). The PAR endpoint is read from the discovery doc's `pushed_authorization_request_endpoint` or operator-supplied at `create({ pushedAuthorizationRequestEndpoint })`. Both helpers throw `auth-oauth/no-end-session-endpoint` / `auth-oauth/no-par-endpoint` when neither the discovery doc nor the operator opts supply the endpoint.
|
package/index.js
CHANGED
|
@@ -139,6 +139,7 @@ var auth = {
|
|
|
139
139
|
lockout: require("./lib/auth/lockout"),
|
|
140
140
|
dpop: require("./lib/auth/dpop"),
|
|
141
141
|
aal: require("./lib/auth/aal"),
|
|
142
|
+
statusList: require("./lib/auth/status-list"),
|
|
142
143
|
};
|
|
143
144
|
var template = require("./lib/template");
|
|
144
145
|
var render = require("./lib/render");
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OAuth Token Status List (draft-ietf-oauth-status-list-20).
|
|
4
|
+
*
|
|
5
|
+
* An issuer publishes a JWT-wrapped bitstring at a URL; relying
|
|
6
|
+
* parties fetch + check the bit at index N to determine if the
|
|
7
|
+
* credential whose `status_list` claim points at that URL + index
|
|
8
|
+
* has been revoked / suspended / is still valid. The format is the
|
|
9
|
+
* canonical replacement for the older "status list" mechanisms in
|
|
10
|
+
* SD-JWT VC and OpenID for Verifiable Credentials.
|
|
11
|
+
*
|
|
12
|
+
* Status values per draft §4.2:
|
|
13
|
+
* 0 = VALID
|
|
14
|
+
* 1 = INVALID
|
|
15
|
+
* 2 = SUSPENDED
|
|
16
|
+
* 3 = APPLICATION_SPECIFIC
|
|
17
|
+
* ... (1-bit / 2-bit / 4-bit / 8-bit `bits` size — operator picks)
|
|
18
|
+
*
|
|
19
|
+
* var list = b.auth.statusList.create({ size: 1024, bits: 1 });
|
|
20
|
+
* list.set(42, 1); // mark idx 42 INVALID
|
|
21
|
+
* var jwt = await list.toJwt({
|
|
22
|
+
* issuer: "https://issuer.example.com",
|
|
23
|
+
* subject: "https://issuer.example.com/status/list/1",
|
|
24
|
+
* privateKey: env("STATUS_LIST_PRIVATE_KEY_PEM"),
|
|
25
|
+
* algorithm: "ML-DSA-87", // matches b.auth.jwt's PQC default
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Receive side:
|
|
29
|
+
* var rv = await b.auth.statusList.fromJwt(jwt, { publicKey: pem });
|
|
30
|
+
* rv.list.get(42) // → 1 (INVALID)
|
|
31
|
+
*
|
|
32
|
+
* The JWT payload shape per draft §6.1:
|
|
33
|
+
* {
|
|
34
|
+
* iss: "<issuer>",
|
|
35
|
+
* sub: "<this-list-uri>",
|
|
36
|
+
* iat: <issued-at>,
|
|
37
|
+
* exp: <optional-expires>,
|
|
38
|
+
* ttl: <optional-cache-ttl>,
|
|
39
|
+
* status_list: { bits: 1|2|4|8, lst: "<base64url(zlib(bitstring))>" },
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
42
|
+
* The bitstring is zlib-deflated (RFC 1951 raw deflate per draft
|
|
43
|
+
* §6.1.4) before base64url encoding so a million-entry list collapses
|
|
44
|
+
* to a few KB on the wire when most bits are zero.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
var nodeCrypto = require("crypto");
|
|
48
|
+
var zlib = require("node:zlib");
|
|
49
|
+
var safeJson = require("../safe-json");
|
|
50
|
+
var validateOpts = require("../validate-opts");
|
|
51
|
+
var C = require("../constants");
|
|
52
|
+
var jwt = require("./jwt");
|
|
53
|
+
var { defineClass } = require("../framework-error");
|
|
54
|
+
|
|
55
|
+
var StatusListError = defineClass("StatusListError", { alwaysPermanent: true });
|
|
56
|
+
|
|
57
|
+
var SUPPORTED_BIT_SIZES = { 1: 1, 2: 1, 4: 1, 8: 1 }; // allow:raw-byte-literal — bit-size enum (1/2/4/8 bits per status), not bytes
|
|
58
|
+
var STATUS_VALID = 0;
|
|
59
|
+
var STATUS_INVALID = 1;
|
|
60
|
+
var STATUS_SUSPENDED = 2;
|
|
61
|
+
var STATUS_APPLICATION_SPECIFIC = 3;
|
|
62
|
+
|
|
63
|
+
// Cap the on-the-wire compressed payload at 1 MiB (a million 1-bit
|
|
64
|
+
// entries compress to ~125 KB when most are zero; 8 MiB on the wire
|
|
65
|
+
// is more than the spec's expected use). Operators publishing larger
|
|
66
|
+
// status lists should shard.
|
|
67
|
+
var MAX_LIST_BYTES = C.BYTES.mib(1);
|
|
68
|
+
|
|
69
|
+
function _b64url(buf) {
|
|
70
|
+
return buf.toString("base64").replace(/=+$/g, "").replace(/\+/g, "-").replace(/\//g, "_");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function _fromB64url(s) {
|
|
74
|
+
var padded = s.replace(/-/g, "+").replace(/_/g, "/");
|
|
75
|
+
while (padded.length % 4) padded += "="; // allow:raw-byte-literal — base64 quartet padding
|
|
76
|
+
return Buffer.from(padded, "base64");
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _validateBits(bits) {
|
|
80
|
+
if (!SUPPORTED_BIT_SIZES[bits]) {
|
|
81
|
+
throw new StatusListError("status-list/bad-bits",
|
|
82
|
+
"statusList: bits must be 1, 2, 4, or 8 (draft §6.1.1) — got " + bits);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function _validateStatus(status, bits) {
|
|
87
|
+
if (typeof status !== "number" || !isFinite(status) || status < 0 || (status >> 0) !== status) {
|
|
88
|
+
throw new StatusListError("status-list/bad-status",
|
|
89
|
+
"statusList: status must be a non-negative integer — got " + status);
|
|
90
|
+
}
|
|
91
|
+
var max = (1 << bits) - 1;
|
|
92
|
+
if (status > max) {
|
|
93
|
+
throw new StatusListError("status-list/bad-status",
|
|
94
|
+
"statusList: status " + status + " exceeds bits=" + bits + " ceiling " + max);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function create(opts) {
|
|
99
|
+
validateOpts.requireObject(opts, "statusList.create", StatusListError);
|
|
100
|
+
validateOpts(opts, ["size", "bits", "fill"], "statusList.create");
|
|
101
|
+
var size = opts.size;
|
|
102
|
+
if (typeof size !== "number" || !isFinite(size) || size <= 0 || (size >> 0) !== size) {
|
|
103
|
+
throw new StatusListError("status-list/bad-size",
|
|
104
|
+
"statusList.create: size must be a positive integer — got " + size);
|
|
105
|
+
}
|
|
106
|
+
var bits = opts.bits === undefined ? 1 : opts.bits;
|
|
107
|
+
_validateBits(bits);
|
|
108
|
+
// Allocate the bit-packed buffer up front. byteCount = ceil(size*bits/8).
|
|
109
|
+
var bitBytes = Math.ceil((size * bits) / 8); // allow:raw-byte-literal — bits-per-byte conversion
|
|
110
|
+
var bytes = Buffer.alloc(bitBytes);
|
|
111
|
+
if (opts.fill !== undefined && opts.fill !== 0) {
|
|
112
|
+
_validateStatus(opts.fill, bits);
|
|
113
|
+
for (var i = 0; i < size; i += 1) _setAt(bytes, bits, i, opts.fill);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function set(idx, status) {
|
|
117
|
+
if (typeof idx !== "number" || idx < 0 || idx >= size || (idx >> 0) !== idx) {
|
|
118
|
+
throw new StatusListError("status-list/bad-index",
|
|
119
|
+
"statusList.set: idx out of range — got " + idx + ", size=" + size);
|
|
120
|
+
}
|
|
121
|
+
_validateStatus(status, bits);
|
|
122
|
+
_setAt(bytes, bits, idx, status);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function get(idx) {
|
|
126
|
+
if (typeof idx !== "number" || idx < 0 || idx >= size || (idx >> 0) !== idx) {
|
|
127
|
+
throw new StatusListError("status-list/bad-index",
|
|
128
|
+
"statusList.get: idx out of range — got " + idx + ", size=" + size);
|
|
129
|
+
}
|
|
130
|
+
return _getAt(bytes, bits, idx);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function snapshot() {
|
|
134
|
+
return { size: size, bits: bits, bytes: Buffer.from(bytes) };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// ---- JWT issuance ----
|
|
138
|
+
// Returns the canonical JWT payload + signed compact form per
|
|
139
|
+
// draft §6.1. The signing key is operator-supplied; the framework
|
|
140
|
+
// wraps b.auth.jwt.sign with the status-list-specific claim shape.
|
|
141
|
+
async function toJwt(jwtOpts) {
|
|
142
|
+
validateOpts.requireObject(jwtOpts, "statusList.toJwt", StatusListError);
|
|
143
|
+
validateOpts(jwtOpts, [
|
|
144
|
+
"issuer", "subject", "privateKey", "algorithm",
|
|
145
|
+
"expiresInSec", "notBeforeSec", "now", "ttl",
|
|
146
|
+
], "statusList.toJwt");
|
|
147
|
+
validateOpts.requireNonEmptyString(jwtOpts.issuer,
|
|
148
|
+
"statusList.toJwt: issuer", StatusListError, "status-list/bad-issuer");
|
|
149
|
+
validateOpts.requireNonEmptyString(jwtOpts.subject,
|
|
150
|
+
"statusList.toJwt: subject", StatusListError, "status-list/bad-subject");
|
|
151
|
+
var deflated = zlib.deflateRawSync(bytes);
|
|
152
|
+
if (deflated.length > MAX_LIST_BYTES) {
|
|
153
|
+
throw new StatusListError("status-list/too-large",
|
|
154
|
+
"statusList.toJwt: compressed list exceeds " + MAX_LIST_BYTES + " bytes — shard the list");
|
|
155
|
+
}
|
|
156
|
+
var lst = _b64url(deflated);
|
|
157
|
+
var claims = {
|
|
158
|
+
iss: jwtOpts.issuer,
|
|
159
|
+
sub: jwtOpts.subject,
|
|
160
|
+
status_list: { bits: bits, lst: lst },
|
|
161
|
+
};
|
|
162
|
+
if (typeof jwtOpts.ttl === "number") claims.ttl = jwtOpts.ttl;
|
|
163
|
+
return await jwt.sign(claims, {
|
|
164
|
+
privateKey: jwtOpts.privateKey,
|
|
165
|
+
algorithm: jwtOpts.algorithm,
|
|
166
|
+
typ: "statuslist+jwt",
|
|
167
|
+
expiresInSec: jwtOpts.expiresInSec,
|
|
168
|
+
notBeforeSec: jwtOpts.notBeforeSec,
|
|
169
|
+
now: jwtOpts.now,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
set: set,
|
|
175
|
+
get: get,
|
|
176
|
+
size: size,
|
|
177
|
+
bits: bits,
|
|
178
|
+
snapshot: snapshot,
|
|
179
|
+
toJwt: toJwt,
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---- bit-packed helpers ----
|
|
184
|
+
|
|
185
|
+
function _setAt(bytes, bits, idx, status) {
|
|
186
|
+
if (bits === 8) { bytes[idx] = status & 0xff; return; } // allow:raw-byte-literal — byte mask
|
|
187
|
+
var bitOffset = idx * bits;
|
|
188
|
+
var byteIdx = Math.floor(bitOffset / 8); // allow:raw-byte-literal — bits-per-byte
|
|
189
|
+
var bitInByte = bitOffset % 8; // allow:raw-byte-literal — bits-per-byte
|
|
190
|
+
var mask = ((1 << bits) - 1) << bitInByte;
|
|
191
|
+
bytes[byteIdx] = (bytes[byteIdx] & ~mask) | ((status << bitInByte) & mask);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function _getAt(bytes, bits, idx) {
|
|
195
|
+
if (bits === 8) return bytes[idx]; // allow:raw-byte-literal — 8-bit fast path
|
|
196
|
+
var bitOffset = idx * bits;
|
|
197
|
+
var byteIdx = Math.floor(bitOffset / 8); // allow:raw-byte-literal — bits-per-byte
|
|
198
|
+
var bitInByte = bitOffset % 8; // allow:raw-byte-literal — bits-per-byte
|
|
199
|
+
var mask = (1 << bits) - 1;
|
|
200
|
+
return (bytes[byteIdx] >> bitInByte) & mask;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ---- JWT verification ----
|
|
204
|
+
|
|
205
|
+
async function fromJwt(token, opts) {
|
|
206
|
+
validateOpts.requireObject(opts, "statusList.fromJwt", StatusListError);
|
|
207
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
208
|
+
throw new StatusListError("status-list/bad-token",
|
|
209
|
+
"statusList.fromJwt: token must be a non-empty string");
|
|
210
|
+
}
|
|
211
|
+
// Verify the JWT signature using the framework's b.auth.jwt verifier.
|
|
212
|
+
// Allow operator-supplied algorithms (defaults to PQC list).
|
|
213
|
+
var claims = await jwt.verify(token, {
|
|
214
|
+
publicKey: opts.publicKey,
|
|
215
|
+
keyResolver: opts.keyResolver,
|
|
216
|
+
algorithms: opts.algorithms,
|
|
217
|
+
issuer: opts.expectedIssuer,
|
|
218
|
+
audience: opts.expectedAudience,
|
|
219
|
+
clockToleranceSec: opts.clockToleranceSec,
|
|
220
|
+
now: opts.now,
|
|
221
|
+
});
|
|
222
|
+
var sl = claims.status_list;
|
|
223
|
+
if (!sl || typeof sl !== "object" || typeof sl.lst !== "string") {
|
|
224
|
+
throw new StatusListError("status-list/bad-claims",
|
|
225
|
+
"statusList.fromJwt: payload missing status_list.lst (draft §6.1)");
|
|
226
|
+
}
|
|
227
|
+
var bits = sl.bits === undefined ? 1 : sl.bits;
|
|
228
|
+
_validateBits(bits);
|
|
229
|
+
var deflated;
|
|
230
|
+
try { deflated = _fromB64url(sl.lst); }
|
|
231
|
+
catch (e) {
|
|
232
|
+
throw new StatusListError("status-list/bad-base64",
|
|
233
|
+
"statusList.fromJwt: lst is not valid base64url: " + ((e && e.message) || String(e)));
|
|
234
|
+
}
|
|
235
|
+
if (deflated.length > MAX_LIST_BYTES) {
|
|
236
|
+
throw new StatusListError("status-list/too-large",
|
|
237
|
+
"statusList.fromJwt: compressed list exceeds " + MAX_LIST_BYTES + " bytes");
|
|
238
|
+
}
|
|
239
|
+
var inflated;
|
|
240
|
+
try { inflated = zlib.inflateRawSync(deflated, { maxOutputLength: MAX_LIST_BYTES * 8 }); } // allow:raw-byte-literal — 8x compression-ratio cap
|
|
241
|
+
catch (e) {
|
|
242
|
+
throw new StatusListError("status-list/inflate-failed",
|
|
243
|
+
"statusList.fromJwt: zlib inflate failed: " + ((e && e.message) || String(e)));
|
|
244
|
+
}
|
|
245
|
+
// Reconstruct the list object pointing at the inflated bytes.
|
|
246
|
+
var size = (inflated.length * 8) / bits; // allow:raw-byte-literal — bits-per-byte
|
|
247
|
+
return {
|
|
248
|
+
list: {
|
|
249
|
+
size: size,
|
|
250
|
+
bits: bits,
|
|
251
|
+
get: function (idx) { return _getAt(inflated, bits, idx); },
|
|
252
|
+
snapshot: function () { return { size: size, bits: bits, bytes: Buffer.from(inflated) }; },
|
|
253
|
+
},
|
|
254
|
+
claims: claims,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Provide structured-error helpers so a tree-shake-friendly consumer
|
|
259
|
+
// can write switch(status) { case b.auth.statusList.STATUS_VALID: ... }.
|
|
260
|
+
void safeJson; // imported for symmetry; reserved for future helpers
|
|
261
|
+
void nodeCrypto;
|
|
262
|
+
|
|
263
|
+
module.exports = {
|
|
264
|
+
create: create,
|
|
265
|
+
fromJwt: fromJwt,
|
|
266
|
+
STATUS_VALID: STATUS_VALID,
|
|
267
|
+
STATUS_INVALID: STATUS_INVALID,
|
|
268
|
+
STATUS_SUSPENDED: STATUS_SUSPENDED,
|
|
269
|
+
STATUS_APPLICATION_SPECIFIC: STATUS_APPLICATION_SPECIFIC,
|
|
270
|
+
StatusListError: StatusListError,
|
|
271
|
+
};
|
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:c3167b3e-7af4-4368-b131-46f9d9c44ccc",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-06T06:
|
|
8
|
+
"timestamp": "2026-05-06T06:22:16.402Z",
|
|
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.7.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.7.85",
|
|
23
23
|
"type": "library",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.7.
|
|
25
|
+
"version": "0.7.85",
|
|
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.7.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.7.85",
|
|
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.7.
|
|
57
|
+
"ref": "@blamejs/core@0.7.85",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|