Skip to content

Commit 137e130

Browse files
committed
fix: preserve markdown links in table plain output by escaping user data at source
Fixes plain-mode table escaping that was destroying intentional markdown links in SHORT ID column. Moved escaping of user-supplied data (TITLE, NAME, URL fields) to column definitions following the pattern used by log/trace tables. Removed blanket escapeMarkdownCell from buildMarkdownTable to preserve intentional markdown syntax like [text](url) links.
1 parent 735b8f8 commit 137e130

File tree

6 files changed

+11
-7
lines changed

6 files changed

+11
-7
lines changed

src/commands/org/list.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { buildCommand } from "../../lib/command.js";
1010
import { DEFAULT_SENTRY_HOST } from "../../lib/constants.js";
1111
import { getAllOrgRegions } from "../../lib/db/regions.js";
1212
import { writeFooter, writeJson } from "../../lib/formatters/index.js";
13+
import { escapeMarkdownCell } from "../../lib/formatters/markdown.js";
1314
import { type Column, writeTable } from "../../lib/formatters/table.js";
1415
import { buildListLimitFlag, LIST_JSON_FLAG } from "../../lib/list-command.js";
1516

@@ -108,7 +109,7 @@ export const listCommand = buildCommand({
108109
...(showRegion
109110
? [{ header: "REGION", value: (r: OrgRow) => r.region ?? "" }]
110111
: []),
111-
{ header: "NAME", value: (r) => r.name },
112+
{ header: "NAME", value: (r) => escapeMarkdownCell(r.name) },
112113
];
113114

114115
writeTable(stdout, rows, columns);

src/commands/project/list.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
} from "../../lib/db/pagination.js";
3333
import { AuthError, ContextError } from "../../lib/errors.js";
3434
import { writeFooter, writeJson } from "../../lib/formatters/index.js";
35+
import { escapeMarkdownCell } from "../../lib/formatters/markdown.js";
3536
import { type Column, writeTable } from "../../lib/formatters/table.js";
3637
import {
3738
buildListCommand,
@@ -229,7 +230,7 @@ async function resolveOrgsForAutoDetect(cwd: string): Promise<OrgResolution> {
229230
const PROJECT_COLUMNS: Column<ProjectWithOrg>[] = [
230231
{ header: "ORG", value: (p) => p.orgSlug || "" },
231232
{ header: "PROJECT", value: (p) => p.slug },
232-
{ header: "NAME", value: (p) => p.name },
233+
{ header: "NAME", value: (p) => escapeMarkdownCell(p.name) },
233234
{ header: "PLATFORM", value: (p) => p.platform || "" },
234235
];
235236

src/commands/repo/list.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
listRepositories,
1515
listRepositoriesPaginated,
1616
} from "../../lib/api-client.js";
17+
import { escapeMarkdownCell } from "../../lib/formatters/markdown.js";
1718
import { type Column, writeTable } from "../../lib/formatters/table.js";
1819
import {
1920
buildOrgListCommand,
@@ -31,10 +32,10 @@ type RepositoryWithOrg = SentryRepository & { orgSlug?: string };
3132
/** Column definitions for the repository table. */
3233
const REPO_COLUMNS: Column<RepositoryWithOrg>[] = [
3334
{ header: "ORG", value: (r) => r.orgSlug || "" },
34-
{ header: "NAME", value: (r) => r.name },
35+
{ header: "NAME", value: (r) => escapeMarkdownCell(r.name) },
3536
{ header: "PROVIDER", value: (r) => r.provider.name },
3637
{ header: "STATUS", value: (r) => r.status },
37-
{ header: "URL", value: (r) => r.url || "" },
38+
{ header: "URL", value: (r) => escapeMarkdownCell(r.url || "") },
3839
];
3940

4041
/** Shared config that plugs into the org-list framework. */

src/commands/team/list.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
listTeams,
1616
listTeamsPaginated,
1717
} from "../../lib/api-client.js";
18+
import { escapeMarkdownCell } from "../../lib/formatters/markdown.js";
1819
import { type Column, writeTable } from "../../lib/formatters/table.js";
1920
import {
2021
buildOrgListCommand,
@@ -33,7 +34,7 @@ type TeamWithOrg = SentryTeam & { orgSlug?: string };
3334
const TEAM_COLUMNS: Column<TeamWithOrg>[] = [
3435
{ header: "ORG", value: (t) => t.orgSlug || "" },
3536
{ header: "SLUG", value: (t) => t.slug },
36-
{ header: "NAME", value: (t) => t.name },
37+
{ header: "NAME", value: (t) => escapeMarkdownCell(t.name) },
3738
{
3839
header: "MEMBERS",
3940
value: (t) => String(t.memberCount ?? ""),

src/lib/formatters/human.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ export function writeIssueTable(
477477
},
478478
{
479479
header: "TITLE",
480-
value: ({ issue }) => issue.title,
480+
value: ({ issue }) => escapeMarkdownCell(issue.title),
481481
}
482482
);
483483

src/lib/formatters/table.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export function buildMarkdownTable<T>(
5555
const rows = items
5656
.map(
5757
(item) =>
58-
`| ${columns.map((c) => escapeMarkdownCell(stripColorTags(c.value(item)))).join(" | ")} |`
58+
`| ${columns.map((c) => stripColorTags(c.value(item))).join(" | ")} |`
5959
)
6060
.join("\n");
6161
return `${header}\n${separator}\n${rows}`;

0 commit comments

Comments
 (0)