Compare commits

...

13 Commits

Author SHA1 Message Date
Meghan Denny
f88ba440a5 fix 2025-07-16 22:26:24 -07:00
autofix-ci[bot]
9f03331fca [autofix.ci] apply automated fixes 2025-07-17 04:23:42 +00:00
Meghan Denny
b17c400f85 Merge branch 'main' into nektro-patch-5884 2025-07-16 20:20:24 -08:00
Meghan Denny
b1b275fbb0 Merge remote-tracking branch 'origin/main' into nektro-patch-5884 2025-05-01 23:08:52 -07:00
Meghan Denny
3234c79227 windows fixes 2025-05-01 23:06:55 -07:00
Meghan Denny
8e56d6a3b3 this doesnt exist in node 2025-05-01 00:07:53 -07:00
Meghan Denny
b69081532e skip this on linux for now and document why 2025-04-30 23:35:36 -07:00
Meghan Denny
284050c11f revert that 2025-04-30 20:51:00 -07:00
Meghan Denny
2320d69cf6 🤦 2025-04-30 19:28:55 -07:00
Meghan Denny
030083684e oops 2025-04-30 19:05:37 -07:00
Meghan Denny
eb563fa3e4 windows fixes 2025-04-30 18:51:59 -07:00
Meghan Denny
e0b3ef0683 this was supposed to check the key not the value 2025-04-29 23:57:17 -07:00
Meghan Denny
c953544881 node: add custom setters to process.env 2025-04-29 22:31:33 -07:00
8 changed files with 415 additions and 38 deletions

View File

@@ -1,18 +1,17 @@
#include "root.h"
#include "ZigGlobalObject.h"
#include "helpers.h"
#include <JavaScriptCore/JSObject.h>
#include <JavaScriptCore/ObjectConstructor.h>
#include <JavaScriptCore/JSArray.h>
#include <JavaScriptCore/JSArrayInlines.h>
#include <JavaScriptCore/JSString.h>
#include <JavaScriptCore/JSStringInlines.h>
#include "BunClientData.h"
#include "wtf/Compiler.h"
#include "wtf/Forward.h"
#include "JSEnvironmentVariableMap.h"
#include "ErrorCode.h"
using namespace JSC;
@@ -267,7 +266,6 @@ JSC_DEFINE_HOST_FUNCTION(jsEditWindowsEnvVar, (JSGlobalObject * global, JSC::Cal
WTF::String string1 = callFrame->uncheckedArgument(0).toWTFString(global);
RETURN_IF_EXCEPTION(scope, {});
JSValue arg2 = callFrame->uncheckedArgument(1);
ASSERT(arg2.isNull() || arg2.isString());
if (arg2.isCell()) {
WTF::String string2 = arg2.toWTFString(global);
RETURN_IF_EXCEPTION(scope, {});
@@ -279,6 +277,55 @@ JSC_DEFINE_HOST_FUNCTION(jsEditWindowsEnvVar, (JSGlobalObject * global, JSC::Cal
}
#endif
const JSC::ClassInfo JSEnvironmentVariableMap::s_info = { "EnvironmentVariableMap"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSEnvironmentVariableMap) };
void JSEnvironmentVariableMap::finishCreation(JSC::VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
}
bool JSEnvironmentVariableMap::defineOwnProperty(JSC::JSObject* object, JSC::JSGlobalObject* globalObject, JSC::PropertyName propertyName, const JSC::PropertyDescriptor& descriptor, bool shouldThrow)
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
if (propertyName.isSymbol()) {
throwTypeError(globalObject, scope, "Cannot convert a symbol to a string"_s);
return false;
}
if (descriptor.getterPresent() || descriptor.setterPresent()) {
scope.throwException(globalObject, createError(globalObject, Bun::ErrorCode::ERR_INVALID_OBJECT_DEFINE_PROPERTY, "'process.env' does not accept an accessor(getter/setter) descriptor"_s));
return false;
}
if (!descriptor.configurable() || !descriptor.writable() || !descriptor.enumerable()) {
scope.throwException(globalObject, createError(globalObject, Bun::ErrorCode::ERR_INVALID_OBJECT_DEFINE_PROPERTY, "'process.env' only accepts a configurable, writable, and enumerable data descriptor"_s));
return false;
}
auto value = descriptor.value();
value = value ? value : jsUndefined();
if (propertyName.publicName()->length() == 0) return false;
auto string = value.toStringOrNull(globalObject);
EXCEPTION_ASSERT(!!string == !scope.exception());
RETURN_IF_EXCEPTION(scope, {});
descriptor.value() = string;
RELEASE_AND_RETURN(scope, Base::defineOwnProperty(object, globalObject, propertyName, descriptor, shouldThrow));
}
bool JSEnvironmentVariableMap::put(JSC::JSCell* cell, JSC::JSGlobalObject* globalObject, JSC::PropertyName propertyName, JSC::JSValue value, JSC::PutPropertySlot& slot)
{
auto& vm = JSC::getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
if (propertyName.isSymbol()) {
throwTypeError(globalObject, scope, "Cannot convert a symbol to a string"_s);
return false;
}
if (propertyName.publicName()->length() == 0) return false;
auto string = value.toStringOrNull(globalObject);
EXCEPTION_ASSERT(!!string == !scope.exception());
RETURN_IF_EXCEPTION(scope, {});
RELEASE_AND_RETURN(scope, Base::put(cell, globalObject, propertyName, string, slot));
}
JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
{
VM& vm = globalObject->vm();
@@ -286,12 +333,11 @@ JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
void* list;
size_t count = Bun__getEnvCount(globalObject, &list);
JSC::JSObject* object = nullptr;
if (count < 63) {
object = constructEmptyObject(globalObject, globalObject->objectPrototype(), count);
} else {
object = constructEmptyObject(globalObject, globalObject->objectPrototype());
}
#if OS(WINDOWS)
auto* object = constructEmptyObject(globalObject, globalObject->objectPrototype());
#else
auto* object = JSEnvironmentVariableMap::create(vm, globalObject, JSEnvironmentVariableMap::createStructure(vm, globalObject, globalObject->objectPrototype()));
#endif
#if OS(WINDOWS)
JSArray* keyArray = constructEmptyArray(globalObject, nullptr, count);
@@ -362,25 +408,19 @@ JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject)
if (!hasTZ) {
TZAttrs |= JSC::PropertyAttribute::DontEnum;
}
object->putDirectCustomAccessor(
vm,
Identifier::fromString(vm, TZ), JSC::CustomGetterSetter::create(vm, jsTimeZoneEnvironmentVariableGetter, jsTimeZoneEnvironmentVariableSetter), TZAttrs);
object->putDirectCustomAccessor(vm, Identifier::fromString(vm, TZ), JSC::CustomGetterSetter::create(vm, jsTimeZoneEnvironmentVariableGetter, jsTimeZoneEnvironmentVariableSetter), TZAttrs);
unsigned int NODE_TLS_REJECT_UNAUTHORIZED_Attrs = JSC::PropertyAttribute::CustomAccessor | 0;
if (!hasNodeTLSRejectUnauthorized) {
NODE_TLS_REJECT_UNAUTHORIZED_Attrs |= JSC::PropertyAttribute::DontEnum;
}
object->putDirectCustomAccessor(
vm,
Identifier::fromString(vm, NODE_TLS_REJECT_UNAUTHORIZED), JSC::CustomGetterSetter::create(vm, jsNodeTLSRejectUnauthorizedGetter, jsNodeTLSRejectUnauthorizedSetter), NODE_TLS_REJECT_UNAUTHORIZED_Attrs);
object->putDirectCustomAccessor(vm, Identifier::fromString(vm, NODE_TLS_REJECT_UNAUTHORIZED), JSC::CustomGetterSetter::create(vm, jsNodeTLSRejectUnauthorizedGetter, jsNodeTLSRejectUnauthorizedSetter), NODE_TLS_REJECT_UNAUTHORIZED_Attrs);
unsigned int BUN_CONFIG_VERBOSE_FETCH_Attrs = JSC::PropertyAttribute::CustomAccessor | 0;
if (!hasBunConfigVerboseFetch) {
BUN_CONFIG_VERBOSE_FETCH_Attrs |= JSC::PropertyAttribute::DontEnum;
}
object->putDirectCustomAccessor(
vm,
Identifier::fromString(vm, BUN_CONFIG_VERBOSE_FETCH), JSC::CustomGetterSetter::create(vm, jsBunConfigVerboseFetchGetter, jsBunConfigVerboseFetchSetter), BUN_CONFIG_VERBOSE_FETCH_Attrs);
object->putDirectCustomAccessor(vm, Identifier::fromString(vm, BUN_CONFIG_VERBOSE_FETCH), JSC::CustomGetterSetter::create(vm, jsBunConfigVerboseFetchGetter, jsBunConfigVerboseFetchSetter), BUN_CONFIG_VERBOSE_FETCH_Attrs);
#if OS(WINDOWS)
auto editWindowsEnvVar = JSC::JSFunction::create(vm, globalObject, 0, String("editWindowsEnvVar"_s), jsEditWindowsEnvVar, ImplementationVisibility::Public);

View File

@@ -1,4 +1,6 @@
#pragma once
#include "root.h"
#include "BunClientData.h"
namespace Zig {
class GlobalObject;
@@ -12,4 +14,43 @@ namespace Bun {
JSC::JSValue createEnvironmentVariablesMap(Zig::GlobalObject* globalObject);
class JSEnvironmentVariableMap : public JSC::JSNonFinalObject {
public:
using Base = JSC::JSNonFinalObject;
static constexpr unsigned StructureFlags = Base::StructureFlags | JSC::OverridesPut;
~JSEnvironmentVariableMap();
static JSEnvironmentVariableMap* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure)
{
JSEnvironmentVariableMap* ptr = new (NotNull, JSC::allocateCell<JSEnvironmentVariableMap>(vm)) JSEnvironmentVariableMap(vm, structure);
ptr->finishCreation(vm);
return ptr;
}
DECLARE_INFO;
template<typename, JSC::SubspaceAccess mode>
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
{
return &vm.plainObjectSpace();
}
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
{
return JSC::Structure::create(vm, globalObject, prototype, JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
}
protected:
static bool defineOwnProperty(JSC::JSObject*, JSC::JSGlobalObject*, JSC::PropertyName, const JSC::PropertyDescriptor&, bool shouldThrow);
static bool put(JSC::JSCell*, JSC::JSGlobalObject*, JSC::PropertyName, JSC::JSValue, JSC::PutPropertySlot&);
private:
JSEnvironmentVariableMap(JSC::VM& vm, JSC::Structure* structure)
: Base(vm, structure)
{
}
void finishCreation(JSC::VM&);
};
}

View File

@@ -113,6 +113,7 @@
#include "JSPublicKeyObject.h"
#include "JSPrivateKeyObject.h"
#include "CryptoKeyType.h"
#include "JSEnvironmentVariableMap.h"
#include "JSNodePerformanceHooksHistogram.h"
#if USE(CG)
@@ -2656,7 +2657,11 @@ SerializationReturnCode CloneSerializer::serialize(JSValue in)
// objects have been handled. If we reach this point and
// the input is not an Object object then we should throw
// a DataCloneError.
if (inObject->classInfo() != JSFinalObject::info())
auto* inInfo = inObject->classInfo();
if (
inInfo != JSFinalObject::info()
&& inInfo != JSEnvironmentVariableMap::info()
&& true)
return SerializationReturnCode::DataCloneError;
inputObjectStack.append(inObject);
indexStack.append(0);

View File

@@ -374,58 +374,115 @@ export function windowsEnv(
//
// it throws "Cannot convert a Symbol value to a string"
// maps uppercase->original case
const keyStore = new Map<string, string>();
for (const key in internalEnv) {
keyStore.set(key.toUpperCase(), key);
}
for (const key of [
"hasOwnProperty",
"isPrototypeOf",
"propertyIsEnumerable",
"toLocaleString",
"toString",
"valueOf",
]) {
const k = key.toUpperCase();
if (!keyStore.has(k)) keyStore.set(k, key);
}
(internalEnv as any)[Bun.inspect.custom] = () => {
let o = {};
for (let k of envMapList) {
o[k] = internalEnv[k.toUpperCase()];
o[k] = internalEnv[keyStore.get(k.toUpperCase())!];
}
return o;
};
(internalEnv as any).toJSON = () => {
return { ...internalEnv };
};
return new Proxy(internalEnv, {
get(_, p) {
return typeof p === "string" ? internalEnv[p.toUpperCase()] : undefined;
if (typeof p !== "string") return undefined;
const k = p.toUpperCase();
if (!keyStore.has(k)) return undefined;
return internalEnv[keyStore.get(k)!];
},
set(_, p, value) {
const k = String(p).toUpperCase();
if (typeof p === "symbol") throw new TypeError("Cannot convert a symbol to a string");
if (typeof value === "symbol") throw new TypeError("Cannot convert a symbol to a string");
$assert(typeof p === "string"); // proxy is only string and symbol. the symbol would have thrown by now
if (p.length === 0) return true;
value = String(value); // If toString() throws, we want to avoid it existing in the envMapList
if (!(k in internalEnv) && !envMapList.includes(p)) {
if (keyStore.has(k)) {
p = keyStore.get(k)!;
} else {
keyStore.set(k, p);
}
if (!envMapList.includes(p)) {
envMapList.push(p);
}
if (internalEnv[k] !== value) {
editWindowsEnvVar(k, value);
internalEnv[k] = value;
if (internalEnv[p] !== value) {
editWindowsEnvVar(p, value);
internalEnv[p] = value;
}
return true;
},
has(_, p) {
return typeof p !== "symbol" ? String(p).toUpperCase() in internalEnv : false;
return typeof p !== "symbol" ? keyStore.has(String(p).toUpperCase()) : false;
},
deleteProperty(_, p) {
if (typeof p === "symbol") return true;
const k = String(p).toUpperCase();
const i = envMapList.findIndex(x => x.toUpperCase() === k);
if (keyStore.has(k)) {
p = keyStore.get(k)!;
keyStore.delete(k);
}
const i = envMapList.findIndex(x => x === p);
if (i !== -1) {
envMapList.splice(i, 1);
}
editWindowsEnvVar(k, null);
return typeof p !== "symbol" ? delete internalEnv[k] : false;
editWindowsEnvVar(p, null);
return typeof p !== "symbol" ? delete internalEnv[p] : false;
},
defineProperty(_, p, attributes) {
if (attributes.get)
throw $ERR_INVALID_OBJECT_DEFINE_PROPERTY(
`'process.env' does not accept an accessor(getter/setter) descriptor`,
);
if (attributes.set)
throw $ERR_INVALID_OBJECT_DEFINE_PROPERTY(
`'process.env' does not accept an accessor(getter/setter) descriptor`,
);
if (!attributes.writable)
throw $ERR_INVALID_OBJECT_DEFINE_PROPERTY(
`'process.env' only accepts a configurable, writable, and enumerable data descriptor`,
);
if (!attributes.enumerable)
throw $ERR_INVALID_OBJECT_DEFINE_PROPERTY(
`'process.env' only accepts a configurable, writable, and enumerable data descriptor`,
);
if (!attributes.configurable)
throw $ERR_INVALID_OBJECT_DEFINE_PROPERTY(
`'process.env' only accepts a configurable, writable, and enumerable data descriptor`,
);
const k = String(p).toUpperCase();
$assert(typeof p === "string"); // proxy is only string and symbol. the symbol would have thrown by now
if (!(k in internalEnv) && !envMapList.includes(p)) {
if (keyStore.has(k)) {
p = keyStore.get(k)!;
} else {
keyStore.set(k, p);
}
if (!envMapList.includes(p)) {
envMapList.push(p);
}
editWindowsEnvVar(k, internalEnv[k]);
return $Object.$defineProperty(internalEnv, k, attributes);
editWindowsEnvVar(p, attributes.value);
return $Object.$defineProperty(internalEnv, p, attributes);
},
getOwnPropertyDescriptor(target, p) {
return typeof p === "string" ? Reflect.getOwnPropertyDescriptor(target, p.toUpperCase()) : undefined;
if (typeof p !== "string") return undefined;
const k = keyStore.get(p.toUpperCase());
if (!k) return undefined;
return Reflect.getOwnPropertyDescriptor(target, k);
},
ownKeys() {
// .slice() because paranoia that there is a way to call this without the engine cloning it for us

View File

@@ -0,0 +1,13 @@
'use strict';
require('../common');
const assert = require('assert');
process.env.foo = 'foo';
assert.strictEqual(process.env.foo, 'foo');
process.env.foo = undefined;
assert.strictEqual(process.env.foo, 'undefined');
process.env.foo = 'foo';
assert.strictEqual(process.env.foo, 'foo');
delete process.env.foo;
assert.strictEqual(process.env.foo, undefined);

View File

@@ -0,0 +1,67 @@
'use strict';
require('../common');
const assert = require('assert');
assert.throws(
() => {
Object.defineProperty(process.env, 'foo', {
value: 'foo1'
});
},
{
code: 'ERR_INVALID_OBJECT_DEFINE_PROPERTY',
name: 'TypeError',
message: '\'process.env\' only accepts a ' +
'configurable, writable,' +
' and enumerable data descriptor'
}
);
assert.strictEqual(process.env.foo, undefined);
process.env.foo = 'foo2';
assert.strictEqual(process.env.foo, 'foo2');
assert.throws(
() => {
Object.defineProperty(process.env, 'goo', {
get() {
return 'goo';
},
set() {}
});
},
{
code: 'ERR_INVALID_OBJECT_DEFINE_PROPERTY',
name: 'TypeError',
message: '\'process.env\' does not accept an ' +
'accessor(getter/setter) descriptor'
}
);
const attributes = ['configurable', 'writable', 'enumerable'];
for (const attribute of attributes) {
assert.throws(
() => {
Object.defineProperty(process.env, 'goo', {
[attribute]: false
});
},
{
code: 'ERR_INVALID_OBJECT_DEFINE_PROPERTY',
name: 'TypeError',
message: '\'process.env\' only accepts a ' +
'configurable, writable,' +
' and enumerable data descriptor'
}
);
}
assert.strictEqual(process.env.goo, undefined);
Object.defineProperty(process.env, 'goo', {
value: 'goo',
configurable: true,
writable: true,
enumerable: true
});
assert.strictEqual(process.env.goo, 'goo');

View File

@@ -0,0 +1,31 @@
'use strict';
require('../common');
const assert = require('assert');
const symbol = Symbol('sym');
// Verify that getting via a symbol key returns undefined.
assert.strictEqual(process.env[symbol], undefined);
// Verify that assigning via a symbol key throws.
// The message depends on the JavaScript engine and so will be different between
// different JavaScript engines. Confirm that the `Error` is a `TypeError` only.
assert.throws(() => {
process.env[symbol] = 42;
}, TypeError);
// Verify that assigning a symbol value throws.
// The message depends on the JavaScript engine and so will be different between
// different JavaScript engines. Confirm that the `Error` is a `TypeError` only.
assert.throws(() => {
process.env.foo = symbol;
}, TypeError);
// Verify that using a symbol with the in operator returns false.
assert.strictEqual(symbol in process.env, false);
// Verify that deleting a symbol key returns true.
assert.strictEqual(delete process.env[symbol], true);
// Checks that well-known symbols like `Symbol.toStringTag` wont throw.
Object.prototype.toString.call(process.env);

View File

@@ -0,0 +1,123 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// 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.
'use strict';
const common = require('../common');
const assert = require('assert');
// Changes in environment should be visible to child processes
if (process.argv[2] === 'you-are-the-child') {
assert.strictEqual('NODE_PROCESS_ENV_DELETED' in process.env, false);
assert.strictEqual(process.env.NODE_PROCESS_ENV, '42');
assert.strictEqual(process.env.hasOwnProperty, 'asdf');
const has = Object.hasOwn(process.env, 'hasOwnProperty');
assert.strictEqual(has, true);
process.exit(0);
}
{
const spawn = require('child_process').spawn;
assert.strictEqual(Object.prototype.hasOwnProperty,
process.env.hasOwnProperty);
const has = Object.hasOwn(process.env, 'hasOwnProperty');
assert.strictEqual(has, false);
process.env.hasOwnProperty = 'asdf';
process.env.NODE_PROCESS_ENV = 42;
assert.strictEqual(process.env.NODE_PROCESS_ENV, '42');
process.env.NODE_PROCESS_ENV_DELETED = 42;
assert.strictEqual('NODE_PROCESS_ENV_DELETED' in process.env, true);
delete process.env.NODE_PROCESS_ENV_DELETED;
assert.strictEqual('NODE_PROCESS_ENV_DELETED' in process.env, false);
const child = spawn(process.argv[0], [process.argv[1], 'you-are-the-child']);
child.stdout.on('data', function(data) { console.log(data.toString()); });
child.stderr.on('data', function(data) { console.log(data.toString()); });
child.on('exit', function(statusCode) {
if (statusCode !== 0) {
process.exit(statusCode); // Failed assertion in child process
}
});
}
// Delete should return true except for non-configurable properties
// https://github.com/nodejs/node/issues/7960
delete process.env.NON_EXISTING_VARIABLE;
assert(delete process.env.NON_EXISTING_VARIABLE);
// For the moment we are not going to support setting the timezone via the
// environment variables. The problem is that various V8 platform backends
// deal with timezone in different ways. The Windows platform backend caches
// the timezone value while the Linux one hits libc for every query.
//
// https://github.com/joyent/node/blob/08782931205bc4f6d28102ebc29fd806e8ccdf1f/deps/v8/src/platform-linux.cc#L339-345
// https://github.com/joyent/node/blob/08782931205bc4f6d28102ebc29fd806e8ccdf1f/deps/v8/src/platform-win32.cc#L590-596
//
// // set the timezone; see tzset(3)
// process.env.TZ = 'Europe/Amsterdam';
//
// // time difference between Greenwich and Amsterdam is +2 hours in the summer
// date = new Date('Fri, 10 Sep 1982 03:15:00 GMT');
// assert.strictEqual(3, date.getUTCHours());
// assert.strictEqual(5, date.getHours());
// Environment variables should be case-insensitive on Windows, and
// case-sensitive on other platforms.
process.env.TEST = 'test';
assert.strictEqual(process.env.TEST, 'test');
// Check both mixed case and lower case, to avoid any regressions that might
// simply convert input to lower case.
if (common.isWindows) {
assert.strictEqual(process.env.test, 'test');
assert.strictEqual(process.env.teST, 'test');
} else {
assert.strictEqual(process.env.test, undefined);
assert.strictEqual(process.env.teST, undefined);
}
{
const keys = Object.keys(process.env);
assert.ok(keys.length > 0);
}
// https://github.com/nodejs/node/issues/45380
// BUN: TODO: on windows
if (!common.isWindows) {
const env = structuredClone(process.env);
// deepEqual(), not deepStrictEqual(), because of different prototypes.
// eslint-disable-next-line no-restricted-properties
assert.deepEqual(env, process.env);
}
// Setting environment variables on Windows with empty names should not cause
// an assertion failure.
// https://github.com/nodejs/node/issues/32920
{
process.env[''] = '';
assert.strictEqual(process.env[''], undefined);
}