feat(smartenv): add Bun and Deno OS detection support and update test runtime compatibility

This commit is contained in:
2026-05-01 11:03:45 +00:00
parent 8514c4def0
commit 1c609cc638
14 changed files with 4366 additions and 5894 deletions
+28
View File
@@ -0,0 +1,28 @@
{
"@git.zone/cli": {
"projectType": "npm",
"module": {
"githost": "code.foss.global",
"gitscope": "push.rocks",
"gitrepo": "smartenv",
"description": "A module for storing and accessing environment details across different platforms.",
"npmPackagename": "@push.rocks/smartenv",
"license": "MIT",
"projectDomain": "push.rocks"
},
"release": {
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
}
},
"@git.zone/tsdoc": {
"legal": "\n## License and Legal Information\n\nThis 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. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
},
"@ship.zone/szci": {
"npmGlobalTools": [],
"npmRegistryUrl": "registry.npmjs.org"
}
}
+8
View File
@@ -1,5 +1,13 @@
# Changelog # Changelog
## 2026-05-01 - 6.1.0 - feat(smartenv)
add Bun and Deno OS detection support and update test runtime compatibility
- extends async OS detection helpers to work in Bun and Deno by loading the appropriate os module for each runtime
- improves runtime-specific typings and safe global access for Deno, Bun, and importScripts usage
- updates tests to export tap.start(), rename the browser test target to chromium, and clean up console output for newer test tooling
- refreshes build and test tooling configuration and package metadata for the current release workflow
## 2025-11-01 - 6.0.0 - BREAKING CHANGE(Smartenv) ## 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) Add Deno and Bun runtime detection, introduce getSafeModuleFor API, update docs and tests, and make isNode semantics Node-only (breaking change)
Generated
+2175 -2790
View File
File diff suppressed because it is too large Load Diff
+1 -2
View File
@@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2015 Task Venture Capital GmbH Copyright (c) 2026 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
@@ -19,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.
+14 -6
View File
@@ -1,9 +1,5 @@
{ {
"npmci": { "@git.zone/cli": {
"npmGlobalTools": [],
"npmAccessLevel": "public"
},
"gitzone": {
"projectType": "npm", "projectType": "npm",
"module": { "module": {
"githost": "code.foss.global", "githost": "code.foss.global",
@@ -12,6 +8,7 @@
"description": "A module for storing and accessing environment details across different platforms.", "description": "A module for storing and accessing environment details across different platforms.",
"npmPackagename": "@push.rocks/smartenv", "npmPackagename": "@push.rocks/smartenv",
"license": "MIT", "license": "MIT",
"projectDomain": "push.rocks",
"keywords": [ "keywords": [
"environment detection", "environment detection",
"cross-platform", "cross-platform",
@@ -24,9 +21,20 @@
"typescript", "typescript",
"async" "async"
] ]
},
"release": {
"registries": [
"https://verdaccio.lossless.digital",
"https://registry.npmjs.org"
],
"accessLevel": "public"
} }
}, },
"tsdoc": { "@git.zone/tsdoc": {
"legal": "\n## License and Legal Information\n\nThis 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. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n" "legal": "\n## License and Legal Information\n\nThis 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. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
},
"@ship.zone/szci": {
"npmGlobalTools": [],
"npmRegistryUrl": "registry.npmjs.org"
} }
} }
+12 -10
View File
@@ -6,9 +6,9 @@
"typings": "dist_ts/index.d.ts", "typings": "dist_ts/index.d.ts",
"type": "module", "type": "module",
"scripts": { "scripts": {
"test": "(tstest test/ --web)", "test": "tstest test/ --verbose",
"build": "(tsbuild --web --allowimplicitany && tsbundle npm)", "build": "tsbuild --web && tsbundle npm",
"testbrowser": "(npm test) && (node testbrowser.js)", "testbrowser": "pnpm test && node testbrowser.js",
"buildDocs": "tsdoc" "buildDocs": "tsdoc"
}, },
"repository": { "repository": {
@@ -34,14 +34,14 @@
}, },
"homepage": "https://code.foss.global/push.rocks/smartenv", "homepage": "https://code.foss.global/push.rocks/smartenv",
"dependencies": { "dependencies": {
"@push.rocks/smartpromise": "^4.0.2" "@push.rocks/smartpromise": "^4.2.3"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.6.8", "@git.zone/tsbuild": "^4.4.0",
"@git.zone/tsbundle": "^2.0.15", "@git.zone/tsbundle": "^2.10.1",
"@git.zone/tsrun": "^1.6.2", "@git.zone/tsrun": "^2.0.3",
"@git.zone/tstest": "^2.7.0", "@git.zone/tstest": "^3.6.3",
"@types/node": "^22.0.0" "@types/node": "^25.6.0"
}, },
"private": false, "private": false,
"files": [ "files": [
@@ -53,11 +53,13 @@
"dist_ts_web/**/*", "dist_ts_web/**/*",
"assets/**/*", "assets/**/*",
"cli.js", "cli.js",
".smartconfig.json",
"license",
"npmextra.json", "npmextra.json",
"readme.md" "readme.md"
], ],
"browserslist": [ "browserslist": [
"last 1 chrome versions" "last 1 chrome versions"
], ],
"packageManager": "pnpm@10.11.0+sha512.6540583f41cc5f628eb3d9773ecee802f4f9ef9923cc45b69890fb47991d4b092964694ec3a4f738a420c918a333062c8b925d312f42e4f0c263eb603551f977" "packageManager": "pnpm@10.28.2"
} }
+2068 -3043
View File
File diff suppressed because it is too large Load Diff
+6 -6
View File
@@ -13,7 +13,7 @@ tap.test('should detect Bun runtime correctly', async () => {
expect(testEnv.isNode).toBeFalse(); expect(testEnv.isNode).toBeFalse();
expect(testEnv.isDeno).toBeFalse(); expect(testEnv.isDeno).toBeFalse();
expect(testEnv.isBrowser).toBeFalse(); expect(testEnv.isBrowser).toBeFalse();
console.log(' Bun runtime detected correctly'); console.log('Bun runtime detected correctly');
}); });
tap.test('should get Bun version', async () => { tap.test('should get Bun version', async () => {
@@ -31,27 +31,27 @@ tap.test('should load modules for Bun', async () => {
const pathModule = await testEnv.getSafeModuleFor('bun', 'path'); const pathModule = await testEnv.getSafeModuleFor('bun', 'path');
expect(pathModule).not.toBeUndefined(); expect(pathModule).not.toBeUndefined();
expect(typeof pathModule.join).toEqual('function'); expect(typeof pathModule.join).toEqual('function');
console.log(' Successfully loaded module for Bun'); console.log('Successfully loaded module for Bun');
}); });
tap.test('should load modules for server runtimes', async () => { tap.test('should load modules for server runtimes', async () => {
const pathModule = await testEnv.getSafeModuleFor('server', 'path'); const pathModule = await testEnv.getSafeModuleFor('server', 'path');
expect(pathModule).not.toBeUndefined(); expect(pathModule).not.toBeUndefined();
expect(typeof pathModule.join).toEqual('function'); expect(typeof pathModule.join).toEqual('function');
console.log(' Successfully loaded module with server target'); console.log('Successfully loaded module with server target');
}); });
tap.test('should load modules for array of runtimes', async () => { tap.test('should load modules for array of runtimes', async () => {
const pathModule = await testEnv.getSafeModuleFor(['bun', 'node'], 'path'); const pathModule = await testEnv.getSafeModuleFor(['bun', 'node'], 'path');
expect(pathModule).not.toBeUndefined(); expect(pathModule).not.toBeUndefined();
expect(typeof pathModule.join).toEqual('function'); expect(typeof pathModule.join).toEqual('function');
console.log(' Successfully loaded module with array target'); console.log('Successfully loaded module with array target');
}); });
tap.test('should not load modules for wrong runtime', async () => { tap.test('should not load modules for wrong runtime', async () => {
const result = await testEnv.getSafeModuleFor('node', 'path'); const result = await testEnv.getSafeModuleFor('node', 'path');
expect(result).toBeUndefined(); expect(result).toBeUndefined();
console.log(' Correctly rejected Node.js-only module in Bun'); console.log('Correctly rejected Node.js-only module in Bun');
}); });
tap.test('should detect OS', async () => { tap.test('should detect OS', async () => {
@@ -78,4 +78,4 @@ tap.test('should detect CI environment if present', async () => {
console.log('CI detection: ' + testEnv.isCI); console.log('CI detection: ' + testEnv.isCI);
}); });
tap.start(); export default tap.start();
@@ -13,7 +13,7 @@ tap.test('should detect browser runtime correctly', async () => {
expect(testEnv.isNode).toBeFalse(); expect(testEnv.isNode).toBeFalse();
expect(testEnv.isDeno).toBeFalse(); expect(testEnv.isDeno).toBeFalse();
expect(testEnv.isBun).toBeFalse(); expect(testEnv.isBun).toBeFalse();
console.log(' Browser runtime detected correctly'); console.log('Browser runtime detected correctly');
}); });
tap.test('should get user agent', async () => { tap.test('should get user agent', async () => {
@@ -31,18 +31,18 @@ tap.test('should not get server runtime versions', async () => {
expect(testEnv.nodeVersion).toEqual('undefined'); expect(testEnv.nodeVersion).toEqual('undefined');
expect(testEnv.denoVersion).toEqual('undefined'); expect(testEnv.denoVersion).toEqual('undefined');
expect(testEnv.bunVersion).toEqual('undefined'); expect(testEnv.bunVersion).toEqual('undefined');
console.log(' Server runtime versions correctly return undefined in browser'); console.log('Server runtime versions correctly return undefined in browser');
}); });
tap.test('should not load server modules', async () => { tap.test('should not load server modules', async () => {
const result = await testEnv.getSafeModuleFor('server', 'path'); const result = await testEnv.getSafeModuleFor('server', 'path');
expect(result).toBeUndefined(); expect(result).toBeUndefined();
console.log(' Correctly rejected server module in browser'); console.log('Correctly rejected server module in browser');
}); });
tap.test('should not detect as CI', async () => { tap.test('should not detect as CI', async () => {
expect(testEnv.isCI).toBeFalse(); expect(testEnv.isCI).toBeFalse();
console.log(' CI detection correctly false in browser'); console.log('CI detection correctly false in browser');
}); });
tap.test('OS detection should return false in browser', async () => { tap.test('OS detection should return false in browser', async () => {
@@ -52,7 +52,7 @@ tap.test('OS detection should return false in browser', async () => {
expect(resultMac).toBeFalse(); expect(resultMac).toBeFalse();
expect(resultLinux).toBeFalse(); expect(resultLinux).toBeFalse();
expect(resultWindows).toBeFalse(); expect(resultWindows).toBeFalse();
console.log(' OS detection correctly returns false in browser'); console.log('OS detection correctly returns false in browser');
}); });
tap.start(); export default tap.start();
+5 -5
View File
@@ -13,7 +13,7 @@ tap.test('should detect Deno runtime correctly', async () => {
expect(testEnv.isNode).toBeFalse(); expect(testEnv.isNode).toBeFalse();
expect(testEnv.isBun).toBeFalse(); expect(testEnv.isBun).toBeFalse();
expect(testEnv.isBrowser).toBeFalse(); expect(testEnv.isBrowser).toBeFalse();
console.log(' Deno runtime detected correctly'); console.log('Deno runtime detected correctly');
}); });
tap.test('should get Deno version', async () => { tap.test('should get Deno version', async () => {
@@ -32,7 +32,7 @@ tap.test('should load modules for Deno', async () => {
const pathModule = await testEnv.getSafeModuleFor('deno', 'node:path'); const pathModule = await testEnv.getSafeModuleFor('deno', 'node:path');
expect(pathModule).not.toBeUndefined(); expect(pathModule).not.toBeUndefined();
expect(typeof pathModule.join).toEqual('function'); expect(typeof pathModule.join).toEqual('function');
console.log(' Successfully loaded module for Deno'); console.log('Successfully loaded module for Deno');
}); });
tap.test('should load modules for server runtimes', async () => { tap.test('should load modules for server runtimes', async () => {
@@ -40,13 +40,13 @@ tap.test('should load modules for server runtimes', async () => {
const pathModule = await testEnv.getSafeModuleFor('server', 'node:path'); const pathModule = await testEnv.getSafeModuleFor('server', 'node:path');
expect(pathModule).not.toBeUndefined(); expect(pathModule).not.toBeUndefined();
expect(typeof pathModule.join).toEqual('function'); expect(typeof pathModule.join).toEqual('function');
console.log(' Successfully loaded module with server target'); console.log('Successfully loaded module with server target');
}); });
tap.test('should not load modules for wrong runtime', async () => { tap.test('should not load modules for wrong runtime', async () => {
const result = await testEnv.getSafeModuleFor('node', 'node:path'); const result = await testEnv.getSafeModuleFor('node', 'node:path');
expect(result).toBeUndefined(); expect(result).toBeUndefined();
console.log(' Correctly rejected Node.js-only module in Deno'); console.log('Correctly rejected Node.js-only module in Deno');
}); });
tap.test('should detect CI environment if present', async () => { tap.test('should detect CI environment if present', async () => {
@@ -56,4 +56,4 @@ tap.test('should detect CI environment if present', async () => {
console.log('CI detection in Deno: ' + isCI); console.log('CI detection in Deno: ' + isCI);
}); });
tap.start(); export default tap.start();
+1 -1
View File
@@ -78,4 +78,4 @@ tap.test('should state wether we are in CI', async () => {
} }
}); });
tap.start(); export default tap.start();
+1 -1
View File
@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartenv', name: '@push.rocks/smartenv',
version: '6.0.0', version: '6.1.0',
description: 'A module for storing and accessing environment details across different platforms.' description: 'A module for storing and accessing environment details across different platforms.'
} }
+36 -19
View File
@@ -1,6 +1,18 @@
import * as plugins from './smartenv.plugins.js'; import * as plugins from './smartenv.plugins.js';
import * as interfaces from './interfaces/index.js'; import * as interfaces from './interfaces/index.js';
type TSmartenvGlobalThis = typeof globalThis & {
Deno?: {
version?: {
deno?: string;
};
};
Bun?: {
version?: string;
};
importScripts?: (urlArg: string) => void;
};
// interfaces // interfaces
export interface IEnvObject { export interface IEnvObject {
name: string; name: string;
@@ -30,10 +42,10 @@ 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 | undefined> {
if (!this.isNode && !this.isDeno && !this.isBun) { 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.`); console.error(`You tried to load a server module in a wrong context: ${moduleNameArg}. This does not throw.`);
return; return undefined;
} }
// tslint:disable-next-line: function-constructor // tslint:disable-next-line: function-constructor
const returnValue: T = await (new Function(`return import('${moduleNameArg}')`)() as Promise<T>); const returnValue: T = await (new Function(`return import('${moduleNameArg}')`)() as Promise<T>);
@@ -57,8 +69,9 @@ export class Smartenv {
} }
const done = plugins.smartpromise.defer(); const done = plugins.smartpromise.defer();
if (globalThis.importScripts) { const smartenvGlobal = globalThis as TSmartenvGlobalThis;
globalThis.importScripts(urlArg); if (smartenvGlobal.importScripts) {
smartenvGlobal.importScripts(urlArg);
done.resolve(); done.resolve();
} else { } else {
const script = document.createElement('script'); const script = document.createElement('script');
@@ -73,15 +86,17 @@ export class Smartenv {
} }
public get runtimeEnv(): interfaces.TRuntimeType { public get runtimeEnv(): interfaces.TRuntimeType {
const smartenvGlobal = globalThis as TSmartenvGlobalThis;
// Check Deno first (most distinctive) // Check Deno first (most distinctive)
if (typeof globalThis.Deno !== 'undefined' && if (typeof smartenvGlobal.Deno !== 'undefined' &&
typeof (globalThis as any).Deno?.version !== 'undefined') { typeof smartenvGlobal.Deno.version !== 'undefined') {
return 'deno'; return 'deno';
} }
// Check Bun second (most distinctive) // Check Bun second (most distinctive)
if (typeof globalThis.Bun !== 'undefined' && if (typeof smartenvGlobal.Bun !== 'undefined' &&
typeof (globalThis as any).Bun?.version !== 'undefined') { typeof smartenvGlobal.Bun.version !== 'undefined') {
return 'bun'; return 'bun';
} }
@@ -135,14 +150,16 @@ export class Smartenv {
public get denoVersion(): string { public get denoVersion(): string {
if (this.isDeno) { if (this.isDeno) {
return (globalThis as any).Deno.version.deno; const smartenvGlobal = globalThis as TSmartenvGlobalThis;
return smartenvGlobal.Deno?.version?.deno ?? 'undefined';
} }
return 'undefined'; return 'undefined';
} }
public get bunVersion(): string { public get bunVersion(): string {
if (this.isBun) { if (this.isBun) {
return (globalThis as any).Bun.version; const smartenvGlobal = globalThis as TSmartenvGlobalThis;
return smartenvGlobal.Bun?.version ?? 'undefined';
} }
return 'undefined'; return 'undefined';
} }
@@ -212,27 +229,27 @@ export class Smartenv {
} }
public async isMacAsync(): Promise<boolean> { public async isMacAsync(): Promise<boolean> {
if (this.isNode) { if (this.isNode || this.isDeno || this.isBun) {
const os = await this.getSafeNodeModule('os'); const os = await this.getSafeNodeModule<typeof import('node:os')>(this.isDeno ? 'node:os' : 'os');
return os.platform() === 'darwin'; return os?.platform() === 'darwin';
} else { } else {
return false; return false;
} }
} }
public async isWindowsAsync(): Promise<boolean> { public async isWindowsAsync(): Promise<boolean> {
if (this.isNode) { if (this.isNode || this.isDeno || this.isBun) {
const os = await this.getSafeNodeModule('os'); const os = await this.getSafeNodeModule<typeof import('node:os')>(this.isDeno ? 'node:os' : 'os');
return os.platform() === 'win32'; return os?.platform() === 'win32';
} else { } else {
return false; return false;
} }
} }
public async isLinuxAsync(): Promise<boolean> { public async isLinuxAsync(): Promise<boolean> {
if (this.isNode) { if (this.isNode || this.isDeno || this.isBun) {
const os = await this.getSafeNodeModule('os'); const os = await this.getSafeNodeModule<typeof import('node:os')>(this.isDeno ? 'node:os' : 'os');
return os.platform() === 'linux'; return os?.platform() === 'linux';
} else { } else {
return false; return false;
} }
+4 -4
View File
@@ -5,10 +5,10 @@
"target": "ES2022", "target": "ES2022",
"module": "NodeNext", "module": "NodeNext",
"moduleResolution": "NodeNext", "moduleResolution": "NodeNext",
"noImplicitAny": true,
"esModuleInterop": true, "esModuleInterop": true,
"verbatimModuleSyntax": true "verbatimModuleSyntax": true,
"types": ["node"]
}, },
"exclude": [ "exclude": ["dist_*/**/*.d.ts"]
"dist_*/**/*.d.ts"
]
} }