Skip to content

feat: type: 'number' flags, long-form aliases, and unparseFlags()#38

Draft
voxpelli wants to merge 4 commits into
mainfrom
feat/number-aliases-unparse
Draft

feat: type: 'number' flags, long-form aliases, and unparseFlags()#38
voxpelli wants to merge 4 commits into
mainfrom
feat/number-aliases-unparse

Conversation

@voxpelli

Copy link
Copy Markdown
Owner

Summary

Three related features for Mocha and similar CLI tools that need richer flag declarations than util.parseArgs() exposes:

  • type: 'number' flag support — declare numeric flags directly; peowly remaps them to type: 'string' before parseArgs and coerces back afterwards, with NaN validation and full default/multiple support
  • aliases: string[] — long-form argv aliases (e.g. --colors → --color) rewritten before parsing; supports plain, =value, and multiple-alias forms; opt-in help annotation via showAliasInHelp: true; collision detection throws on duplicate or conflicting names
  • unparseFlags(flags, flagDefs) → string[] — serialises a parsed flags object back to an argv array for child_process.spawn(), e.g. in parallel test runners

Test plan

  • npm test passes (96 runtime tests, 108 type tests across TS 5.8 / 5.9 / 6.0, type coverage 99.87%)
  • npm run check:knip — clean
  • Verify type: 'number' round-trips: parse → flags.count is number, not string
  • Verify alias rewriting: --colors resolves to flags.color
  • Verify unparseFlags round-trip: peowly(args).flags → unparseFlags → peowly gives identical flags

🤖 Generated with Claude Code

voxpelli and others added 4 commits March 27, 2026 15:03
peowly only supported 'string' and 'boolean' (the two types util.parseArgs()
natively understands). Consumers like Mocha need numeric flags for --retries
and --jobs without manual Number() coercion in application code.

A pre-parseArgs step remaps number flags to type:'string', and a post-parseArgs
coercion loop converts parsed string values back to numbers. Numeric defaults
are tracked separately and applied after coercion.

Note: negative numbers require '--flag=-N' syntax due to a parseArgs limitation
(it treats '--flag -N' as ambiguous with short flag clusters).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Allows declaring long-form aliases (e.g. --colors → --color) via an
`aliases` property on any flag definition. Aliases are rewritten in
argv before parseArgs sees them, supporting plain, =value, and multiple
alias forms. Opt-in alias display in help via `showAliasInHelp: true`.
Collision detection throws on duplicate or conflicting alias names.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Useful for rebuilding a CLI argv for child_process.spawn(), e.g.
in parallel test runners. Boolean true emits --flag, false is omitted,
strings/numbers emit --flag value, multiples emit one pair per element.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
index.js is auto-detected from package.json exports, so the explicit
entry was redundant. jq is used in test:tstyche to read the minimum
TypeScript version but is a system tool rather than an npm package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@voxpelli
voxpelli force-pushed the feat/number-aliases-unparse branch from 7b3bf59 to 7ef8b55 Compare March 27, 2026 16:26
@voxpelli
voxpelli requested a review from Copilot May 25, 2026 08:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds richer flag declaration capabilities to peowly (built on Node’s util.parseArgs()), aimed at Mocha-like CLIs needing numeric flags, long-form aliases, and the ability to serialize parsed flags back into an argv array.

Changes:

  • Add type: 'number' support by remapping number flags to parse as strings and coercing back to numbers (with NaN validation and default/multiple support).
  • Add aliases: string[] for long-form flag aliases (with optional help annotations and collision detection) by rewriting argv before parsing.
  • Add unparseFlags(flags, flagDefs) => string[] to serialize a flags object back into argv (plus tests/type-tests).

Reviewed changes

Copilot reviewed 11 out of 13 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/peowly.js Implements number-flag remapping/coercion and argv alias rewriting prior to parseArgs().
lib/flag-types.d.ts Extends flag typings to include type: 'number', aliases, and showAliasInHelp; introduces internal ParseArgsCompatibleFlag.
lib/peowly-types.d.ts Updates TypedFlags typing to support numeric flags; adjusts ExtendedParseArgsConfig to omit options.
lib/format-lists.js Displays numeric defaults and optionally annotates aliases in help output.
lib/unparse.js Adds unparseFlags() implementation with documented serialization rules.
lib/main.js Re-exports unparseFlags from the public entrypoint.
test/peowly-number-flags.spec.js Runtime coverage for parsing/coercing numeric flags (including defaults and multiple).
test/peowly-aliases.spec.js Runtime coverage for alias rewriting, collision detection, and help annotation behavior.
test/peowly-unparse.spec.js Runtime coverage for unparseFlags output and basic round-trip cases.
typetests/number-flags.tst.ts Type-level assertions for inferred numeric flag result types and assignability.
typetests/aliases.tst.ts Type-level assertions for aliases/showAliasInHelp in flag definitions.
typetests/unparse.tst.ts Type-level assertions for unparseFlags return type and compatibility with peowly() output.
.knip.jsonc Knip config cleanup and ignores (jq binary, typetests).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/peowly.js
for (const [canonicalName, flag] of Object.entries(resolvedFlags)) {
for (const alias of flag.aliases ?? []) {
if (resolvedFlags[alias] !== undefined) {
throw new Error(`Flag alias "${alias}" conflicts with existing flag "${canonicalName}"`);
Comment thread lib/peowly.js
Comment on lines +109 to +129
: args.map(arg => {
if (!arg.startsWith('--')) return arg;
const eqIdx = arg.indexOf('=');
if (eqIdx !== -1) {
const alias = arg.slice(2, eqIdx);
const canonical = aliasMap.get(alias);
return canonical ? `--${canonical}=${arg.slice(eqIdx + 1)}` : arg;
}
const flagName = arg.slice(2);
const canonical = aliasMap.get(flagName);
if (canonical) return `--${canonical}`;
if (flagName.startsWith('no-')) {
const aliasedName = flagName.slice(3);
const canonicalForNo = aliasMap.get(aliasedName);
if (canonicalForNo && resolvedFlags[canonicalForNo]?.type === 'boolean') {
return `--no-${canonicalForNo}`;
}
}
return arg;
});

Comment thread lib/unparse.js
Comment on lines +5 to +8
* Rules:
* - `boolean true` → `['--name']`
* - `boolean false` → `[]` (omitted; `--no-name` is not emitted)
* - `string`/`number` → `['--name', String(value)]`
Comment thread lib/peowly.js

for (const [canonicalName, flag] of Object.entries(resolvedFlags)) {
for (const alias of flag.aliases ?? []) {
if (resolvedFlags[alias] !== undefined) {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants