diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index cf532e2f..64c0cc6d 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -67,10 +67,11 @@ When creating your Sentry OAuth application: ## Environment Variables -| Variable | Description | Default | -| ------------------ | ------------------------------------ | -------------------- | -| `SENTRY_CLIENT_ID` | Sentry OAuth app client ID | (required) | -| `SENTRY_URL` | Sentry instance URL (for self-hosted)| `https://sentry.io` | +| Variable | Description | Default | +| ------------------ | ----------------------------------------------------- | -------------------- | +| `SENTRY_CLIENT_ID` | Sentry OAuth app client ID | (required) | +| `SENTRY_HOST` | Sentry instance URL (for self-hosted, takes precedence) | `https://sentry.io` | +| `SENTRY_URL` | Alias for `SENTRY_HOST` | `https://sentry.io` | ## Building diff --git a/docs/src/content/docs/configuration.md b/docs/src/content/docs/configuration.md index 7e0efec2..9c18eaad 100644 --- a/docs/src/content/docs/configuration.md +++ b/docs/src/content/docs/configuration.md @@ -7,16 +7,22 @@ The Sentry CLI can be configured through environment variables and a local datab ## Environment Variables -### `SENTRY_URL` +### `SENTRY_HOST` Base URL of your Sentry instance. **Only needed for [self-hosted Sentry](./self-hosted/).** SaaS users (sentry.io) should not set this. ```bash -export SENTRY_URL=https://sentry.example.com +export SENTRY_HOST=https://sentry.example.com ``` When set, all API requests (including OAuth login) are directed to this URL instead of `https://sentry.io`. The CLI also sets this automatically when you pass a self-hosted Sentry URL as a command argument. +`SENTRY_HOST` takes precedence over `SENTRY_URL`. Both work identically — use whichever you prefer. + +### `SENTRY_URL` + +Alias for `SENTRY_HOST`. If both are set, `SENTRY_HOST` takes precedence. + ### `SENTRY_ORG` Default organization slug. Skips organization auto-detection. diff --git a/docs/src/content/docs/self-hosted.md b/docs/src/content/docs/self-hosted.md index d5ccb5b7..6c8396a7 100644 --- a/docs/src/content/docs/self-hosted.md +++ b/docs/src/content/docs/self-hosted.md @@ -3,10 +3,10 @@ title: Self-Hosted Sentry description: Using the Sentry CLI with a self-hosted Sentry instance --- -The CLI works with self-hosted Sentry instances. Set the `SENTRY_URL` environment variable to point at your instance: +The CLI works with self-hosted Sentry instances. Set the `SENTRY_HOST` (or `SENTRY_URL`) environment variable to point at your instance: ```bash -export SENTRY_URL=https://sentry.example.com +export SENTRY_HOST=https://sentry.example.com ``` ## Authenticating @@ -27,14 +27,14 @@ The OAuth device flow requires **Sentry 26.1.0 or later** and a public OAuth app Pass your instance URL and the client ID: ```bash -SENTRY_URL=https://sentry.example.com SENTRY_CLIENT_ID=your-client-id sentry auth login +SENTRY_HOST=https://sentry.example.com SENTRY_CLIENT_ID=your-client-id sentry auth login ``` :::tip You can export both variables in your shell profile so every CLI invocation picks them up: ```bash -export SENTRY_URL=https://sentry.example.com +export SENTRY_HOST=https://sentry.example.com export SENTRY_CLIENT_ID=your-client-id ``` ::: @@ -48,7 +48,7 @@ If your instance is on an older version or you prefer not to create an OAuth app 3. Pass it to the CLI: ```bash -SENTRY_URL=https://sentry.example.com sentry auth login --token YOUR_TOKEN +SENTRY_HOST=https://sentry.example.com sentry auth login --token YOUR_TOKEN ``` ## After Login @@ -66,7 +66,8 @@ If you pass a self-hosted Sentry URL as a command argument (e.g., an issue or ev | Variable | Description | |----------|-------------| -| `SENTRY_URL` | Base URL of your Sentry instance | +| `SENTRY_HOST` | Base URL of your Sentry instance (takes precedence over `SENTRY_URL`) | +| `SENTRY_URL` | Alias for `SENTRY_HOST` | | `SENTRY_CLIENT_ID` | Client ID of your public OAuth application | | `SENTRY_ORG` | Default organization slug | | `SENTRY_PROJECT` | Default project slug (supports `org/project` format) | diff --git a/src/lib/constants.ts b/src/lib/constants.ts index b0eaccf2..0ac4016a 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -11,6 +11,14 @@ export const DEFAULT_SENTRY_HOST = "sentry.io"; /** Default Sentry SaaS URL (control silo for OAuth and region discovery) */ export const DEFAULT_SENTRY_URL = `https://${DEFAULT_SENTRY_HOST}`; +/** + * Resolve the Sentry instance URL from environment variables. + * Checks SENTRY_HOST first, then SENTRY_URL, then falls back to undefined. + */ +export function getConfiguredSentryUrl(): string | undefined { + return process.env.SENTRY_HOST || process.env.SENTRY_URL || undefined; +} + /** CLI version string, available for help output and other uses */ export const CLI_VERSION = typeof SENTRY_CLI_VERSION !== "undefined" ? SENTRY_CLI_VERSION : "0.0.0-dev"; diff --git a/src/lib/dsn/code-scanner.ts b/src/lib/dsn/code-scanner.ts index 8c0fabae..e4698686 100644 --- a/src/lib/dsn/code-scanner.ts +++ b/src/lib/dsn/code-scanner.ts @@ -19,7 +19,7 @@ import path from "node:path"; import * as Sentry from "@sentry/bun"; import ignore, { type Ignore } from "ignore"; import pLimit from "p-limit"; -import { DEFAULT_SENTRY_HOST } from "../constants.js"; +import { DEFAULT_SENTRY_HOST, getConfiguredSentryUrl } from "../constants.js"; import { ConfigError } from "../errors.js"; import { logger } from "../logger.js"; import { withTracingSpan } from "../telemetry.js"; @@ -316,7 +316,7 @@ function isCommentedLine(trimmedLine: string): boolean { * @returns The expected host domain for DSN validation */ function getExpectedHost(): string { - const sentryUrl = process.env.SENTRY_URL; + const sentryUrl = getConfiguredSentryUrl(); if (sentryUrl) { // Self-hosted: only accept DSNs matching the configured host @@ -324,10 +324,10 @@ function getExpectedHost(): string { const url = new URL(sentryUrl); return url.host; } catch { - // Invalid SENTRY_URL - throw immediately since nothing will work + // Invalid SENTRY_HOST/SENTRY_URL - throw immediately since nothing will work throw new ConfigError( - `SENTRY_URL "${sentryUrl}" is not a valid URL`, - "Set SENTRY_URL to a valid URL (e.g., https://sentry.example.com) or unset it to use sentry.io" + `SENTRY_HOST/SENTRY_URL "${sentryUrl}" is not a valid URL`, + "Set SENTRY_HOST/SENTRY_URL to a valid URL (e.g., https://sentry.example.com) or unset it to use sentry.io" ); } } diff --git a/src/lib/oauth.ts b/src/lib/oauth.ts index 988fae20..85a8879c 100644 --- a/src/lib/oauth.ts +++ b/src/lib/oauth.ts @@ -11,6 +11,7 @@ import { TokenErrorResponseSchema, TokenResponseSchema, } from "../types/index.js"; +import { DEFAULT_SENTRY_URL, getConfiguredSentryUrl } from "./constants.js"; import { setAuthToken } from "./db/auth.js"; import { ApiError, AuthError, ConfigError, DeviceFlowError } from "./errors.js"; import { withHttpSpan } from "./telemetry.js"; @@ -23,7 +24,7 @@ import { withHttpSpan } from "./telemetry.js"; * by the device flow and token refresh. */ function getSentryUrl(): string { - return process.env.SENTRY_URL ?? "https://sentry.io"; + return getConfiguredSentryUrl() ?? DEFAULT_SENTRY_URL; } /** diff --git a/src/lib/region.ts b/src/lib/region.ts index 25f1f091..d8f20d91 100644 --- a/src/lib/region.ts +++ b/src/lib/region.ts @@ -6,6 +6,7 @@ */ import { retrieveAnOrganization } from "@sentry/api"; +import { getConfiguredSentryUrl } from "./constants.js"; import { getOrgByNumericId, getOrgRegion, setOrgRegion } from "./db/regions.js"; import { stripDsnOrgPrefix } from "./dsn/index.js"; import { withAuthGuard } from "./errors.js"; @@ -66,8 +67,8 @@ export async function resolveOrgRegion(orgSlug: string): Promise { * Returns false for self-hosted instances that don't have regional URLs. */ export function isMultiRegionEnabled(): boolean { - // Self-hosted instances (custom SENTRY_URL) typically don't have multi-region - const baseUrl = process.env.SENTRY_URL; + // Self-hosted instances (custom SENTRY_HOST/SENTRY_URL) typically don't have multi-region + const baseUrl = getConfiguredSentryUrl(); if (baseUrl && !isSentrySaasUrl(baseUrl)) { return false; } diff --git a/src/lib/sentry-client.ts b/src/lib/sentry-client.ts index 90e7f259..5a202252 100644 --- a/src/lib/sentry-client.ts +++ b/src/lib/sentry-client.ts @@ -8,7 +8,11 @@ * through the SDK function options (baseUrl, fetch, headers). */ -import { DEFAULT_SENTRY_URL, getUserAgent } from "./constants.js"; +import { + DEFAULT_SENTRY_URL, + getConfiguredSentryUrl, + getUserAgent, +} from "./constants.js"; import { getAuthToken, isEnvTokenActive, refreshToken } from "./db/auth.js"; import { getCachedResponse, storeCachedResponse } from "./response-cache.js"; import { withHttpSpan } from "./telemetry.js"; @@ -391,7 +395,7 @@ function getAuthenticatedFetch(): typeof fetch { * Supports self-hosted instances via SENTRY_URL env var. */ export function getApiBaseUrl(): string { - return process.env.SENTRY_URL || DEFAULT_SENTRY_URL; + return getConfiguredSentryUrl() ?? DEFAULT_SENTRY_URL; } /** @@ -402,7 +406,7 @@ export function getApiBaseUrl(): string { * (e.g., from URL argument parsing for self-hosted instances) is respected. */ export function getControlSiloUrl(): string { - return process.env.SENTRY_URL || DEFAULT_SENTRY_URL; + return getConfiguredSentryUrl() ?? DEFAULT_SENTRY_URL; } /** diff --git a/src/lib/sentry-url-parser.ts b/src/lib/sentry-url-parser.ts index 450d55f3..e75dac83 100644 --- a/src/lib/sentry-url-parser.ts +++ b/src/lib/sentry-url-parser.ts @@ -191,10 +191,13 @@ export function parseSentryUrl(input: string): ParsedSentryUrl | null { export function applySentryUrlContext(baseUrl: string): void { if (isSentrySaasUrl(baseUrl)) { // Clear any self-hosted URL so API calls fall back to default SaaS routing. - // Without this, a stale SENTRY_URL would route SaaS requests to the wrong host. + // Without this, a stale SENTRY_HOST/SENTRY_URL would route SaaS requests to the wrong host. + // biome-ignore lint/performance/noDelete: process.env requires delete to truly unset; assignment coerces to string in Node.js + delete process.env.SENTRY_HOST; // biome-ignore lint/performance/noDelete: process.env requires delete to truly unset; assignment coerces to string in Node.js delete process.env.SENTRY_URL; return; } + process.env.SENTRY_HOST = baseUrl; process.env.SENTRY_URL = baseUrl; } diff --git a/src/lib/sentry-urls.ts b/src/lib/sentry-urls.ts index 07b67715..b2aac728 100644 --- a/src/lib/sentry-urls.ts +++ b/src/lib/sentry-urls.ts @@ -5,14 +5,18 @@ * Supports self-hosted instances via SENTRY_URL environment variable. */ -import { DEFAULT_SENTRY_HOST, DEFAULT_SENTRY_URL } from "./constants.js"; +import { + DEFAULT_SENTRY_HOST, + DEFAULT_SENTRY_URL, + getConfiguredSentryUrl, +} from "./constants.js"; /** * Get the Sentry web base URL. * Supports self-hosted instances via SENTRY_URL env var. */ export function getSentryBaseUrl(): string { - return process.env.SENTRY_URL ?? DEFAULT_SENTRY_URL; + return getConfiguredSentryUrl() ?? DEFAULT_SENTRY_URL; } /** diff --git a/test/commands/event/view.test.ts b/test/commands/event/view.test.ts index 8d9c43f9..091b8162 100644 --- a/test/commands/event/view.test.ts +++ b/test/commands/event/view.test.ts @@ -158,13 +158,16 @@ describe("parsePositionalArgs", () => { }); }); - // URL integration tests — applySentryUrlContext may set SENTRY_URL as a side effect + // URL integration tests — applySentryUrlContext may set SENTRY_HOST/SENTRY_URL as a side effect describe("Sentry URL inputs", () => { let savedSentryUrl: string | undefined; + let savedSentryHost: string | undefined; beforeEach(() => { savedSentryUrl = process.env.SENTRY_URL; + savedSentryHost = process.env.SENTRY_HOST; delete process.env.SENTRY_URL; + delete process.env.SENTRY_HOST; }); afterEach(() => { @@ -173,6 +176,11 @@ describe("parsePositionalArgs", () => { } else { delete process.env.SENTRY_URL; } + if (savedSentryHost !== undefined) { + process.env.SENTRY_HOST = savedSentryHost; + } else { + delete process.env.SENTRY_HOST; + } }); test("event URL extracts eventId and passes org as OrgAll target", () => { diff --git a/test/lib/arg-parsing.test.ts b/test/lib/arg-parsing.test.ts index 23a38409..0542170d 100644 --- a/test/lib/arg-parsing.test.ts +++ b/test/lib/arg-parsing.test.ts @@ -109,13 +109,16 @@ describe("parseOrgProjectArg", () => { }); }); - // URL integration tests — applySentryUrlContext may set SENTRY_URL as a side effect + // URL integration tests — applySentryUrlContext may set SENTRY_HOST/SENTRY_URL as a side effect describe("Sentry URL inputs", () => { let savedSentryUrl: string | undefined; + let savedSentryHost: string | undefined; beforeEach(() => { savedSentryUrl = process.env.SENTRY_URL; + savedSentryHost = process.env.SENTRY_HOST; delete process.env.SENTRY_URL; + delete process.env.SENTRY_HOST; }); afterEach(() => { @@ -124,6 +127,11 @@ describe("parseOrgProjectArg", () => { } else { delete process.env.SENTRY_URL; } + if (savedSentryHost !== undefined) { + process.env.SENTRY_HOST = savedSentryHost; + } else { + delete process.env.SENTRY_HOST; + } }); test("issue URL returns org-all", () => { @@ -228,13 +236,16 @@ describe("parseIssueArg", () => { }); }); - // URL integration tests — applySentryUrlContext may set SENTRY_URL as a side effect + // URL integration tests — applySentryUrlContext may set SENTRY_HOST/SENTRY_URL as a side effect describe("Sentry URL inputs", () => { let savedSentryUrl: string | undefined; + let savedSentryHost: string | undefined; beforeEach(() => { savedSentryUrl = process.env.SENTRY_URL; + savedSentryHost = process.env.SENTRY_HOST; delete process.env.SENTRY_URL; + delete process.env.SENTRY_HOST; }); afterEach(() => { @@ -243,6 +254,11 @@ describe("parseIssueArg", () => { } else { delete process.env.SENTRY_URL; } + if (savedSentryHost !== undefined) { + process.env.SENTRY_HOST = savedSentryHost; + } else { + delete process.env.SENTRY_HOST; + } }); test("issue URL with numeric ID returns explicit-org-numeric", () => { diff --git a/test/lib/dsn/code-scanner.test.ts b/test/lib/dsn/code-scanner.test.ts index 12df116a..5d07aa57 100644 --- a/test/lib/dsn/code-scanner.test.ts +++ b/test/lib/dsn/code-scanner.test.ts @@ -17,6 +17,7 @@ describe("Code Scanner", () => { afterEach(() => { rmSync(testDir, { recursive: true, force: true }); + delete process.env.SENTRY_HOST; delete process.env.SENTRY_URL; }); @@ -211,9 +212,30 @@ describe("Code Scanner", () => { // Invalid SENTRY_URL should throw immediately since nothing will work expect(() => extractDsnsFromContent(content)).toThrow( - /SENTRY_URL.*not a valid URL/ + /SENTRY_HOST\/SENTRY_URL.*not a valid URL/ ); }); + + test("accepts self-hosted DSNs when SENTRY_HOST is set", () => { + process.env.SENTRY_HOST = "https://sentry.mycompany.com:9000"; + const content = ` + const DSN = "https://abc@sentry.mycompany.com:9000/123"; + `; + const dsns = extractDsnsFromContent(content); + expect(dsns).toEqual(["https://abc@sentry.mycompany.com:9000/123"]); + }); + + test("SENTRY_HOST takes precedence over SENTRY_URL for DSN validation", () => { + process.env.SENTRY_HOST = "https://sentry.mycompany.com:9000"; + process.env.SENTRY_URL = "https://sentry.other.com"; + const content = ` + const DSN1 = "https://abc@sentry.mycompany.com:9000/123"; + const DSN2 = "https://def@sentry.other.com/456"; + `; + const dsns = extractDsnsFromContent(content); + // Only the SENTRY_HOST DSN should be accepted + expect(dsns).toEqual(["https://abc@sentry.mycompany.com:9000/123"]); + }); }); describe("extractFirstDsnFromContent", () => { diff --git a/test/lib/region.test.ts b/test/lib/region.test.ts index 198400ed..a17085eb 100644 --- a/test/lib/region.test.ts +++ b/test/lib/region.test.ts @@ -17,18 +17,21 @@ import { useTestConfigDir } from "../helpers.js"; useTestConfigDir("region-resolve-"); beforeEach(async () => { - // Clear any SENTRY_URL override for most tests + // Clear any SENTRY_HOST/SENTRY_URL override for most tests + delete process.env.SENTRY_HOST; delete process.env.SENTRY_URL; // Set up auth token for API tests await setAuthToken("test-token"); }); afterEach(() => { + delete process.env.SENTRY_HOST; delete process.env.SENTRY_URL; }); describe("getSentryBaseUrl", () => { test("returns sentry.io by default", () => { + delete process.env.SENTRY_HOST; delete process.env.SENTRY_URL; expect(getSentryBaseUrl()).toBe("https://sentry.io"); }); @@ -37,6 +40,17 @@ describe("getSentryBaseUrl", () => { process.env.SENTRY_URL = "https://sentry.mycompany.com"; expect(getSentryBaseUrl()).toBe("https://sentry.mycompany.com"); }); + + test("respects SENTRY_HOST env var", () => { + process.env.SENTRY_HOST = "https://sentry.mycompany.com"; + expect(getSentryBaseUrl()).toBe("https://sentry.mycompany.com"); + }); + + test("SENTRY_HOST takes precedence over SENTRY_URL", () => { + process.env.SENTRY_HOST = "https://host.example.com"; + process.env.SENTRY_URL = "https://url.example.com"; + expect(getSentryBaseUrl()).toBe("https://host.example.com"); + }); }); describe("isMultiRegionEnabled", () => { @@ -85,6 +99,17 @@ describe("isMultiRegionEnabled", () => { process.env.SENTRY_URL = "not-a-valid-url"; expect(isMultiRegionEnabled()).toBe(false); }); + + test("respects SENTRY_HOST for self-hosted", () => { + process.env.SENTRY_HOST = "https://sentry.mycompany.com"; + expect(isMultiRegionEnabled()).toBe(false); + }); + + test("SENTRY_HOST takes precedence over SENTRY_URL", () => { + process.env.SENTRY_HOST = "https://sentry.mycompany.com"; + process.env.SENTRY_URL = "https://us.sentry.io"; + expect(isMultiRegionEnabled()).toBe(false); + }); }); describe("resolveOrgRegion", () => { diff --git a/test/lib/sentry-url-parser.test.ts b/test/lib/sentry-url-parser.test.ts index d7f6b9ee..87dcf05f 100644 --- a/test/lib/sentry-url-parser.test.ts +++ b/test/lib/sentry-url-parser.test.ts @@ -311,10 +311,13 @@ describe("parseSentryUrl", () => { describe("applySentryUrlContext", () => { let originalSentryUrl: string | undefined; + let originalSentryHost: string | undefined; beforeEach(() => { originalSentryUrl = process.env.SENTRY_URL; + originalSentryHost = process.env.SENTRY_HOST; delete process.env.SENTRY_URL; + delete process.env.SENTRY_HOST; }); afterEach(() => { @@ -323,43 +326,58 @@ describe("applySentryUrlContext", () => { } else { delete process.env.SENTRY_URL; } + if (originalSentryHost !== undefined) { + process.env.SENTRY_HOST = originalSentryHost; + } else { + delete process.env.SENTRY_HOST; + } }); - test("sets SENTRY_URL for self-hosted instance", () => { + test("sets both SENTRY_HOST and SENTRY_URL for self-hosted instance", () => { applySentryUrlContext("https://sentry.example.com"); + expect(process.env.SENTRY_HOST).toBe("https://sentry.example.com"); expect(process.env.SENTRY_URL).toBe("https://sentry.example.com"); }); - test("does not set SENTRY_URL for SaaS (sentry.io)", () => { + test("does not set SENTRY_HOST or SENTRY_URL for SaaS (sentry.io)", () => { applySentryUrlContext("https://sentry.io"); + expect(process.env.SENTRY_HOST).toBeUndefined(); expect(process.env.SENTRY_URL).toBeUndefined(); }); - test("does not set SENTRY_URL for SaaS subdomain (us.sentry.io)", () => { + test("does not set SENTRY_HOST or SENTRY_URL for SaaS subdomain (us.sentry.io)", () => { applySentryUrlContext("https://us.sentry.io"); + expect(process.env.SENTRY_HOST).toBeUndefined(); expect(process.env.SENTRY_URL).toBeUndefined(); }); - test("overrides existing SENTRY_URL (parsed URL takes precedence)", () => { + test("overrides existing env vars (parsed URL takes precedence)", () => { + process.env.SENTRY_HOST = "https://existing.example.com"; process.env.SENTRY_URL = "https://existing.example.com"; applySentryUrlContext("https://sentry.other.com"); + expect(process.env.SENTRY_HOST).toBe("https://sentry.other.com"); expect(process.env.SENTRY_URL).toBe("https://sentry.other.com"); }); - test("sets SENTRY_URL for self-hosted with port", () => { + test("sets both env vars for self-hosted with port", () => { applySentryUrlContext("https://sentry.acme.internal:9000"); + expect(process.env.SENTRY_HOST).toBe("https://sentry.acme.internal:9000"); expect(process.env.SENTRY_URL).toBe("https://sentry.acme.internal:9000"); }); - test("clears existing SENTRY_URL when SaaS URL is detected", () => { + test("clears both env vars when SaaS URL is detected", () => { + process.env.SENTRY_HOST = "https://sentry.example.com"; process.env.SENTRY_URL = "https://sentry.example.com"; applySentryUrlContext("https://sentry.io"); + expect(process.env.SENTRY_HOST).toBeUndefined(); expect(process.env.SENTRY_URL).toBeUndefined(); }); - test("clears existing SENTRY_URL when SaaS subdomain is detected", () => { + test("clears both env vars when SaaS subdomain is detected", () => { + process.env.SENTRY_HOST = "https://sentry.example.com"; process.env.SENTRY_URL = "https://sentry.example.com"; applySentryUrlContext("https://us.sentry.io"); + expect(process.env.SENTRY_HOST).toBeUndefined(); expect(process.env.SENTRY_URL).toBeUndefined(); }); }); diff --git a/test/lib/sentry-urls.property.test.ts b/test/lib/sentry-urls.property.test.ts index e0b810e3..32f2c7af 100644 --- a/test/lib/sentry-urls.property.test.ts +++ b/test/lib/sentry-urls.property.test.ts @@ -30,10 +30,13 @@ import { DEFAULT_NUM_RUNS } from "../model-based/helpers.js"; // Save original env let originalSentryUrl: string | undefined; +let originalSentryHost: string | undefined; beforeEach(() => { originalSentryUrl = process.env.SENTRY_URL; - // Clear SENTRY_URL for consistent base URL in tests + originalSentryHost = process.env.SENTRY_HOST; + // Clear SENTRY_HOST/SENTRY_URL for consistent base URL in tests + delete process.env.SENTRY_HOST; delete process.env.SENTRY_URL; }); @@ -43,6 +46,11 @@ afterEach(() => { } else { delete process.env.SENTRY_URL; } + if (originalSentryHost !== undefined) { + process.env.SENTRY_HOST = originalSentryHost; + } else { + delete process.env.SENTRY_HOST; + } }); // Arbitraries @@ -456,6 +464,23 @@ describe("buildTraceUrl properties", () => { }); }); +describe("SENTRY_HOST precedence", () => { + test("SENTRY_HOST takes precedence over SENTRY_URL for URL builders", () => { + process.env.SENTRY_HOST = "https://host.company.com"; + process.env.SENTRY_URL = "https://url.company.com"; + expect(buildOrgUrl("my-org")).toContain("host.company.com"); + expect(buildOrgUrl("my-org")).not.toContain("url.company.com"); + }); + + test("SENTRY_HOST alone configures self-hosted URL builders", () => { + process.env.SENTRY_HOST = "https://sentry.company.com"; + expect(getOrgBaseUrl("my-org")).toBe("https://sentry.company.com"); + expect(buildOrgUrl("my-org")).toBe( + "https://sentry.company.com/organizations/my-org/" + ); + }); +}); + describe("self-hosted URLs", () => { const SELF_HOSTED_URL = "https://sentry.company.com"; diff --git a/test/preload.ts b/test/preload.ts index 2c444128..fefe07cf 100644 --- a/test/preload.ts +++ b/test/preload.ts @@ -58,6 +58,7 @@ delete process.env.SENTRY_AUTH_TOKEN; delete process.env.SENTRY_TOKEN; delete process.env.SENTRY_CLIENT_ID; delete process.env.SENTRY_URL; +delete process.env.SENTRY_HOST; delete process.env.SENTRY_ORG; delete process.env.SENTRY_PROJECT;