Skip to content

v1.0.0

Choose a tag to compare

@takeokunn takeokunn released this 25 Jul 17:34
v1.0.0
7af392d

The API is now covered by Semantic Versioning. What that promise does and does
not cover is written down in
docs/src/compatibility.md,
along with the supported-implementation matrix and the deprecation policy.

This release is mostly about earning that promise rather than adding features.
The breaking changes below are deliberately concentrated here, because 1.0 is
the last point at which they are free: an accessor family that could not be
read, a condition taxonomy that made a programmer's bug look like a user's,
and a machine-readable format with no version marker would all otherwise have
been permanent. Verification was rebuilt to match: nix flake check was
previously guaranteed to fail, the checks that run generated scripts through
real shells were almost entirely skipping, and no non-SBCL implementation could
compile the suite at all.

Added

  • The positional accessor family is exported. app-positionals,
    command-positionals, and make-positional were all public, so callers
    legitimately received positional-spec structs -- but not one of its slot
    readers was exported, leaving them opaque unless you reached through
    cl-cli::. All sixteen are public now, renamed from the positional-spec-
    conc-name to positional- to match the option- / command- / app-
    families they sit beside. Also newly exported: command-subcommands and
    command-default-command (nested subcommands are a documented feature and
    app-default-command was already public), option-group plus
    option-group-members / -mode / -required-p (so a custom help renderer
    can render "exactly one of ..." instead of degrading to pairwise
    conflicts), and built-in-option-specs (so it can see the synthesized
    --help / --version). Between them, a third-party renderer can now
    reproduce everything the built-in one prints.
  • cli-missing-required-option, a subtype of cli-missing-option-value
    for the single most common CLI mistake there is: a required option never
    supplied. It was previously indistinguishable from "--output typed with
    nothing after it". Being a subtype, a handler that does not need the
    distinction is unaffected.
  • cli-response-file-error (with a cli-response-file-error-path
    reader), for the three @file expansion failures that previously signaled
    a bare cli-usage-error -- "your @args.txt is missing" deserves a
    different message from "you mistyped a flag".
  • Docstrings on every exported condition class and slot reader. The
    condition hierarchy is the primary integration point for a consumer CLI and
    had no prose at all; cli-invalid-option-value-cause in particular was
    unguessable.
  • tests/cases-public-api.lisp, which pins the exported symbol set in both
    directions. :export fails silently in the direction that matters -- a
    misspelled name interns a dead symbol rather than signaling -- and no other
    test would notice, since they all call these symbols from a package that
    :uses cl-cli. It also asserts that every exported condition is reachable
    from cli-error and that cli-invalid-specification stays out of the
    cli-usage-error branch.
  • render-json now emits a schemaVersion member first, and exports
    +json-schema-version+ (currently 1). A machine-readable format with no
    version marker gives a consumer nothing to branch on; a tool written against
    today's shape would silently misread a future one. Adding it now, at the
    1.0 boundary, is the last point where it costs nothing. New members may
    still appear in a minor release -- readers should ignore unknown members --
    but a change that would break a reader of the previous shape bumps the
    number. This changes render-json output; a consumer that parses the
    0.3.0 document and rejects unknown keys needs updating.
  • docs/src/compatibility.md, stating what the version number promises
    now that the project is on Semantic Versioning for real: the exported
    symbols of the CL-CLI package are covered, cl-cli:: internals and the
    byte-for-byte text of rendered help/completion/man output are not, plus a
    supported-implementation matrix and a deprecation policy.
  • Dedicated unit tests for the PowerShell, Nushell, and Elvish completion
    renderers (tests/cases-completion-powershell.lisp, -nushell.lisp,
    -elvish.lisp). Three of the six shipped renderers previously had no
    assertions on what they emit at all -- only a performance benchmark and a
    "does it parse" smoke check.
  • An Elvish leg in tests/cases-shell-verification.lisp: the generated script
    is compiled by real elvish -compileonly. Because elvish only exposes its
    edit: module interactively, the check binds edit: to a stub namespace so
    the generated code itself is what gets compiled; a negative-control case
    asserts that a deliberately broken completer still fails, so the check
    cannot pass vacuously.

Changed

  • cli-invalid-specification is no longer a cli-usage-error. A new
    cli-error root splits the hierarchy in two: cli-usage-error for what the
    user typed, cli-invalid-specification for what the programmer
    declared. The idiomatic
    (handler-case (run-app ...) (cli-usage-error (e) (print-usage) (exit 2)))
    previously swallowed the developer's own malformed spec and answered it with
    a usage message shown to the end user. If such an error does reach
    run-app, it now exits with :error-exit-code (70, EX_SOFTWARE)
    instead of :usage-exit-code (64, EX_USAGE) -- which is what a bug in
    the program actually is. In practice the condition is raised while the spec
    is being built, before parsing.
    cli-error-app / cli-error-command are the new names for the context
    readers; cli-usage-error-app / -command still read the same slots.
  • The test suite is split into two ASDF systems. cl-cli/tests is the
    portable core; cl-cli/tests/shell-verification adds the checks that shell
    out to real tools and is the only half that needs cl-process-kit. The
    transitive cl-log-kit dependency hard-codes sb-thread:*
    (upstream #1), which
    previously made the entire suite fail to compile on any non-SBCL
    implementation. ECL now runs 637 tests green instead of not building.
    tests/run-tests.lisp prints which half it loaded, so a run covering less
    than expected cannot be mistaken for a pass.
  • The fuzz suite and the benchmark budgets are now gated on the capability
    each actually needs, rather than failing where it is absent: the fuzz suite
    needs cl-weave's :timeout platform capability, and the benchmark
    thresholds are absolute milliseconds calibrated against SBCL. Both report as
    skips with a reason. No threshold was widened and no assertion was removed.
  • The benchmark workloads are resized so contention on a shared CI runner
    cannot fail them. One case measured 520ms idle and 2534ms against a 2000ms
    budget inside a concurrent nix flake check -- a flaky gate, and an
    intermittently red check gets read as noise, which is how a real regression
    gets waved through. Each case now targets an idle median around a tenth of
    its budget; the 2000ms convention itself is unchanged, since raising it is
    the one response that keeps the test green while destroying what it
    measures.
  • nix flake check is green again, and now means something it did not before.
    The ecl check was previously guaranteed to fail, so CI on main was
    permanently red. Alongside the split above:
    • the flake defines outputs for aarch64-linux, x86_64-darwin, and
      aarch64-darwin as well as x86_64-linux, so nix develop and
      nix flake check work on a macOS machine instead of silently checking
      nothing;
    • the checks put bash, zsh, fish, nushell, pwsh, elvish, and
      mandoc on PATH. The shell-verification cases skip themselves when a
      tool is missing, so in CI they had been skipping almost entirely -- the
      generated scripts were never actually run by the shells that consume
      them. The SBCL suite is now 664 passed, 0 skipped;
    • the documentation build is a check too, so a broken docs link fails CI
      rather than the Pages deploy.
  • CI runs the flake check on aarch64-darwin as well as x86_64-linux, so
    the macOS support the project claims is verified rather than assumed.
  • consume-value-option (src/parser-option-consumption.lisp) had a
    provably unreachable :flag-kind guard clause -- its sole call site is
    the (:value :optional-value :key-value) case arm of
    consume-long-option-token, so (option-kind spec) can never be :flag
    inside its body. Removed as dead code; no observable behavior changed.
  • Bumped cl-weave, cl-process-kit, cl-boundary-kit, cl-log-kit, and
    cl-json-kit to their latest upstream revisions (cl-prolog was already
    current). Verified cl-boundary-kit's new default 60-second process
    timeout (previously unbounded when :timeout was omitted) doesn't affect
    cl-cli: every process-kit:run call site in the test suite already
    passes an explicit :timeout.

Security

  • expand-response-files (:expand-response-files t) had no bound on the
    total bytes read across a response-file expansion -- only
    +response-file-max-depth+ limited nesting depth, not fan-out (many
    files referenced at one depth) or a single huge file. A caller piping a
    response-file path derived from untrusted input into an app that
    opted into response-file expansion could exhaust memory. Added
    +response-file-max-total-bytes+ (8 MiB, generous for any legitimate
    argument list) as a total-size budget checked after every file read;
    exceeding it signals cli-usage-error the same way exceeding the depth
    limit already did.