import * as plugins from './smartenv.plugins.js'; import * as interfaces from './interfaces/index.js'; // interfaces export interface IEnvObject { name: string; value: string; } /** * Smartenv class that makes it easy */ export class Smartenv { public async getEnvAwareModule(optionsArg: { nodeModuleName: string; webUrlArg: string; getFunction: () => any; }) { if (this.isNode) { const moduleResult = await this.getSafeNodeModule(optionsArg.nodeModuleName); return moduleResult; } else if (this.isBrowser) { const moduleResult = await this.getSafeWebModule( optionsArg.webUrlArg, optionsArg.getFunction ); return moduleResult; } else { console.error('platform for loading not supported by smartenv'); } } public async getSafeNodeModule(moduleNameArg: string, runAfterFunc?: (moduleArg: T) => Promise): Promise { if (!this.isNode && !this.isDeno && !this.isBun) { console.error(`You tried to load a server module in a wrong context: ${moduleNameArg}. This does not throw.`); return; } // tslint:disable-next-line: function-constructor const returnValue: T = await (new Function(`return import('${moduleNameArg}')`)() as Promise); if (runAfterFunc) { await runAfterFunc(returnValue); } return returnValue; } public loadedScripts: string[] = []; public async getSafeWebModule(urlArg: string, getFunctionArg: () => any) { if (!this.isBrowser) { console.error('You tried to load a web module in a wrong context'); return; } if (this.loadedScripts.includes(urlArg)) { return getFunctionArg(); } else { this.loadedScripts.push(urlArg); } const done = plugins.smartpromise.defer(); if (globalThis.importScripts) { globalThis.importScripts(urlArg); done.resolve(); } else { const script = document.createElement('script'); script.onload = () => { done.resolve(); }; script.src = urlArg; document.head.appendChild(script); } await done.promise; return getFunctionArg(); } public get runtimeEnv(): interfaces.TRuntimeType { // Check Deno first (most distinctive) if (typeof globalThis.Deno !== 'undefined' && typeof (globalThis as any).Deno?.version !== 'undefined') { return 'deno'; } // Check Bun second (most distinctive) if (typeof globalThis.Bun !== 'undefined' && typeof (globalThis as any).Bun?.version !== 'undefined') { return 'bun'; } // Check Node.js (be explicit about versions to avoid Deno/Bun false positives) if (typeof globalThis.process !== 'undefined' && typeof (globalThis as any).process?.versions?.node !== 'undefined') { return 'node'; } // Check Browser (default fallback) if (typeof globalThis.window !== 'undefined' && typeof (globalThis as any).document !== 'undefined') { return 'browser'; } // Safe fallback return 'browser'; } public get isBrowser(): boolean { return this.runtimeEnv === 'browser'; } public get isNode(): boolean { return this.runtimeEnv === 'node'; } public get isDeno(): boolean { return this.runtimeEnv === 'deno'; } public get isBun(): boolean { return this.runtimeEnv === 'bun'; } public get userAgent(): string { if (this.isBrowser) { // make sure we are in Browser return navigator.userAgent; } else { return 'undefined'; } } public get nodeVersion(): string { if (this.isNode) { return process.version; } return 'undefined'; } public get denoVersion(): string { if (this.isDeno) { return (globalThis as any).Deno.version.deno; } return 'undefined'; } public get bunVersion(): string { if (this.isBun) { return (globalThis as any).Bun.version; } return 'undefined'; } /** * Load a module only if the current runtime matches the target runtime(s) * @param target - Single runtime, array of runtimes, or 'server' for all server-side runtimes * @param moduleNameOrUrl - Module name (for Node/Deno/Bun) or URL (for browser) * @param getFunction - Optional function to retrieve the module in browser context * @returns The loaded module or undefined if runtime doesn't match */ public async getSafeModuleFor( target: interfaces.TRuntimeTarget | interfaces.TRuntimeTarget[], moduleNameOrUrl: string, getFunction?: () => any ): Promise { // Normalize target to array let targetRuntimes: interfaces.TRuntimeType[]; if (Array.isArray(target)) { // Expand 'server' if present in array targetRuntimes = target.flatMap(t => t === 'server' ? ['node', 'deno', 'bun'] as interfaces.TRuntimeType[] : [t as interfaces.TRuntimeType] ); } else if (target === 'server') { targetRuntimes = ['node', 'deno', 'bun']; } else { targetRuntimes = [target]; } // Check if current runtime matches any target if (!targetRuntimes.includes(this.runtimeEnv)) { console.warn( `Module "${moduleNameOrUrl}" requested for runtime(s) [${targetRuntimes.join(', ')}] ` + `but current runtime is "${this.runtimeEnv}". Skipping load.` ); return undefined; } // Load based on current runtime if (this.isNode || this.isDeno || this.isBun) { // Server-side runtimes use dynamic import const moduleResult = await this.getSafeNodeModule(moduleNameOrUrl); return moduleResult; } else if (this.isBrowser) { if (!getFunction) { console.error(`Browser module load requires getFunction parameter for "${moduleNameOrUrl}"`); return undefined; } const moduleResult = await this.getSafeWebModule(moduleNameOrUrl, getFunction); return moduleResult as T; } return undefined; } public get isCI(): boolean { if (this.isNode) { if (process.env.CI) { return true; } else { return false; } } else { return false; } } public async isMacAsync(): Promise { if (this.isNode) { const os = await this.getSafeNodeModule('os'); return os.platform() === 'darwin'; } else { return false; } } public async isWindowsAsync(): Promise { if (this.isNode) { const os = await this.getSafeNodeModule('os'); return os.platform() === 'win32'; } else { return false; } } public async isLinuxAsync(): Promise { if (this.isNode) { const os = await this.getSafeNodeModule('os'); return os.platform() === 'linux'; } else { return false; } } /** * prints the environment to console */ public async printEnv() { if (this.isNode) { console.log('running on NODE'); console.log('node version is ' + this.nodeVersion); } else if (this.isDeno) { console.log('running on DENO'); console.log('deno version is ' + this.denoVersion); } else if (this.isBun) { console.log('running on BUN'); console.log('bun version is ' + this.bunVersion); } else { console.log('running on BROWSER'); console.log('browser is ' + this.userAgent); } } }