Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
bengl committed Sep 23, 2023
0 parents commit 27a396c
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
19 changes: 19 additions & 0 deletions LICENSE.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2023 Bryan English

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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
SOFTWARE.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# is-really-node

Sometimes you want to make sure that the JavaScript runtime you're expecting to
be running on is the one you're actually running on. With this library, you can
detect whether you're actually running inside Node.js, and not Deno or Bun. A
single (default) export is provided, and it's a boolean that's true if we're
definitely running inside Node.js.

This is done by first checking verious globals, and then attempting to alter v8
options, and testing those options have been altered. This is only currently
possible in Node.js. The aim is to never have false negatives, and also load
quickly if the globals-checking gives us a negative.

## Some Details (FAQ)

* Why not TypeScript or at least JSDoc?
* The module is very small, and the code does some things that would probably
piss off `tsc`.
* I don't use TypeScript in my day job, so I tend not to default to it. Don't
worry, I'm not one of those haters you see on Twitter.
* Submit a PR that works correctly and I'll add it.
* Okay, but why no `.d.ts`?
* I'm lazy and Keep forgetting how to do that for default exports, since I
don't use TypeScript in my day job. Submit a PR and I'll add it.
* Do you just hate Bun and Deno? Are you some Node.js purist? They're both
Node.js compatible anyway!
* No. A lot of code I work with targets very specific runtime things that
only work in Node.js, so I want an easy way to bail early if that stuff
isn't going to work, even if the runtime is trying to fake that it's
Node.js. That's all. Please feel free to create `is-really-bun` or
`is-really-deno` if that suits your work.
* Isn't checking the globals alone enough?
* Nope. If folks are trying to fake that they're in Node.js, they can always
mess with the globals. Instead we need to do something that's _truly_
impossible in the other platforms, like messing with v8 options.
* Why no CommonJS?
* Deno doesn't support CommonJS, AFAIK.
* That's a bad excuse though. Really I should make this work with `require`
on platforms that support it. Submit a PR that handles this and I'll add
it.

## License

The MIT License. See LICENSE.txt
78 changes: 78 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/**
* First of all, this is a CommonJS module. These aren't supported by Deno,
* but that can be worked around with various bundling techniques, etc.
*/

/**
* Let's check for some basic globals. If these aren't present, game over.
*/
function hasNodeGlobals() {
return typeof global === 'object' &&
typeof globalThis === 'object' &&
global === globalThis &&
global.global === global &&
globalThis.globalThis === globalThis &&
typeof process === 'object'
}

/**
* Now, we check for the Bun global object. The Bun object is
* non-configurable, so it would be very hard to get past this if trying to
* pretend to be Node.js.
*/
function hasBunGlobal() {
return typeof Bun !== 'undefined' ||
typeof global.Bun !== 'undefined'
}

/**
* We'll do the same here for Deno. The Deno object _can_ be delted, so this
* could be faked around, so we need the subsequent test as well.
*/
function hasDenoGlobal() {
return typeof Deno === 'object' ||
typeof global.Deno === 'object'
}

/**
* V8 has "natives syntax" which allows us to do some sneaky things. We'll now
* test that those sneaky things are there and working. If it works, then
* we're definitely not in Bun, which uses JSC. If it does, we'll try to
* disable it, ruling out Deno.
*/
async function isNodeIshV8() {
let v8
try {
v8 = await import('node:v8')
} catch {
return false
}

function privateSymbol() {
try {
const sym = eval('%CreatePrivateSymbol("testing")')
const obj = {}
obj[sym] = 3
return !Reflect.ownKeys(obj).includes(sym) && obj[sym] === 3
} catch {
return false
}
}

// JSC/Bun doesn't support V8's native syntax.
if (privateSymbol()) {
// Natives syntax was on. Try turning it off. That' won't work in Deno.
v8.setFlagsFromString('--no-allow-natives-syntax')
return !privateSymbol()
}

// Deno "supports" this, but it does nothing. So if it failed before, it
// will fail again.
v8.setFlagsFromString('--allow-natives-syntax')
return privateSymbol()
}

export default hasNodeGlobals() &&
!hasBunGlobal() &&
!hasDenoGlobal() &&
await isNodeIshV8()
27 changes: 27 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "is-really-node",
"version": "1.0.0",
"description": "Determine whether the current runtime is _really_ Node.js, or some impostor.",
"main": "index.js",
"scripts": {
"test": "node test"
},
"type": "module",
"repository": {
"type": "git",
"url": "git+ssh://[email protected]/bengl/is-really-node.git"
},
"keywords": [
"Node.js",
"Bun",
"Deno",
"detect",
"environment"
],
"author": "Bryan English <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/bengl/is-really-node/issues"
},
"homepage": "https://github.com/bengl/is-really-node#readme"
}
61 changes: 61 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { exec } from 'child_process'
import test from 'node:test'
import assert from 'assert'

function run(...args) {
return new Promise((resolve, reject) => {
exec(...args, (err, stdout, stderr) => {
if (err) reject(err)
resolve({ stdout, stderr })
})
})
}

test('Node.js', async () => {
await run('node test/node-test.js')
})

test('Node.js (allow-natives-syntax)', async () => {
await run('node --allow-natives-syntax test/node-test.js')
})

test('Node.js (no-allow-natives-syntax)', async () => {
await run('node --no-allow-natives-syntax test/node-test.js')
})

test('Deno', async () => {
await assert.rejects(
() => run('deno run test/node-test.js')
)
})

test('Deno (faking Node.js)', async () => {
await assert.rejects(
() => run('deno run test/deno-test.js')
)
})

test('Deno (faking Node.js) (allow-natives-syntax)', async () => {
await assert.rejects(
() => run('deno run --v8-flags="--allow-natives-syntax" test/deno-test.js')
)
})

test('Deno (faking Node.js) (no-allow-natives-syntax)', async () => {
await assert.rejects(
() => run('deno run --v8-flags="--allow-natives-syntax" test/deno-test.js')
)
})

test('Bun', async () => {
await assert.rejects(
() => run('deno run test/node-test.js')
)
})

test('Bun (faking Node.js)', async () => {
await assert.rejects(
() => run('deno run test/deno-test.js')
)
})

6 changes: 6 additions & 0 deletions test/bun-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
globalThis.global = globalThis

//// Not possible in Bun
// delete globalThis.Bun

import('./node-test.js')
7 changes: 7 additions & 0 deletions test/deno-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
globalThis.global = globalThis

globalThis.process = {}

delete globalThis.Deno

import('./node-test.js')
4 changes: 4 additions & 0 deletions test/node-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import isNode from '../index.js'
import assert from 'node:assert'

assert.strictEqual(true, isNode)

0 comments on commit 27a396c

Please sign in to comment.