stdlib-js / stdlib-js/stdlib

[RFC]: Bringing `minimist` Dependency In-House

未关闭
#11,080 2 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
JavaScript
星标
6k
派生
1.3k
平均合并
1 天 3 小时
30 天内合并 PR
611

描述

# Replace `minimist` Dependency with In-House `@stdlib/cli/parse-args`

Replace the third-party `minimist` dependency with a stdlib-native equivalent to reduce supply-chain security risk and bring all production code into conformance with stdlib conventions (docs, tests, examples, benchmarks, backward-compatibility).

## Background

- **[`minimist`](https://github.com/minimistjs/minimist)** is used exclusively as a command-line argument parser
- **Primary consumer**: [`@stdlib/cli/ctor/lib/main.js`]— the central CLI class used by every stdlib package with a CLI
- **Secondary consumers**: ~12 internal tool scripts under `@stdlib/_tools/package-json/scripts/` and `@stdlib/_tools/modules/pkg-deps/bin/cli`
- The existing code has a [TODO comment]: `// TODO: replace with stdlib equivalent`
- The minimist source is 264 lines — straightforward to reimplement

### API Surface Analysis

The full minimist API supports these options: `boolean`, `string`, `alias`, `default`, `stopEarly`, `--` separator, `unknown` callback, and dotted key expansion.

**What stdlib actually uses** (confirmed by auditing every `cli_opts.json` and direct call site):

| Option | Used? | Example |
|---|---|---|
| `boolean` | ✅ Yes | `["help", "version"]` |
| `string` | ✅ Yes | `["split", "aliases"]` |
| `alias` | ✅ Yes | `{ "help": ["h"], "version": ["V"] }` |
| `default` | ❌ No | — |
| `stopEarly` | ❌ No | — |
| `unknown` | ❌ No | — |
| `--` separator | ❌ No | — |

> [!IMPORTANT]
> **Decision**: Implement the full minimist API (all 7 options) for backward-compatibility and completeness, but prioritize testing the 3 options actually used. This ensures the replacement is a true drop-in and supports any future stdlib packages that might need additional options.

---

## Proposed Changes

### 1. New Package: `@stdlib/cli/parse-args`

Create a brand-new stdlib package that reimplements minimist's `parseArgs(args, opts)` function.

#### Folder Structure

```
lib/node_modules/@stdlib/cli/parse-args/
├── package.json
├── README.md
├── lib/
│ ├── index.js # Standard stdlib entry point
│ └── main.js # Core argument parser implementation
├── test/
│ ├── test.js # Main export, basic parsing, type checks
│ ├── test.bool.js # boolean option, --no- prefix, allBools
│ ├── test.string.js # string option, number coercion suppression
│ ├── test.alias.js # alias option, bidirectional resolution
│ ├── test.default.js # default option
│ ├── test.long.js # Long flags (--key, --key=val, --key val)
│ ├── test.short.js # Short flags (-k, -abc, -k=val, -k val)
│ ├── test.dash.js # -- separator and opts['--']
│ ├── test.stop_early.js # stopEarly option
│ ├── test.unknown.js # unknown callback
│ ├── test.num.js # Number coercion
│ └── test.proto.js # Prototype pollution prevention
├── benchmark/
│ └── benchmark.js # Performance benchmarks
├── examples/
│ └── index.js # Runnable usage examples
└── docs/
└── types/
├── index.d.ts # TypeScript declarations
└── test.ts # TypeScript compile-time tests
```

---

#### main.js

Core implementation. The function signature:

```js
function parseArgs( args, opts ) → { _: Array, ...flags }
```

Behavior to reimplement (in order of parsing priority):
1. **`--key=value`** — long flag with inline value
2. **`--no-key`** — long flag negation → sets `key: false`
3. **`--key value`** — long flag with next-arg value
4. **`--key`** (boolean) — long boolean flag → `true`
5. **`-abc`** — short flag grouping (each letter is a boolean flag)
6. **`-k value`** — short flag with next-arg value
7. **`-k=value`** — short flag with inline value
8. **`--`** — stop parsing, rest goes to `_` (or `argv['--']`)
9. **Positional args** — everything else goes to `_`
10. **Number coercion** — numeric strings → Number (unless `string` option set)
11. **Alias expansion** — set value on all aliases simultaneously
12. **Default values** — apply defaults for keys not explicitly set
13. **`stopEarly`** — after first positional, everything goes to `_`
14. **`unknown` callback** — called for undefined flags; if returns `false`, skip
15. **Prototype pollution guard** — reject `__proto__` and `constructor`

### 2. Update Consumer: `@stdlib/cli/ctor`

#### [MODIFY] @stdlib/cli/ctor/lib/main.js

Single-line change on line 25:

```diff
-var parseArgs = require( 'minimist' ); // TODO: replace with stdlib equivalent
+var parseArgs = require( '@stdlib/cli/parse-args' );
```

---

### 3. Update Consumers: Internal Tool Scripts

#### [MODIFY] 12 files under `@stdlib/_tools/`

Same single-line change in each file:

```diff
-var parseArgs = require( 'minimist' );
+var parseArgs = require( '@stdlib/cli/parse-args' );
```

Files to update:
- `_tools/modules/pkg-deps/bin/cli`
- `_tools/package-json/scripts/update_directories`
- `_tools/package-json/scripts/update_license`
- `_tools/package-json/scripts/update_engines_node_version`
- `_tools/package-json/scripts/update_homepage`
- `_tools/package-json/scripts/update_author`
- `_tools/package-json/scripts/update_os`
- `_tools/package-json/scripts/update_bugs`
- `_tools/package-json/scripts/update_contributors`
- `_tools/package-json/scripts/update_engines_npm_version`
- `_tools/package-json/scripts/update_gypfile`
- `_tools/package-json/scripts/update_repository`

---

### 4. Remove `minimist` from Dependencies

```
npm uninstall minimist
```
---

## Verification Plan

### Automated Tests

1. **New package unit tests** (primary verification):
```bash
make TESTS_FILTER=".*/cli/parse-args/.*"
```

2. **Existing `@stdlib/cli/ctor` tests** (regression — must continue passing after swap):
```bash
make TESTS_FILTER=".*/cli/ctor/.*"
```

3. **Equivalence test** — Run the existing minimist test suite against our implementation to verify behavioral parity:
```bash
# We can create a shim that points minimist's tests at our implementation
NODE_PATH=lib/node_modules node -e "var parse = require('@stdlib/cli/parse-args'); console.log(JSON.stringify(parse(['--foo', 'bar', '-abc', '--no-debug', '--count=5'])))"
```

### Manual Verification

4. **End-to-end CLI test** — Run a real stdlib CLI command and verify it still works:
```bash
NODE_PATH=lib/node_modules node bin/cli is-leap-year 2024
# Expected: true

NODE_PATH=lib/node_modules node bin/cli is-leap-year 2023
# Expected: false

NODE_PATH=lib/node_modules node bin/cli --help
# Expected: prints usage text

NODE_PATH=lib/node_modules node bin/cli --version
# Expected: prints version
```

贡献指南

打开贡献指南

调研方向

Start with the proposed @stdlib/cli/parse-args structure and its lib/main.js entry point, then inspect @stdlib/cli/ctor/lib/main.js and the listed _tools consumers. Run the parse-args, cli/ctor, and end-to-end CLI verification commands. Done means the documented minimist behavior is covered, consumers use the stdlib package, and minimist is removed.

由索引模型根据 Issue 内容生成。

评估

技术栈
javascript, nodejs
领域
cli, tooling
Issue 类型
功能
难度
5/5
预计耗时
一周以上
活跃度
冷清
描述清晰度
描述清楚
新手友好度
55/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。