Skip to content

feat(nxz): add --memlimit-decompress flag to CLI#117

Merged
oorabona merged 3 commits into
masterfrom
feat/nxz-memlimit-decompress
Apr 30, 2026
Merged

feat(nxz): add --memlimit-decompress flag to CLI#117
oorabona merged 3 commits into
masterfrom
feat/nxz-memlimit-decompress

Conversation

@oorabona

Copy link
Copy Markdown
Owner

Summary

nxz (the CLI packaged as nxz-cli) gains a new --memlimit-decompress <SIZE> flag, matching the standard xz CLI's flag of the same name. Until now nxz called unxzSync() and createUnxz() with hardcoded defaults — users had no way to cap decompression memory from the CLI even though node-liblzma's LZMAOptions.memlimit had supported it since PRs #111 (WASM) and #112 (Native).

What's new

  • CLI flag : --memlimit-decompress <SIZE>, accepting plain integer bytes, IEC binary suffixes (KiB, MiB, GiB, TiB), and SI decimal suffixes (KB, MB, GB, TB). 0 and max (case-insensitive) explicitly mean "no limit", matching xz CLI semantics.
  • Helper : parseMemlimitSize(s: string): bigint | undefined exported from nxz.ts. Throws TypeError on malformed input.
  • Plumbing : threaded { memlimit } through 5 decompression call sites (3× unxzSync, 1× createUnxz, 1× benchmark cross-decompress) via a single decompressOpts variable per function — keeps decompressFile's cognitive complexity at 15 (was about to bump to 17 with inline ternaries).
  • Error surfacing : LZMAMemoryLimitError now surfaces as a CLI error with exit 1 when the limit is exceeded.

Diff

2 files, +238 / -5 :

  • packages/nxz/src/nxz.ts : +101 / -5
  • packages/nxz/test/cli.test.ts : +142 / -3

Testing

7 new vitest cases :

  1. Parse plain integer bytes
  2. Parse IEC binary suffix (KiB/MiB/GiB/TiB)
  3. Parse SI decimal suffix (KB/MB/GB/TB)
  4. max and 0 → no limit (no memlimit key in opts)
  5. Reject malformed input → TypeError
  6. LZMAMemoryLimitError thrown when limit exceeded (smoke test on real fixture)
  7. No-flag → byte-identical default behavior

Gates : pnpm install --frozen-lockfile ✅, pnpm type-check ✅, biome check packages/nxz/ ✅, pnpm test ✅ (678 / 0 / 3 skipped, +7 new in nxz).

Out-of-scope

  • CHANGELOG transitive notes (true streaming + Win32 TOCTOU benefits via tar-xz@6.1.0) — added post-merge as a manual [Unreleased] annotation before triggering release.yml. The new GIT_CHANGELOG_PATH=. scoping (shipped via PR chore(release): scope per-package CHANGELOG via GIT_CHANGELOG_PATH #116) correctly filters out non-nxz commits, so transitive benefits need explicit annotation.
  • Compression-side memlimit — encoder doesn't enforce memlimit on input (only decoder does), so --memlimit-compress would have no effect and isn't added.

Test plan

  • CI passes (lint, typecheck, tests)
  • Copilot review surfaces no S/M findings
  • After merge, gh workflow run release.yml -f target_package=nxz-cli -f increment=minor produces a clean v6.1.0 CHANGELOG entry scoped to packages/nxz/ (no node-liblzma C++/wasm/CI leakage)
  • Published nxz-cli@6.1.0 exposes --memlimit-decompress in nxz --help

Support memory limit configuration during decompression via new
--memlimit-decompress <SIZE> flag, matching xz CLI semantics.

- Added parseMemlimitSize() helper accepting plain bytes, IEC
  binary suffixes (KiB, MiB, GiB, TiB), and SI decimal suffixes
  (KB, MB, GB, TB).
- Special values 0 and max (case-insensitive) mean "no limit".
- Threaded memlimit option through unxzSync() and createUnxz()
  call sites and benchmark cross-decompress.
- LZMAMemoryLimitError now surfaces as CLI exit 1 when limit
  exceeded.
- Added 7 vitest cases covering suffix parsing, edge cases, error
  handling, and default behavior preservation.
- Reject decimal mantissa in --memlimit-decompress (e.g. `1.5MiB` is
  now an error). xz CLI parity. Removes a silent-rounding pit and a
  Number() precision-loss path for very large suffixed inputs.
- Parse the integer mantissa directly via BigInt (no Number()
  intermediary, no Math.round) — exact arbitrary-precision result.
- Treat any zero form (`0`, `0MiB`, `0kb`, `max`, `MAX`) as
  "no limit". Previously `0<suffix>` produced 0n which the decoder
  rejected on every input — surprising vs the documented `0` synonym.
- Apply memlimit symmetrically to both WASM decompression sites in
  `benchmarkFile` (was only applied to native sides). Native vs WASM
  benchmark is now apples-to-apples under --memlimit-decompress.
- Add 29 direct unit tests for parseMemlimitSize covering decimal
  rejection, mixed-case suffix, negative input, empty string,
  whitespace tolerance, all-zero forms, and arbitrary-precision values
  beyond UINT64_MAX (downstream validateMemlimit handles upper bound).
- Update JSDoc and --help to document integer-only mantissa, IEC
  (1024-based) vs SI (1000-based) suffix semantics, and the
  any-zero → no-limit synonym.
The unit-test file imported `parseMemlimitSize` from `nxz.ts`, which
runs `main()` unconditionally on import. Under stricter vitest
configurations the module's `process.exit(1)` fires during test
collection.

Extract `parseMemlimitSize` into `packages/nxz/src/memlimit.ts` as a
side-effect-free helper module. `nxz.ts` imports + re-exports it for
public API stability. Test file points at the new module directly.

No behavior change: 29/29 unit cases still pass; 63 total in nxz
(2 test files); workspace test suite stays at 707 passing.
@oorabona oorabona merged commit 2e3c25f into master Apr 30, 2026
6 of 7 checks passed
@oorabona oorabona deleted the feat/nxz-memlimit-decompress branch April 30, 2026 00:52
oorabona added a commit that referenced this pull request Apr 30, 2026
- Curate `packages/nxz/CHANGELOG.md` v6.1.0 entry: keep the
  `--memlimit-decompress` Added line, drop noise from older commits
  the populate script picked up via the root `latestTag` baseline,
  add a `### Notes` section pointing at transitive `tar-xz@6.1.0`
  benefits (true streaming for `extract`/`list`, Win32 TOCTOU
  hardening, native `memlimit` parity).
- TODO: mark #26 ✅ with PR #117 squash (`2e3c25f`) and release
  commit (`ecff028`); add LOW follow-ups for the upstream
  populate-script tag-baseline gap and the pre-existing
  `parseCliArgs` cognitive-complexity-17 refactor candidate.
- Update package version matrix to reflect `nxz-cli@6.1.0`.
oorabona added a commit that referenced this pull request Apr 30, 2026
…#121)

The PR #120 extract-method refactor introduced three defensive paths
in parseSuffixed that vitest's v8 instrumentation flagged as
unreachable: the '?? ""' fallbacks on regex capture groups
(unreachable when exec returns non-null) and the 'mult === undefined'
guard (unreachable because the regex constrains the suffix to keys
present in the multiplier map).

- Replace 'match[1] ?? ""' and 'match[2] ?? ""' with 'as string'
  type assertions: compile-time only, no runtime branch for v8 to
  instrument, and not flagged by biome's noNonNullAssertion rule
  (unlike non-null '!' which would re-introduce the lint warning we
  resolved in PR #117 fix-round 1).
- Wrap the noUncheckedIndexedAccess-required mult-undefined guard
  with /* v8 ignore start ... stop */: TypeScript strict mode demands
  the check but the regex+map alignment makes it structurally
  unreachable. Project convention is start/stop pairs (never 'next').

Coverage: lines 95.2 -> 100, branches 83.3 -> 100 on memlimit.ts.
707 tests still pass byte-identical.
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.

1 participant