[RFC]: Bringing the `debug` Dependency In-House
- Langage dominant
- JavaScript
- Étoiles
- 6k
- Forks
- 1.3k
- Merge moyen
- 1 j 3 h
- PR mergées (30 j)
- 611
Description
# 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).
A key improvement: the in-house replacement will leverage `@stdlib/string/format` for string interpolation, providing significantly richer formatting capabilities than the external `debug` package's limited `%s`/`%d` placeholders
## 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. The implementation leverages the existing `@stdlib/string/format` package for string interpolation for richer debug messages — e.g., `debug( 'Value: %.6f, Index: %03d', 3.14, 7 )` → `'Value: 3.140000, Index: 007'`
---
### 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**:
```js
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):
```js
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 );
// Now with richer formatting via @stdlib/string/format:
debug( 'Computed value: %.6f at index %03d.', 3.14159, 7 );
debug( 'Hex: %x, Binary: %b, Octal: %o.', 255, 255, 255 );
debug( 'Padded: %10s | %-10s.', 'right', 'left' );
// 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
- Internal dependencies only: `@stdlib/string/format`, `@stdlib/time/ms`
---
### 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' )`
There are two primary usage patterns across the codebase:
1. **Pattern 1: Dedicated Wrapper Files (53 Packages)** Files like `@stdlib/random/streams/arcsine/lib/debug.js` whose sole purpose is to initialize and export a namespaced logger.
2. **Pattern 2: Direct Imports (645 Files)** Implementation files like `@stdlib/fs/read-file-list/lib/async.js` that import the utility directly alongside other modules.
In both patterns, only a single line changes:
```diff
-var logger = require( 'debug' );
+var logger = require( '@stdlib/console/debug' );
```
**Migration strategy** ((phased to account for both patterns)::
| 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 ` 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`
```bash
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`
```bash
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 strings are processed via `@stdlib/string/format` (all specifiers: `%s`, `%d`, `%f`, `%e`, `%x`, `%b`, etc.)
- Width/precision/flags are correctly passed through (e.g., `%.2f`, `%05d`, `%-10s`)
- Color assignment is deterministic per namespace
- Diff timestamp is appended
#### 3. Regression tests for migrated modules
Run the full test suite for each phase:
```bash
# 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`:
```bash
node test_script.js
```
→ Verify: no debug output appears (silent by default)
3. Run **with** `DEBUG`:
```bash
DEBUG='random:streams:*' node test_script.js
```
→ Verify: colored namespace-prefixed debug messages appear on stderr, with `+Xms` diff timestamps
4. Run with **exclusion**:
```bash
DEBUG='*,-random:*' node test_script.js
```
→ Verify: excluded namespaces produce no output
Guide de contribution
Ouvrir le guide de contribution
Piste de recherche
Start by reviewing the proposed @stdlib/time/ms and @stdlib/console/debug package layouts, then inspect existing require('debug') usage across the listed consumer areas. Run the scoped TESTS_FILTER commands to understand project conventions. Done means the replacements, tests, documentation, and phased migration are complete, with the full suite and DEBUG-based manual checks passing.
Rédigé par le modèle d'indexation à partir du texte de l'issue.
Évaluation
- Stack technique
- javascript, node.js
- Domaine
- security, tooling
- Type d'issue
- Fonctionnalité
- Difficulté
- 5/5
- Temps estimé
- Plus d'une semaine
- Activité
- Calme
- Clarté
- Plutôt claire
- Accessibilité débutants
- 25/100