Skip to content

node:util: give debuglog() .enabled, .name, and invoke the optional callback - #34606

Closed
robobun wants to merge 6 commits into
mainfrom
claude/farm/0dacfef6/util-debuglog-enabled
Closed

robobun wants to merge 6 commits into
mainfrom
claude/farm/0dacfef6/util-debuglog-enabled

Conversation

@robobun

@robobun robobun commented Jul 18, 2026 •

Copy link
Copy Markdown
Collaborator

util.debuglog() emitted byte-identical output to Node.js but returned a bare closure: .enabled was undefined, the optional callback was never invoked, and .name was "".

Repro

// NODE_DEBUG=utailx
import util from "node:util";
const dbg = util.debuglog("utailx");
console.log(typeof dbg.enabled);   // node: "boolean"   bun: "undefined"
console.log(dbg.name);             // node: "logger"    bun: ""
let cb = "never";
util.debuglog("utailx", fn => { cb = typeof fn; })("msg");
console.log(cb);                   // node: "function"  bun: "never"

.enabled is the documented gate for skipping expensive debug-only work; undefined is falsy, so guarded diagnostics silently never run under Bun even with NODE_DEBUG set. The callback is the documented hook for caching the optimized logger.

Fix

Mirror lib/internal/util/debuglog.js in src/js/node/util.ts:

  • Return a logger function (.name === "logger") with an enabled accessor (configurable, enumerable) driven by the existing NODE_DEBUG regex.
  • Resolve the underlying impl lazily on the first call and, if a callback was supplied, hand it the resolved function once (.name === "debug", own .enabled accessor). The callback fires for disabled sections too, matching Node.
  • emitWarningIfNeeded and the "%s %d: %s" stderr line are unchanged.

Function names are set via Object.defineProperty because the builtin bundler renames local bindings.

Verification

New util.debuglog cases in test/js/node/util/util.test.js spawn a child with and without NODE_DEBUG and assert .name, .enabled, the callback contract, the enabled descriptor shape, util.debug === util.debuglog, and the stderr output.

  • USE_SYSTEM_BUN=1 bun test test/js/node/util/util.test.js -t debuglog: 2 fail
  • bun bd test test/js/node/util/util.test.js: 195 pass

[review] gate passed · iteration 1 · 2 files touched

fails on main (without fix)
ASAN without fix: 1 FAILED
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/util/util.test.js
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (fef1cd2d9)

test/js/node/util/util.test.js:
(pass) util > toUSVString [4.76ms]
(pass) util > inherits [5.49ms]
(pass) util > isArray > all cases [7.60ms]
(pass) util > isRegExp > all cases [4.39ms]
(pass) util > isDate > all cases [5.70ms]
(pass) util > isError > all cases [16.68ms]
(pass) util > isObject > all cases [5.85ms]
(pass) util > isPrimitive > all cases [9.41ms]
(pass) util > isBuffer > all cases [3.75ms]
(pass) util > _extend > all cases [10.27ms]
(pass) util > isBoolean > all cases [2.76ms]
(pass) util > isNull > all cases [3.42ms]
(pass) util > isUndefined > all cases [3.01ms]
(pass) util > isNullOrUndefined > all cases [4.11ms]
(pass) util > isNumber > all cases [2.73ms]
(pass) util > isString > all cases [2.78ms]
(pass) 
... (truncated)

release without fix: 1 FAILED
bun test v1.4.0-canary.1 (ee2310568)

test/js/node/util/util.test.js:
(pass) util > toUSVString [0.13ms]
(pass) util > inherits [0.12ms]
(pass) util > isArray > all cases [0.12ms]
(pass) util > isRegExp > all cases [0.06ms]
(pass) util > isDate > all cases [0.13ms]
(pass) util > isError > all cases [0.22ms]
(pass) util > isObject > all cases [0.05ms]
(pass) util > isPrimitive > all cases [0.13ms]
(pass) util > isBuffer > all cases [0.05ms]
(pass) util > _extend > all cases [0.11ms]
(pass) util > isBoolean > all cases [0.03ms]
(pass) util > isNull > all cases [0.03ms]
(pass) util > isUndefined > all cases [0.03ms]
(pass) util > isNullOrUndefined > all cases [0.02ms]
(pass) util > isNumber > all cases [0.02ms]
(pass) util > isString > all cases [0.02ms]
(pass) util > isSymbol > all cases [0.02ms]
(pass) util > isFunction > all cases [0.03ms]
(pass) util > types.isNativeError > all cases [0.06ms]
(pass) util > TextEncoder > is same as global TextEncoder [0.01ms]
(pass) util > TextDecoder > is same as global TextDecoder [0.01ms]
(pass) util > format [0.23ms]
(pass) util > formatWithOptions [1.64ms]
(pass) util > multiplecolors [0.09ms]
(pass) util > styleText [0.68ms]
(
... (truncated)
passes on PR (with fix)
ASAN with fix: all passed
$ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test "--reporter=junit" "--reporter-outfile=/tmp/mechgate.xml" test/js/node/util/util.test.js
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
bun test v1.4.0 (fef1cd2d9)

test/js/node/util/util.test.js:
(pass) util > toUSVString [5.29ms]
(pass) util > inherits [5.06ms]
(pass) util > isArray > all cases [7.62ms]
(pass) util > isRegExp > all cases [4.43ms]
(pass) util > isDate > all cases [4.95ms]
(pass) util > isError > all cases [17.30ms]
(pass) util > isObject > all cases [4.27ms]
(pass) util > isPrimitive > all cases [8.91ms]
(pass) util > isBuffer > all cases [4.05ms]
(pass) util > _extend > all cases [8.71ms]
(pass) util > isBoolean > all cases [2.72ms]
(pass) util > isNull > all cases [3.07ms]
(pass) util > isUndefined > all cases [3.05ms]
(pass) util > isNullOrUndefined > all cases [3.04ms]
(pass) util > isNumber > all cases [2.67ms]
(pass) util > isString > all cases [2.70ms]
(pass) u
... (truncated)

release with fix: all passed
$ bun scripts/build.ts --profile=release
info: syncing channel updates for nightly-2026-05-06-x86_64-unknown-linux-gnu
info: latest update on 2026-05-06 for version 1.97.0-nightly (e95e73209 2026-05-05)
info: component rust-src is up to date
info: checking for self-update (current version: 1.29.0)
[configured] bun-profile → bun (stripped) in 832ms (unchanged)
ninja: Entering directory `/workspace/bun/build/release'
[1/20] gen JS modules (bundle-modules)
Preprocess modules (8236ms)
Bundle modules (77ms)
Postprocesss modules (19ms)
Bundle Functions (613ms)
Generate Code (92ms)

[9.05s] Bundled "src/js" for production
  2037 kb
  165 internal modules
  13 native modules
  90 internal functions across 19 files
[build] done
bun test v1.4.0-canary.1 (fef1cd2d9)

test/js/node/util/util.test.js:
(pass) util > toUSVString [0.11ms]
(pass) util > inherits [0.12ms]
(pass) util > isArray > all cases [0.12ms]
(pass) util > isRegExp > all cases [0.06ms]
(pass) util > isDate > all cases [0.13ms]
(pass) util > isError > all cases [0.26ms]
(pass) util > isObject > all cases [0.07ms]
(pass) util > isPrimitive > all cases [0.12ms]
(pass) util > isBuffer > all cases [0.04ms]
(pass) ut
... (truncated)
diff hotspot
src/js/node/util.ts            | 62 +++++++++++++++++++++++++-----
 test/js/node/util/util.test.js | 86 +++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 138 insertions(+), 10 deletions(-)

gate history · 1 passed · 1 rejected · iteration 1

evidence per changed file
file                            reads  edits  tests
src/js/node/util.ts                 6      9      0
test/js/node/util/util.test.js      3      7      0

…allback

The returned function was a bare closure: .enabled was undefined (so the
documented "if (log.enabled) expensive()" gate never ran), the optional
second argument was ignored, and .name was "".

Match the Node.js lib/internal/util/debuglog.js surface: return a
"logger" function with an "enabled" accessor backed by the same
NODE_DEBUG test, resolve the underlying impl lazily on first call, and
pass it to the optional callback once (with its own .enabled and
.name === "debug"). Output bytes are unchanged.
@robobun

robobun commented Jul 18, 2026 •

Copy link
Copy Markdown
Collaborator Author

Status: diff is green; CI red on unrelated lanes.

Reproduced with NODE_DEBUG=utailx bun -e 'const d=require("util").debuglog("utailx");console.log(typeof d.enabled,d.name)' printing undefined "" on 1.4.0; after this change it prints boolean logger and the callback form receives the resolved logger once.

test/js/node/util/util.test.js (194 tests, includes the new util.debuglog case) and test/js/node/test/parallel/test-http-debug.js pass on every lane. Remaining CI failures on build 75429 are unrelated to this diff and are being tracked separately:

  • test/cli/install/migration/complex-workspace.test.ts (x64-asan): setup bun install failed, cascading 21 errors.
  • test/js/node/net/node-net.test.ts (ubuntu x64-baseline): mimalloc page-count threshold 11 vs <10; same test is flaky-green on debian x64.
  • test/js/node/test/parallel/test-net-connect-memleak.js (alpine x64/x64-baseline): GC-timing assertion.
  • test/js/node/test/parallel/test-worker-message-port-transfer-terminate.js (x64-asan): pre-existing intermittent JSC !scope.exception() SIGABRT.
  • bun-server / webview-chrome / in-process-cron / test-gc-http-client-timeout: flaky, passed on retry.

Ready for a maintainer to merge.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

The debuglog implementation now accepts an optional callback, lazily resolves enabled logging behavior, exposes .enabled and .name, caches warnings, and reuses disabled implementations. Integration tests cover enabled and disabled subprocess execution.

debuglog behavior

Layer / File(s) Summary
Implement lazy debuglog behavior
src/js/node/util.ts
debuglogImpl memoizes logging implementations, while debuglog(set, cb) adds callback initialization, enabled getters, warning caching, and lazy resolution.
Validate debuglog subprocess behavior
test/js/node/util/util.test.js
Subprocess tests verify debug output, logger metadata, callback state, disabled behavior, and exit codes.

Suggested reviewers: cirospaciari

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main util.debuglog change and its new callback and properties.
Description check ✅ Passed The description covers what changed and how it was verified, though it uses custom headings instead of the template sections.

Comment @coderabbitai help to get the list of available commands.

@robobun

robobun commented Jul 18, 2026 •

Copy link
Copy Markdown
Collaborator Author
Updated 8:40 AM PT - Jul 18th, 2026

❌ @robobun, your commit fef1cd2 has 4 failures in Build #75429 (All Failures):


🧪   To try this PR locally:

bunx bun-pr 34606

That installs a local version of the PR into your bun-34606 executable, so you can run:

bun-34606 --bun

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beyond the inline nit: making emitWarningIfNeeded lazy breaks src/js/node/http.ts:14, which calls require("node:util").debuglog("http") purely for the side-effect warning and discards the result. With this change the warning never fires, so test/js/node/test/parallel/test-http-debug.js will fail. That call site (and any similar ones) needs updating in the same PR.

Also checked: the if (enabled === false) short-circuit in logger when .enabled is read before the first call on a disabled section — verifiers concluded the callback-skip there is acceptable.

Extended reasoning...

The new debuglog defers debuglogImpl (and therefore emitWarningIfNeeded) until the returned logger is actually invoked, matching Node's lazy shape. But Bun's src/js/node/http.ts relies on the old eager behavior: it calls debuglog("http") at module load and throws away the return value, solely to trigger the "can expose sensitive data" process warning under NODE_DEBUG=http. After this PR that line becomes a no-op, and the existing Node-parallel test test-http-debug.js (which asserts the warning appears in stderr) will regress. This is a same-class sibling site that should be fixed alongside the util.ts change.

Comment thread test/js/node/util/util.test.js Outdated
…till passes

The sensitive-data warning now fires in debuglog() itself (once per set),
not inside the lazily-resolved impl. src/js/node/http.ts creates
debuglog("http") at load time and discards it; with the warning deferred
to the first call it never fired.

Also guard the disabled-logger short-circuit so the optional callback is
still invoked once, and filter stderr for debuglog-prefixed lines instead
of asserting it is empty.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — focused Node compat fix for util.debuglog(); the earlier stderr-empty nit was addressed in 3bca31f.

What was reviewed:

  • Traced the new lazy-resolve/logger wrapper against Node's lib/internal/util/debuglog.js — .enabled accessor, .name, and one-shot callback semantics match for both enabled and disabled sections.
  • Checked the shared noop for cross-section contamination: only disabled sections reach it, so redefining its .enabled getter (always false, configurable) is harmless.
  • Confirmed the eager emitWarningIfNeeded (deduped via warnedSets) keeps test/js/node/test/parallel/test-http-debug.js passing given src/js/node/http.ts discards the returned logger.
  • Tests spawn with bunEnv, drain both pipes, filter stderr by section prefix, and assert exit code last.
Extended reasoning...

Overview

This PR reworks util.debuglog() in src/js/node/util.ts (~50 lines) to match Node.js's lib/internal/util/debuglog.js: the returned function now has .name === "logger" and an .enabled accessor, and the optional second-argument callback is invoked once on first call with the resolved impl (.name === "debug", own .enabled). The stderr output format and NODE_DEBUG regex are unchanged. Two new subprocess tests in test/js/node/util/util.test.js cover the enabled and disabled paths.

Security risks

None. This is a pure-JS builtin-module change to a diagnostics helper. No untrusted-input parsing was added; set.toUpperCase() and debugEnvRegex.test are unchanged from before. The format.$apply thisArg changed from cjs_exports to undefined, which is inert since format doesn't use this.

Level of scrutiny

Medium — it rewrites a small builtin function rather than tweaking a line, so I traced the closure/reassignment flow carefully. The resolved && !enabled early-return short-circuits repeat noop calls; the debug self-reassignment on first call correctly swaps the initializer for the cached impl on subsequent enabled calls. The shared noop is only ever reached by disabled sections, so repeatedly defining a configurable .enabled getter returning false on it is safe. The only in-tree caller (src/js/node/http.ts:14) discards the return value, so the change from returning a cached function to a fresh logger wrapper per call has no internal fallout.

Other factors

The one deliberate deviation from Node — emitting the HTTP/HTTP2 warning eagerly at debuglog() time rather than on first log — is called out in the commit title and is required because Bun's http.ts never actually logs through the returned function; test-http-debug.js would otherwise regress. My earlier review comment about expect(stderr).toBe("") was addressed: the second test now filters for section-prefixed lines like the first. Tests follow harness conventions (bunEnv spread, concurrent pipe drain, exit-code asserted last) and the PR description reports USE_SYSTEM_BUN=1 fails / bun bd passes.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — focused Node compat fix; the earlier stderr-empty nit is addressed.

What was reviewed:

  • debuglog rewrite against Node's lib/internal/util/debuglog.js shape: .enabled accessor, .name, lazy impl resolution, callback fired once for both enabled and disabled sections.
  • emitWarningIfNeeded timing: still eager (matches pre-PR behavior via warnedSets dedup), so test-http-debug.js is unaffected.
  • Shared noop for disabled sections getting .enabled defined on it — all closures capture enabled=false, so redefines are idempotent.
  • Test subprocess pattern: pipes drained concurrently, stderr filtered for debuglog-prefixed lines only, exit code asserted last.
Extended reasoning...

Overview

Rewrites util.debuglog() in src/js/node/util.ts (~50 lines) to match Node's documented contract: the returned function now has .name === "logger" and an .enabled accessor, and the optional second-argument callback is invoked once on first call with the resolved impl (.name === "debug", own .enabled). Adds two subprocess tests in test/js/node/util/util.test.js covering enabled/disabled sections, callback contract, descriptor shape, and stderr output.

Security risks

None. This is a debug-logging helper gated on NODE_DEBUG; no auth, crypto, filesystem, or network surface. The existing emitWarningIfNeeded sensitive-data warning for HTTP/HTTP2 is preserved and still fires eagerly (deduplicated via a module-level warnedSets Set, replacing the old !debugs[set] guard that served the same purpose).

Level of scrutiny

Low-to-medium. Built-in JS module code, but the change is a straight port of Node's lib/internal/util/debuglog.js structure onto Bun's existing debugEnvRegex/format/console.error plumbing. The "%s %d: %s" output line is byte-identical to before. The only behavior-adjacent choice — keeping the HTTP warning eager rather than lazy like upstream Node — preserves Bun's pre-PR behavior and the existing test-http-debug.js assertion, which is the right call for a compat-fill PR.

Other factors

  • I checked the shared module-level noop used for all disabled sections: when a callback is supplied, .enabled is defineProperty'd onto it with a getter closing over enabled. Since noop is only reached when enabled === false, every such redefine installs an equivalent getter — no cross-section leakage. Node similarly shares a single noop for disabled sections.
  • format.$apply(cjs_exports, …) → format.$apply(undefined, …) is a no-op (format ignores this).
  • The prior inline review comment about expect(stderr).toBe("") was addressed in 3bca31f — the disabled-section test now filters stderr for DBGSECT|NOTENABLED|ALSONOT-prefixed lines before asserting emptiness.
  • Tests follow harness conventions (bunEnv spread, await using, concurrent pipe drain, exit code last) and the PR description confirms USE_SYSTEM_BUN=1 fails / bun bd passes.

…ently

ASAN-debug child startup is slow enough that two sequential 5s-default
tests timed out under gate load. Run both NODE_DEBUG variants in one
test with a 20s timeout; assertions are unchanged.

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — the earlier stderr-empty concern was addressed in ad5d75a, and the remaining inline note is just the 20_000 test-timeout nit. The debuglog rewrite mirrors Node's lib/internal/util/debuglog.js closely and the sole internal caller (src/js/node/http.ts:14, which relies on the eager emitWarningIfNeeded) is unchanged in behavior.

Extended reasoning...

Overview

This PR rewrites util.debuglog() in src/js/node/util.ts to return a logger function with a working .enabled accessor and .name === "logger", and to invoke the optional second-argument callback once with the resolved impl (.name === "debug", own .enabled). It adds a subprocess-based test in test/js/node/util/util.test.js covering both the NODE_DEBUG=dbgsect and NODE_DEBUG="" paths, callback semantics, descriptor shape, and the util.debug === util.debuglog alias.

Security risks

None. Pure builtin-JS change; no native code, no new I/O surface, no user-controlled parsing beyond the pre-existing NODE_DEBUG regex which is unchanged.

Level of scrutiny

Medium — a ~50-line Node-compat rewrite of a low-risk diagnostic helper. I traced the closure/lazy-resolve flow: first call reassigns debug to the cached impl (or shared noop), fires the callback once, then applies; subsequent calls hit the resolved impl directly (or short-circuit when disabled). The shared noop and cached debugs[set] having .enabled redefined by multiple callbacks is safe because the property is configurable and enabled is deterministic per set. emitWarningIfNeeded remains eager (as it was before this PR), so src/js/node/http.ts's bare debuglog("http") call and test-http-debug.js are unaffected. Spot-checked existing Node parallel-test consumers (test-net-throttle.js, test-child-process-fork.js) — they use the plain const debug = util.debuglog('test'); debug(...) pattern, which the new logger wrapper handles identically.

Other factors

My prior review's expect(stderr).toBe("") concern was addressed — both subprocess results now filter stderr for debuglog-prefixed lines via the shared run() helper. The one remaining finding this run is the explicit 20_000 per-test timeout, which test/CLAUDE.md says not to set; it's a nit with no correctness impact. Test coverage is thorough (enabled + disabled, callback fires once, .name/.enabled on both the logger and the callback arg, descriptor shape, alias), pipes are drained concurrently, and bunEnv is spread when overriding NODE_DEBUG.

Comment thread test/js/node/util/util.test.js
Comment thread src/js/node/util.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM — all three prior review rounds are addressed and the current revision traces correctly against Node's lib/internal/util/debuglog.js.

What was reviewed:

  • .enabled accessor, .name, and callback-once semantics for both enabled and disabled sections; the shared noop redefinition is safe (configurable: true, always false).
  • emitWarningIfNeeded still fires eagerly at debuglog() time (pre-existing Bun behavior), so src/js/node/http.ts:14 and test-http-debug.js are unaffected.
  • Test spawns drain stdout/stderr/exited concurrently, filter stderr for debuglog-prefixed lines only, and the timeout override is justified with a comment per the ASAN gate failure.
Extended reasoning...

Overview

Rewrites util.debuglog(set, cb) in src/js/node/util.ts to mirror Node's lib/internal/util/debuglog.js: returns a logger closure with a .name === "logger" and an .enabled accessor, lazily resolves the underlying impl on first call, and invokes the optional callback once with the resolved function (.name "debug" when enabled, "noop" when disabled). Adds a subprocess test in test/js/node/util/util.test.js covering both NODE_DEBUG states.

Security risks

None. debuglog is a diagnostic helper that writes formatted strings to stderr; no new external input is parsed, no auth/crypto/permissions surface is touched. The emitWarningIfNeeded path (HTTP/HTTP2 sensitive-data warning) is preserved with identical timing to the previous implementation.

Level of scrutiny

Moderate. This is a built-in JS module (src/js/), which REVIEW.md flags as hot-path code, but debuglog() itself is called once per section at module-load time and the returned logger short-circuits for disabled sections after the first call — matching Node's own allocation profile. The change is a focused Node-compat fix (documented .enabled was missing), not a redesign.

Other factors

This PR has been through three prior review iterations from me (stderr-empty assertion → filtered; per-test timeout → kept with justifying comment after an ASAN gate failure; disabled-path .name "debug" → "noop"), all resolved in the current head fef1cd2. I re-traced the lazy-resolve logic: the resolved && !enabled fast path is correct, debug is reassigned to the cached impl on first call so subsequent enabled calls go straight through, and redefining .enabled on the shared noop cannot throw because the descriptor is configurable: true and every disabled section closes over enabled === false. The only internal caller (src/js/node/http.ts:14) relies solely on the eager-warning side effect, which is preserved via the new warnedSets guard. Test coverage asserts the full contract (names, enabled flag, callback count, descriptor shape, util.debug alias, stderr output) for both NODE_DEBUG variants, and the bug-hunting pass found nothing new.

@robobun

robobun commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator Author

Superseded by #40353 from a community contributor. That PR is current with main and implements the same fix: the enabled getter and the callback path, with the logger name already matching Node. The one piece left from this branch, the debug and noop names on the function passed to the callback, is suggested on #40353 as a review comment. Closing this one.

@robobun robobun closed this Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant