@davidwells/dx-args
v0.2.2
Published
A forgiving argv parser for CLIs that accepts loose key/value syntax, globs, and typo-tolerant long flags.
Maintainers
Readme
@davidwells/dx-args
A forgiving argv parser for CLIs that should keep working when someone types a slightly imperfect command.
@davidwells/dx-args keeps oparser as the value parser. It adds argv normalization, file/glob grouping, single-dash long option recovery, and a predictable result shape around it.
const { dxParse } = require('@davidwells/dx-args')
const result = dxParse(['-files', 'README.md', '-dry'])
console.log(result.mergedOptions)
// { files: 'README.md', dry: true }
console.log(result.globGroups)
// [{ key: 'files', rawKey: '-files', values: ['README.md'] }]Install
npm install @davidwells/dx-argsAPI
const {
dxParse,
getGlobGroupsFromArgs,
splitOutsideQuotes,
} = require('@davidwells/dx-args')dxParse(argv, opts)
Parses an argv array and returns a ParseResult.
const result = dxParse(['--stage', 'prod', 'cool', '=', 'true'])
console.log(result.mergedOptions)
// { stage: 'prod', cool: true }getGlobGroupsFromArgs(argv, opts)
Extracts file and glob-like values into grouped collections.
const result = getGlobGroupsFromArgs(
['--files', 'README.md', 'docs/**/*.md'],
{ globKeys: ['files'] }
)
console.log(result.globGroups)
// [{ key: 'files', rawKey: '--files', values: ['README.md', 'docs/**/*.md'] }]splitOutsideQuotes(input)
Splits a normalized option string while preserving quoted strings, objects, arrays, and key = value forms.
splitOutsideQuotes('name = "David Wells" config={ enabled: true }')
// ['name="David Wells"', 'config={ enabled: true }']Accepted Input Forms
| Input | mergedOptions |
| --- | --- |
| --stage prod | { stage: 'prod' } |
| --stage=prod | { stage: 'prod' } |
| stage = prod | { stage: 'prod' } |
| stage=prod | { stage: 'prod' } |
| -stage prod | { stage: 'prod' } |
| -f README.md | { f: 'README.md' } |
| --no-cache | { cache: false } |
| --count 3 | { count: 3 } |
| --items=[1,2,3] | { items: [1, 2, 3] } |
File and glob-like values are also exposed through globGroups.
const result = dxParse(['README.md', 'docs/**/*.md', '--stage', 'dev'])
console.log(result.globGroups)
// [{ key: '', rawKey: '', values: ['README.md', 'docs/**/*.md'] }]
console.log(result.mergedOptions)
// { stage: 'dev' }Result Shape
interface GlobGroup {
key: string
rawKey: string
values: Array<string | RegExp>
}
interface ParseResult {
rawArgv: string
leadingCommands: string[]
globGroups: GlobGroup[]
extraParse: Record<string, unknown>
mriOptionsOriginal: Record<string, unknown>
mriOptionsClean: Record<string, unknown>
mriDiff: boolean
yargsParsed: string
mergedOptions: Record<string, unknown>
}Most callers should use:
mergedOptionsfor parsed options.globGroupsfor file and glob-aware workflows.leadingCommandswhen a CLI has command-like leading words before options.
The remaining fields are diagnostic:
extraParseis theoparser-derived parse result after argv normalization.mriOptionsOriginalis the rawmriresult.mriOptionsCleanismriafter cleanup of artifacts such as letters from-stage.mriDiffis true when cleanup changed themriresult.yargsParsedis not active; it is currently a compatibility/debug field.
Glob Groups
@davidwells/dx-args groups file-looking and glob-looking arguments so CLIs can handle files separately from ordinary options.
dxParse(['--files', 'README.md', 'docs/**/*.md']).globGroups
// [{ key: 'files', rawKey: '--files', values: ['README.md', 'docs/**/*.md'] }]Bare file and glob values use an empty key:
dxParse(['README.md', 'docs/**/*.md']).globGroups
// [{ key: '', rawKey: '', values: ['README.md', 'docs/**/*.md'] }]Custom glob keys are configurable:
dxParse(['--ignore', 'dist/**/*.md'], {
globKeys: ['files', 'file', 'path', 'ignore']
}).globGroups
// [{ key: 'ignore', rawKey: '--ignore', values: ['dist/**/*.md'] }]Shell-expanded file lists are intentionally not treated as leading commands:
const result = dxParse(['README.md', 'NOTES.md', 'build', '=', 'false'])
console.log(result.globGroups)
// [{ key: '', rawKey: '', values: ['README.md', 'NOTES.md'] }]
console.log(result.mergedOptions)
// { build: false }Values containing node_modules/ are currently filtered from glob groups.
Duplicate Values
Duplicate flags are last-write-wins by default.
dxParse(['--stage', 'dev', '--stage', 'prod']).mergedOptions
// { stage: 'prod' }Accumulation is explicit:
dxParse(['--tag', 'one', '--tag', 'two'], {
accumulate: ['tag']
}).mergedOptions
// { tag: ['one', 'two'] }The parser currently accepts three option names for accumulation:
accumulateaccumulateFlagsarrayKeys
Prefer accumulate in new code.
Single Dash Policy
Multi-character single-dash options are treated as forgiving long options:
dxParse(['-stage', 'prod']).mergedOptions
// { stage: 'prod' }That also works when the value looks like a file:
dxParse(['-config', 'md.config.js']).mergedOptions
// { config: 'md.config.js' }Separate short flags still work:
dxParse(['-l', '-a', '-h']).mergedOptions
// { l: true, a: true, h: true }Short clusters are opt-in because they conflict with forgiving long option recovery:
dxParse(['-abc']).mergedOptions
// { abc: true }
dxParse(['-abc'], {
allowShortClusters: true,
shortFlags: ['a', 'b', 'c']
}).mergedOptions
// { a: true, b: true, c: true }Markdown Magic Integration
Markdown Magic uses dxParse as its CLI parse boundary.
Markdown Magic maps parser output like this:
file,files,path, and bare file groups becomeconfig.files.ignoregroups becomeconfig.ignore.- Other
mergedOptionsbecome Markdown Magic config overrides. outputandoutputDirnormalization happens in Markdown Magic, not@davidwells/dx-args.
Example Markdown Magic commands:
md-magic -files README.md -config md.config.js -dry
md-magic --path "docs/**/*.md" --ignore "dist/**/*.md"Debugging
The package includes a small CLI that prints the full parse result:
dx-args -files README.md -config md.config.js -dryWhen debugging, check:
mergedOptionsglobGroupsmriOptionsOriginalmriOptionsCleanmriDiff
Known Gaps
- Space-separated list capture like
--numbers 1 2 3 --strings a b cis not supported yet. - Real shell argv with quotes should be tested as arrays, not only string-split fixtures.
- Diagnostic fields may move under a
debugortracefield before a stable1.0.0. node_modules/filtering is currently built into glob grouping.--rest passthrough should be specified and tested before it is documented as public API.
