88// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
99import * as Sentry from "@sentry/bun" ;
1010import type { SentryContext } from "../../context.js" ;
11- import { findProjectsBySlug , listLogs } from "../../lib/api-client.js" ;
12- import { parseOrgProjectArg } from "../../lib/arg-parsing.js" ;
11+ import { listLogs } from "../../lib/api-client.js" ;
12+ import { validateLimit } from "../../lib/arg-parsing.js" ;
1313import { buildCommand } from "../../lib/command.js" ;
14- import { AuthError , ContextError } from "../../lib/errors.js" ;
14+ import { AuthError } from "../../lib/errors.js" ;
1515import {
1616 formatLogRow ,
1717 formatLogsHeader ,
1818 writeFooter ,
1919 writeJson ,
2020} from "../../lib/formatters/index.js" ;
21- import { resolveOrgAndProject } from "../../lib/resolve-target.js" ;
21+ import { resolveOrgProjectFromArg } from "../../lib/resolve-target.js" ;
2222import { getUpdateNotification } from "../../lib/version-check.js" ;
2323import type { SentryLog , Writer } from "../../types/index.js" ;
2424
@@ -29,9 +29,6 @@ type ListFlags = {
2929 readonly json : boolean ;
3030} ;
3131
32- /** Usage hint for ContextError messages */
33- const USAGE_HINT = "sentry log list <org>/<project>" ;
34-
3532/** Maximum allowed value for --limit flag */
3633const MAX_LIMIT = 1000 ;
3734
@@ -44,17 +41,14 @@ const DEFAULT_LIMIT = 100;
4441/** Default poll interval in seconds for --follow mode */
4542const DEFAULT_POLL_INTERVAL = 2 ;
4643
44+ /** Command name used in resolver error messages */
45+ const COMMAND_NAME = "log list" ;
46+
4747/**
48- * Validate that --limit value is within allowed range.
49- *
50- * @throws Error if value is outside MIN_LIMIT..MAX_LIMIT range
48+ * Parse --limit flag, delegating range validation to shared utility.
5149 */
52- function validateLimit ( value : string ) : number {
53- const num = Number . parseInt ( value , 10 ) ;
54- if ( Number . isNaN ( num ) || num < MIN_LIMIT || num > MAX_LIMIT ) {
55- throw new Error ( `--limit must be between ${ MIN_LIMIT } and ${ MAX_LIMIT } ` ) ;
56- }
57- return num ;
50+ function parseLimit ( value : string ) : number {
51+ return validateLimit ( value , MIN_LIMIT , MAX_LIMIT ) ;
5852}
5953
6054/**
@@ -233,83 +227,6 @@ async function executeFollowMode(options: FollowModeOptions): Promise<void> {
233227 }
234228}
235229
236- /** Resolved org and project for log commands */
237- type ResolvedLogTarget = {
238- org : string ;
239- project : string ;
240- } ;
241-
242- /**
243- * Resolve org/project from parsed argument or auto-detection.
244- *
245- * Handles:
246- * - explicit: "org/project" → use directly
247- * - project-search: "project" → find project across all orgs
248- * - auto-detect: no input → use DSN detection or config defaults
249- *
250- * @throws {ContextError } When target cannot be resolved
251- */
252- async function resolveLogTarget (
253- target : string | undefined ,
254- cwd : string
255- ) : Promise < ResolvedLogTarget > {
256- const parsed = parseOrgProjectArg ( target ) ;
257-
258- switch ( parsed . type ) {
259- case "explicit" :
260- return { org : parsed . org , project : parsed . project } ;
261-
262- case "org-all" :
263- throw new ContextError (
264- "Project" ,
265- `Please specify a project: sentry log list ${ parsed . org } /<project>`
266- ) ;
267-
268- case "project-search" : {
269- // Find project across all orgs
270- const matches = await findProjectsBySlug ( parsed . projectSlug ) ;
271-
272- if ( matches . length === 0 ) {
273- throw new ContextError (
274- "Project" ,
275- `No project '${ parsed . projectSlug } ' found in any accessible organization.\n\n` +
276- `Try: sentry log list <org>/${ parsed . projectSlug } `
277- ) ;
278- }
279-
280- if ( matches . length > 1 ) {
281- const options = matches
282- . map ( ( m ) => ` sentry log list ${ m . orgSlug } /${ m . slug } ` )
283- . join ( "\n" ) ;
284- throw new ContextError (
285- "Project" ,
286- `Found '${ parsed . projectSlug } ' in ${ matches . length } organizations. Please specify:\n${ options } `
287- ) ;
288- }
289-
290- // Safe: we checked matches.length === 1 above, so first element exists
291- const match = matches [ 0 ] as ( typeof matches ) [ number ] ;
292- return { org : match . orgSlug , project : match . slug } ;
293- }
294-
295- case "auto-detect" : {
296- const resolved = await resolveOrgAndProject ( {
297- cwd,
298- usageHint : USAGE_HINT ,
299- } ) ;
300- if ( ! resolved ) {
301- throw new ContextError ( "Organization and project" , USAGE_HINT ) ;
302- }
303- return { org : resolved . org , project : resolved . project } ;
304- }
305-
306- default : {
307- const _exhaustiveCheck : never = parsed ;
308- throw new Error ( `Unexpected parsed type: ${ _exhaustiveCheck } ` ) ;
309- }
310- }
311- }
312-
313230export const listCommand = buildCommand ( {
314231 docs : {
315232 brief : "List logs from a project" ,
@@ -341,7 +258,7 @@ export const listCommand = buildCommand({
341258 flags : {
342259 limit : {
343260 kind : "parsed" ,
344- parse : validateLimit ,
261+ parse : parseLimit ,
345262 brief : `Number of log entries (${ MIN_LIMIT } -${ MAX_LIMIT } )` ,
346263 default : String ( DEFAULT_LIMIT ) ,
347264 } ,
@@ -378,7 +295,11 @@ export const listCommand = buildCommand({
378295 const { stdout, stderr, cwd, setContext } = this ;
379296
380297 // Resolve org/project from positional arg, config, or DSN auto-detection
381- const { org, project } = await resolveLogTarget ( target , cwd ) ;
298+ const { org, project } = await resolveOrgProjectFromArg (
299+ target ,
300+ cwd ,
301+ COMMAND_NAME
302+ ) ;
382303 setContext ( [ org ] , [ project ] ) ;
383304
384305 if ( flags . follow ) {
0 commit comments