Skip to content

Commit 7835aed

Browse files
authored
Merge pull request #16 from betegon/feat/org-project-get
feat(cli): add org get and project get commands
2 parents 9ffc22e + 8d364b1 commit 7835aed

File tree

15 files changed

+489
-22
lines changed

15 files changed

+489
-22
lines changed

packages/cli/src/commands/api.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import { buildCommand } from "@stricli/core";
99
import type { SentryContext } from "../context.js";
1010
import { rawApiRequest } from "../lib/api-client.js";
11+
import type { Writer } from "../types/index.js";
1112

1213
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
1314

@@ -124,7 +125,7 @@ function parseHeaders(headers: string[]): Record<string, string> {
124125
* Write response headers to stdout
125126
*/
126127
function writeResponseHeaders(
127-
stdout: NodeJS.WriteStream,
128+
stdout: Writer,
128129
status: number,
129130
headers: Headers
130131
): void {
@@ -138,7 +139,7 @@ function writeResponseHeaders(
138139
/**
139140
* Write response body to stdout
140141
*/
141-
function writeResponseBody(stdout: NodeJS.WriteStream, body: unknown): void {
142+
function writeResponseBody(stdout: Writer, body: unknown): void {
142143
if (body === null || body === undefined) {
143144
return;
144145
}

packages/cli/src/commands/auth/status.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {
1515
readConfig,
1616
} from "../../lib/config.js";
1717
import { formatExpiration, maskToken } from "../../lib/formatters/human.js";
18-
import type { SentryConfig } from "../../types/index.js";
18+
import type { SentryConfig, Writer } from "../../types/index.js";
1919

2020
type StatusFlags = {
2121
readonly showToken: boolean;
@@ -25,7 +25,7 @@ type StatusFlags = {
2525
* Write token information
2626
*/
2727
function writeTokenInfo(
28-
stdout: NodeJS.WriteStream,
28+
stdout: Writer,
2929
config: SentryConfig,
3030
showToken: boolean
3131
): void {
@@ -46,7 +46,7 @@ function writeTokenInfo(
4646
/**
4747
* Write default settings
4848
*/
49-
async function writeDefaults(stdout: NodeJS.WriteStream): Promise<void> {
49+
async function writeDefaults(stdout: Writer): Promise<void> {
5050
const defaultOrg = await getDefaultOrganization();
5151
const defaultProject = await getDefaultProject();
5252

@@ -66,7 +66,7 @@ async function writeDefaults(stdout: NodeJS.WriteStream): Promise<void> {
6666
/**
6767
* Verify credentials by fetching organizations
6868
*/
69-
async function verifyCredentials(stdout: NodeJS.WriteStream): Promise<void> {
69+
async function verifyCredentials(stdout: Writer): Promise<void> {
7070
stdout.write("\nVerifying credentials...\n");
7171

7272
try {

packages/cli/src/commands/event/get.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ type GetFlags = {
2626
* @param detectedFrom - Optional source description for auto-detection
2727
*/
2828
function writeHumanOutput(
29-
stdout: NodeJS.WriteStream,
29+
stdout: Writer,
3030
event: SentryEvent,
3131
detectedFrom?: string
3232
): void {

packages/cli/src/commands/issue/get.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async function tryGetLatestEvent(
4343
* Write human-readable issue output
4444
*/
4545
function writeHumanOutput(
46-
stdout: NodeJS.WriteStream,
46+
stdout: Writer,
4747
issue: SentryIssue,
4848
event?: SentryEvent
4949
): void {

packages/cli/src/commands/issue/list.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function parseSort(value: string): SortValue {
4848
* Write the issue list header with column titles.
4949
*/
5050
function writeListHeader(
51-
stdout: NodeJS.WriteStream,
51+
stdout: Writer,
5252
org: string,
5353
project: string,
5454
count: number
@@ -61,10 +61,7 @@ function writeListHeader(
6161
/**
6262
* Write formatted issue rows to stdout.
6363
*/
64-
function writeIssueRows(
65-
stdout: NodeJS.WriteStream,
66-
issues: SentryIssue[]
67-
): void {
64+
function writeIssueRows(stdout: Writer, issues: SentryIssue[]): void {
6865
for (const issue of issues) {
6966
stdout.write(`${formatIssueRow(issue)}\n`);
7067
}
@@ -73,7 +70,7 @@ function writeIssueRows(
7370
/**
7471
* Write footer with usage tip.
7572
*/
76-
function writeListFooter(stdout: NodeJS.WriteStream): void {
73+
function writeListFooter(stdout: Writer): void {
7774
stdout.write(
7875
"\nTip: Use 'sentry issue get <SHORT_ID>' to view issue details.\n"
7976
);
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/**
2+
* sentry org get
3+
*
4+
* Get detailed information about a Sentry organization.
5+
*/
6+
7+
import { buildCommand } from "@stricli/core";
8+
import type { SentryContext } from "../../context.js";
9+
import { getOrganization } from "../../lib/api-client.js";
10+
import { formatOrgDetails } from "../../lib/formatters/human.js";
11+
import { writeJson } from "../../lib/formatters/json.js";
12+
import { resolveOrg } from "../../lib/resolve-target.js";
13+
14+
type GetFlags = {
15+
readonly json: boolean;
16+
};
17+
18+
/**
19+
* Write human-readable organization output to stdout.
20+
*
21+
* @param stdout - Stream to write formatted output
22+
* @param org - Organization data to display
23+
* @param detectedFrom - Optional source description if org was auto-detected
24+
*/
25+
function writeHumanOutput(
26+
stdout: Writer,
27+
org: Parameters<typeof formatOrgDetails>[0],
28+
detectedFrom?: string
29+
): void {
30+
const lines = formatOrgDetails(org);
31+
stdout.write(`${lines.join("\n")}\n`);
32+
33+
if (detectedFrom) {
34+
stdout.write(`\nDetected from ${detectedFrom}\n`);
35+
}
36+
}
37+
38+
export const getCommand = buildCommand({
39+
docs: {
40+
brief: "Get details of an organization",
41+
fullDescription:
42+
"Retrieve detailed information about a Sentry organization.\n\n" +
43+
"The organization is resolved from:\n" +
44+
" 1. Positional argument <org-slug>\n" +
45+
" 2. Config defaults\n" +
46+
" 3. SENTRY_DSN environment variable or source code detection",
47+
},
48+
parameters: {
49+
positional: {
50+
kind: "tuple",
51+
parameters: [
52+
{
53+
brief: "Organization slug (optional if auto-detected)",
54+
parse: String,
55+
optional: true,
56+
},
57+
],
58+
},
59+
flags: {
60+
json: {
61+
kind: "boolean",
62+
brief: "Output as JSON",
63+
default: false,
64+
},
65+
},
66+
},
67+
async func(
68+
this: SentryContext,
69+
flags: GetFlags,
70+
orgSlug?: string
71+
): Promise<void> {
72+
const { process, cwd } = this;
73+
const { stdout } = process;
74+
75+
const resolved = await resolveOrg({ org: orgSlug, cwd });
76+
77+
if (!resolved) {
78+
throw new Error(
79+
"Organization is required.\n\n" +
80+
"Please specify it using:\n" +
81+
" sentry org get <org-slug>\n\n" +
82+
"Or set SENTRY_DSN environment variable for automatic detection."
83+
);
84+
}
85+
86+
const org = await getOrganization(resolved.org);
87+
88+
if (flags.json) {
89+
writeJson(stdout, org);
90+
return;
91+
}
92+
93+
writeHumanOutput(stdout, org, resolved.detectedFrom);
94+
},
95+
});

packages/cli/src/commands/org/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { buildRouteMap } from "@stricli/core";
2+
import { getCommand } from "./get.js";
23
import { listCommand } from "./list.js";
34

45
export const orgRoute = buildRouteMap({
56
routes: {
67
list: listCommand,
8+
get: getCommand,
79
},
810
docs: {
911
brief: "Work with Sentry organizations",
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/**
2+
* sentry project get
3+
*
4+
* Get detailed information about a Sentry project.
5+
*/
6+
7+
import { buildCommand } from "@stricli/core";
8+
import type { SentryContext } from "../../context.js";
9+
import { getProject } from "../../lib/api-client.js";
10+
import { formatProjectDetails } from "../../lib/formatters/human.js";
11+
import { writeJson } from "../../lib/formatters/json.js";
12+
import { resolveOrgAndProject } from "../../lib/resolve-target.js";
13+
14+
type GetFlags = {
15+
readonly org?: string;
16+
readonly json: boolean;
17+
};
18+
19+
/**
20+
* Write human-readable project output to stdout.
21+
*
22+
* @param stdout - Stream to write formatted output
23+
* @param project - Project data to display
24+
* @param detectedFrom - Optional source description if project was auto-detected
25+
*/
26+
function writeHumanOutput(
27+
stdout: Writer,
28+
project: Parameters<typeof formatProjectDetails>[0],
29+
detectedFrom?: string
30+
): void {
31+
const lines = formatProjectDetails(project);
32+
stdout.write(`${lines.join("\n")}\n`);
33+
34+
if (detectedFrom) {
35+
stdout.write(`\nDetected from ${detectedFrom}\n`);
36+
}
37+
}
38+
39+
export const getCommand = buildCommand({
40+
docs: {
41+
brief: "Get details of a project",
42+
fullDescription:
43+
"Retrieve detailed information about a Sentry project.\n\n" +
44+
"The organization and project are resolved from:\n" +
45+
" 1. Positional argument <project-slug> and --org flag\n" +
46+
" 2. Config defaults\n" +
47+
" 3. SENTRY_DSN environment variable or source code detection",
48+
},
49+
parameters: {
50+
positional: {
51+
kind: "tuple",
52+
parameters: [
53+
{
54+
brief: "Project slug (optional if auto-detected)",
55+
parse: String,
56+
optional: true,
57+
},
58+
],
59+
},
60+
flags: {
61+
org: {
62+
kind: "parsed",
63+
parse: String,
64+
brief: "Organization slug",
65+
optional: true,
66+
},
67+
json: {
68+
kind: "boolean",
69+
brief: "Output as JSON",
70+
default: false,
71+
},
72+
},
73+
},
74+
async func(
75+
this: SentryContext,
76+
flags: GetFlags,
77+
projectSlug?: string
78+
): Promise<void> {
79+
const { process, cwd } = this;
80+
const { stdout } = process;
81+
82+
const resolved = await resolveOrgAndProject({
83+
org: flags.org,
84+
project: projectSlug,
85+
cwd,
86+
});
87+
88+
if (!resolved) {
89+
throw new Error(
90+
"Organization and project are required.\n\n" +
91+
"Please specify them using:\n" +
92+
" sentry project get <project-slug> --org <org-slug>\n\n" +
93+
"Or set SENTRY_DSN environment variable for automatic detection."
94+
);
95+
}
96+
97+
const project = await getProject(resolved.org, resolved.project);
98+
99+
if (flags.json) {
100+
writeJson(stdout, project);
101+
return;
102+
}
103+
104+
writeHumanOutput(stdout, project, resolved.detectedFrom);
105+
},
106+
});

packages/cli/src/commands/project/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { buildRouteMap } from "@stricli/core";
2+
import { getCommand } from "./get.js";
23
import { listCommand } from "./list.js";
34

45
export const projectRoute = buildRouteMap({
56
routes: {
67
list: listCommand,
8+
get: getCommand,
79
},
810
docs: {
911
brief: "Work with Sentry projects",

packages/cli/src/commands/project/list.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,15 @@ function filterByPlatform(
8282
/**
8383
* Write the column header row for project list output.
8484
*/
85-
function writeHeader(stdout: NodeJS.WriteStream, slugWidth: number): void {
85+
function writeHeader(stdout: Writer, slugWidth: number): void {
8686
stdout.write(`${"SLUG".padEnd(slugWidth)} ${"PLATFORM".padEnd(20)} NAME\n`);
8787
}
8888

8989
/**
9090
* Write formatted project rows to stdout.
9191
*/
9292
function writeRows(
93-
stdout: NodeJS.WriteStream,
93+
stdout: Writer,
9494
projects: ProjectWithOrg[],
9595
slugWidth: number,
9696
showOrg: boolean

0 commit comments

Comments
 (0)