@blamejs/core 0.11.40 → 0.11.41
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/lib/calendar.js +169 -5
- package/package.json +1 -1
- package/sbom.cdx.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.11.x
|
|
10
10
|
|
|
11
|
+
- v0.11.41 (2026-05-21) — **`b.calendar.expandRecurrence` picks up BYSETPOS (RFC 5545 §3.3.10).** Closes the v0.11.31 deferral on BYSETPOS — the recurrence filter that picks the Nth candidate from a BY*-filtered set within a FREQ interval. Common operator patterns now work directly: `FREQ=MONTHLY;BYDAY=FR;BYSETPOS=-1` for last Friday of each month, `FREQ=MONTHLY;BYDAY=TU;BYSETPOS=2` for second Tuesday, `FREQ=YEARLY;BYMONTH=10;BYDAY=SU;BYSETPOS=1` for first Sunday of October (the DST-end announcement pattern). Supported for `FREQ=MONTHLY` / `YEARLY` / `WEEKLY` at day-granularity (time-of-day inherited from `start`). `FREQ=DAILY` + BYSETPOS is refused with `calendar/bad-recurrence` since the semantics aren't meaningful at sub-day frequency. **Added:** *`bySetPos` filter on RecurrenceRule* — `recurrenceRules[i].bySetPos: [1, -1, 2]` picks the listed positions from the BY*-filtered candidate set within each FREQ interval. Positive values are 1-indexed from the start of the period; negative values count from the end. Multiple positions emit per period (e.g. `[1, -1]` emits both the first and last matching day of each month). Out-of-range positions silently drop per RFC 5545's tolerant grammar. · *MONTHLY / YEARLY / WEEKLY support* — BYSETPOS expands within month / year / WKST-aligned-week boundaries. For each period, all day-level candidates that pass the existing BY* filters (byDay / byMonth / byMonthDay / byWeekNo / byYearDay) are enumerated, sorted ascending, then indexed by `bySetPos`. The expand loop's step budget (`MAX_EXPAND_INSTANCES * 366`) is shared across periods so the BYSETPOS path can't outrun the v0.11.31 DoS bound. · *DAILY frequency refused* — `FREQ=DAILY` + BYSETPOS throws `calendar/bad-recurrence` at expand time. The combination has no meaningful per-period set semantics (a day has no sub-day BY* set to pick from in the v1 day-granularity model). Operators with a sub-day BYSETPOS use case use `FREQ=HOURLY` semantics directly without BYSETPOS, or open an issue with the use case. **Security:** *Step budget shared with non-BYSETPOS path* — BYSETPOS enumeration decrements the same `MAX_EXPAND_INSTANCES * 366` step budget the non-BYSETPOS path uses, and the per-period day-loop has a hard 400-iteration safety cap (covers max-366-days in a leap year + slack). Adversarial events combining many rules + sparse BY* filters + BYSETPOS can't outpace the original single-rule DoS bound. **References:** [RFC 5545 §3.3.10 (RRULE — BYSETPOS)](https://www.rfc-editor.org/rfc/rfc5545.html#section-3.3.10) · [RFC 8984 §4.3.2 (JSCalendar RecurrenceRule)](https://www.rfc-editor.org/rfc/rfc8984.html#section-4.3.2)
|
|
12
|
+
|
|
11
13
|
- v0.11.40 (2026-05-21) — **Wiki Docker default port moves from 8080 → 3008.** The `examples/wiki` Docker stack now defaults to port 3008 throughout — `WIKI_PORT`, the Dockerfile `EXPOSE` directive, the in-container HEALTHCHECK URL, the dev `docker-compose.yml` host mapping, the Caddy reverse-proxy upstream, and the `server.js` + `lib/build-app.js` code defaults. Production deployments (`docker-compose.prod.yml`) are operator-invisible since Caddy fronts the container on 80/443 — the upstream port is never exposed to the host. Dev users hitting the wiki container directly now use `http://localhost:3008`. Operators who set `WIKI_PORT` explicitly in their `.env` keep working unchanged. **Changed:** *Wiki default port 8080 → 3008* — Default `WIKI_PORT` moves from 8080 (HTTP-alt, frequently collides with Tomcat / Jenkins / Confluence / Spring-Boot defaults) to 3008 (IANA-unassigned, no service-convention collision). The new default applies consistently across: `examples/wiki/Dockerfile` (`ENV WIKI_PORT`, `EXPOSE`, HEALTHCHECK URL), `examples/wiki/server.js`, `examples/wiki/lib/build-app.js`, `examples/wiki/docker-compose.yml` (host mapping `3008:3008`), `examples/wiki/docker-compose.prod.yml` (internal-network expose), `examples/wiki/Caddyfile` (upstream resolver), `examples/wiki/README.md`, `examples/wiki/DEPLOY.md`. · *Operator action* — Dev users with `localhost:8080` bookmarks, curl scripts, host-firewall allowlists, or IDE port forwarders pointed at the wiki need to switch to `localhost:3008`. Operators who set `WIKI_PORT=8080` explicitly in their `.env` keep working unchanged. Production deployments behind Caddy see no change — the upstream port is internal-network only. **References:** [IANA Service Name and Port Number Registry](https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml)
|
|
12
14
|
|
|
13
15
|
- v0.11.39 (2026-05-21) — **`b.calendar.expandRecurrence` honors multiple `recurrenceRules` (RFC 8984 §4.3.2).** Closes the v0.11.31 deferral on the multi-rule path. `expandRecurrence` now walks every entry in `event.recurrenceRules`, expands each rule independently against the same `start` anchor, and UNIONs the resulting instances (dedup + sorted ascending). Per-rule `count` caps apply per-rule per RFC 8984 §4.3.2; the global `max` / `MAX_EXPAND_INSTANCES` cap applies to the unioned set. The step budget (`MAX_EXPAND_INSTANCES * 366`) is shared across all rules in the same expand call so an N-rule fan-out can't amplify the worst-case loop past the single-rule bound. **Added:** *Multi-rule expansion + UNION* — `event.recurrenceRules` of length > 1 now expands every rule, not just the first. Each rule's stepping (frequency / interval / count / until / BY* filters) operates independently; the resulting ISO 8601 UTC instant strings dedupe via object-keyed set semantics, then sort ascending. The single-rule case is structurally unchanged — same step loop, same per-rule cap behaviour. · *Per-rule `count` applies per-rule (RFC 8984 §4.3.2)* — Two rules with `count: 3` and `count: 2` produce up to 5 instances in the unioned output (minus any timestamps that dedupe across rules), not 5 instances total across the rules. Matches RFC 8984 §4.3.2 phrasing: each Recurrence Rule's count is applied per rule. · *Global step budget shared across rules* — The `MAX_EXPAND_INSTANCES * 366` step budget (introduced in v0.11.31) now tracks across rules in the same expand call. A 4-rule event with sparse BY* filters can't accumulate 4× the single-rule worst-case work; the budget is depleted across the full rule set. **Security:** *DoS amplification across N rules bounded by the same step budget* — The step budget is shared (not per-rule), so adversarial multi-rule events can't bypass the v0.11.31 BY*-filter cap by stacking N rules with sparse filters. Sparse-filter rules still complete within the original 10-year `MAX_EXPAND_SPAN_MS` window cap. **Detectors:** *No new detector — covered by existing recurrence-test surface* — Multi-rule UNION + dedup + global step budget are exercised by three new tests in `test/layer-0-primitives/calendar.test.js` (multi-rule union, same-rule dedup, global max applied to union). No detector needed — the bug class is testable end-to-end and the test file IS the canonical regression surface. **References:** [RFC 8984 §4.3.2 (JSCalendar RecurrenceRule — multi-rule expansion)](https://www.rfc-editor.org/rfc/rfc8984.html#section-4.3.2) · [RFC 5545 §3.8.5.3 (iCalendar RRULE — semantics under composition)](https://www.rfc-editor.org/rfc/rfc5545.html#section-3.8.5.3)
|
package/lib/calendar.js
CHANGED
|
@@ -425,13 +425,14 @@ function toIcal(jsCal, opts) {
|
|
|
425
425
|
*
|
|
426
426
|
* v1 supports FREQ=DAILY/WEEKLY/MONTHLY/YEARLY with INTERVAL, COUNT,
|
|
427
427
|
* UNTIL. BYDAY / BYMONTH / BYMONTHDAY / BYWEEKNO / BYYEARDAY /
|
|
428
|
-
* BYHOUR / BYMINUTE / BYSECOND refine the base frequency.
|
|
428
|
+
* BYHOUR / BYMINUTE / BYSECOND refine the base frequency. BYSETPOS
|
|
429
|
+
* picks the Nth candidate from the BY*-filtered set within a FREQ
|
|
430
|
+
* interval (positive = 1-indexed from start, negative = from end);
|
|
431
|
+
* supported for FREQ=MONTHLY / YEARLY / WEEKLY with day-granularity
|
|
432
|
+
* candidates (time-of-day inherited from start). Multiple
|
|
429
433
|
* `recurrenceRules` are expanded independently and UNIONed; per
|
|
430
434
|
* RFC 8984 §4.3.2 each rule's `count` cap applies per-rule, not to
|
|
431
|
-
* the combined set.
|
|
432
|
-
* (requires expanding ALL candidates within a FREQ interval and
|
|
433
|
-
* picking the Nth — a structural restructure of the step loop;
|
|
434
|
-
* RFC 7529 non-Gregorian calendars not in scope either).
|
|
435
|
+
* the combined set. (RFC 7529 non-Gregorian calendars not in scope.)
|
|
435
436
|
*
|
|
436
437
|
* @opts
|
|
437
438
|
* from: string, // ISO 8601 UTC timestamp — lower bound of expansion window
|
|
@@ -645,6 +646,36 @@ function _expandSingleRule(rule, startMs, ctx) {
|
|
|
645
646
|
if (bySecondSet && !bySecondSet[d.getUTCSeconds()]) return false;
|
|
646
647
|
return true;
|
|
647
648
|
}
|
|
649
|
+
// RFC 5545 §3.3.10 BYSETPOS — picks the Nth candidate from the
|
|
650
|
+
// BY*-filtered set within a FREQ interval. Positive = 1-indexed
|
|
651
|
+
// from start; negative = from end. Operators reach for this most
|
|
652
|
+
// often with "last Friday of month" (FREQ=MONTHLY;BYDAY=FR;
|
|
653
|
+
// BYSETPOS=-1) or "second Tuesday of month" (BYSETPOS=2).
|
|
654
|
+
//
|
|
655
|
+
// v1 supports BYSETPOS for FREQ=MONTHLY / YEARLY / WEEKLY at
|
|
656
|
+
// day-granularity — candidates are days within the period at the
|
|
657
|
+
// start's time-of-day. Sub-day BY* filters (byHour/byMinute/
|
|
658
|
+
// bySecond) are ignored under BYSETPOS for v1; the rare combo
|
|
659
|
+
// (BYSETPOS + byHour) reverts to the standard non-bysetpos step
|
|
660
|
+
// path when applicable.
|
|
661
|
+
var bySetPosArr = _bySetPosArray(rule.bySetPos);
|
|
662
|
+
if (bySetPosArr) {
|
|
663
|
+
return _expandWithBysetpos({
|
|
664
|
+
rule: rule,
|
|
665
|
+
startMs: startMs,
|
|
666
|
+
freq: freq,
|
|
667
|
+
interval: interval,
|
|
668
|
+
count: count,
|
|
669
|
+
untilMs: untilMs,
|
|
670
|
+
fromMs: fromMs,
|
|
671
|
+
toMs: toMs,
|
|
672
|
+
maxCount: maxCount,
|
|
673
|
+
matchesBy: _matchesBy,
|
|
674
|
+
bySetPos: bySetPosArr,
|
|
675
|
+
stepBudgetRef: ctx.stepBudgetRef,
|
|
676
|
+
});
|
|
677
|
+
}
|
|
678
|
+
|
|
648
679
|
var t = startMs;
|
|
649
680
|
// Safety cap on the step loop: at most MAX_EXPAND_INSTANCES * 366
|
|
650
681
|
// iterations so BY* filters that match sparsely (e.g. FREQ=DAILY;
|
|
@@ -670,6 +701,139 @@ function _expandSingleRule(rule, startMs, ctx) {
|
|
|
670
701
|
return { instances: out, stepBudgetRemaining: ctx.stepBudgetRef.remaining };
|
|
671
702
|
}
|
|
672
703
|
|
|
704
|
+
// Parse + validate rule.bySetPos. Returns null when absent / empty;
|
|
705
|
+
// otherwise an array of integers in [-366, -1] U [1, 366] (RFC 5545
|
|
706
|
+
// grammar). Zero values + out-of-range values are silently dropped.
|
|
707
|
+
function _bySetPosArray(raw) {
|
|
708
|
+
if (!Array.isArray(raw) || raw.length === 0) return null;
|
|
709
|
+
var out = [];
|
|
710
|
+
for (var i = 0; i < raw.length; i += 1) {
|
|
711
|
+
var n = parseInt(raw[i], 10);
|
|
712
|
+
if (isFinite(n) && n !== 0 && n >= -366 && n <= 366) out.push(n); // allow:raw-byte-literal — RFC 5545 §3.3.10 bysetpos range
|
|
713
|
+
}
|
|
714
|
+
return out.length > 0 ? out : null;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// BYSETPOS expander. Iterates by FREQ interval; for each period,
|
|
718
|
+
// enumerates day-level candidates within the period; applies the
|
|
719
|
+
// caller's matchesBy filter; sorts ascending; picks the position(s)
|
|
720
|
+
// per bySetPos. Time-of-day per candidate matches the rule's start.
|
|
721
|
+
function _expandWithBysetpos(ctx) {
|
|
722
|
+
var startMs = ctx.startMs;
|
|
723
|
+
var freq = ctx.freq;
|
|
724
|
+
var interval = ctx.interval;
|
|
725
|
+
var count = ctx.count;
|
|
726
|
+
var untilMs = ctx.untilMs;
|
|
727
|
+
var fromMs = ctx.fromMs;
|
|
728
|
+
var toMs = ctx.toMs;
|
|
729
|
+
var maxCount = ctx.maxCount;
|
|
730
|
+
var matchesBy = ctx.matchesBy;
|
|
731
|
+
var bySetPos = ctx.bySetPos;
|
|
732
|
+
var stepBudgetRef = ctx.stepBudgetRef;
|
|
733
|
+
|
|
734
|
+
if (freq !== "monthly" && freq !== "yearly" && freq !== "weekly") {
|
|
735
|
+
throw new CalendarError("calendar/bad-recurrence",
|
|
736
|
+
"b.calendar.expandRecurrence: BYSETPOS supported only with FREQ=MONTHLY / YEARLY / WEEKLY (got '" + freq + "')");
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
var startDate = new Date(startMs);
|
|
740
|
+
var hh = startDate.getUTCHours();
|
|
741
|
+
var mm = startDate.getUTCMinutes();
|
|
742
|
+
var ss = startDate.getUTCSeconds();
|
|
743
|
+
var ms = startDate.getUTCMilliseconds();
|
|
744
|
+
|
|
745
|
+
var out = [];
|
|
746
|
+
// Period anchor (period 0 = start's period).
|
|
747
|
+
var periodIndex = 0;
|
|
748
|
+
|
|
749
|
+
while (out.length < count && out.length < maxCount && stepBudgetRef.remaining > 0) {
|
|
750
|
+
var period = _periodForIndex(freq, startDate, periodIndex * interval);
|
|
751
|
+
periodIndex += 1;
|
|
752
|
+
// Out-of-window early exit. Window-uppper applies once the period
|
|
753
|
+
// start crosses toMs; until applies once period-start crosses untilMs.
|
|
754
|
+
if (period.startMs > untilMs) break;
|
|
755
|
+
if (toMs !== null && period.startMs > toMs) break;
|
|
756
|
+
|
|
757
|
+
// Enumerate day-level candidates within the period at start's
|
|
758
|
+
// time-of-day. The budget decrements per candidate so adversarial
|
|
759
|
+
// periods (e.g. YEARLY = 366 days) can't loop forever.
|
|
760
|
+
var candidates = [];
|
|
761
|
+
var dayMs = period.startMs;
|
|
762
|
+
var safety = 400; // allow:raw-byte-literal — period day cap (covers leap year 366 + slack)
|
|
763
|
+
while (dayMs <= period.endMs && safety-- > 0 && stepBudgetRef.remaining > 0) {
|
|
764
|
+
stepBudgetRef.remaining -= 1;
|
|
765
|
+
var candidate = _withTimeOfDay(dayMs, hh, mm, ss, ms);
|
|
766
|
+
if (matchesBy(candidate)) candidates.push(candidate);
|
|
767
|
+
dayMs += 86400000; // allow:raw-time-literal — 86400000 ms/day step // allow:raw-byte-literal — same constant in ms/day form
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// Sort + apply BYSETPOS. Positive index 1-based from start;
|
|
771
|
+
// negative from end. Out-of-range positions silently drop.
|
|
772
|
+
candidates.sort(function (a, b) { return a - b; });
|
|
773
|
+
var picked = Object.create(null);
|
|
774
|
+
for (var pi = 0; pi < bySetPos.length; pi += 1) {
|
|
775
|
+
var pos = bySetPos[pi];
|
|
776
|
+
var idx = pos > 0 ? pos - 1 : candidates.length + pos;
|
|
777
|
+
if (idx >= 0 && idx < candidates.length) picked[candidates[idx]] = true;
|
|
778
|
+
}
|
|
779
|
+
// Emit picked candidates in ascending order, gated by window +
|
|
780
|
+
// untilMs + per-rule count cap.
|
|
781
|
+
//
|
|
782
|
+
// Codex P1 — recurrence instances MUST NOT precede DTSTART (per
|
|
783
|
+
// RFC 5545 §3.8.5.3). The period-boundary enumeration above
|
|
784
|
+
// includes candidates BEFORE startMs when the period containing
|
|
785
|
+
// startMs has earlier BY*-matching days (e.g. start = May 20
|
|
786
|
+
// Friday, BYDAY=FR;BYSETPOS=1 → enumeration would pick May 1).
|
|
787
|
+
// Refusing pre-start candidates here both fixes the semantics
|
|
788
|
+
// AND avoids consuming the per-rule COUNT cap on instances the
|
|
789
|
+
// operator never asked for.
|
|
790
|
+
var pickedKeys = Object.keys(picked).map(Number).sort(function (a, b) { return a - b; });
|
|
791
|
+
for (var ki = 0; ki < pickedKeys.length; ki += 1) {
|
|
792
|
+
var pickedMs = pickedKeys[ki];
|
|
793
|
+
if (pickedMs < startMs) continue;
|
|
794
|
+
if (pickedMs > untilMs) { count = out.length; break; }
|
|
795
|
+
if (toMs !== null && pickedMs > toMs) { count = out.length; break; }
|
|
796
|
+
if (fromMs !== null && pickedMs < fromMs) continue;
|
|
797
|
+
if (out.length >= count || out.length >= maxCount) break;
|
|
798
|
+
out.push(_msToIsoZ(pickedMs));
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return { instances: out, stepBudgetRemaining: stepBudgetRef.remaining };
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Compute period [startMs, endMs] given a base start date + interval
|
|
805
|
+
// offset. Period START is anchored at the first second of the period
|
|
806
|
+
// (Jan 1 for YEARLY, day 1 for MONTHLY, WKST-Monday for WEEKLY) so
|
|
807
|
+
// the day-enumeration loop strides whole-day from there.
|
|
808
|
+
function _periodForIndex(freq, startDate, offset) {
|
|
809
|
+
if (freq === "yearly") {
|
|
810
|
+
var year = startDate.getUTCFullYear() + offset;
|
|
811
|
+
var ys = Date.UTC(year, 0, 1, 0, 0, 0, 0);
|
|
812
|
+
var ye = Date.UTC(year + 1, 0, 1, 0, 0, 0, 0) - 1;
|
|
813
|
+
return { startMs: ys, endMs: ye };
|
|
814
|
+
}
|
|
815
|
+
if (freq === "monthly") {
|
|
816
|
+
var bm = startDate.getUTCMonth() + offset;
|
|
817
|
+
var by = startDate.getUTCFullYear() + Math.floor(bm / 12); // allow:raw-byte-literal — months/year
|
|
818
|
+
var mm = ((bm % 12) + 12) % 12; // allow:raw-byte-literal — months/year
|
|
819
|
+
var ms = Date.UTC(by, mm, 1, 0, 0, 0, 0);
|
|
820
|
+
var me = Date.UTC(by, mm + 1, 1, 0, 0, 0, 0) - 1;
|
|
821
|
+
return { startMs: ms, endMs: me };
|
|
822
|
+
}
|
|
823
|
+
// weekly — align to WKST=Monday (RFC 5545 default WKST).
|
|
824
|
+
var anchor = new Date(Date.UTC(startDate.getUTCFullYear(), startDate.getUTCMonth(), startDate.getUTCDate(), 0, 0, 0, 0));
|
|
825
|
+
var dow = anchor.getUTCDay() || 7;
|
|
826
|
+
anchor.setUTCDate(anchor.getUTCDate() - (dow - 1) + offset * 7); // allow:raw-byte-literal — days/week
|
|
827
|
+
var ws = anchor.getTime();
|
|
828
|
+
var we = ws + 7 * 86400000 - 1; // allow:raw-byte-literal + allow:raw-time-literal — 7-day window
|
|
829
|
+
return { startMs: ws, endMs: we };
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
function _withTimeOfDay(dayMs, hh, mm, ss, ms) {
|
|
833
|
+
var d = new Date(dayMs);
|
|
834
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate(), hh, mm, ss, ms);
|
|
835
|
+
}
|
|
836
|
+
|
|
673
837
|
// ---- Internal helpers ----------------------------------------------------
|
|
674
838
|
|
|
675
839
|
function _veventToJsCalEvent(ve) {
|
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:5557c11a-5977-46f0-8a4f-c25e3b621e70",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-05-
|
|
8
|
+
"timestamp": "2026-05-22T01:26:53.795Z",
|
|
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.11.
|
|
22
|
+
"bom-ref": "@blamejs/core@0.11.41",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "blamejs",
|
|
25
|
-
"version": "0.11.
|
|
25
|
+
"version": "0.11.41",
|
|
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.11.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/core@0.11.41",
|
|
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.11.
|
|
57
|
+
"ref": "@blamejs/core@0.11.41",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|