Skip to content

Commit 24504cb

Browse files
committed
fix: suggest similar projects when project not found in org (CLI-C0)
When users specify an explicit org/project (e.g., sentry issue list elide/elide-server) and the project isn't found, the error now suggests similar project slugs from the org. This helps users who misspell slugs or use wrong casing (CLI-C0, 36 users). Added findSimilarProjects() that uses case-insensitive prefix/substring matching to find up to 3 similar slugs. Falls back gracefully on API errors since it's a best-effort hint. Suggestions appear before the existing project-settings URL link.
1 parent aaabc30 commit 24504cb

File tree

1 file changed

+59
-3
lines changed

1 file changed

+59
-3
lines changed

src/lib/resolve-target.ts

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
findProjectsByPattern,
2222
findProjectsBySlug,
2323
getProject,
24+
listProjects,
2425
} from "./api-client.js";
2526
import { type ParsedOrgProject, parseOrgProjectArg } from "./arg-parsing.js";
2627
import { getDefaultOrganization, getDefaultProject } from "./db/defaults.js";
@@ -551,12 +552,60 @@ function resolveFromEnvVars(): {
551552
return null;
552553
}
553554

555+
/**
556+
* Find project slugs in the org that are similar to the given slug.
557+
*
558+
* Uses case-insensitive prefix/substring matching — lightweight and
559+
* sufficient for the most common typo patterns (wrong casing, partial
560+
* slug, extra/missing hyphens). Falls back gracefully on API errors
561+
* since this is a best-effort hint, not a critical path.
562+
*
563+
* @param org - Organization slug to search in
564+
* @param slug - The project slug that wasn't found
565+
* @returns Up to 3 similar project slugs, or empty array on error
566+
*/
567+
async function findSimilarProjects(
568+
org: string,
569+
slug: string
570+
): Promise<string[]> {
571+
try {
572+
const projects = await listProjects(org);
573+
const lower = slug.toLowerCase();
574+
575+
// Score each project: exact-case-insensitive > prefix > substring > none
576+
const scored = projects
577+
.map((p) => {
578+
const pLower = p.slug.toLowerCase();
579+
if (pLower === lower) {
580+
return { slug: p.slug, score: 3 };
581+
}
582+
if (pLower.startsWith(lower) || lower.startsWith(pLower)) {
583+
return { slug: p.slug, score: 2 };
584+
}
585+
if (pLower.includes(lower) || lower.includes(pLower)) {
586+
return { slug: p.slug, score: 1 };
587+
}
588+
return { slug: p.slug, score: 0 };
589+
})
590+
.filter((s) => s.score > 0)
591+
.sort((a, b) => b.score - a.score);
592+
593+
return scored.slice(0, 3).map((s) => s.slug);
594+
} catch {
595+
// Best-effort — don't let listing failures block the error message
596+
return [];
597+
}
598+
}
599+
554600
/**
555601
* Fetch the numeric project ID for an explicit org/project pair.
556602
*
557603
* Throws on auth errors and 404s (user-actionable). Returns undefined
558604
* for transient failures (network, 500s) so the command can still
559605
* attempt slug-based querying as a fallback.
606+
*
607+
* On 404, attempts to list similar projects in the org to help the
608+
* user find the correct slug (CLI-C0, 36 users).
560609
*/
561610
export async function fetchProjectId(
562611
org: string,
@@ -568,13 +617,20 @@ export async function fetchProjectId(
568617
projectResult.error instanceof ApiError &&
569618
projectResult.error.status === 404
570619
) {
620+
const similar = await findSimilarProjects(org, project);
621+
const suggestions = [
622+
`Check the project slug at https://sentry.io/organizations/${org}/projects/`,
623+
];
624+
if (similar.length > 0) {
625+
suggestions.unshift(
626+
`Similar projects: ${similar.map((s) => `'${s}'`).join(", ")}`
627+
);
628+
}
571629
throw new ResolutionError(
572630
`Project '${project}'`,
573631
`not found in organization '${org}'`,
574632
`sentry issue list ${org}/<project>`,
575-
[
576-
`Check the project slug at https://sentry.io/organizations/${org}/projects/`,
577-
]
633+
suggestions
578634
);
579635
}
580636
return;

0 commit comments

Comments
 (0)