stdlib-js / stdlib-js/stdlib

[RFC]: Bringing the `debug` Dependency In-House

Đang mở
#11,081 1 bình luận 0 reaction 0 người được giao Xem trên GitHub
Ngôn ngữ chính
JavaScript
Star
6k
Fork
1.3k
Merge trung bình
1 ngày 3 giờ
Pull request đã merge (30 ngày)
611

Mô tả

# 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

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
javascript, node.js
Lĩnh vực
security, tooling
Loại issue
Tính năng
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
25/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.