Skip to content

[RFC]: Bringing the debug Dependency In-House #11081

@therealharshit

Description

@therealharshit

Bringing the debug Dependency In-House

Replacing the external debug npm package (v2.6.9) with stdlib-native equivalents to eliminate a supply-chain risk and align all production code with stdlib's quality standards (docs, tests, examples, benchmarks, backward compatibility).

Background

What debug v2.6.9 Does

A tiny JavaScript debugging utility modelled after Node.js core's debugging technique. Works in Node.js and web browsers.
More here: https://github.com/debug-js/debug

Its sole dependency

  • ms v2.0.0: Converts milliseconds to/from human-readable strings (e.g., 1500"1.5s"). Used only for the +1.5s diff suffix in log output.

How stdlib uses it

  • 680+ files across: @stdlib/random/streams/*, @stdlib/plot/*, @stdlib/streams/*, @stdlib/math/base/special/*, @stdlib/fs/*, @stdlib/repl/*, @stdlib/_tools/*, and more.
  • Only the factory function is used: var logger = require('debug'); var debug = logger('namespace'); then debug('message: %s.', val);.
  • No advanced API usage: none of the 680+ files call enable(), disable(), enabled(), or register custom formatters.

Proposed Changes

The plan creates 2 new packages mirroring stdlib's decomposable architecture, with each package being independently consumable.


Component 1: @stdlib/time/ms

Replaces the ms npm dependency (transitive via debug).

A utility to convert between millisecond values and human-readable time strings.

Scope: Only the ms → string direction is needed by the debug logger (e.g., 1500"1.5s"). Optionally support string → ms for completeness.

@stdlib/time/ms/
├── lib/
│   ├── index.js          # re-export main
│   └── main.js           # ms(val) → string or number
├── test/
│   └── test.js
├── benchmark/
│   └── benchmark.js
├── docs/
│   ├── repl.txt
│   └── types/
│       ├── index.d.ts
│       └── test.ts
├── examples/
│   └── index.js
├── README.md
└── package.json

API:

var ms = require( '@stdlib/time/ms' );

ms( 1500 );     // => '1.5s'
ms( 60000 );    // => '1m'
ms( 3600000 );  // => '1h'
ms( 86400000 ); // => '1d'
ms( 500 );      // => '500ms'

Component 2: @stdlib/console/debug

The core package — the direct replacement for require('debug').

A namespace-based diagnostic logging utility. This is the main factory function that all 680+ files will switch to.

@stdlib/console/debug/
├── lib/
│   ├── index.js          # re-export main
│   ├── main.js           # createDebug(namespace) factory (core logic)
│   ├── enable.js         # enable(namespaces) — parse glob patterns
│   ├── disable.js        # disable()
│   ├── enabled.js        # enabled(name) → boolean
│   ├── format_args.js    # environment-specific argument formatting
│   ├── colors.js         # ANSI color selection (Node.js)
│   └── defaults.js       # default options
├── test/
│   ├── test.js           # main export tests
│   ├── test.enable.js    # enable/disable/enabled tests
│   ├── test.format.js    # format string placeholder tests
│   └── test.colors.js    # color selection tests
├── benchmark/
│   ├── benchmark.js           # benchmark: logger creation
│   └── benchmark.disabled.js  # benchmark: no-op when disabled (perf)
├── docs/
│   ├── repl.txt
│   └── types/
│       ├── index.d.ts
│       └── test.ts
├── examples/
│   └── index.js
├── README.md
└── package.json

API (drop-in compatible with current usage):

var logger = require( '@stdlib/console/debug' );

// Create a namespaced logger:
var debug = logger( 'sparkline:unicode:main' );

// Log messages (no-op when disabled):
debug( 'Creating an instance with config: %s.', JSON.stringify( opts ) );
debug( 'Current value: %s.', this._yMin );

// Programmatic control (used internally, not by consumers):
logger.enable( 'sparkline:*' );
logger.disable();
logger.enabled( 'sparkline:unicode:main' ); // => true/false

Key implementation details:

  • Reads DEBUG env var (Node.js) or localStorage.debug (browser) on load
  • Supports glob patterns: * matches any characters, - prefix excludes
  • When disabled (the default), the function is a no-op with zero cost (early return)
  • Format placeholders: %s (string), %d (number), %o (inspect compact), %O (inspect multiline), %j (JSON), %% (escaped %)
  • ANSI colors in Node.js via deterministic namespace hashing
  • Appends +Xms diff timestamp between successive calls
  • Output target: stderr in Node.js, console.log in browsers
  • No external dependencies (uses @stdlib/time/ms for humanized diffs)

Component 3: Migration — Updating All Consumers

This is the mechanical bulk change, done after the new packages are created and verified.

[MODIFY] All 680+ files using require( 'debug' )

The change is a single-line replacement per file:

-var logger = require( 'debug' );
+var logger = require( '@stdlib/console/debug' );

Migration strategy (phased to reduce risk):

Phase Scope Files
Phase 1 @stdlib/random/streams/* ~200 files (all have identical debug.js pattern)
Phase 2 @stdlib/plot/* and @stdlib/streams/* ~150 files
Phase 3 @stdlib/math/base/special/* ~100 files
Phase 4 @stdlib/fs/*, @stdlib/repl/*, @stdlib/_tools/*, and remaining ~200+ files

Each phase follows the same process:

  1. Run find + sed to replace require( 'debug' )require( '@stdlib/console/debug' )
  2. Run existing tests for the affected module area
  3. Verify with DEBUG='*' node <example> that output matches current behavior

Uninstall the dependancy

After all phases are complete:

npm uninstall debug

Verification Plan

Automated Tests

1. Unit tests for @stdlib/time/ms

make TESTS_FILTER=".*/time/ms/.*" test

Tests should cover:

  • Millisecond → string conversion for all ranges (ms, seconds, minutes, hours, days)
  • Edge cases: 0, negative values, Infinity, NaN
  • Type validation: non-numeric input throws

2. Unit tests for @stdlib/console/debug

make TESTS_FILTER=".*/console/debug/.*" test

Tests should cover:

  • Factory returns a function
  • Logger is a no-op when DEBUG is unset
  • Logger outputs when DEBUG matches namespace
  • enable()/disable()/enabled() work correctly
  • Glob matching: *, -prefix exclusion, multiple comma-separated patterns
  • Format placeholders: %s, %d, %o, %O, %j, %%
  • Color assignment is deterministic per namespace
  • Diff timestamp is appended

3. Regression tests for migrated modules

Run the full test suite for each phase:

# Phase 1:
make TESTS_FILTER=".*/random/streams/.*" test

# Phase 2:
make TESTS_FILTER=".*/plot/.*" test
make TESTS_FILTER=".*/streams/.*" test

# Phase 3:
make TESTS_FILTER=".*/math/base/special/.*" test

# Phase 4: Full suite
make test

Manual Verification

After migration, verify that debug output works end-to-end:

  1. Create a test script that uses @stdlib/random/streams/t (or any migrated module)

  2. Run without DEBUG:

    node test_script.js

    → Verify: no debug output appears (silent by default)

  3. Run with DEBUG:

    DEBUG='random:streams:*' node test_script.js

    → Verify: colored namespace-prefixed debug messages appear on stderr, with +Xms diff timestamps

  4. Run with exclusion:

    DEBUG='*,-random:*' node test_script.js

    → Verify: excluded namespaces produce no output

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions