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:
2025-11-01 15:26:26 +00:00
parent ce338e27ea
commit aeac92f3fd
15 changed files with 10653 additions and 2151 deletions

View File

@@ -1,8 +1,8 @@
/**
* autocreated commitinfo by @pushrocks/commitinfo
* autocreated commitinfo by @push.rocks/commitinfo
*/
export const commitinfo = {
name: '@push.rocks/smartenv',
version: '5.0.12',
description: 'store things about your environment and let them travel across modules'
version: '6.0.0',
description: 'A module for storing and accessing environment details across different platforms.'
}

View File

@@ -1,4 +1,15 @@
export let defaultme = null;
/**
* Runtime type representing the detected JavaScript runtime environment
*/
export type TRuntimeType = 'node' | 'deno' | 'bun' | 'browser';
/**
* Runtime target for module loading - can be a specific runtime or 'server' for all server-side runtimes
*/
export type TRuntimeTarget = TRuntimeType | 'server';
declare global {
namespace NodeJS {
interface Global {

View File

@@ -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);