Skip to content

Commit b0b15e6

Browse files
betegonclaude
andcommitted
feat(project): display platform suggestions and list in multi-column tables
Extract platform validation into a dedicated module with fuzzy matching, and render "Did you mean?" suggestions and "Common platforms" as bordered 3-column tables using renderTextTable instead of plain indented lists. Also adds client-side platform validation before making the API call, giving faster feedback for typos. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent ea2925a commit b0b15e6

File tree

4 files changed

+484
-51
lines changed

4 files changed

+484
-51
lines changed

src/commands/project/create.ts

Lines changed: 48 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ import {
3131
withAuthGuard,
3232
} from "../../lib/errors.js";
3333
import { formatProjectCreated, writeJson } from "../../lib/formatters/index.js";
34+
import { renderTextTable } from "../../lib/formatters/text-table.js";
35+
import {
36+
COMMON_PLATFORMS,
37+
isValidPlatform,
38+
suggestPlatform,
39+
} from "../../lib/platforms.js";
3440
import { resolveOrg } from "../../lib/resolve-target.js";
3541
import {
3642
buildOrgNotFoundError,
@@ -48,43 +54,6 @@ type CreateFlags = {
4854
readonly json: boolean;
4955
};
5056

51-
/**
52-
* Common Sentry platform identifiers shown when platform arg is missing or invalid.
53-
*
54-
* These use hyphen-separated format matching Sentry's internal platform registry
55-
* (see sentry/src/sentry/utils/platform_categories.py). This is a curated subset
56-
* of the ~120 supported values — the full list is available via the API endpoint
57-
* referenced in `buildPlatformError`.
58-
*/
59-
const PLATFORMS = [
60-
"javascript",
61-
"javascript-react",
62-
"javascript-nextjs",
63-
"javascript-vue",
64-
"javascript-angular",
65-
"javascript-svelte",
66-
"javascript-remix",
67-
"javascript-astro",
68-
"node",
69-
"node-express",
70-
"python",
71-
"python-django",
72-
"python-flask",
73-
"python-fastapi",
74-
"go",
75-
"ruby",
76-
"ruby-rails",
77-
"php",
78-
"php-laravel",
79-
"java",
80-
"android",
81-
"dotnet",
82-
"react-native",
83-
"apple-ios",
84-
"rust",
85-
"elixir",
86-
] as const;
87-
8857
/**
8958
* Normalize common platform format mistakes.
9059
*
@@ -96,6 +65,19 @@ const PLATFORMS = [
9665
* Safe to auto-correct because the input is already invalid (dots are never
9766
* valid in platform identifiers) and the correction is unambiguous.
9867
*/
68+
/** Reshape a flat list into rows of `cols` columns, padding the last row. */
69+
function toColumnRows(items: string[], cols: number): string[][] {
70+
const rows: string[][] = [];
71+
for (let i = 0; i < items.length; i += cols) {
72+
const row = items.slice(i, i + cols);
73+
while (row.length < cols) {
74+
row.push("");
75+
}
76+
rows.push(row);
77+
}
78+
return rows;
79+
}
80+
9981
function normalizePlatform(platform: string, stderr: Writer): string {
10082
if (!platform.includes(".")) {
10183
return platform;
@@ -142,16 +124,37 @@ function isPlatformError(error: ApiError): boolean {
142124
* @param platform - The invalid platform string, if provided
143125
*/
144126
function buildPlatformError(nameArg: string, platform?: string): string {
145-
const list = PLATFORMS.map((p) => ` ${p}`).join("\n");
146127
const heading = platform
147128
? `Invalid platform '${platform}'.`
148129
: "Platform is required.";
149130

131+
let didYouMean = "";
132+
if (platform) {
133+
const suggestions = suggestPlatform(platform);
134+
if (suggestions.length > 0) {
135+
const rows = toColumnRows(suggestions, 3);
136+
const table = renderTextTable(rows[0] ?? [], rows.slice(1), {
137+
headerSeparator: false,
138+
});
139+
didYouMean = `\nDid you mean?\n${table}`;
140+
}
141+
}
142+
143+
const platformRows = toColumnRows([...COMMON_PLATFORMS], 3);
144+
const platformTable = renderTextTable(
145+
platformRows[0] ?? [],
146+
platformRows.slice(1),
147+
{
148+
headerSeparator: false,
149+
}
150+
);
151+
150152
return (
151-
`${heading}\n\n` +
152-
"Usage:\n" +
153+
`${heading}\n` +
154+
didYouMean +
155+
"\nUsage:\n" +
153156
` sentry project create ${nameArg} <platform>\n\n` +
154-
`Available platforms:\n\n${list}\n\n` +
157+
`Common platforms:\n\n${platformTable}\n` +
155158
"Run 'sentry project create <name> <platform>' with any valid Sentry platform identifier."
156159
);
157160
}
@@ -332,6 +335,10 @@ export const createCommand = buildCommand({
332335

333336
const platform = normalizePlatform(platformArg, this.stderr);
334337

338+
if (!isValidPlatform(platform)) {
339+
throw new CliError(buildPlatformError(nameArg, platform));
340+
}
341+
335342
const parsed = parseOrgProjectArg(nameArg);
336343

337344
let explicitOrg: string | undefined;

0 commit comments

Comments
 (0)