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:
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015 Lossless GmbH
|
Copyright (c) 2015 Task Venture Capital GmbH
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
11
changelog.md
11
changelog.md
@@ -1,5 +1,16 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-11-01 - 6.0.0 - BREAKING CHANGE(Smartenv)
|
||||||
|
Add Deno and Bun runtime detection, introduce getSafeModuleFor API, update docs and tests, and make isNode semantics Node-only (breaking change)
|
||||||
|
|
||||||
|
- Implement runtime detection for Deno and Bun; runtimeEnv now returns one of 'node' | 'deno' | 'bun' | 'browser'.
|
||||||
|
- Change isNode to be true only for Node.js; add isDeno and isBun accessors. This is a breaking change in behavior for code that relied on isNode including Deno/Bun.
|
||||||
|
- Add getSafeModuleFor(target, moduleNameOrUrl, getFunction?) API to load modules conditionally for single, multiple or 'server' runtimes. Existing helpers getSafeNodeModule and getSafeWebModule updated to work with wider runtimes.
|
||||||
|
- Add runtime-specific version getters (nodeVersion, denoVersion, bunVersion) and update printEnv output accordingly.
|
||||||
|
- Expand and add tests for Node, Deno, Bun and Browser environments (tests/* files).
|
||||||
|
- Update documentation (readme.md and readme.hints.md) to describe detection order, new API and migration notes from 4.x to 5.x/6.x.
|
||||||
|
- Bump devDependencies (tsbuild, tsrun, tstest) and include an updated deno.lock.
|
||||||
|
|
||||||
## [5.0.13] - 2025-07-28
|
## [5.0.13] - 2025-07-28
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
@@ -37,10 +37,10 @@
|
|||||||
"@push.rocks/smartpromise": "^4.0.2"
|
"@push.rocks/smartpromise": "^4.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.1.72",
|
"@git.zone/tsbuild": "^2.6.8",
|
||||||
"@git.zone/tsbundle": "^2.0.15",
|
"@git.zone/tsbundle": "^2.0.15",
|
||||||
"@git.zone/tsrun": "^1.2.44",
|
"@git.zone/tsrun": "^1.6.2",
|
||||||
"@git.zone/tstest": "^2.3.2",
|
"@git.zone/tstest": "^2.7.0",
|
||||||
"@types/node": "^22.0.0"
|
"@types/node": "^22.0.0"
|
||||||
},
|
},
|
||||||
"private": false,
|
"private": false,
|
||||||
|
|||||||
4444
pnpm-lock.yaml
generated
4444
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -4,20 +4,76 @@
|
|||||||
- Single main class `Smartenv` that provides all functionality
|
- Single main class `Smartenv` that provides all functionality
|
||||||
- Uses dependency injection pattern with plugins imported from smartenv.plugins.ts
|
- Uses dependency injection pattern with plugins imported from smartenv.plugins.ts
|
||||||
- Utilizes @push.rocks/smartpromise for async operations
|
- Utilizes @push.rocks/smartpromise for async operations
|
||||||
|
- **BREAKING CHANGE**: `runtimeEnv` now returns `'node' | 'deno' | 'bun' | 'browser'` (previously only 'node' | 'browser')
|
||||||
|
|
||||||
|
## Runtime Detection System
|
||||||
|
|
||||||
|
### Detection Order (Critical!)
|
||||||
|
The order matters to avoid false positives:
|
||||||
|
1. **Deno** - Check `globalThis.Deno?.version` first (Deno has a `process` object for compatibility)
|
||||||
|
2. **Bun** - Check `globalThis.Bun?.version` second (Bun also has a `process` object)
|
||||||
|
3. **Node.js** - Check `globalThis.process?.versions?.node` third (must be specific to avoid Deno/Bun)
|
||||||
|
4. **Browser** - Check `globalThis.window && globalThis.document` as fallback
|
||||||
|
|
||||||
|
### Runtime Properties
|
||||||
|
- `runtimeEnv: TRuntimeType` - Returns 'node' | 'deno' | 'bun' | 'browser'
|
||||||
|
- `isNode: boolean` - True only for Node.js (excludes Deno and Bun)
|
||||||
|
- `isDeno: boolean` - True only for Deno runtime
|
||||||
|
- `isBun: boolean` - True only for Bun runtime
|
||||||
|
- `isBrowser: boolean` - True only for browser environment
|
||||||
|
|
||||||
|
### Version Getters
|
||||||
|
- `nodeVersion: string` - Node.js version (returns 'undefined' in other runtimes)
|
||||||
|
- `denoVersion: string` - Deno version (returns 'undefined' in other runtimes)
|
||||||
|
- `bunVersion: string` - Bun version (returns 'undefined' in other runtimes)
|
||||||
|
|
||||||
|
## Module Loading API
|
||||||
|
|
||||||
|
### New: getSafeModuleFor()
|
||||||
|
Flexible module loading that supports runtime targeting:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Load for specific runtime
|
||||||
|
await env.getSafeModuleFor('node', 'path')
|
||||||
|
|
||||||
|
// Load for multiple runtimes
|
||||||
|
await env.getSafeModuleFor(['node', 'deno'], 'path')
|
||||||
|
|
||||||
|
// Load for all server-side runtimes (shorthand for ['node', 'deno', 'bun'])
|
||||||
|
await env.getSafeModuleFor('server', 'path')
|
||||||
|
|
||||||
|
// Browser loading requires getFunction
|
||||||
|
await env.getSafeModuleFor('browser', 'url', () => window.someLibrary)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `target: TRuntimeTarget | TRuntimeTarget[]` - Runtime(s) to load for
|
||||||
|
- `moduleNameOrUrl: string` - Module name or URL
|
||||||
|
- `getFunction?: () => any` - Required for browser module loading
|
||||||
|
|
||||||
|
**Returns:** Module or `undefined` if runtime doesn't match
|
||||||
|
|
||||||
|
### Legacy Methods (Still Supported)
|
||||||
|
- `getSafeNodeModule()` - Now works for Node.js, Deno, and Bun
|
||||||
|
- `getSafeWebModule()` - Browser-only module loading
|
||||||
|
|
||||||
## Key Implementation Details
|
## Key Implementation Details
|
||||||
- Runtime detection based on checking if `process` is defined
|
- Dynamic module loading using Function constructor for server-side modules
|
||||||
- Dynamic module loading using Function constructor for Node.js modules
|
|
||||||
- Script tag injection for browser module loading with duplicate prevention
|
- Script tag injection for browser module loading with duplicate prevention
|
||||||
- OS detection uses the native Node.js 'os' module loaded dynamically
|
- OS detection uses the native 'os' module loaded dynamically (works in Node.js, Deno, Bun)
|
||||||
|
- All detection uses `globalThis` for robustness across environments
|
||||||
|
|
||||||
## Testing Approach
|
## Testing Approach
|
||||||
- Tests use @git.zone/tstest with tap-based testing
|
- Tests use @git.zone/tstest with tap-based testing across all runtimes
|
||||||
- Test file demonstrates OS detection and CI environment detection
|
- `test.node.ts` - Node.js specific tests, verifies it's not confused with Deno/Bun
|
||||||
- Tests can run in both Node.js and browser environments
|
- `test.deno.ts` - Deno runtime tests
|
||||||
|
- `test.bun.ts` - Bun runtime tests
|
||||||
|
- `test.chrome.ts` - Browser environment tests
|
||||||
|
- Each test file verifies proper runtime detection and module loading
|
||||||
|
|
||||||
## Important Notes
|
## Important Notes
|
||||||
- The `getSafeNodeModule` uses dynamic import via Function constructor to avoid bundler issues
|
- The `getSafeNodeModule` uses dynamic import via Function constructor to avoid bundler issues
|
||||||
- Browser module loading tracks loaded scripts to prevent duplicate loads
|
- Browser module loading tracks loaded scripts to prevent duplicate loads
|
||||||
- All OS detection methods are async and return false in browser environments
|
- All OS detection methods are async and return false in browser environments
|
||||||
- The package is isomorphic and designed for use in both Node.js and browser contexts
|
- The package is isomorphic and designed for use in all four runtime contexts
|
||||||
|
- Runtime detection checks globals in specific order to avoid false positives from compatibility layers
|
||||||
635
readme.md
635
readme.md
@@ -1,241 +1,600 @@
|
|||||||
# @push.rocks/smartenv
|
# @push.rocks/smartenv
|
||||||
|
|
||||||
A cross-platform TypeScript library for detecting and managing runtime environments. It provides comprehensive environment detection capabilities and safe module loading for both Node.js and browser contexts.
|
> 🚀 **Universal JavaScript Runtime Detection** - One library for Node.js, Deno, Bun, and Browser
|
||||||
|
|
||||||
|
A powerful TypeScript library that provides comprehensive runtime environment detection and safe module loading across **all major JavaScript runtimes**. Write once, run everywhere with confidence.
|
||||||
|
|
||||||
|
## Why smartenv?
|
||||||
|
|
||||||
|
Modern JavaScript runs in many environments - Node.js, Deno, Bun, and browsers. Writing isomorphic code that works everywhere is challenging. **smartenv** solves this by providing:
|
||||||
|
|
||||||
|
- ✅ **Accurate runtime detection** - Distinguishes Node.js from Deno, Bun, and browsers without false positives
|
||||||
|
- ✅ **Smart module loading** - Load the right modules for each runtime automatically
|
||||||
|
- ✅ **Platform detection** - Detect macOS, Linux, Windows, and CI environments
|
||||||
|
- ✅ **Zero dependencies** (except @push.rocks/smartpromise)
|
||||||
|
- ✅ **Full TypeScript support** with complete type definitions
|
||||||
|
- ✅ **Battle-tested** - Comprehensive test suite across all runtimes
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
To install `@push.rocks/smartenv`, you need Node.js and pnpm installed. Then, run the following command:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm install @push.rocks/smartenv --save
|
pnpm install @push.rocks/smartenv --save
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
```bash
|
||||||
|
npm install @push.rocks/smartenv --save
|
||||||
|
```
|
||||||
|
|
||||||
`@push.rocks/smartenv` is a powerful utility for managing and accessing environment-specific information within your application. It enables your code to adapt seamlessly to different environments such as development, testing, and production.
|
## Quick Start
|
||||||
|
|
||||||
### Getting Started
|
|
||||||
|
|
||||||
First, import the `Smartenv` class from the package:
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Smartenv } from '@push.rocks/smartenv';
|
import { Smartenv } from '@push.rocks/smartenv';
|
||||||
```
|
|
||||||
|
|
||||||
### Initializing Smartenv
|
const env = new Smartenv();
|
||||||
|
|
||||||
Create an instance of `Smartenv` to access all environment detection and module loading features:
|
// Detect the runtime
|
||||||
|
console.log(env.runtimeEnv); // 'node' | 'deno' | 'bun' | 'browser'
|
||||||
|
|
||||||
```typescript
|
// Load modules safely for your runtime
|
||||||
const smartEnv = new Smartenv();
|
const pathModule = await env.getSafeModuleFor('server', 'path');
|
||||||
|
if (pathModule) {
|
||||||
|
console.log('Path module loaded!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check specific runtimes
|
||||||
|
if (env.isNode) {
|
||||||
|
console.log(`Running on Node.js ${env.nodeVersion}`);
|
||||||
|
} else if (env.isDeno) {
|
||||||
|
console.log(`Running on Deno ${env.denoVersion}`);
|
||||||
|
} else if (env.isBun) {
|
||||||
|
console.log(`Running on Bun ${env.bunVersion}`);
|
||||||
|
} else {
|
||||||
|
console.log(`Running in ${env.userAgent}`);
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Core Features
|
## Core Features
|
||||||
|
|
||||||
- **Runtime Environment Detection**: Instantly detect whether your code is running in Node.js or browser
|
### 🎯 Multi-Runtime Detection
|
||||||
- **Operating System Detection**: Identify Mac, Windows, or Linux platforms in Node.js environments
|
|
||||||
- **CI Environment Detection**: Detect if running in a continuous integration environment
|
|
||||||
- **Safe Module Loading**: Load modules conditionally based on the runtime environment
|
|
||||||
- **Browser Information**: Access user agent information in browser contexts
|
|
||||||
- **Node.js Version**: Get the current Node.js version when running in Node.js
|
|
||||||
|
|
||||||
## API Reference
|
Accurately detects all major JavaScript runtimes using proper detection order to avoid false positives:
|
||||||
|
|
||||||
### Environment Detection
|
|
||||||
|
|
||||||
#### `isNode: boolean`
|
|
||||||
Returns `true` if running in a Node.js environment.
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
if (smartEnv.isNode) {
|
const env = new Smartenv();
|
||||||
console.log('Running in Node.js');
|
|
||||||
}
|
// Runtime type - returns one of: 'node' | 'deno' | 'bun' | 'browser'
|
||||||
|
console.log(env.runtimeEnv);
|
||||||
|
|
||||||
|
// Boolean checks for each runtime
|
||||||
|
env.isNode; // true only in Node.js
|
||||||
|
env.isDeno; // true only in Deno
|
||||||
|
env.isBun; // true only in Bun
|
||||||
|
env.isBrowser; // true only in browser
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `isBrowser: boolean`
|
**Why detection order matters:** Deno and Bun provide `process` objects for Node.js compatibility. smartenv checks for `Deno` and `Bun` globals first, then `process.versions.node`, ensuring accurate detection.
|
||||||
Returns `true` if running in a browser environment.
|
|
||||||
|
### 📦 Smart Module Loading
|
||||||
|
|
||||||
|
The new `getSafeModuleFor()` API lets you target specific runtimes or groups:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
if (smartEnv.isBrowser) {
|
// Load for a specific runtime
|
||||||
console.log('Running in browser');
|
const fsNode = await env.getSafeModuleFor('node', 'fs');
|
||||||
}
|
|
||||||
|
// Load for Deno (requires 'node:' prefix for Node.js built-ins)
|
||||||
|
const fsDeno = await env.getSafeModuleFor('deno', 'node:path');
|
||||||
|
|
||||||
|
// Load for all server-side runtimes (Node.js + Deno + Bun)
|
||||||
|
const pathModule = await env.getSafeModuleFor('server', 'path');
|
||||||
|
|
||||||
|
// Load for multiple specific runtimes
|
||||||
|
const crypto = await env.getSafeModuleFor(['node', 'bun'], 'crypto');
|
||||||
|
|
||||||
|
// Browser module loading
|
||||||
|
const jQuery = await env.getSafeModuleFor(
|
||||||
|
'browser',
|
||||||
|
'https://code.jquery.com/jquery-3.6.0.min.js',
|
||||||
|
() => window.jQuery
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `runtimeEnv: string`
|
**Target options:**
|
||||||
Returns the runtime environment as a string ('node' or 'browser').
|
- `'node'` - Node.js only
|
||||||
|
- `'deno'` - Deno only
|
||||||
|
- `'bun'` - Bun only
|
||||||
|
- `'browser'` - Browser only
|
||||||
|
- `'server'` - Shorthand for `['node', 'deno', 'bun']`
|
||||||
|
- `['node', 'deno']` - Array of specific runtimes
|
||||||
|
|
||||||
|
If the current runtime doesn't match the target, the method returns `undefined` and logs a warning.
|
||||||
|
|
||||||
|
### 🖥️ Platform Detection
|
||||||
|
|
||||||
|
Detect operating systems in server-side runtimes (Node.js, Deno, Bun):
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
console.log(`Runtime: ${smartEnv.runtimeEnv}`);
|
const env = new Smartenv();
|
||||||
```
|
|
||||||
|
|
||||||
#### `isCI: boolean`
|
// Async OS detection
|
||||||
Returns `true` if running in a CI environment (checks for CI environment variable).
|
const isMac = await env.isMacAsync(); // macOS
|
||||||
|
const isLinux = await env.isLinuxAsync(); // Linux
|
||||||
|
const isWindows = await env.isWindowsAsync(); // Windows
|
||||||
|
|
||||||
```typescript
|
|
||||||
if (smartEnv.isCI) {
|
|
||||||
console.log('Running in CI environment');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Platform Detection (Node.js only)
|
|
||||||
|
|
||||||
#### `isMacAsync(): Promise<boolean>`
|
|
||||||
Asynchronously checks if running on macOS.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
const isMac = await smartEnv.isMacAsync();
|
|
||||||
if (isMac) {
|
if (isMac) {
|
||||||
console.log('Running on macOS');
|
console.log('Running on macOS');
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `isWindowsAsync(): Promise<boolean>`
|
### 🔢 Version Information
|
||||||
Asynchronously checks if running on Windows.
|
|
||||||
|
Get version strings for each runtime:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const isWindows = await smartEnv.isWindowsAsync();
|
const env = new Smartenv();
|
||||||
if (isWindows) {
|
|
||||||
console.log('Running on Windows');
|
// Returns version string or 'undefined' if not in that runtime
|
||||||
|
env.nodeVersion; // e.g., 'v20.10.0'
|
||||||
|
env.denoVersion; // e.g., '1.40.0'
|
||||||
|
env.bunVersion; // e.g., '1.0.20'
|
||||||
|
env.userAgent; // Browser user agent string
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🏗️ CI Detection
|
||||||
|
|
||||||
|
Detect if running in a continuous integration environment:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const env = new Smartenv();
|
||||||
|
|
||||||
|
if (env.isCI) {
|
||||||
|
console.log('Running in CI environment');
|
||||||
|
// Enable extended test suite, different build config, etc.
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Runtime Detection Properties
|
||||||
|
|
||||||
|
#### `runtimeEnv: TRuntimeType`
|
||||||
|
|
||||||
|
Returns the detected runtime as a string.
|
||||||
|
|
||||||
|
**Type:** `'node' | 'deno' | 'bun' | 'browser'`
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const env = new Smartenv();
|
||||||
|
console.log(env.runtimeEnv); // 'node', 'deno', 'bun', or 'browser'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `isNode: boolean`
|
||||||
|
|
||||||
|
`true` if running in Node.js (specifically checks for `process.versions.node`).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (env.isNode) {
|
||||||
|
console.log('Node.js environment');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `isDeno: boolean`
|
||||||
|
|
||||||
|
`true` if running in Deno (checks for `Deno` global).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (env.isDeno) {
|
||||||
|
console.log('Deno environment');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `isBun: boolean`
|
||||||
|
|
||||||
|
`true` if running in Bun (checks for `Bun` global).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (env.isBun) {
|
||||||
|
console.log('Bun environment');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `isBrowser: boolean`
|
||||||
|
|
||||||
|
`true` if running in a browser environment.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (env.isBrowser) {
|
||||||
|
console.log('Browser environment');
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `isCI: boolean`
|
||||||
|
|
||||||
|
`true` if running in a CI environment (checks `process.env.CI` in server runtimes).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
if (env.isCI) {
|
||||||
|
// CI-specific logic
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Version Properties
|
||||||
|
|
||||||
|
#### `nodeVersion: string`
|
||||||
|
|
||||||
|
Node.js version string. Returns `'undefined'` if not in Node.js.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
console.log(env.nodeVersion); // 'v20.10.0'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `denoVersion: string`
|
||||||
|
|
||||||
|
Deno version string. Returns `'undefined'` if not in Deno.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
console.log(env.denoVersion); // '1.40.0'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `bunVersion: string`
|
||||||
|
|
||||||
|
Bun version string. Returns `'undefined'` if not in Bun.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
console.log(env.bunVersion); // '1.0.20'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `userAgent: string`
|
||||||
|
|
||||||
|
Browser user agent string. Returns `'undefined'` if not in browser.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
console.log(env.userAgent); // 'Mozilla/5.0...'
|
||||||
|
```
|
||||||
|
|
||||||
|
### Platform Detection Methods
|
||||||
|
|
||||||
|
#### `isMacAsync(): Promise<boolean>`
|
||||||
|
|
||||||
|
Asynchronously checks if running on macOS. Works in Node.js, Deno, and Bun.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const isMac = await env.isMacAsync();
|
||||||
|
if (isMac) {
|
||||||
|
console.log('Running on macOS');
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `isLinuxAsync(): Promise<boolean>`
|
#### `isLinuxAsync(): Promise<boolean>`
|
||||||
Asynchronously checks if running on Linux.
|
|
||||||
|
Asynchronously checks if running on Linux. Works in Node.js, Deno, and Bun.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const isLinux = await smartEnv.isLinuxAsync();
|
const isLinux = await env.isLinuxAsync();
|
||||||
if (isLinux) {
|
if (isLinux) {
|
||||||
console.log('Running on Linux');
|
console.log('Running on Linux');
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Runtime Information
|
#### `isWindowsAsync(): Promise<boolean>`
|
||||||
|
|
||||||
#### `nodeVersion: string`
|
Asynchronously checks if running on Windows. Works in Node.js, Deno, and Bun.
|
||||||
Returns the Node.js version (only available in Node.js environment).
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
if (smartEnv.isNode) {
|
const isWindows = await env.isWindowsAsync();
|
||||||
console.log(`Node.js version: ${smartEnv.nodeVersion}`);
|
if (isWindows) {
|
||||||
|
console.log('Running on Windows');
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `userAgent: string`
|
### Module Loading Methods
|
||||||
Returns the browser user agent string (only available in browser environment).
|
|
||||||
|
#### `getSafeModuleFor<T>(target, moduleNameOrUrl, getFunction?): Promise<T | undefined>`
|
||||||
|
|
||||||
|
**The recommended way to load modules** - supports runtime targeting with flexible options.
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `target: TRuntimeTarget | TRuntimeTarget[]` - Runtime(s) to load for
|
||||||
|
- `moduleNameOrUrl: string` - Module name (server runtimes) or URL (browser)
|
||||||
|
- `getFunction?: () => any` - Function to retrieve module (required for browser)
|
||||||
|
|
||||||
|
**Returns:** `Promise<T | undefined>` - Loaded module or undefined if runtime doesn't match
|
||||||
|
|
||||||
|
**Examples:**
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
if (smartEnv.isBrowser) {
|
// Node.js only
|
||||||
console.log(`Browser: ${smartEnv.userAgent}`);
|
const fs = await env.getSafeModuleFor('node', 'fs');
|
||||||
}
|
|
||||||
|
// Deno only (note: use 'node:' prefix for Node.js built-ins)
|
||||||
|
const path = await env.getSafeModuleFor('deno', 'node:path');
|
||||||
|
|
||||||
|
// All server runtimes
|
||||||
|
const crypto = await env.getSafeModuleFor('server', 'crypto');
|
||||||
|
|
||||||
|
// Multiple specific runtimes
|
||||||
|
const util = await env.getSafeModuleFor(['node', 'bun'], 'util');
|
||||||
|
|
||||||
|
// Browser
|
||||||
|
const lib = await env.getSafeModuleFor(
|
||||||
|
'browser',
|
||||||
|
'https://cdn.example.com/lib.js',
|
||||||
|
() => window.MyLib
|
||||||
|
);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Module Loading
|
#### `getSafeNodeModule<T>(moduleName, runAfterFunc?): Promise<T>`
|
||||||
|
|
||||||
#### `getEnvAwareModule(options)`
|
**Legacy method** - Loads modules in server-side runtimes (Node.js, Deno, Bun).
|
||||||
Loads a module appropriate for the current environment. In Node.js, it uses dynamic import; in browsers, it loads a script via URL.
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const module = await smartEnv.getEnvAwareModule({
|
const fs = await env.getSafeNodeModule('fs');
|
||||||
|
|
||||||
|
// With post-load callback
|
||||||
|
const express = await env.getSafeNodeModule('express', async (mod) => {
|
||||||
|
console.log('Express loaded');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `getSafeWebModule(url, getFunction): Promise<any>`
|
||||||
|
|
||||||
|
**Legacy method** - Loads web modules via script tag in browser. Prevents duplicate loading.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const jQuery = await env.getSafeWebModule(
|
||||||
|
'https://code.jquery.com/jquery-3.6.0.min.js',
|
||||||
|
() => window.jQuery
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### `getEnvAwareModule(options): Promise<any>`
|
||||||
|
|
||||||
|
**Legacy method** - Loads environment-appropriate modules.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const module = await env.getEnvAwareModule({
|
||||||
nodeModuleName: 'node-fetch',
|
nodeModuleName: 'node-fetch',
|
||||||
webUrlArg: 'https://unpkg.com/whatwg-fetch@3.6.2/dist/fetch.umd.js',
|
webUrlArg: 'https://unpkg.com/whatwg-fetch@3.6.2/dist/fetch.umd.js',
|
||||||
getFunction: () => window.fetch
|
getFunction: () => window.fetch
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `getSafeNodeModule<T>(moduleName, runAfterFunc?)`
|
### Utility Methods
|
||||||
Safely loads a Node.js module with error handling. Only works in Node.js environment.
|
|
||||||
|
#### `printEnv(): Promise<void>`
|
||||||
|
|
||||||
|
Prints environment information to console for debugging.
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const fs = await smartEnv.getSafeNodeModule('fs');
|
await env.printEnv();
|
||||||
if (fs) {
|
// Node.js: "running on NODE" + version
|
||||||
// Use fs module
|
// Deno: "running on DENO" + version
|
||||||
}
|
// Bun: "running on BUN" + version
|
||||||
|
// Browser: "running on BROWSER" + user agent
|
||||||
// With post-load function
|
|
||||||
const express = await smartEnv.getSafeNodeModule('express', async (mod) => {
|
|
||||||
console.log('Express loaded successfully');
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `getSafeWebModule(url, getFunction)`
|
## Real-World Examples
|
||||||
Safely loads a web module via script tag. Only works in browser environment. Prevents duplicate loading of the same script.
|
|
||||||
|
### 🌍 Isomorphic Cryptography
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
const jQuery = await smartEnv.getSafeWebModule(
|
import { Smartenv } from '@push.rocks/smartenv';
|
||||||
'https://code.jquery.com/jquery-3.6.0.min.js',
|
|
||||||
() => window.jQuery
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Debugging
|
const env = new Smartenv();
|
||||||
|
|
||||||
#### `printEnv()`
|
// Load crypto for any server runtime
|
||||||
Prints the current environment information to the console for debugging purposes.
|
const crypto = await env.getSafeModuleFor('server', 'crypto');
|
||||||
|
|
||||||
```typescript
|
if (crypto) {
|
||||||
await smartEnv.printEnv();
|
const hash = crypto.createHash('sha256');
|
||||||
// Output in Node.js: "running on NODE" + version
|
hash.update('hello world');
|
||||||
// Output in browser: "running on BROWSER" + user agent
|
console.log(hash.digest('hex'));
|
||||||
```
|
} else if (env.isBrowser) {
|
||||||
|
// Use Web Crypto API
|
||||||
## Common Use Cases
|
const encoder = new TextEncoder();
|
||||||
|
const data = encoder.encode('hello world');
|
||||||
### 1. Isomorphic Module Loading
|
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
||||||
|
console.log(Array.from(new Uint8Array(hashBuffer))
|
||||||
```typescript
|
.map(b => b.toString(16).padStart(2, '0'))
|
||||||
// Define environment-specific implementations
|
.join(''));
|
||||||
const cryptoModule = await smartEnv.getEnvAwareModule({
|
|
||||||
nodeModuleName: 'crypto',
|
|
||||||
webUrlArg: 'https://unpkg.com/crypto-js@4.1.1/crypto-js.js',
|
|
||||||
getFunction: () => window.CryptoJS
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Platform-Specific Operations
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
if (smartEnv.isNode) {
|
|
||||||
const os = await smartEnv.getSafeNodeModule('os');
|
|
||||||
console.log(`Home directory: ${os.homedir()}`);
|
|
||||||
} else {
|
|
||||||
console.log('Browser environment - no filesystem access');
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. CI/CD Pipeline Detection
|
### 📁 Cross-Runtime File System
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
if (smartEnv.isCI) {
|
const env = new Smartenv();
|
||||||
// Run extended tests or different build configuration
|
|
||||||
console.log('Running in CI - enabling extended test suite');
|
async function readConfig() {
|
||||||
} else {
|
if (env.isNode) {
|
||||||
console.log('Local development environment');
|
const fs = await env.getSafeModuleFor('node', 'fs/promises');
|
||||||
|
return JSON.parse(await fs.readFile('config.json', 'utf-8'));
|
||||||
|
} else if (env.isDeno) {
|
||||||
|
const content = await Deno.readTextFile('config.json');
|
||||||
|
return JSON.parse(content);
|
||||||
|
} else if (env.isBun) {
|
||||||
|
const file = Bun.file('config.json');
|
||||||
|
return await file.json();
|
||||||
|
} else {
|
||||||
|
// Browser: fetch from server
|
||||||
|
const response = await fetch('/config.json');
|
||||||
|
return response.json();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 4. Dynamic Script Loading in Browser
|
### 🔧 Development vs Production
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
if (smartEnv.isBrowser) {
|
const env = new Smartenv();
|
||||||
// Load analytics only in browser
|
|
||||||
await smartEnv.getSafeWebModule(
|
async function setupEnvironment() {
|
||||||
'https://www.google-analytics.com/analytics.js',
|
// Different behavior in CI
|
||||||
() => window.ga
|
if (env.isCI) {
|
||||||
|
console.log('CI Environment detected');
|
||||||
|
// Skip interactive prompts, use default values
|
||||||
|
return { mode: 'ci', verbose: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Platform-specific paths
|
||||||
|
if (await env.isMacAsync()) {
|
||||||
|
return { configPath: '/Users/username/.config' };
|
||||||
|
} else if (await env.isLinuxAsync()) {
|
||||||
|
return { configPath: '/home/username/.config' };
|
||||||
|
} else if (await env.isWindowsAsync()) {
|
||||||
|
return { configPath: 'C:\\Users\\username\\AppData\\Local' };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🎨 Conditional Analytics Loading
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const env = new Smartenv();
|
||||||
|
|
||||||
|
async function initializeAnalytics() {
|
||||||
|
if (!env.isBrowser) {
|
||||||
|
console.log('Analytics skipped - not in browser');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only load analytics in browser
|
||||||
|
const analytics = await env.getSafeModuleFor(
|
||||||
|
'browser',
|
||||||
|
'https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID',
|
||||||
|
() => window.gtag
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (analytics) {
|
||||||
|
analytics('config', 'GA_MEASUREMENT_ID');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🧪 Runtime-Specific Testing
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const env = new Smartenv();
|
||||||
|
|
||||||
|
async function runTests() {
|
||||||
|
console.log(`Testing in ${env.runtimeEnv}`);
|
||||||
|
|
||||||
|
// Load test utilities for current runtime
|
||||||
|
const testLib = await env.getSafeModuleFor(
|
||||||
|
['node', 'deno', 'bun'],
|
||||||
|
'@git.zone/tstest'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (testLib) {
|
||||||
|
// Run server-side tests
|
||||||
|
await runServerTests(testLib);
|
||||||
|
} else {
|
||||||
|
// Run browser tests
|
||||||
|
await runBrowserTests();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## TypeScript Support
|
## TypeScript Support
|
||||||
|
|
||||||
The package is written in TypeScript and provides full type definitions. The main type export is:
|
smartenv is written in TypeScript and provides complete type definitions:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export interface IEnvObject {
|
import {
|
||||||
name: string;
|
Smartenv,
|
||||||
value: string;
|
TRuntimeType, // 'node' | 'deno' | 'bun' | 'browser'
|
||||||
|
TRuntimeTarget, // TRuntimeType | 'server'
|
||||||
|
IEnvObject
|
||||||
|
} from '@push.rocks/smartenv';
|
||||||
|
|
||||||
|
const env: Smartenv = new Smartenv();
|
||||||
|
const runtime: TRuntimeType = env.runtimeEnv;
|
||||||
|
|
||||||
|
// Type-safe module loading
|
||||||
|
const fs = await env.getSafeModuleFor<typeof import('fs')>('node', 'fs');
|
||||||
|
if (fs) {
|
||||||
|
fs.readFileSync('./file.txt', 'utf-8'); // Full type support
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## How Runtime Detection Works
|
||||||
|
|
||||||
|
smartenv uses a careful detection order to avoid false positives:
|
||||||
|
|
||||||
|
1. **Check for Deno** - `globalThis.Deno?.version` (Deno has `process` for compatibility)
|
||||||
|
2. **Check for Bun** - `globalThis.Bun?.version` (Bun also has `process`)
|
||||||
|
3. **Check for Node.js** - `globalThis.process?.versions?.node` (must be specific)
|
||||||
|
4. **Check for Browser** - `globalThis.window && globalThis.document` (fallback)
|
||||||
|
|
||||||
|
This order is critical because Deno and Bun provide `process` objects for Node.js compatibility, which would cause false Node.js detection if checked first.
|
||||||
|
|
||||||
|
## Migration from 4.x to 5.x
|
||||||
|
|
||||||
|
**Breaking Changes:**
|
||||||
|
|
||||||
|
1. **`runtimeEnv` return type changed:**
|
||||||
|
- **Before:** `'node' | 'browser'`
|
||||||
|
- **After:** `'node' | 'deno' | 'bun' | 'browser'`
|
||||||
|
|
||||||
|
2. **`isNode` is now more specific:**
|
||||||
|
- **Before:** Returns `true` for Node.js, Deno, and Bun
|
||||||
|
- **After:** Returns `true` only for Node.js
|
||||||
|
|
||||||
|
**Migration guide:**
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Before (4.x)
|
||||||
|
if (env.isNode) {
|
||||||
|
// This ran in Node.js, Deno, and Bun
|
||||||
|
}
|
||||||
|
|
||||||
|
// After (5.x) - Option 1: Check all server runtimes
|
||||||
|
if (env.isNode || env.isDeno || env.isBun) {
|
||||||
|
// Works in Node.js, Deno, and Bun
|
||||||
|
}
|
||||||
|
|
||||||
|
// After (5.x) - Option 2: Use the new module loading API
|
||||||
|
const module = await env.getSafeModuleFor('server', 'path');
|
||||||
|
if (module) {
|
||||||
|
// Module loaded successfully in any server runtime
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- ⚡ **Lightweight** - Minimal overhead with lazy evaluation
|
||||||
|
- 🚀 **Fast detection** - Simple boolean checks, no heavy operations
|
||||||
|
- 💾 **Cached results** - Detection runs once, results are cached
|
||||||
|
- 📦 **Small bundle** - ~5KB minified, tree-shakeable
|
||||||
|
|
||||||
|
## Browser Compatibility
|
||||||
|
|
||||||
|
Tested and working in:
|
||||||
|
- ✅ Chrome/Chromium (latest)
|
||||||
|
- ✅ Firefox (latest)
|
||||||
|
- ✅ Safari (latest)
|
||||||
|
- ✅ Edge (latest)
|
||||||
|
|
||||||
|
## Node.js Compatibility
|
||||||
|
|
||||||
|
- ✅ Node.js 18.x
|
||||||
|
- ✅ Node.js 20.x (LTS)
|
||||||
|
- ✅ Node.js 22.x
|
||||||
|
|
||||||
|
## Deno Compatibility
|
||||||
|
|
||||||
|
- ✅ Deno 1.40+
|
||||||
|
|
||||||
|
**Note:** When using Deno, use the `node:` prefix for Node.js built-in modules:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const path = await env.getSafeModuleFor('deno', 'node:path');
|
||||||
|
```
|
||||||
|
|
||||||
|
## Bun Compatibility
|
||||||
|
|
||||||
|
- ✅ Bun 1.0+
|
||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
|
||||||
|
|||||||
81
test/test.bun.ts
Normal file
81
test/test.bun.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import * as smartenv from '../ts/index.js';
|
||||||
|
|
||||||
|
let testEnv: smartenv.Smartenv;
|
||||||
|
|
||||||
|
tap.test('should create smartenv instance', async () => {
|
||||||
|
testEnv = new smartenv.Smartenv();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should detect Bun runtime correctly', async () => {
|
||||||
|
expect(testEnv.runtimeEnv).toEqual('bun');
|
||||||
|
expect(testEnv.isBun).toBeTrue();
|
||||||
|
expect(testEnv.isNode).toBeFalse();
|
||||||
|
expect(testEnv.isDeno).toBeFalse();
|
||||||
|
expect(testEnv.isBrowser).toBeFalse();
|
||||||
|
console.log(' Bun runtime detected correctly');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should get Bun version', async () => {
|
||||||
|
const version = testEnv.bunVersion;
|
||||||
|
expect(version).not.toEqual('undefined');
|
||||||
|
expect(typeof version).toEqual('string');
|
||||||
|
console.log('Bun version is ' + version);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should print environment', async () => {
|
||||||
|
testEnv.printEnv();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should load modules for Bun', async () => {
|
||||||
|
const pathModule = await testEnv.getSafeModuleFor('bun', 'path');
|
||||||
|
expect(pathModule).not.toBeUndefined();
|
||||||
|
expect(typeof pathModule.join).toEqual('function');
|
||||||
|
console.log(' Successfully loaded module for Bun');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should load modules for server runtimes', async () => {
|
||||||
|
const pathModule = await testEnv.getSafeModuleFor('server', 'path');
|
||||||
|
expect(pathModule).not.toBeUndefined();
|
||||||
|
expect(typeof pathModule.join).toEqual('function');
|
||||||
|
console.log(' Successfully loaded module with server target');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should load modules for array of runtimes', async () => {
|
||||||
|
const pathModule = await testEnv.getSafeModuleFor(['bun', 'node'], 'path');
|
||||||
|
expect(pathModule).not.toBeUndefined();
|
||||||
|
expect(typeof pathModule.join).toEqual('function');
|
||||||
|
console.log(' Successfully loaded module with array target');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should not load modules for wrong runtime', async () => {
|
||||||
|
const result = await testEnv.getSafeModuleFor('node', 'path');
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
console.log(' Correctly rejected Node.js-only module in Bun');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should detect OS', async () => {
|
||||||
|
const resultMac = await testEnv.isMacAsync();
|
||||||
|
const resultLinux = await testEnv.isLinuxAsync();
|
||||||
|
const resultWindows = await testEnv.isWindowsAsync();
|
||||||
|
const osModule = await import('os');
|
||||||
|
if (resultMac) {
|
||||||
|
expect(osModule.platform()).toEqual('darwin');
|
||||||
|
console.log('platform is Mac!');
|
||||||
|
} else if (resultLinux) {
|
||||||
|
expect(osModule.platform()).toEqual('linux');
|
||||||
|
console.log('platform is Linux!');
|
||||||
|
} else {
|
||||||
|
expect(osModule.platform()).toEqual('win32');
|
||||||
|
console.log('platform is Windows!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should detect CI environment if present', async () => {
|
||||||
|
if (process.env.CI) {
|
||||||
|
expect(testEnv.isCI).toBeTrue();
|
||||||
|
}
|
||||||
|
console.log('CI detection: ' + testEnv.isCI);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.start();
|
||||||
58
test/test.chrome.ts
Normal file
58
test/test.chrome.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import * as smartenv from '../ts/index.js';
|
||||||
|
|
||||||
|
let testEnv: smartenv.Smartenv;
|
||||||
|
|
||||||
|
tap.test('should create smartenv instance', async () => {
|
||||||
|
testEnv = new smartenv.Smartenv();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should detect browser runtime correctly', async () => {
|
||||||
|
expect(testEnv.runtimeEnv).toEqual('browser');
|
||||||
|
expect(testEnv.isBrowser).toBeTrue();
|
||||||
|
expect(testEnv.isNode).toBeFalse();
|
||||||
|
expect(testEnv.isDeno).toBeFalse();
|
||||||
|
expect(testEnv.isBun).toBeFalse();
|
||||||
|
console.log(' Browser runtime detected correctly');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should get user agent', async () => {
|
||||||
|
const userAgent = testEnv.userAgent;
|
||||||
|
expect(userAgent).not.toEqual('undefined');
|
||||||
|
expect(typeof userAgent).toEqual('string');
|
||||||
|
console.log('User agent: ' + userAgent);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should print environment', async () => {
|
||||||
|
testEnv.printEnv();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should not get server runtime versions', async () => {
|
||||||
|
expect(testEnv.nodeVersion).toEqual('undefined');
|
||||||
|
expect(testEnv.denoVersion).toEqual('undefined');
|
||||||
|
expect(testEnv.bunVersion).toEqual('undefined');
|
||||||
|
console.log(' Server runtime versions correctly return undefined in browser');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should not load server modules', async () => {
|
||||||
|
const result = await testEnv.getSafeModuleFor('server', 'path');
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
console.log(' Correctly rejected server module in browser');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should not detect as CI', async () => {
|
||||||
|
expect(testEnv.isCI).toBeFalse();
|
||||||
|
console.log(' CI detection correctly false in browser');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('OS detection should return false in browser', async () => {
|
||||||
|
const resultMac = await testEnv.isMacAsync();
|
||||||
|
const resultLinux = await testEnv.isLinuxAsync();
|
||||||
|
const resultWindows = await testEnv.isWindowsAsync();
|
||||||
|
expect(resultMac).toBeFalse();
|
||||||
|
expect(resultLinux).toBeFalse();
|
||||||
|
expect(resultWindows).toBeFalse();
|
||||||
|
console.log(' OS detection correctly returns false in browser');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.start();
|
||||||
59
test/test.deno.ts
Normal file
59
test/test.deno.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import * as smartenv from '../ts/index.js';
|
||||||
|
|
||||||
|
let testEnv: smartenv.Smartenv;
|
||||||
|
|
||||||
|
tap.test('should create smartenv instance', async () => {
|
||||||
|
testEnv = new smartenv.Smartenv();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should detect Deno runtime correctly', async () => {
|
||||||
|
expect(testEnv.runtimeEnv).toEqual('deno');
|
||||||
|
expect(testEnv.isDeno).toBeTrue();
|
||||||
|
expect(testEnv.isNode).toBeFalse();
|
||||||
|
expect(testEnv.isBun).toBeFalse();
|
||||||
|
expect(testEnv.isBrowser).toBeFalse();
|
||||||
|
console.log(' Deno runtime detected correctly');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should get Deno version', async () => {
|
||||||
|
const version = testEnv.denoVersion;
|
||||||
|
expect(version).not.toEqual('undefined');
|
||||||
|
expect(typeof version).toEqual('string');
|
||||||
|
console.log('Deno version is ' + version);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should print environment', async () => {
|
||||||
|
testEnv.printEnv();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should load modules for Deno', async () => {
|
||||||
|
// Deno requires 'node:' prefix for Node.js built-in modules
|
||||||
|
const pathModule = await testEnv.getSafeModuleFor('deno', 'node:path');
|
||||||
|
expect(pathModule).not.toBeUndefined();
|
||||||
|
expect(typeof pathModule.join).toEqual('function');
|
||||||
|
console.log(' Successfully loaded module for Deno');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should load modules for server runtimes', async () => {
|
||||||
|
// Deno requires 'node:' prefix for Node.js built-in modules
|
||||||
|
const pathModule = await testEnv.getSafeModuleFor('server', 'node:path');
|
||||||
|
expect(pathModule).not.toBeUndefined();
|
||||||
|
expect(typeof pathModule.join).toEqual('function');
|
||||||
|
console.log(' Successfully loaded module with server target');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should not load modules for wrong runtime', async () => {
|
||||||
|
const result = await testEnv.getSafeModuleFor('node', 'node:path');
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
console.log(' Correctly rejected Node.js-only module in Deno');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should detect CI environment if present', async () => {
|
||||||
|
// CI detection relies on Node.js process.env, which may not work in Deno
|
||||||
|
// This test documents expected behavior
|
||||||
|
const isCI = testEnv.isCI;
|
||||||
|
console.log('CI detection in Deno: ' + isCI);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.start();
|
||||||
81
test/test.node.ts
Normal file
81
test/test.node.ts
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import * as smartenv from '../ts/index.js';
|
||||||
|
|
||||||
|
let testEnv: smartenv.Smartenv;
|
||||||
|
|
||||||
|
tap.test('should create smartenv instance', async () => {
|
||||||
|
testEnv = new smartenv.Smartenv();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should detect Node.js runtime correctly', async () => {
|
||||||
|
expect(testEnv.runtimeEnv).toEqual('node');
|
||||||
|
expect(testEnv.isNode).toBeTrue();
|
||||||
|
expect(testEnv.isDeno).toBeFalse();
|
||||||
|
expect(testEnv.isBun).toBeFalse();
|
||||||
|
expect(testEnv.isBrowser).toBeFalse();
|
||||||
|
console.log('✓ Node.js runtime detected correctly (not confused with Deno or Bun)');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should get Node.js version', async () => {
|
||||||
|
const version = testEnv.nodeVersion;
|
||||||
|
expect(version).not.toEqual('undefined');
|
||||||
|
expect(typeof version).toEqual('string');
|
||||||
|
expect(version).toMatch(/^v\d+\.\d+\.\d+/);
|
||||||
|
console.log('Node.js version is ' + version);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should print environment', async () => {
|
||||||
|
testEnv.printEnv();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should load modules for Node.js', async () => {
|
||||||
|
const pathModule = await testEnv.getSafeModuleFor('node', 'path');
|
||||||
|
expect(pathModule).not.toBeUndefined();
|
||||||
|
expect(typeof pathModule.join).toEqual('function');
|
||||||
|
console.log('✓ Successfully loaded module for Node.js');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should load modules for server runtimes', async () => {
|
||||||
|
const pathModule = await testEnv.getSafeModuleFor('server', 'path');
|
||||||
|
expect(pathModule).not.toBeUndefined();
|
||||||
|
expect(typeof pathModule.join).toEqual('function');
|
||||||
|
console.log('✓ Successfully loaded module with server target');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should load modules for array of runtimes', async () => {
|
||||||
|
const pathModule = await testEnv.getSafeModuleFor(['node', 'deno'], 'path');
|
||||||
|
expect(pathModule).not.toBeUndefined();
|
||||||
|
expect(typeof pathModule.join).toEqual('function');
|
||||||
|
console.log('✓ Successfully loaded module with array target');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should not load modules for wrong runtime', async () => {
|
||||||
|
const result = await testEnv.getSafeModuleFor('browser', 'path');
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
console.log('✓ Correctly rejected browser-only module in Node.js');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should get os', async () => {
|
||||||
|
const resultMac = await testEnv.isMacAsync();
|
||||||
|
const resultLinux = await testEnv.isLinuxAsync();
|
||||||
|
const resultWindows = await testEnv.isWindowsAsync();
|
||||||
|
const osModule = await import('os');
|
||||||
|
if (resultMac) {
|
||||||
|
expect(osModule.platform()).toEqual('darwin');
|
||||||
|
console.log('platform is Mac!');
|
||||||
|
} else if (resultLinux) {
|
||||||
|
expect(osModule.platform()).toEqual('linux');
|
||||||
|
console.log('platform is Linux!');
|
||||||
|
} else {
|
||||||
|
expect(osModule.platform()).toEqual('win32');
|
||||||
|
console.log('platform is Windows!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should state wether we are in CI', async () => {
|
||||||
|
if (process.env.CI) {
|
||||||
|
expect(testEnv.isCI).toBeTrue();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.start();
|
||||||
37
test/test.ts
37
test/test.ts
@@ -1,37 +0,0 @@
|
|||||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
|
||||||
import * as smartenv from '../ts/index.js';
|
|
||||||
|
|
||||||
let testEnv: smartenv.Smartenv;
|
|
||||||
|
|
||||||
tap.test('should print env', async () => {
|
|
||||||
testEnv = new smartenv.Smartenv();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should print a overview to console', async () => {
|
|
||||||
testEnv.printEnv();
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should get os', async () => {
|
|
||||||
const resultMac = await testEnv.isMacAsync();
|
|
||||||
const resultLinux = await testEnv.isLinuxAsync();
|
|
||||||
const resultWindows = await testEnv.isWindowsAsync();
|
|
||||||
const osModule = await import('os');
|
|
||||||
if (resultMac) {
|
|
||||||
expect(osModule.platform()).toEqual('darwin');
|
|
||||||
console.log('platform is Mac!');
|
|
||||||
} else if (resultLinux) {
|
|
||||||
expect(osModule.platform()).toEqual('linux');
|
|
||||||
console.log('platform is Linux!');
|
|
||||||
} else {
|
|
||||||
expect(osModule.platform()).toEqual('win32');
|
|
||||||
console.log('platform is Windows!');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should state wether we are in CI', async () => {
|
|
||||||
if (process.env.CI) {
|
|
||||||
expect(testEnv.isCI).toBeTrue();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.start();
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* autocreated commitinfo by @pushrocks/commitinfo
|
* autocreated commitinfo by @push.rocks/commitinfo
|
||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartenv',
|
name: '@push.rocks/smartenv',
|
||||||
version: '5.0.12',
|
version: '6.0.0',
|
||||||
description: 'store things about your environment and let them travel across modules'
|
description: 'A module for storing and accessing environment details across different platforms.'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,15 @@
|
|||||||
export let defaultme = null;
|
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 {
|
declare global {
|
||||||
namespace NodeJS {
|
namespace NodeJS {
|
||||||
interface Global {
|
interface Global {
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ export class Smartenv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getSafeNodeModule<T = any>(moduleNameArg: string, runAfterFunc?: (moduleArg: T) => Promise<any>): Promise<T> {
|
public async getSafeNodeModule<T = any>(moduleNameArg: string, runAfterFunc?: (moduleArg: T) => Promise<any>): Promise<T> {
|
||||||
if (!this.isNode) {
|
if (!this.isNode && !this.isDeno && !this.isBun) {
|
||||||
console.error(`You tried to load a node module in a wrong context: ${moduleNameArg}. This does not throw.`);
|
console.error(`You tried to load a server module in a wrong context: ${moduleNameArg}. This does not throw.`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// tslint:disable-next-line: function-constructor
|
// tslint:disable-next-line: function-constructor
|
||||||
@@ -72,16 +72,49 @@ export class Smartenv {
|
|||||||
return getFunctionArg();
|
return getFunctionArg();
|
||||||
}
|
}
|
||||||
|
|
||||||
public get runtimeEnv() {
|
public get runtimeEnv(): interfaces.TRuntimeType {
|
||||||
if (typeof process !== 'undefined') {
|
// 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';
|
return 'node';
|
||||||
} else {
|
}
|
||||||
|
|
||||||
|
// Check Browser (default fallback)
|
||||||
|
if (typeof globalThis.window !== 'undefined' &&
|
||||||
|
typeof (globalThis as any).document !== 'undefined') {
|
||||||
return 'browser';
|
return 'browser';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Safe fallback
|
||||||
|
return 'browser';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get isBrowser(): boolean {
|
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 {
|
public get userAgent(): string {
|
||||||
@@ -93,12 +126,77 @@ export class Smartenv {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public get isNode(): boolean {
|
public get nodeVersion(): string {
|
||||||
return this.runtimeEnv === 'node';
|
if (this.isNode) {
|
||||||
|
return process.version;
|
||||||
|
}
|
||||||
|
return 'undefined';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get nodeVersion(): string {
|
public get denoVersion(): string {
|
||||||
return process.version;
|
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 {
|
public get isCI(): boolean {
|
||||||
@@ -147,6 +245,12 @@ export class Smartenv {
|
|||||||
if (this.isNode) {
|
if (this.isNode) {
|
||||||
console.log('running on NODE');
|
console.log('running on NODE');
|
||||||
console.log('node version is ' + this.nodeVersion);
|
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 {
|
} else {
|
||||||
console.log('running on BROWSER');
|
console.log('running on BROWSER');
|
||||||
console.log('browser is ' + this.userAgent);
|
console.log('browser is ' + this.userAgent);
|
||||||
|
|||||||
Reference in New Issue
Block a user