Skip to content

Commit 72f1012

Browse files
committed
fix(init): refine recursive setup command guards
1 parent 17b0571 commit 72f1012

4 files changed

Lines changed: 71 additions & 8 deletions

File tree

src/lib/init/tools/command-utils.ts

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,24 +119,78 @@ function isSentryCliPackageSpec(token: string): boolean {
119119
return lower === "@sentry/cli" || lower.startsWith("@sentry/cli@");
120120
}
121121

122+
function isSentryWizardPackageSpec(token: string): boolean {
123+
const lower = token.toLowerCase();
124+
return lower === "@sentry/wizard" || lower.startsWith("@sentry/wizard@");
125+
}
126+
122127
function isExecutablePackageSpec(executable: string, name: string): boolean {
123128
return executable === name || executable.startsWith(`${name}@`);
124129
}
125130

126-
function isRecursiveSentrySetup(tokens: string[]): boolean {
127-
if (tokens.some((token) => token.toLowerCase().includes("@sentry/wizard"))) {
128-
return true;
131+
function findFirstNonOptionIndex(
132+
tokens: string[],
133+
startIndex: number
134+
): number | undefined {
135+
for (let index = startIndex; index < tokens.length; index += 1) {
136+
const token = tokens[index];
137+
if (!token) {
138+
continue;
139+
}
140+
if (token === "--") {
141+
return index + 1 < tokens.length ? index + 1 : undefined;
142+
}
143+
if (token.startsWith("-")) {
144+
continue;
145+
}
146+
return index;
129147
}
130148

149+
return;
150+
}
151+
152+
function findPackageExecutionTokenIndex(tokens: string[]): number | undefined {
153+
const firstExecutable = normalizeExecutableName(tokens[0] ?? "");
154+
if (
155+
isExecutablePackageSpec(firstExecutable, "npx") ||
156+
isExecutablePackageSpec(firstExecutable, "bunx")
157+
) {
158+
return findFirstNonOptionIndex(tokens, 1);
159+
}
160+
161+
const subcommandIndex = findFirstNonOptionIndex(tokens, 1);
162+
if (subcommandIndex === undefined) {
163+
return;
164+
}
165+
166+
const subcommand = normalizeExecutableName(tokens[subcommandIndex] ?? "");
167+
if (subcommand !== "exec" && subcommand !== "dlx") {
168+
return;
169+
}
170+
171+
return findFirstNonOptionIndex(tokens, subcommandIndex + 1);
172+
}
173+
174+
function canExecuteToken(tokens: string[], index: number): boolean {
175+
return index === 0 || index === findPackageExecutionTokenIndex(tokens);
176+
}
177+
178+
function isRecursiveSentrySetup(tokens: string[]): boolean {
131179
return tokens.some((token, index) => {
132-
if (isSentryCliPackageSpec(token)) {
133-
return hasInitArgAfter(tokens, index);
180+
if (!canExecuteToken(tokens, index)) {
181+
return false;
134182
}
135183

136184
const executable = normalizeExecutableName(token);
137-
if (isExecutablePackageSpec(executable, "sentry-wizard")) {
185+
if (
186+
isSentryWizardPackageSpec(token) ||
187+
isExecutablePackageSpec(executable, "sentry-wizard")
188+
) {
138189
return true;
139190
}
191+
if (isSentryCliPackageSpec(token)) {
192+
return hasInitArgAfter(tokens, index);
193+
}
140194
if (
141195
!(
142196
isExecutablePackageSpec(executable, "sentry") ||

src/lib/init/tools/run-commands.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function isWindowsBatchShim(executable: string): boolean {
1717
}
1818

1919
function quoteWindowsCommandArg(value: string): string {
20-
return `"${value.replace(/\^/g, "^^").replace(/"/g, '""')}"`;
20+
return `"${value.replace(/"/g, '""')}"`;
2121
}
2222

2323
function buildWindowsBatchCommand(executable: string, args: string[]): string {

test/lib/init/tools/run-commands-spawn.mocked.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ describe("runCommands spawn options", () => {
112112
"/d",
113113
"/s",
114114
"/c",
115-
'""C:\\Tools\\pnpm.CMD" "--filter" "./apps/web app" "add" "@sentry/nextjs@^^8.0.0""',
115+
'""C:\\Tools\\pnpm.CMD" "--filter" "./apps/web app" "add" "@sentry/nextjs@^8.0.0""',
116116
],
117117
options: { shell: false },
118118
});

test/lib/init/tools/run-commands.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ describe("validateCommand", () => {
3737
).toBeUndefined();
3838
expect(validateCommand("dotnet list package")).toBeUndefined();
3939
expect(validateCommand("futurepm explain sentry-sdk")).toBeUndefined();
40+
expect(validateCommand("futurepm explain sentry-wizard")).toBeUndefined();
41+
expect(validateCommand("npm uninstall sentry-wizard")).toBeUndefined();
42+
expect(validateCommand("npm uninstall @sentry/wizard")).toBeUndefined();
4043
});
4144

4245
test("allows path-prefixed package managers but blocks dangerous ones", () => {
@@ -108,6 +111,12 @@ describe("validateCommand", () => {
108111
expect(validateCommand("npx sentry-wizard@latest -i nextjs")).toContain(
109112
"invokes Sentry setup recursively"
110113
);
114+
expect(validateCommand("npm exec @sentry/wizard -i nextjs")).toContain(
115+
"invokes Sentry setup recursively"
116+
);
117+
expect(validateCommand("pnpm dlx sentry-wizard -i nextjs")).toContain(
118+
"invokes Sentry setup recursively"
119+
);
111120
expect(validateCommand("C:\\Tools\\sentry-wizard.cmd -i nextjs")).toContain(
112121
"invokes Sentry setup recursively"
113122
);

0 commit comments

Comments
 (0)