Skip to content

Commit b1fbb58

Browse files
fix(init): validate only user-provided slugs, tighten looksLikePath
Move validateResourceId into resolveTarget so it only runs on user-provided values (explicit and org-all cases), not on API-resolved slugs from resolveProjectBySlug. Tighten looksLikePath to match ~ only as '~' or '~/' — not '~foo', which could be a valid slug. Rename misleading test accordingly.
1 parent 6337d42 commit b1fbb58

File tree

3 files changed

+18
-12
lines changed

3 files changed

+18
-12
lines changed

src/commands/init.ts

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,12 @@ async function resolveTarget(targetArg: string | undefined): Promise<{
106106

107107
switch (parsed.type) {
108108
case "explicit":
109+
// Validate user-provided slugs before they reach API calls
110+
validateResourceId(parsed.org, "organization slug");
111+
validateResourceId(parsed.project, "project name");
109112
return { org: parsed.org, project: parsed.project };
110113
case "org-all":
114+
validateResourceId(parsed.org, "organization slug");
111115
return { org: parsed.org, project: undefined };
112116
case "project-search": {
113117
// Bare slug — search for a project with this name across all orgs.
@@ -219,18 +223,12 @@ export const initCommand = buildCommand<
219223
.filter(Boolean);
220224

221225
// 4. Resolve target → org + project
226+
// Validation of user-provided slugs happens inside resolveTarget.
227+
// API-resolved values (from resolveProjectBySlug) are already valid.
222228
const { org: explicitOrg, project: explicitProject } =
223229
await resolveTarget(targetArg);
224230

225-
// 5. Validate explicit slugs before passing to API calls
226-
if (explicitOrg) {
227-
validateResourceId(explicitOrg, "organization slug");
228-
}
229-
if (explicitProject) {
230-
validateResourceId(explicitProject, "project name");
231-
}
232-
233-
// 6. Run the wizard
231+
// 5. Run the wizard
234232
await runWizard({
235233
directory: targetDir,
236234
yes: flags.yes,

src/lib/arg-parsing.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,12 +89,17 @@ export function looksLikeIssueShortId(str: string): boolean {
8989
* - `.` (current directory)
9090
* - `./foo`, `../foo` (relative paths)
9191
* - `/foo` (absolute paths)
92-
* - `~/foo` (home directory paths)
92+
* - `~` or `~/foo` (home directory paths)
9393
*
9494
* Bare names like `my-org` or `my-project` never match, which is what makes
9595
* this useful for disambiguating positional arguments that could be either
9696
* a filesystem path or an org/project target.
9797
*
98+
* Note: `~` is only matched as `~` alone or `~/...`, not `~foo`. This avoids
99+
* false positives on slugs that happen to start with tilde (valid in Sentry slugs).
100+
* Shell expansion of `~/foo` happens before the CLI sees the argument, so a literal
101+
* `~/foo` reaching this function means the shell didn't expand it (e.g., it was quoted).
102+
*
98103
* @param arg - CLI argument string to check
99104
* @returns true if the string looks like a filesystem path
100105
*
@@ -104,16 +109,19 @@ export function looksLikeIssueShortId(str: string): boolean {
104109
* looksLikePath("../parent") // true
105110
* looksLikePath("/absolute") // true
106111
* looksLikePath("~/home") // true
112+
* looksLikePath("~") // true
113+
* looksLikePath("~foo") // false (could be a slug)
107114
* looksLikePath("my-project") // false
108115
* looksLikePath("acme/app") // false
109116
*/
110117
export function looksLikePath(arg: string): boolean {
111118
return (
112119
arg === "." ||
120+
arg === "~" ||
113121
arg.startsWith("./") ||
114122
arg.startsWith("../") ||
115123
arg.startsWith("/") ||
116-
arg.startsWith("~")
124+
arg.startsWith("~/")
117125
);
118126
}
119127

test/commands/init.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ describe("init command func", () => {
179179
expect(capturedArgs?.org).toBeUndefined();
180180
});
181181

182-
test("~/home resolves relative to cwd", async () => {
182+
test("~/path treated as literal path (no shell expansion)", async () => {
183183
const ctx = makeContext("/projects/app");
184184
await func.call(ctx, DEFAULT_FLAGS, "~/projects/other");
185185
expect(capturedArgs?.directory).toBe(

0 commit comments

Comments
 (0)