Skip to content

Commit 4d9f967

Browse files
committed
fix(setup): suppress agent skills and welcome messages on upgrade
During upgrades, suppress noisy output that is only relevant for fresh installs: - Agent skills: Only print 'Installed to' on first creation, not 'Updated' on every subsequent run (same pattern as completions) - Welcome message: Only show 'Get started' info when --install places a binary for the first time (fresh install), not when overwriting an existing binary (upgrade) - Remove 'Setup complete!' trailing message — the upgrade command already prints its own success message Behavior summary: Fresh install (--install, no existing binary): welcome + getting started Upgrade (--install, binary exists): binary path only Package manager upgrade (no --install): silent Manual re-run: silent
1 parent 7d3c290 commit 4d9f967

File tree

2 files changed

+116
-22
lines changed

2 files changed

+116
-22
lines changed

src/commands/cli/setup.ts

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@
66
* and the upgrade command for curl-based installs).
77
*/
88

9-
import { unlinkSync } from "node:fs";
10-
import { dirname } from "node:path";
9+
import { existsSync, unlinkSync } from "node:fs";
10+
import { dirname, join } from "node:path";
1111
import type { SentryContext } from "../../context.js";
1212
import { installAgentSkills } from "../../lib/agent-skills.js";
13-
import { determineInstallDir, installBinary } from "../../lib/binary.js";
13+
import {
14+
determineInstallDir,
15+
getBinaryFilename,
16+
installBinary,
17+
} from "../../lib/binary.js";
1418
import { buildCommand } from "../../lib/command.js";
1519
import {
1620
type CompletionLocation,
@@ -60,15 +64,19 @@ type Logger = (msg: string) => void;
6064
* On Windows, the temp binary cannot be deleted while running. It will
6165
* be cleaned up by the OS when the temp directory is purged.
6266
*
63-
* @returns The absolute path of the installed binary and its directory
67+
* @returns The installed binary path, its directory, and whether this
68+
* was a fresh install (`created`) or an upgrade of an existing binary
6469
*/
6570
async function handleInstall(
6671
execPath: string,
6772
homeDir: string,
6873
env: NodeJS.ProcessEnv,
6974
log: Logger
70-
): Promise<{ binaryPath: string; binaryDir: string }> {
75+
): Promise<{ binaryPath: string; binaryDir: string; created: boolean }> {
7176
const installDir = determineInstallDir(homeDir, env);
77+
const targetPath = join(installDir, getBinaryFilename());
78+
const alreadyExists = existsSync(targetPath);
79+
7280
const binaryPath = await installBinary(execPath, installDir);
7381
const binaryDir = dirname(binaryPath);
7482

@@ -83,7 +91,7 @@ async function handleInstall(
8391
}
8492
}
8593

86-
return { binaryPath, binaryDir };
94+
return { binaryPath, binaryDir, created: !alreadyExists };
8795
}
8896

8997
/**
@@ -220,13 +228,15 @@ async function handleCompletions(
220228
*
221229
* Detects supported agents (currently Claude Code) and installs the
222230
* version-pinned skill file. Silent when no agent is detected.
231+
*
232+
* Only produces output when the skill file is freshly created. Subsequent
233+
* runs (e.g. after upgrade) silently update without printing.
223234
*/
224235
async function handleAgentSkills(homeDir: string, log: Logger): Promise<void> {
225236
const location = await installAgentSkills(homeDir, CLI_VERSION);
226237

227-
if (location) {
228-
const action = location.created ? "Installed to" : "Updated";
229-
log(`Agent skills: ${action} ${location.path}`);
238+
if (location?.created) {
239+
log(`Agent skills: Installed to ${location.path}`);
230240
}
231241
}
232242

@@ -446,6 +456,7 @@ export const setupCommand = buildCommand({
446456

447457
let binaryPath = process.execPath;
448458
let binaryDir = dirname(binaryPath);
459+
let freshInstall = false;
449460

450461
// 0. Install binary from temp location (when --install is set)
451462
if (flags.install) {
@@ -457,6 +468,7 @@ export const setupCommand = buildCommand({
457468
);
458469
binaryPath = result.binaryPath;
459470
binaryDir = result.binaryDir;
471+
freshInstall = result.created;
460472
}
461473

462474
// 1–4. Run best-effort configuration steps
@@ -470,13 +482,10 @@ export const setupCommand = buildCommand({
470482
warn,
471483
});
472484

473-
// 5. Print welcome message (fresh install) or completion message
474-
if (!flags.quiet) {
475-
if (flags.install) {
476-
printWelcomeMessage(log, CLI_VERSION, binaryPath);
477-
} else {
478-
stdout.write("\nSetup complete!\n");
479-
}
485+
// 5. Print welcome message only on fresh install — upgrades are silent
486+
// since the upgrade command itself prints a success message.
487+
if (!flags.quiet && freshInstall) {
488+
printWelcomeMessage(log, CLI_VERSION, binaryPath);
480489
}
481490
},
482491
});

test/commands/cli/setup.test.ts

Lines changed: 91 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,9 @@ describe("sentry cli setup", () => {
124124
expect(output.join("")).toBe("");
125125
});
126126

127-
test("outputs 'Setup complete!' without --quiet", async () => {
127+
test("produces no welcome or completion output without --install", async () => {
128+
// Without --install, setup is being called for an upgrade or manual re-run.
129+
// Output is suppressed — the upgrade command itself prints success.
128130
const { context, output } = createMockContext({ homeDir: testDir });
129131

130132
await run(
@@ -140,7 +142,7 @@ describe("sentry cli setup", () => {
140142
);
141143

142144
const combined = output.join("");
143-
expect(combined).toContain("Setup complete!");
145+
expect(combined).toBe("");
144146
});
145147

146148
test("records install method when --method is provided", async () => {
@@ -515,6 +517,53 @@ describe("sentry cli setup", () => {
515517
expect(combined).not.toContain("Recorded installation method");
516518
});
517519

520+
test("--install suppresses welcome when binary already exists (upgrade)", async () => {
521+
const installDir = join(testDir, "install-dir");
522+
mkdirSync(installDir, { recursive: true });
523+
// Pre-existing binary — this is an upgrade, not a fresh install
524+
writeFileSync(join(installDir, "sentry"), "old-binary");
525+
526+
const sourceDir = join(testDir, "tmp");
527+
mkdirSync(sourceDir, { recursive: true });
528+
const sourcePath = join(sourceDir, "sentry-download");
529+
writeFileSync(sourcePath, "new-binary");
530+
const { chmodSync } = await import("node:fs");
531+
chmodSync(sourcePath, 0o755);
532+
533+
const { context, output } = createMockContext({
534+
homeDir: testDir,
535+
execPath: sourcePath,
536+
env: {
537+
PATH: "/usr/bin:/bin",
538+
SHELL: "/bin/bash",
539+
SENTRY_INSTALL_DIR: installDir,
540+
},
541+
});
542+
543+
await run(
544+
app,
545+
[
546+
"cli",
547+
"setup",
548+
"--install",
549+
"--method",
550+
"curl",
551+
"--no-modify-path",
552+
"--no-completions",
553+
"--no-agent-skills",
554+
],
555+
context
556+
);
557+
558+
const combined = output.join("");
559+
560+
// Binary placement is still logged
561+
expect(combined).toContain("Binary: Installed to");
562+
// But welcome/getting-started is suppressed for upgrades
563+
expect(combined).not.toContain("Get started:");
564+
expect(combined).not.toContain("sentry auth login");
565+
});
566+
518567
test("--install with --quiet suppresses all output", async () => {
519568
const sourceDir = join(testDir, "tmp");
520569
mkdirSync(sourceDir, { recursive: true });
@@ -620,6 +669,40 @@ describe("sentry cli setup", () => {
620669
expect(combined).not.toContain("Agent skills:");
621670
});
622671

672+
test("suppresses agent skills message on subsequent runs (upgrade scenario)", async () => {
673+
mkdirSync(join(testDir, ".claude"), { recursive: true });
674+
675+
const { context, output } = createMockContext({
676+
homeDir: testDir,
677+
execPath: join(testDir, "bin", "sentry"),
678+
env: {
679+
PATH: `/usr/bin:${join(testDir, "bin")}:/bin`,
680+
SHELL: "/bin/bash",
681+
},
682+
});
683+
684+
// First run — should show "Installed to"
685+
await run(
686+
app,
687+
["cli", "setup", "--no-modify-path", "--no-completions"],
688+
context
689+
);
690+
691+
const firstRun = output.join("");
692+
expect(firstRun).toContain("Agent skills: Installed to");
693+
694+
// Second run — skill file already exists, should be silent
695+
output.length = 0;
696+
await run(
697+
app,
698+
["cli", "setup", "--no-modify-path", "--no-completions"],
699+
context
700+
);
701+
702+
const secondRun = output.join("");
703+
expect(secondRun).not.toContain("Agent skills:");
704+
});
705+
623706
test("skips when --no-agent-skills is set", async () => {
624707
mkdirSync(join(testDir, ".claude"), { recursive: true });
625708

@@ -670,10 +753,10 @@ describe("sentry cli setup", () => {
670753
context
671754
);
672755

673-
// Setup should still complete successfully
756+
// Setup should still complete without errors (no output without --install)
674757
const combined = output.join("");
675-
expect(combined).toContain("Setup complete!");
676758
expect(combined).not.toContain("Agent skills:");
759+
expect(combined).not.toContain("Warning:");
677760
});
678761

679762
test("bestEffort catches errors from steps and setup still completes", async () => {
@@ -704,8 +787,10 @@ describe("sentry cli setup", () => {
704787
);
705788

706789
const combined = output.join("");
707-
// Setup must complete even though the completions step threw
708-
expect(combined).toContain("Setup complete!");
790+
// Setup must complete even though the completions step threw —
791+
// the warning goes to stderr but no crash
792+
expect(combined).toContain("Warning:");
793+
expect(combined).toContain("Shell completions failed");
709794

710795
chmod(zshDir, 0o755);
711796
});

0 commit comments

Comments
 (0)