Skip to content

Commit ece0a29

Browse files
committed
refactor: extract shared buildCommand boilerplate into list-command.ts
Add LIST_TARGET_POSITIONAL, LIST_JSON_FLAG, LIST_CURSOR_FLAG, buildListLimitFlag, and LIST_BASE_ALIASES as shared constants so all four list commands stop redefining the same flag/positional shapes. Add buildOrgListCommand factory (Level B) for team and repo commands whose entire func body is dispatchOrgScopedList; reduces those files to config + column definitions + one call. team/list.ts and repo/list.ts each drop ~40 more lines. project/list.ts and issue/list.ts spread the shared constants, removing the duplicated positional/json/cursor/limit definitions. 12 new tests in test/lib/list-command.test.ts; 1850 unit tests pass.
1 parent 078dfa3 commit ece0a29

File tree

6 files changed

+425
-210
lines changed

6 files changed

+425
-210
lines changed

src/commands/issue/list.ts

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
listProjects,
1515
} from "../../lib/api-client.js";
1616
import { parseOrgProjectArg } from "../../lib/arg-parsing.js";
17-
import { buildCommand, numberParser } from "../../lib/command.js";
17+
import { buildCommand } from "../../lib/command.js";
1818
import {
1919
clearPaginationCursor,
2020
resolveOrgCursor,
@@ -39,6 +39,12 @@ import {
3939
muted,
4040
writeJson,
4141
} from "../../lib/formatters/index.js";
42+
import {
43+
buildListLimitFlag,
44+
LIST_BASE_ALIASES,
45+
LIST_JSON_FLAG,
46+
LIST_TARGET_POSITIONAL,
47+
} from "../../lib/list-command.js";
4248
import {
4349
type ResolvedTarget,
4450
resolveAllTargets,
@@ -398,51 +404,32 @@ export const listCommand = buildCommand({
398404
"In monorepos with multiple Sentry projects, shows issues from all detected projects.",
399405
},
400406
parameters: {
401-
positional: {
402-
kind: "tuple",
403-
parameters: [
404-
{
405-
placeholder: "target",
406-
brief: "Target: <org>/<project>, <org>/, or <project>",
407-
parse: String,
408-
optional: true,
409-
},
410-
],
411-
},
407+
positional: LIST_TARGET_POSITIONAL,
412408
flags: {
413409
query: {
414410
kind: "parsed",
415411
parse: String,
416412
brief: "Search query (Sentry search syntax)",
417413
optional: true,
418414
},
419-
limit: {
420-
kind: "parsed",
421-
parse: numberParser,
422-
brief: "Maximum number of issues to return",
423-
// Stricli requires string defaults (raw CLI input); numberParser converts to number
424-
default: "10",
425-
},
415+
limit: buildListLimitFlag("issues", "10"),
426416
sort: {
427417
kind: "parsed",
428418
parse: parseSort,
429419
brief: "Sort by: date, new, freq, user",
430420
default: "date" as const,
431421
},
432-
json: {
433-
kind: "boolean",
434-
brief: "Output as JSON",
435-
default: false,
436-
},
422+
json: LIST_JSON_FLAG,
437423
cursor: {
438424
kind: "parsed",
439425
parse: String,
426+
// Issue-specific cursor brief: cursor only works in <org>/ mode
440427
brief:
441428
'Pagination cursor — only for <org>/ mode (use "last" to continue)',
442429
optional: true,
443430
},
444431
},
445-
aliases: { q: "query", s: "sort", n: "limit", c: "cursor" },
432+
aliases: { ...LIST_BASE_ALIASES, q: "query", s: "sort" },
446433
},
447434
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: command entry point with inherent complexity
448435
async func(

src/commands/project/list.ts

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import {
2323
type ParsedOrgProject,
2424
parseOrgProjectArg,
2525
} from "../../lib/arg-parsing.js";
26-
import { buildCommand, numberParser } from "../../lib/command.js";
26+
import { buildCommand } from "../../lib/command.js";
2727
import { getDefaultOrganization } from "../../lib/db/defaults.js";
2828
import {
2929
clearPaginationCursor,
@@ -37,6 +37,13 @@ import {
3737
writeFooter,
3838
writeJson,
3939
} from "../../lib/formatters/index.js";
40+
import {
41+
buildListLimitFlag,
42+
LIST_BASE_ALIASES,
43+
LIST_CURSOR_FLAG,
44+
LIST_JSON_FLAG,
45+
LIST_TARGET_POSITIONAL,
46+
} from "../../lib/list-command.js";
4047
import { resolveAllTargets } from "../../lib/resolve-target.js";
4148
import { getApiBaseUrl } from "../../lib/sentry-client.js";
4249
import type { SentryProject, Writer } from "../../types/index.js";
@@ -623,44 +630,19 @@ export const listCommand = buildCommand({
623630
" sentry project list --json # output as JSON",
624631
},
625632
parameters: {
626-
positional: {
627-
kind: "tuple",
628-
parameters: [
629-
{
630-
placeholder: "target",
631-
brief: "Target: <org>/, <org>/<project>, or <project>",
632-
parse: String,
633-
optional: true,
634-
},
635-
],
636-
},
633+
positional: LIST_TARGET_POSITIONAL,
637634
flags: {
638-
limit: {
639-
kind: "parsed",
640-
parse: numberParser,
641-
brief: "Maximum number of projects to list",
642-
// Stricli requires string defaults (raw CLI input); numberParser converts to number
643-
default: "30",
644-
},
645-
json: {
646-
kind: "boolean",
647-
brief: "Output JSON",
648-
default: false,
649-
},
650-
cursor: {
651-
kind: "parsed",
652-
parse: String,
653-
brief: 'Pagination cursor (use "last" to continue from previous page)',
654-
optional: true,
655-
},
635+
limit: buildListLimitFlag("projects"),
636+
json: LIST_JSON_FLAG,
637+
cursor: LIST_CURSOR_FLAG,
656638
platform: {
657639
kind: "parsed",
658640
parse: String,
659641
brief: "Filter by platform (e.g., javascript, python)",
660642
optional: true,
661643
},
662644
},
663-
aliases: { n: "limit", p: "platform", c: "cursor" },
645+
aliases: { ...LIST_BASE_ALIASES, p: "platform" },
664646
},
665647
async func(
666648
this: SentryContext,

src/commands/repo/list.ts

Lines changed: 23 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,16 @@
1010
* - Bare org slug (e.g., sentry) - lists repos for that org
1111
*/
1212

13-
import type { SentryContext } from "../../context.js";
1413
import {
1514
listRepositories,
1615
listRepositoriesPaginated,
1716
} from "../../lib/api-client.js";
18-
import { parseOrgProjectArg } from "../../lib/arg-parsing.js";
19-
import { buildCommand, numberParser } from "../../lib/command.js";
2017
import { type Column, writeTable } from "../../lib/formatters/table.js";
2118
import {
22-
dispatchOrgScopedList,
23-
type OrgListConfig,
24-
} from "../../lib/org-list.js";
19+
buildOrgListCommand,
20+
type OrgListCommandDocs,
21+
} from "../../lib/list-command.js";
22+
import type { OrgListConfig } from "../../lib/org-list.js";
2523
import type { SentryRepository, Writer } from "../../types/index.js";
2624

2725
/** Command key for pagination cursor storage */
@@ -52,74 +50,22 @@ const repoListConfig: OrgListConfig<SentryRepository, RepositoryWithOrg> = {
5250
writeTable(stdout, repos, REPO_COLUMNS),
5351
};
5452

55-
export const listCommand = buildCommand({
56-
docs: {
57-
brief: "List repositories",
58-
fullDescription:
59-
"List repositories connected to an organization.\n\n" +
60-
"Target specification:\n" +
61-
" sentry repo list # auto-detect from DSN or config\n" +
62-
" sentry repo list <org>/ # list all repos in org (paginated)\n" +
63-
" sentry repo list <org>/<proj> # list repos in org (project context)\n" +
64-
" sentry repo list <org> # list repos in org\n\n" +
65-
"Pagination:\n" +
66-
" sentry repo list <org>/ -c last # continue from last page\n\n" +
67-
"Examples:\n" +
68-
" sentry repo list # auto-detect or list all\n" +
69-
" sentry repo list my-org/ # list repositories in my-org (paginated)\n" +
70-
" sentry repo list --limit 10\n" +
71-
" sentry repo list --json",
72-
},
73-
parameters: {
74-
positional: {
75-
kind: "tuple",
76-
parameters: [
77-
{
78-
placeholder: "target",
79-
brief: "Target: <org>/, <org>/<project>, or <org>",
80-
parse: String,
81-
optional: true,
82-
},
83-
],
84-
},
85-
flags: {
86-
limit: {
87-
kind: "parsed",
88-
parse: numberParser,
89-
brief: "Maximum number of repositories to list",
90-
default: "30",
91-
},
92-
json: {
93-
kind: "boolean",
94-
brief: "Output JSON",
95-
default: false,
96-
},
97-
cursor: {
98-
kind: "parsed",
99-
parse: String,
100-
brief: 'Pagination cursor (use "last" to continue from previous page)',
101-
optional: true,
102-
},
103-
},
104-
aliases: { n: "limit", c: "cursor" },
105-
},
106-
async func(
107-
this: SentryContext,
108-
flags: {
109-
readonly limit: number;
110-
readonly json: boolean;
111-
readonly cursor?: string;
112-
},
113-
target?: string
114-
): Promise<void> {
115-
const { stdout, cwd } = this;
116-
const parsed = parseOrgProjectArg(target);
117-
await dispatchOrgScopedList({
118-
config: repoListConfig,
119-
stdout,
120-
cwd,
121-
flags,
122-
parsed,
123-
});
124-
},
125-
});
53+
const docs: OrgListCommandDocs = {
54+
brief: "List repositories",
55+
fullDescription:
56+
"List repositories connected to an organization.\n\n" +
57+
"Target specification:\n" +
58+
" sentry repo list # auto-detect from DSN or config\n" +
59+
" sentry repo list <org>/ # list all repos in org (paginated)\n" +
60+
" sentry repo list <org>/<proj> # list repos in org (project context)\n" +
61+
" sentry repo list <org> # list repos in org\n\n" +
62+
"Pagination:\n" +
63+
" sentry repo list <org>/ -c last # continue from last page\n\n" +
64+
"Examples:\n" +
65+
" sentry repo list # auto-detect or list all\n" +
66+
" sentry repo list my-org/ # list repositories in my-org (paginated)\n" +
67+
" sentry repo list --limit 10\n" +
68+
" sentry repo list --json",
69+
};
70+
71+
export const listCommand = buildOrgListCommand(repoListConfig, docs);

src/commands/team/list.ts

Lines changed: 23 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,13 @@
1010
* - Cross-org project search (e.g., sentry)
1111
*/
1212

13-
import type { SentryContext } from "../../context.js";
1413
import { listTeams, listTeamsPaginated } from "../../lib/api-client.js";
15-
import { parseOrgProjectArg } from "../../lib/arg-parsing.js";
16-
import { buildCommand, numberParser } from "../../lib/command.js";
1714
import { type Column, writeTable } from "../../lib/formatters/table.js";
1815
import {
19-
dispatchOrgScopedList,
20-
type OrgListConfig,
21-
} from "../../lib/org-list.js";
16+
buildOrgListCommand,
17+
type OrgListCommandDocs,
18+
} from "../../lib/list-command.js";
19+
import type { OrgListConfig } from "../../lib/org-list.js";
2220
import type { SentryTeam, Writer } from "../../types/index.js";
2321

2422
/** Command key for pagination cursor storage */
@@ -53,74 +51,22 @@ const teamListConfig: OrgListConfig<SentryTeam, TeamWithOrg> = {
5351
writeTable(stdout, teams, TEAM_COLUMNS),
5452
};
5553

56-
export const listCommand = buildCommand({
57-
docs: {
58-
brief: "List teams",
59-
fullDescription:
60-
"List teams in an organization.\n\n" +
61-
"Target specification:\n" +
62-
" sentry team list # auto-detect from DSN or config\n" +
63-
" sentry team list <org>/ # list all teams in org (paginated)\n" +
64-
" sentry team list <org>/<proj> # list teams in org (project context)\n" +
65-
" sentry team list <org> # list teams in org\n\n" +
66-
"Pagination:\n" +
67-
" sentry team list <org>/ -c last # continue from last page\n\n" +
68-
"Examples:\n" +
69-
" sentry team list # auto-detect or list all\n" +
70-
" sentry team list my-org/ # list teams in my-org (paginated)\n" +
71-
" sentry team list --limit 10\n" +
72-
" sentry team list --json",
73-
},
74-
parameters: {
75-
positional: {
76-
kind: "tuple",
77-
parameters: [
78-
{
79-
placeholder: "target",
80-
brief: "Target: <org>/, <org>/<project>, or <org>",
81-
parse: String,
82-
optional: true,
83-
},
84-
],
85-
},
86-
flags: {
87-
limit: {
88-
kind: "parsed",
89-
parse: numberParser,
90-
brief: "Maximum number of teams to list",
91-
default: "30",
92-
},
93-
json: {
94-
kind: "boolean",
95-
brief: "Output JSON",
96-
default: false,
97-
},
98-
cursor: {
99-
kind: "parsed",
100-
parse: String,
101-
brief: 'Pagination cursor (use "last" to continue from previous page)',
102-
optional: true,
103-
},
104-
},
105-
aliases: { n: "limit", c: "cursor" },
106-
},
107-
async func(
108-
this: SentryContext,
109-
flags: {
110-
readonly limit: number;
111-
readonly json: boolean;
112-
readonly cursor?: string;
113-
},
114-
target?: string
115-
): Promise<void> {
116-
const { stdout, cwd } = this;
117-
const parsed = parseOrgProjectArg(target);
118-
await dispatchOrgScopedList({
119-
config: teamListConfig,
120-
stdout,
121-
cwd,
122-
flags,
123-
parsed,
124-
});
125-
},
126-
});
54+
const docs: OrgListCommandDocs = {
55+
brief: "List teams",
56+
fullDescription:
57+
"List teams in an organization.\n\n" +
58+
"Target specification:\n" +
59+
" sentry team list # auto-detect from DSN or config\n" +
60+
" sentry team list <org>/ # list all teams in org (paginated)\n" +
61+
" sentry team list <org>/<proj> # list teams in org (project context)\n" +
62+
" sentry team list <org> # list teams in org\n\n" +
63+
"Pagination:\n" +
64+
" sentry team list <org>/ -c last # continue from last page\n\n" +
65+
"Examples:\n" +
66+
" sentry team list # auto-detect or list all\n" +
67+
" sentry team list my-org/ # list teams in my-org (paginated)\n" +
68+
" sentry team list --limit 10\n" +
69+
" sentry team list --json",
70+
};
71+
72+
export const listCommand = buildOrgListCommand(teamListConfig, docs);

0 commit comments

Comments
 (0)