From 24dd07905b1f3bdcd25282c5abfef73ed08c97de Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Fri, 22 May 2026 21:08:01 +0000 Subject: [PATCH 1/3] test: add deeper smoke tests for binary and npm bundle Add smoke tests beyond --help that exercise lazy-loaded code paths (SQLite, telemetry, auth DB, Ink sidecar). This catches require resolution and bundle bugs that --help alone misses. CI build smoke tests (ci.yml): - build-binary: run `auth status` (unauthenticated) after build - exercises SQLite init, schema migrations, telemetry lazy import - asserts exit code 10 (AUTH_NOT_AUTHENTICATED) - build-npm: same test via `node dist/bin.cjs auth status` E2E bundle tests (bundle.test.ts): - auth status: SQLite + telemetry + auth DB via npm bundle - cli defaults: SQLite metadata KV store without auth - Ink sidecar import: verify dist/ink-app.js loads and exports mountApp Closes #1010 --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++ test/e2e/bundle.test.ts | 72 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f3266032..3ad6668ea 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -309,6 +309,29 @@ jobs: else ./dist-bin/sentry-${{ matrix.target }} --help fi + - name: Smoke test (deep — SQLite, telemetry, auth DB) + if: matrix.can-test + shell: bash + run: | + if [[ "${{ matrix.target }}" == "windows-x64" ]]; then + BIN=./dist-bin/sentry-windows-x64.exe + else + BIN=./dist-bin/sentry-${{ matrix.target }} + fi + # auth status without a token exercises SQLite init, schema + # migrations, telemetry lazy import, and the CJS require chain. + # Expected: exit 10 (AUTH_NOT_AUTHENTICATED), NOT a crash/syntax error. + OUTPUT=$($BIN auth status 2>&1) && EXIT_CODE=$? || EXIT_CODE=$? + if [[ $EXIT_CODE -ne 10 ]]; then + echo "::error::Expected exit code 10 (not authenticated), got $EXIT_CODE" + echo "$OUTPUT" + exit 1 + fi + if ! echo "$OUTPUT" | grep -qi "not authenticated"; then + echo "::error::Expected 'not authenticated' in output, got:" + echo "$OUTPUT" + exit 1 + fi - name: Upload binary artifact uses: actions/upload-artifact@v7 with: @@ -710,6 +733,23 @@ jobs: run: pnpm run bundle - name: Smoke test (Node.js) run: node dist/bin.cjs --help + - name: Smoke test (Node.js — deep) + shell: bash + run: | + # auth status without a token exercises SQLite init, schema + # migrations, telemetry lazy import, and the CJS require chain. + # Expected: exit 10 (AUTH_NOT_AUTHENTICATED), NOT a crash/syntax error. + OUTPUT=$(node dist/bin.cjs auth status 2>&1) && EXIT_CODE=$? || EXIT_CODE=$? + if [[ $EXIT_CODE -ne 10 ]]; then + echo "::error::Expected exit code 10 (not authenticated), got $EXIT_CODE" + echo "$OUTPUT" + exit 1 + fi + if ! echo "$OUTPUT" | grep -qi "not authenticated"; then + echo "::error::Expected 'not authenticated' in output, got:" + echo "$OUTPUT" + exit 1 + fi - run: npm pack - name: Upload artifact if: matrix.node == '22' diff --git a/test/e2e/bundle.test.ts b/test/e2e/bundle.test.ts index 7268b8c90..be4dc64c7 100644 --- a/test/e2e/bundle.test.ts +++ b/test/e2e/bundle.test.ts @@ -9,6 +9,7 @@ import { spawn } from "node:child_process"; import { existsSync, rmSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { join } from "node:path"; +import { pathToFileURL } from "node:url"; import { afterAll, beforeAll, describe, expect, test } from "vitest"; function noop(): void { @@ -45,6 +46,7 @@ async function spawnCollect( const ROOT_DIR = join(import.meta.dirname, "../.."); const BUNDLE_PATH = join(ROOT_DIR, "dist/bin.cjs"); +const INK_APP_PATH = join(ROOT_DIR, "dist/ink-app.js"); describe("npm bundle", () => { beforeAll(async () => { @@ -149,4 +151,74 @@ describe("npm bundle", () => { const output = stdout + stderr; expect(output.length).toBeGreaterThan(0); }, 15_000); // Allow up to 15s for cold Node.js JIT startup on slow CI runners + + test("bundle exercises SQLite and auth DB (auth status)", async () => { + // Run `auth status` without a token — exercises SQLite initialization, + // schema migrations, auth DB reads, and telemetry lazy import. + // This catches lazy-import and require-resolution bugs that --version misses. + const { stdout, stderr, exitCode } = await spawnCollect( + "node", + [BUNDLE_PATH, "auth", "status"], + { + cwd: ROOT_DIR, + env: { + ...process.env, + // Do NOT set SENTRY_CLI_NO_TELEMETRY — we want to exercise telemetry init + SENTRY_AUTH_TOKEN: "", + SENTRY_TOKEN: "", + }, + } + ); + + const output = stdout + stderr; + + // Must exit 10 (AUTH_NOT_AUTHENTICATED), not crash + expect(exitCode).toBe(10); + expect(output.toLowerCase()).toContain("not authenticated"); + + // Must not contain Node.js module resolution errors + expect(output).not.toContain("Cannot find module"); + expect(output).not.toContain("MODULE_NOT_FOUND"); + expect(output).not.toContain("ERR_MODULE_NOT_FOUND"); + }, 15_000); + + test("bundle exercises SQLite with cli defaults", async () => { + // `cli defaults` (no args) reads all defaults from SQLite — exercises + // DB init and the metadata KV store without requiring auth. + const { stdout, stderr, exitCode } = await spawnCollect( + "node", + [BUNDLE_PATH, "cli", "defaults"], + { + cwd: ROOT_DIR, + env: { + ...process.env, + SENTRY_AUTH_TOKEN: "", + SENTRY_TOKEN: "", + }, + } + ); + + const output = stdout + stderr; + + // Should succeed — cli defaults with no args just shows current state + expect(exitCode).toBe(0); + + // Must not contain module resolution errors + expect(output).not.toContain("Cannot find module"); + expect(output).not.toContain("MODULE_NOT_FOUND"); + }, 15_000); + + test("Ink sidecar can be imported and exports mountApp", async () => { + // The Ink sidecar (dist/ink-app.js) is a pre-bundled self-contained ESM + // module that ships with the npm package. Verify it exists, can be imported + // by Node, and exports mountApp as a function. This catches sidecar + // bundling/resolution bugs — the exact class of bug where `with { type: "file" }` + // crashed in tsx dev mode. + expect(existsSync(INK_APP_PATH)).toBe(true); + + // Node requires a file:// URL for dynamic import of absolute ESM paths + const sidecar = await import(pathToFileURL(INK_APP_PATH).href); + + expect(typeof sidecar.mountApp).toBe("function"); + }, 15_000); }); From 9da45e28ea36cbb31d45bea0c02b963b85ccb503 Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Fri, 22 May 2026 21:24:17 +0000 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20address=20review=20=E2=80=94=20clear?= =?UTF-8?q?=20auth=20env=20vars=20in=20CI,=20isolate=20sidecar=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add explicit SENTRY_AUTH_TOKEN="" and SENTRY_TOKEN="" env overrides to CI smoke test steps to prevent future env leakage on main/release branches where production secrets are available. - Move Ink sidecar import test to a subprocess via spawnCollect to avoid polluting the vitest process with React/Ink globals from the sidecar's bundled dependencies. --- .github/workflows/ci.yml | 6 ++++++ test/e2e/bundle.test.ts | 25 ++++++++++++++++++++----- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ad6668ea..8f558237c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -312,6 +312,9 @@ jobs: - name: Smoke test (deep — SQLite, telemetry, auth DB) if: matrix.can-test shell: bash + env: + SENTRY_AUTH_TOKEN: "" + SENTRY_TOKEN: "" run: | if [[ "${{ matrix.target }}" == "windows-x64" ]]; then BIN=./dist-bin/sentry-windows-x64.exe @@ -735,6 +738,9 @@ jobs: run: node dist/bin.cjs --help - name: Smoke test (Node.js — deep) shell: bash + env: + SENTRY_AUTH_TOKEN: "" + SENTRY_TOKEN: "" run: | # auth status without a token exercises SQLite init, schema # migrations, telemetry lazy import, and the CJS require chain. diff --git a/test/e2e/bundle.test.ts b/test/e2e/bundle.test.ts index be4dc64c7..390d24fd2 100644 --- a/test/e2e/bundle.test.ts +++ b/test/e2e/bundle.test.ts @@ -9,7 +9,6 @@ import { spawn } from "node:child_process"; import { existsSync, rmSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { join } from "node:path"; -import { pathToFileURL } from "node:url"; import { afterAll, beforeAll, describe, expect, test } from "vitest"; function noop(): void { @@ -214,11 +213,27 @@ describe("npm bundle", () => { // by Node, and exports mountApp as a function. This catches sidecar // bundling/resolution bugs — the exact class of bug where `with { type: "file" }` // crashed in tsx dev mode. + // + // Run in a subprocess to avoid polluting the vitest process with + // React/Ink globals from the sidecar's bundled dependencies. expect(existsSync(INK_APP_PATH)).toBe(true); - // Node requires a file:// URL for dynamic import of absolute ESM paths - const sidecar = await import(pathToFileURL(INK_APP_PATH).href); - - expect(typeof sidecar.mountApp).toBe("function"); + const { stdout, stderr, exitCode } = await spawnCollect("node", [ + "--input-type=module", + "-e", + `import { mountApp } from ${JSON.stringify(`file://${INK_APP_PATH}`)};\n` + + 'if (typeof mountApp !== "function") {\n' + + ' process.stderr.write("mountApp is " + typeof mountApp + ", expected function");\n' + + " process.exit(1);\n" + + "}", + ]); + + if (exitCode !== 0) { + const output = stdout + stderr; + throw new Error( + `Ink sidecar import failed (exit ${exitCode}): ${output}` + ); + } + expect(exitCode).toBe(0); }, 15_000); }); From 82d4ae4a59c155a98b96a4c34f2a64983080237b Mon Sep 17 00:00:00 2001 From: Burak Yigit Kaya Date: Fri, 22 May 2026 21:33:07 +0000 Subject: [PATCH 3/3] fix: use pathToFileURL for cross-platform sidecar URL Address Cursor Bugbot review: string interpolation `file://${path}` produces invalid URLs on Windows where join() uses backslashes. Use pathToFileURL() which correctly normalizes to file:///C:/... URLs. --- test/e2e/bundle.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/e2e/bundle.test.ts b/test/e2e/bundle.test.ts index 390d24fd2..4d83a2dfa 100644 --- a/test/e2e/bundle.test.ts +++ b/test/e2e/bundle.test.ts @@ -9,6 +9,7 @@ import { spawn } from "node:child_process"; import { existsSync, rmSync } from "node:fs"; import { readFile } from "node:fs/promises"; import { join } from "node:path"; +import { pathToFileURL } from "node:url"; import { afterAll, beforeAll, describe, expect, test } from "vitest"; function noop(): void { @@ -221,7 +222,7 @@ describe("npm bundle", () => { const { stdout, stderr, exitCode } = await spawnCollect("node", [ "--input-type=module", "-e", - `import { mountApp } from ${JSON.stringify(`file://${INK_APP_PATH}`)};\n` + + `import { mountApp } from ${JSON.stringify(pathToFileURL(INK_APP_PATH).href)};\n` + 'if (typeof mountApp !== "function") {\n' + ' process.stderr.write("mountApp is " + typeof mountApp + ", expected function");\n' + " process.exit(1);\n" +