BREAKING CHANGE(Smartenv): Add Deno and Bun runtime detection, introduce getSafeModuleFor API, update docs and tests, and make isNode semantics Node-only (breaking change)
This commit is contained in:
		@@ -31,8 +31,8 @@ export class Smartenv {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async getSafeNodeModule<T = any>(moduleNameArg: string, runAfterFunc?: (moduleArg: T) => Promise<any>): Promise<T> {
 | 
			
		||||
    if (!this.isNode) {
 | 
			
		||||
      console.error(`You tried to load a node module in a wrong context: ${moduleNameArg}. This does not throw.`);
 | 
			
		||||
    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
 | 
			
		||||
@@ -72,16 +72,49 @@ export class Smartenv {
 | 
			
		||||
    return getFunctionArg();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get runtimeEnv() {
 | 
			
		||||
    if (typeof process !== 'undefined') {
 | 
			
		||||
  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';
 | 
			
		||||
    } else {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // 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.isNode;
 | 
			
		||||
    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 {
 | 
			
		||||
@@ -93,12 +126,77 @@ export class Smartenv {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get isNode(): boolean {
 | 
			
		||||
    return this.runtimeEnv === 'node';
 | 
			
		||||
  public get nodeVersion(): string {
 | 
			
		||||
    if (this.isNode) {
 | 
			
		||||
      return process.version;
 | 
			
		||||
    }
 | 
			
		||||
    return 'undefined';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public get nodeVersion(): string {
 | 
			
		||||
    return process.version;
 | 
			
		||||
  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<T = any>(
 | 
			
		||||
    target: interfaces.TRuntimeTarget | interfaces.TRuntimeTarget[],
 | 
			
		||||
    moduleNameOrUrl: string,
 | 
			
		||||
    getFunction?: () => any
 | 
			
		||||
  ): Promise<T | undefined> {
 | 
			
		||||
    // 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<T>(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 {
 | 
			
		||||
@@ -147,6 +245,12 @@ export class Smartenv {
 | 
			
		||||
    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);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user