fix: count rb storage in telemetry and encode storage URIs - #161
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The gap
rb storageis the first command with nestedsubCommands(list/ls) andno top-level
run. The usage-telemetry wrapper insrc/main.tsonly wrappedeach top-level command's
run(), sorb storage listandrb storage lsnever emitted the
cli_commandusage event that every other command emits.The fix
Moved the wrapper from
main.tsintolib/telemetry.tsasinstrumentCommandsand made it recurse through
subCommandsto any depth, instead of onlyhandling the top level. It is generic -- nothing in it is specific to
storage.A nested command is named
"parent child"in the existing telemetrycommandproperty (e.g."storage list"), the same words citty's own usageline uses for it. No new schema field. A mid-level command with no
runofits own (like
storageitself) still does not emit anything -- only aresolved command that actually has a
rungets wrapped.The wrapper now takes an injectable capture function, defaulting to
captureCommand, the same DI patterncheckForUpdatealready uses forfetch. That's what makes it unit-testable without a network call or a realtelemetry key.
Related gap (reported, not fixed here)
Commands that terminate via
process.exit()mid-run skip the wrapper'sfinally, so no usage event flushes for that invocation. This is not aone-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, riskierchange than this one and out of scope here.
Tests
Added
src/__tests__/telemetry-instrumentation.test.ts, TDD'd against thereal citty
runCommanddispatch (not a hand-rolled stand-in), covering:runis wrapped and captures once under its own name"parent child"runof its own does not capturesuccess: falseand still rethrownstoragenames, proving itisn't special-cased
pnpm test(272/272) andpnpm typecheckare green.Storage URI encoding (Refs rendobar/rendobar#694)
An object key can hold
%,?or#.rb storage lsprinted those rawafter
storage://<id>/, which produced a URI a?or#breaks in transitand that
parseStorageTargetcould not always read back (a bare%notfollowed by two hex digits throws in
decodeURIComponent, and a literal?or
#is rejected downstream).Added
encodeStoragePathtosrc/lib/storage-view.ts, matching the platformcontract in
packages/shared/src/storage/refs.ts(rendobar/rendobar#696):escape
%first, then?, then#, nothing else.formatListingnow runsevery folder and key through it before building the printed
storage://<id>/<path>line.rb storage list --jsonandrb storage ls --jsoncarry rawfolders/keyfields, not URIs, so they are unchanged.parseStorageTargetnow decodes astorage://URI's folder part the sameway the platform does (
decodeURIComponent, literal text if that throws),so a URI copied from
rb storage lsoutput browses the right folder again.A bare
<id>/<folder>typed by hand stays literal, since nothing encoded it.--deliveris 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, andparseStorageTargetdecoding astorage://URI while leaving a typed path literal.