Repository navigation
feat(cli): Parse and resolve org/project targets in their positional - #1594
Merged
Merged
Conversation
Add parseOrgProjectTargetArg, a positional parser that hands func a TargetArg wrapping the raw input and its parseOrgProjectArg result. The parse runs lazily on first access inside func, so bad input still throws the parser's CliError with its exit code instead of Stricli's argument parse error. Plural-alias interception, help recovery and telemetry read the raw input. The typed SDK invoke path now runs positional parsers too, since it calls func without Stricli's scanner. Completion kinds now come from a tag on the parser (withCompletionTarget), so parsing and marker-only parsers complete the same way. Migrate trace list, replay list, feedback list, project view and project delete. Refs #1564 Co-Authored-By: Burak Yigit Kaya <burak.kaya@sentry.io>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
BYK
requested changes
Oct 22, 2026
BYK
left a comment
Member
There was a problem hiding this comment.
Not really a fan of the two-stage parsing. I do understand the benefits but I was hoping to find a way to avoid the in-func resolve or parse call.
Also I'd love to make tests use the applyPositionalParsers or something like that rather than repeating the resolver/parser calls over and over.
Review on #1594 asked to drop the two-stage TargetArg parse inside func. parseOrgProjectPositional now returns the ParsedOrgProject directly and commands take it as their argument, defaulting an omitted optional target to AUTO_DETECT_TARGET. buildCommand wraps every positional parser (positional-parse.ts). A CliError from a parser comes back as a DeferredPositionalError value, and the command wrapper throws it before the auth guard. Bad input keeps its CliError exit code and output instead of Stricli's "Failed to parse" (exit -4). The wrapper also records the raw input of parsed values, which plural-alias interception, help recovery and telemetry read through rawPositional. Tests call commands through loadCommandFunc, a test helper that runs the command's own positional parsers via applyPositionalParsers, so they pass raw strings again. Refs #1564
…t-positionals # Conflicts: # packages/cli/src/lib/sdk-invoke.ts # packages/cli/test/helpers.ts Co-Authored-By: BYK <burak.kaya@sentry.io>
BYK
requested changes
Oct 23, 2026
BYK
left a comment
Member
There was a problem hiding this comment.
What's the URL side effect you keep mentioning with the new approach and what are the dangers/gotchas of it?
Review on #1594 asked for lazy parsing that stays transparent to commands, tests, and the SDK. parseOrgProjectPositional now hands func a stand-in typed as ParsedOrgProject that runs parseOrgProjectArg on first property access. Commands read it like the parsed value, but the parse (and its URL-context side effect) runs inside func after the auth guard and rc URL check, as on main. Bad input throws its CliError from func, so there is nothing to defer out of Stricli's scanner. buildCommand runs these lazy parsers on positionals that arrive as plain strings, so tests calling command.loader() and the SDK invoke path pass raw strings again. That drops DeferredPositionalError, loadCommandFunc, and the SDK's applyPositionalParsers. Refs #1564 Co-Authored-By: BYK <burak.kaya@sentry.io>
Target positionals now carry their resolver. A command declares `orgOptionalTarget(COMMAND_NAME)`, `projectBoundTarget(COMMAND_NAME)`, or `orgProjectTarget(resolve)` as the parser and gets back a value that reads like a `ParsedOrgProject` plus a `resolved` promise. `func` awaits `target.resolved` instead of choosing and calling a resolver. Parsing still runs on the first property read and resolution on the first `resolved` read, both inside `func`, so the auth guard, rc URL check, and pasted-URL host handling keep main's order. Omitted optional targets get a stand-in too, so there is no default value to remember. Refs #1564 Co-Authored-By: Burak Yigit Kaya <burak.kaya@sentry.io>
BYK
requested changes
Oct 23, 2026
Target resolvers now read the command name from `commandPrefix`, which Stricli's `forCommand` sets and the SDK invoker now sets too. So `orgOptionalTarget` and `projectBoundTarget` are plain parsers and the `COMMAND_NAME` constants they needed are gone. `buildDeleteCommand` marks its lazy positionals destructive, and the target resolver then refuses an auto-detected target with `requireExplicitTarget`. `project delete` no longer needs its own resolver for that. A new `sentry-cli/require-target-resolution` lint rule fails a command function that takes a bound target type but never reads `.resolved`, since skipping it would also skip the resolver and the destructive guard. Refs #1564 Co-Authored-By: Burak Yigit Kaya <burak.kaya@sentry.io>
`LazyPositional` now exposes `parsed`, `resolved`, and `raw` as plain class members instead of a Proxy that mimics the parsed value. Commands read `target.parsed.type`. The thenable guard, own-key and descriptor traps, and the raw-input WeakMap are gone; `rawPositional` is an `instanceof` check. The typed SDK invoker also sets `directInvoke` on its context, which turns off `help` positional recovery. A failing SDK call whose target is `help` throws its error again, as on main, instead of returning help text. Refs #1564 Co-Authored-By: Burak Yigit Kaya <burak.kaya@sentry.io>
BYK
marked this pull request as ready for review
October 23, 2026 19:41
sentry-junior Bot
added a commit
that referenced
this pull request
Oct 23, 2026
Follow-up to #1594: `buildCommand` now calls `markDestructive` when `destructive: true`, instead of `buildDeleteCommand` doing it, so the `destructive` flag is the single source for the Safety Rules list and the auto-detect guard. `issue merge` and `trial start` get the guard if they move to lazy target positionals. check:doc-examples now recognizes target positionals by their `org/project` completion kind, which covers the new lazy target parsers as well as `parseOrgProjectTarget`.
sentry-junior Bot
added a commit
that referenced
this pull request
Oct 23, 2026
Move attribute list to the target positional API from #1594: the target uses orgOptionalTarget, func awaits target.resolved, and the COMMAND_NAME constant goes away. The help's target section now matches the other migrated list commands. Co-Authored-By: BYK <burak.kaya@sentry.io>
This was referenced Oct 23, 2026
BYK
added a commit
that referenced
this pull request
Oct 24, 2026
Refs #1564. This continues #1594 with the one remaining `[<org>/][<project>]` target that nothing blocks. `explore` declared its target with `String` and called `resolveOrgOptionalFromArg` inside `func`. It now uses `orgOptionalTarget` and awaits `target.resolved`, the same way `feedback list` and `replay list` do. Resolution is unchanged: it still goes through `resolveOrgOptionalTarget` with the same command name. User-facing change: the parser carries the org/project completion tag, so `sentry explore <TAB>` now completes `org/`, `org/project` and project aliases. Before this, `explore` had no target completion. Other users of the `*FromArg` helpers are left alone on purpose: - `agent-conversation list` uses `LIST_TARGET_POSITIONAL`, which waits on #1575. - `log list`, `replay view` and `replay download` take variadic positionals. What each argument means depends on how many were given, so they keep parsing in `func`. <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:local%3Aweb%3Aa3773d7739de1b873375a6f1 --> -- [View Junior Session in Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3Aa3773d7739de1b873375a6f1/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <burak.kaya@sentry.io>
This branch was previously deployed
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.
Refs #1564. This is the first slice: the mechanism plus five commands.
A
[<org>/][<project>]positional now owns both parsing and resolution. The command picks a parser fromsrc/lib/target-positional.ts, and that parser comes with its resolver:funcgets aLazyPositionalwith three members:target.parsed(theParsedOrgProject),target.resolved(a promise of the resolver's result), andtarget.raw(what the user typed):funcnever callsparseOrgProjectArgor a resolver, never picks which resolver to use, and needs no default for an omitted target. An omitted optional target arrives as auto-detect.project viewmoves its target switch intoresolveViewTargetsand readstarget.parsed.type.this.commandPrefix, which Stricli'sforCommandsets and the SDK invoker now sets from its command path. TheCOMMAND_NAMEconstants these commands used are gone. A context withoutcommandPrefixmakes resolution throw rather than print a hint with a blank command name; test mock contexts set it.buildDeleteCommandcallsmarkDestructiveon its lazy positionals, and the target resolver then runsrequireExplicitTarget.project deleteuses plainprojectBoundTarget. Once docs(cli): Generate and check docs that mirror CLI behavior #1626'sdestructiveflag lands, the call can move intobuildCommandsoissue mergeandtrial startget it too.sentry-cli/require-target-resolutionfails a command function that takesOrgOptionalTarget,ProjectBoundTargetorOrgProjectTarget<R>but never reads.resolved, since skipping it skips the resolver and the destructive guard.When things run. Both steps are lazy (
LazyPositionalinsrc/lib/positional-parse.ts, a plain class with memoized getters). The parse runs on the firstparsedread and the resolver on the firstresolvedread; each runs once. Both happen insidefunc, so the order matches main:CliErrorsemantics.sentry project view @latestexits 21 with theValidationError, not Stricli'sFailed to parse …(exit -4). A parse error also rejectsresolved.parseOrgProjectArg's URL side effect still runs after those checks. For a pasted Sentry URL it callsapplySentryUrlContext, which can switchSENTRY_URL/SENTRY_HOST, save that host as the default, or throwHostScopeError. Running it at Stricli scan time would have let the auth guard and rc URL check run against the host taken from the URL.Resolution stays a promise because resolvers need auth, config, DSN detection, and API calls. Shared code that handles any positional (
buildListCommand's plural-alias interception,helprecovery,setArgsContext) reads the raw input withrawPositional(value), aninstanceof LazyPositionalcheck. Stricli calls parsers with the command context asthis; the resolver gets that context pluscommandName.Callers that skip Stricli. The typed SDK (
buildInvoker) and tests that callcommand.loader()pass raw strings.buildCommandrunsapplyLazyPositionalson them, and on omitted optional positionals, so every caller keeps passing what the user typed andfuncalways gets the same shape. When plural-alias interception drops the target,buildListCommandruns it again so the dropped target becomes auto-detect.sdk.project.view({ orgProject: "@latest" })is rejected instead of silently auto-detecting. The SDK invoker also setsdirectInvoke, which turns offhelppositional recovery: a failing SDK call whose target ishelpthrows its error, as on main, instead of returning help text.Shell completion is unchanged. Completion kinds come from a tag on the parser (
withCompletionTarget), shared withparseOrgTargetandparseOrgProjectTarget. The generated targets file matches main.Migrated:
trace list,replay list,feedback list,project view,project delete.Not in this PR:
LIST_TARGET_POSITIONAL/buildOrgListCommand. feat(cli): Name filter for project, team, repo, monitor and integration lists #1575 (splitListTargetAndFilter) reads the raw target and splits it before parsing (for exampleacme/web-*). These should move after it lands, usingtarget.raw.parseIssueTarget/parseDashboardTargetin the same files. When one of the two PRs rebases, its parsers should switch from thePARSER_TARGETSmap towithCompletionTarget.[<org>/<project>/]<id>positionals (trace view,span list,event view,log view, …). Stricli parses each argument on its own, but what each one means depends on how many were given. Those keep parsing infunc.org/versionpositionals. They already shareresolveReleaseTarget.via BYK.
--
View Junior Session [Sentry]