Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 24 additions & 7 deletions packages/cli/src/lib/init/verify-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@

import { type ChildProcess, spawn, spawnSync } from "node:child_process";
import { resolve } from "node:path";
import { captureException } from "@sentry/node-core/light";
import {
addBreadcrumb,
captureException,
setTag,
} from "@sentry/node-core/light";
import { createSpotlightBuffer } from "@spotlightjs/spotlight/sdk";
import { BUFFER_SIZE, shutdownServer } from "../../commands/local/run.js";
import { buildApp, tryListen } from "../../commands/local/server.js";
Expand Down Expand Up @@ -320,6 +324,21 @@ async function cleanupProcessTree(child: ChildProcess): Promise<void> {
}
}

type VerificationSkipReason = "no_dev_command" | "server_bind" | "spawn_failed";

/**
* Expected verification skips are not failures. Emitting them with
* `captureException` produced ERROR events on healthy completed runs.
*/
function recordVerificationSkipped(reason: VerificationSkipReason): void {
addBreadcrumb({
category: "wizard.verify",
level: "info",
message: `skipped:${reason}`,
});
setTag("wizard.verify", "skipped");
}

/**
* Outcome of {@link verifySetup}, surfaced to the completion screen so it can
* celebrate a received event (and deep-link it) instead of only telling the
Expand Down Expand Up @@ -352,12 +371,7 @@ export async function verifySetup(
const detected = await detectDevCommand(cwd);
if (!detected) {
ui.log.info("Skipping verification — could not detect a dev command");
captureException(new Error("init verification skipped"), {
tags: {
"wizard.platform": String(result.result?.platform ?? "unknown"),
"wizard.verify": "no_dev_command",
},
});
recordVerificationSkipped("no_dev_command");
return { verified: false, kind: "skipped" };
}

Expand All @@ -375,6 +389,7 @@ export async function verifySetup(
} catch (error) {
logger.debug("Failed to start verification server", error);
ui.log.warn("Skipping verification — could not start local server.");
recordVerificationSkipped("server_bind");
return { verified: false, kind: "skipped" };
}

Expand Down Expand Up @@ -419,6 +434,7 @@ export async function verifySetup(
logger.debug("Failed to spawn verification child", error);
await shutdownServer(server);
ui.log.warn("Skipping verification — could not start the dev command.");
recordVerificationSkipped("spawn_failed");
return { verified: false, kind: "skipped" };
}

Expand Down Expand Up @@ -558,6 +574,7 @@ function reportOutcome(outcome: VerifyOutcome, ctx: ReportContext): void {
if (outcome.kind === "spawn_error") {
logger.debug("Failed to spawn verification child", outcome.error);
ui.log.warn("Skipping verification — could not start the dev command.");
recordVerificationSkipped("spawn_failed");
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,9 @@ vi.mock("node:child_process", async (importOriginal) => {
});

vi.mock("@sentry/node-core/light", () => ({
addBreadcrumb: vi.fn(),
captureException: vi.fn(),
setTag: vi.fn(),
}));

import { verifySetup } from "../../../src/lib/init/verify-setup.js";
Expand Down
39 changes: 38 additions & 1 deletion packages/cli/test/lib/init/verify-setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ import { verifySetup } from "../../../src/lib/init/verify-setup.js";
import { TEST_TMP_DIR } from "../../constants.js";
import { createMockUI } from "./ui/mock-ui.js";

vi.mock("@sentry/node-core/light", () => ({
const sentryMocks = vi.hoisted(() => ({
addBreadcrumb: vi.fn(),
captureException: vi.fn(),
setTag: vi.fn(),
}));

vi.mock("@sentry/node-core/light", () => sentryMocks);

type FixtureProcesses = {
shellPid: number;
parentPid: number;
Expand All @@ -19,6 +23,9 @@ let tmpDir: string;
let fixtureProcesses: FixtureProcesses | undefined;

beforeEach(async () => {
sentryMocks.addBreadcrumb.mockClear();
sentryMocks.captureException.mockClear();
sentryMocks.setTag.mockClear();
tmpDir = await mkdtemp(join(TEST_TMP_DIR, "verify-setup-test-"));
});

Expand Down Expand Up @@ -133,6 +140,29 @@ async function readFixtureProcesses(): Promise<FixtureProcesses> {
}

describe("verifySetup", () => {
test("records a skipped verify as a scope tag, not an error, when there is no dev command", async () => {
const { ui, calls } = createMockUI();

const result = await verifySetup(
{ status: "success", result: { platform: "cocoa" } },
ui,
tmpDir
);

expect(result).toEqual({ verified: false, kind: "skipped" });
expect(calls).toContainEqual({
kind: "log.info",
message: "Skipping verification — could not detect a dev command",
});
expect(sentryMocks.captureException).not.toHaveBeenCalled();
expect(sentryMocks.setTag).toHaveBeenCalledWith("wizard.verify", "skipped");
expect(sentryMocks.addBreadcrumb).toHaveBeenCalledWith({
category: "wizard.verify",
level: "info",
message: "skipped:no_dev_command",
});
});

test("does not fail init when the detected command cannot be spawned", async () => {
await writeFile(
join(tmpDir, "package.json"),
Expand All @@ -153,6 +183,13 @@ describe("verifySetup", () => {
kind: "log.warn",
message: "Skipping verification — could not start the dev command.",
});
expect(sentryMocks.captureException).not.toHaveBeenCalled();
expect(sentryMocks.setTag).toHaveBeenCalledWith("wizard.verify", "skipped");
expect(sentryMocks.addBreadcrumb).toHaveBeenCalledWith({
category: "wizard.verify",
level: "info",
message: "skipped:spawn_failed",
});
});

test.skipIf(process.platform === "win32")(
Expand Down
Loading