Thanks to visit codestin.com
Credit goes to github.com

Skip to content

fix: count rb storage in telemetry and encode storage URIs - #161

Merged
a-essawy merged 2 commits into
mainfrom
fix/telemetry-nested-subcommands
Sep 13, 2026
Merged

fix: count rb storage in telemetry and encode storage URIs#161
a-essawy merged 2 commits into
mainfrom
fix/telemetry-nested-subcommands

Conversation

@a-essawy

@a-essawy a-essawy commented Sep 13, 2026

Copy link
Copy Markdown
Contributor

The gap

rb storage is the first command with nested subCommands (list/ls) and
no top-level run. The usage-telemetry wrapper in src/main.ts only wrapped
each top-level command's run(), so rb storage list and rb storage ls
never emitted the cli_command usage event that every other command emits.

The fix

Moved the wrapper from main.ts into lib/telemetry.ts as instrumentCommands
and made it recurse through subCommands to any depth, instead of only
handling the top level. It is generic -- nothing in it is specific to
storage.

A nested command is named "parent child" in the existing telemetry
command property (e.g. "storage list"), the same words citty's own usage
line uses for it. No new schema field. A mid-level command with no run of
its own (like storage itself) still does not emit anything -- only a
resolved command that actually has a run gets wrapped.

The wrapper now takes an injectable capture function, defaulting to
captureCommand, the same DI pattern checkForUpdate already uses for
fetch. That's what makes it unit-testable without a network call or a real
telemetry key.

Related gap (reported, not fixed here)

Commands that terminate via process.exit() mid-run skip the wrapper's
finally, so no usage event flushes for that invocation. This is not a
one-off: process.exit() is the standing pattern across nearly every command
(ffmpeg, ffprobe, edit, generate, login, doctor, update, storage, whoami) for
success/error/cancellation exit codes -- dozens of call sites. Fixing it
properly means changing how every command signals its exit code instead of
calling process.exit() directly, which is a materially larger, riskier
change than this one and out of scope here.

Tests

Added src/__tests__/telemetry-instrumentation.test.ts, TDD'd against the
real citty runCommand dispatch (not a hand-rolled stand-in), covering:

  • a top-level command's run is wrapped and captures once under its own name
  • a nested subcommand captures once as "parent child"
  • a parent with no run of its own does not capture
  • a thrown error is captured as success: false and still rethrown
  • recursion works at arbitrary depth with non-storage names, proving it
    isn't special-cased

pnpm test (272/272) and pnpm typecheck are green.

Storage URI encoding (Refs rendobar/rendobar#694)

An object key can hold %, ? or #. rb storage ls printed those raw
after storage://<id>/, which produced a URI a ? or # breaks in transit
and that parseStorageTarget could not always read back (a bare % not
followed by two hex digits throws in decodeURIComponent, and a literal ?
or # is rejected downstream).

Added encodeStoragePath to src/lib/storage-view.ts, matching the platform
contract in packages/shared/src/storage/refs.ts (rendobar/rendobar#696):
escape % first, then ?, then #, nothing else. formatListing now runs
every folder and key through it before building the printed
storage://<id>/<path> line. rb storage list --json and rb storage ls --json carry raw folders/key fields, not URIs, so they are unchanged.

parseStorageTarget now decodes a storage:// URI's folder part the same
way the platform does (decodeURIComponent, literal text if that throws),
so a URI copied from rb storage ls output browses the right folder again.
A bare <id>/<folder> typed by hand stays literal, since nothing encoded it.
--deliver is untouched -- it passes the user's text straight to the API,
which now decodes it on that side.

Covered by new tests in src/__tests__/storage-command.test.ts: the encoder,
the round trip through formatListing, and parseStorageTarget decoding a
storage:// URI while leaving a typed path literal.

BEGIN_COMMIT_OVERRIDE
fix: count rb storage list and ls in usage telemetry
fix: encode %, ? and # in rb storage URIs
END_COMMIT_OVERRIDE

rb storage is the first command with nested subCommands and no
top-level run, so instrumentSubCommands never wrapped its list/ls
run() and rb storage list / rb storage ls never emitted a usage
event.

Rename it to instrumentCommands and walk subCommands recursively,
so any nested command gets the same wrapping as a top-level one.
A nested command is named "parent child" in the existing `command`
telemetry property (e.g. "storage list"), matching how citty's own
usage line names it -- no new schema field. The wrap only fires for
a resolved command that actually has its own run, so a mid-level
command like storage itself (no run, only subCommands) still does
not emit anything on its own.

Moved the wrapper into lib/telemetry.ts, next to captureCommand, and
gave it an injectable capture function (same pattern already used by
checkForUpdate's fetchImpl) so it can be unit tested without a
network call or a real telemetry key.
rb storage ls printed a raw key after storage://<id>/, so a key holding
%, ? or # produced a URI parseStorageTarget could not read back correctly
(a literal ? or # gets rejected, and stray % breaks decodeURIComponent
downstream). Escape those three characters the way the platform's
encodeStoragePath does, and decode a storage:// URI's folder part the
same way on the way back in. A path typed by hand, not copied from
listing output, is left literal.
@a-essawy a-essawy changed the title fix: count rb storage list and ls in usage telemetry fix: count rb storage in telemetry and encode storage URIs Sep 13, 2026
@a-essawy
a-essawy merged commit ab53e46 into main Sep 13, 2026
3 checks passed
@a-essawy
a-essawy deleted the fix/telemetry-nested-subcommands branch September 13, 2026 20:55
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