/** * Environment detection utilities for determining runtime context * * Helps distinguish between: * - Node.js (standard tests) * - Cloudflare Workers / workerd (Workers-specific tests) * - Browser (client-side) * - Miniflare (local Workers simulation) */ // Declare globals that might exist in different environments declare const navigator: { userAgent?: string } | undefined declare const caches: { default?: unknown } | undefined /** * Runtime environment types */ export type RuntimeEnvironment = 'node' | 'workerd' | 'browser' | 'miniflare' | 'unknown' /** * Test environment types for vitest configuration */ export type TestEnvironment = 'node' | 'workerd' | 'browser' /** * Environment capabilities that affect test behavior */ export interface EnvironmentCapabilities { /** Whether WebSocket is natively available */ hasWebSocket: boolean /** Whether WASM can be dynamically compiled at runtime */ hasRuntimeWasmCompile: boolean /** Whether fetch is natively available */ hasFetch: boolean /** Whether crypto.subtle is available */ hasCryptoSubtle: boolean /** Whether Durable Objects stubs are available */ hasDurableObjects: boolean /** Whether R2/KV bindings are available */ hasCloudflareBindings: boolean /** Whether process.env is available */ hasProcessEnv: boolean /** Whether import.meta.url is defined */ hasImportMetaUrl: boolean } /** * Detect if running in Cloudflare Workers/workerd environment * * Detection based on: * - navigator.userAgent containing 'Cloudflare-Workers' * - Presence of caches.default (Cloudflare Cache API) * - Absence of process global */ export function isWorkerd(): boolean { try { // Check for Cloudflare Workers specific globals if (typeof navigator !== 'undefined' && navigator?.userAgent?.includes('Cloudflare-Workers')) { return true } // Check for caches.default (Cloudflare Cache API, not in standard browsers) if (typeof caches !== 'undefined' && caches && 'default' in caches) { return true } // In workerd, there's no process global (unless nodejs_compat is enabled) // But we need to be careful since miniflare polyfills some things if ( typeof globalThis !== 'undefined' && typeof (globalThis as unknown as { process?: unknown }).process === 'undefined' && typeof (globalThis as unknown as { Deno?: unknown }).Deno === 'undefined' && typeof (globalThis as unknown as { window?: unknown }).window === 'undefined' ) { // Additional check: WebSocket should be available but document shouldn't if ( typeof WebSocket !== 'undefined' && typeof (globalThis as unknown as { document?: unknown }).document === 'undefined' ) { return true } } return false } catch { return false } } /** * Detect if running in Node.js environment */ export function isNode(): boolean { try { return ( typeof (globalThis as unknown as { process?: { versions?: { node?: unknown } } }).process !== 'undefined' && typeof (globalThis as unknown as { process: { versions?: { node?: unknown } } }).process .versions?.node !== 'undefined' ) } catch { return false } } /** * Detect if running in browser environment */ export function isBrowser(): boolean { try { return ( typeof (globalThis as unknown as { window?: unknown }).window !== 'undefined' && typeof (globalThis as unknown as { document?: unknown }).document !== 'undefined' && !(globalThis as unknown as { process?: unknown }).process ) } catch { return false } } /** * Detect if running in Miniflare (local Workers simulation) */ export function isMiniflare(): boolean { try { // Miniflare sets MINIFLARE env var or has specific behaviors const env = (globalThis as unknown as { process?: { env?: { MINIFLARE?: string } } }).process ?.env if (env?.MINIFLARE === 'true') { return true } // Also check vitest pool workers environment marker if ( typeof (globalThis as unknown as { __VITEST_POOL_WORKERS__?: boolean }) .__VITEST_POOL_WORKERS__ !== 'undefined' ) { return true } return false } catch { return false } } /** * Get the current runtime environment */ export function getRuntimeEnvironment(): RuntimeEnvironment { if (isMiniflare()) return 'miniflare' if (isWorkerd()) return 'workerd' if (isNode()) return 'node' if (isBrowser()) return 'browser' return 'unknown' } /** * Get capabilities of the current environment */ export function getEnvironmentCapabilities(): EnvironmentCapabilities { const env = getRuntimeEnvironment() return { hasWebSocket: typeof WebSocket !== 'undefined', hasRuntimeWasmCompile: env === 'node' || env === 'browser', hasFetch: typeof fetch !== 'undefined', hasCryptoSubtle: typeof crypto !== 'undefined' && typeof (crypto as { subtle?: unknown }).subtle !== 'undefined', hasDurableObjects: env === 'workerd' || env === 'miniflare', hasCloudflareBindings: env === 'workerd' || env === 'miniflare', hasProcessEnv: typeof (globalThis as unknown as { process?: { env?: unknown } }).process?.env !== 'undefined', hasImportMetaUrl: (() => { try { // This will be evaluated at build time in some environments return typeof import.meta?.url !== 'undefined' } catch { return false } })(), } } /** * Skip test if not in specified environment * * @example * ```ts * import { skipIfNot } from '@dotdo/postgres-shared' * * describe('Workers-only tests', () => { * skipIfNot('workerd') * * it('should work in workerd', () => { ... }) * }) * ``` */ export function skipIfNot( requiredEnv: TestEnvironment | TestEnvironment[] ): { skip: boolean; reason: string } { const currentEnv = getRuntimeEnvironment() const envs = Array.isArray(requiredEnv) ? requiredEnv : [requiredEnv] // Map miniflare to workerd for test purposes const effectiveEnv = currentEnv === 'miniflare' ? 'workerd' : currentEnv if (!envs.includes(effectiveEnv as TestEnvironment)) { return { skip: true, reason: `Test requires ${envs.join(' or ')} environment, but running in ${currentEnv}`, } } return { skip: false, reason: '' } } /** * Skip test if in specified environment * * @example * ```ts * import { skipIf } from '@dotdo/postgres-shared' * * describe('Non-workerd tests', () => { * skipIf('workerd') * * it('should work outside workerd', () => { ... }) * }) * ``` */ export function skipIf( excludedEnv: TestEnvironment | TestEnvironment[] ): { skip: boolean; reason: string } { const currentEnv = getRuntimeEnvironment() const envs = Array.isArray(excludedEnv) ? excludedEnv : [excludedEnv] // Map miniflare to workerd for test purposes const effectiveEnv = currentEnv === 'miniflare' ? 'workerd' : currentEnv if (envs.includes(effectiveEnv as TestEnvironment)) { return { skip: true, reason: `Test is skipped in ${currentEnv} environment`, } } return { skip: false, reason: '' } } /** * Vitest-compatible describe.skipIf helper * * @example * ```ts * import { describe, it } from 'vitest' * import { describeIf } from '@dotdo/postgres-shared' * * // Only run in Node environment * describeIf('node')('Node-only tests', () => { * it('works', () => { ... }) * }) * * // Only run in workerd/miniflare * describeIf('workerd')('Workers tests', () => { * it('works', () => { ... }) * }) * ``` */ export function describeIf(requiredEnv: TestEnvironment | TestEnvironment[]) { const { skip } = skipIfNot(requiredEnv) // Return a function that takes describe-style arguments // The actual describe.skipIf will be handled by the test file return (name: string, fn: () => void) => ({ name, fn, skip }) } /** * Assertion for environment at runtime * * @throws Error if not in expected environment */ export function assertEnvironment(expected: RuntimeEnvironment | RuntimeEnvironment[]): void { const current = getRuntimeEnvironment() const expectedEnvs = Array.isArray(expected) ? expected : [expected] // Map miniflare to workerd for comparison const effectiveCurrent = current === 'miniflare' ? 'workerd' : current if (!expectedEnvs.includes(effectiveCurrent as RuntimeEnvironment)) { throw new Error( `Expected environment ${expectedEnvs.join(' or ')}, but running in ${current}` ) } } /** * Environment info for debugging and logging */ export function getEnvironmentInfo(): { runtime: RuntimeEnvironment capabilities: EnvironmentCapabilities userAgent?: string | undefined nodeVersion?: string | undefined } { const runtime = getRuntimeEnvironment() const capabilities = getEnvironmentCapabilities() const userAgent = typeof navigator !== 'undefined' && navigator ? navigator.userAgent : undefined const nodeVersion = typeof (globalThis as unknown as { process?: { version?: string } }).process?.version === 'string' ? (globalThis as unknown as { process: { version: string } }).process.version : undefined return { runtime, capabilities, ...(userAgent !== undefined && { userAgent }), ...(nodeVersion !== undefined && { nodeVersion }), } }