Compare commits

...

13 Commits

Author SHA1 Message Date
autofix-ci[bot]
997c7df07a [autofix.ci] apply automated fixes 2025-09-07 12:26:27 +00:00
Claude Bot
c3fac090e7 Harden process spawning on Windows
🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-07 12:24:11 +00:00
Jarred Sumner
2daf7ed02e Add src/bun.js/bindings/v8/CLAUDE.md 2025-09-07 00:08:43 -07:00
Jarred Sumner
38e8fea828 De-slop test/bundler/compile-windows-metadata.test.ts 2025-09-06 23:05:34 -07:00
Jarred Sumner
536dc8653b Fix request body streaming in node-fetch wrapper. (#22458)
### What does this PR do?

Fix request body streaming in node-fetch wrapper.

### How did you verify your code works?

Added a test that previously failed

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-06 22:40:41 -07:00
Jarred Sumner
a705dfc63a Remove console.log in a builtin 2025-09-06 22:25:39 -07:00
Jarred Sumner
9fba9de0b5 Add a CLAUDE.md for src/js 2025-09-06 21:58:39 -07:00
robobun
6c3005e412 feat: add --workspaces support for bun run (#22415)
## Summary

This PR implements the `--workspaces` flag for the `bun run` command,
allowing scripts to be run in all workspace packages as defined in the
`"workspaces"` field in package.json.

Fixes the infinite loop issue reported in
https://github.com/threepointone/bun-workspace-bug-repro

## Changes

- Added `--workspaces` flag to run scripts in all workspace packages
- Added `--if-present` flag to gracefully skip packages without the
script
- Root package is excluded when using `--workspaces` to prevent infinite
recursion
- Added comprehensive tests for the new functionality

## Usage

```bash
# Run "test" script in all workspace packages
bun run --workspaces test

# Skip packages that don't have the script
bun run --workspaces --if-present build

# Combine with filters
bun run --filter="@scope/*" test
```

## Behavior

The `--workspaces` flag must come **before** the script name (matching
npm's behavior):
-  `bun run --workspaces test` 
-  `bun run test --workspaces` (treated as passthrough to script)

## Test Plan

- [x] Added test cases in `test/cli/run/workspaces.test.ts`
- [x] Verified fix for infinite loop issue in
https://github.com/threepointone/bun-workspace-bug-repro
- [x] Tested with `--if-present` flag
- [x] All tests pass locally

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-06 13:57:47 -07:00
robobun
40b310c208 Fix child_process stdio properties not enumerable for Object.assign() compatibility (#22322)
## Summary

Fixes compatibility issue with Node.js libraries that use
`Object.assign(promise, childProcess)` pattern, specifically `tinyspawn`
(used by `youtube-dl-exec`).

## Problem

In Node.js, child process stdio properties (`stdin`, `stdout`, `stderr`,
`stdio`) are enumerable own properties that can be copied by
`Object.assign()`. In Bun, they were non-enumerable getters on the
prototype, causing `Object.assign()` to fail copying them.

This broke libraries like:
- `tinyspawn` - uses `Object.assign(promise, childProcess)` to merge
properties
- `youtube-dl-exec` - depends on tinyspawn internally

## Solution

Make stdio properties enumerable own properties during spawn while
preserving:
-  Lazy initialization (streams created only when accessed)
-  Original getter functionality and caching
-  Performance (minimal overhead)

## Testing

- Added comprehensive regression tests
- Verified compatibility with `tinyspawn` and `youtube-dl-exec`
- Existing child_process tests still pass

## Related

- Fixes: https://github.com/microlinkhq/youtube-dl-exec/issues/246

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-06 01:40:36 -07:00
robobun
edb7214e6c feat(perf_hooks): Implement monitorEventLoopDelay() for Node.js compatibility (#22429)
## Summary
This PR implements `perf_hooks.monitorEventLoopDelay()` for Node.js
compatibility, enabling monitoring of event loop delays and collection
of performance metrics via histograms.

Fixes #17650

## Implementation Details

### JavaScript Layer (`perf_hooks.ts`)
- Added `IntervalHistogram` class with:
  - `enable()` / `disable()` methods with proper state tracking
  - `reset()` method to clear histogram data
  - Properties: `min`, `max`, `mean`, `stddev`, `exceeds`, `percentiles`
  - `percentile(p)` method with validation
- Full input validation matching Node.js behavior (TypeError vs
RangeError)

### C++ Bindings (`JSNodePerformanceHooksHistogramPrototype.cpp`)
- `jsFunction_monitorEventLoopDelay` - Creates histogram for event loop
monitoring
- `jsFunction_enableEventLoopDelay` - Enables monitoring and starts
timer
- `jsFunction_disableEventLoopDelay` - Disables monitoring and stops
timer
- `JSNodePerformanceHooksHistogram_recordDelay` - Records delay
measurements

### Zig Implementation (`EventLoopDelayMonitor.zig`)
- Embedded `EventLoopTimer` that fires periodically based on resolution
- Tracks last fire time and calculates delay between expected vs actual
- Records delays > 0 to the histogram
- Integrates seamlessly with existing Timer system

## Testing
 All tests pass:
- Custom test suite with 8 comprehensive tests
- Adapted Node.js core test for full compatibility
- Tests cover enable/disable behavior, percentiles, error handling, and
delay recording

## Test plan
- [x] Run `bun test
test/js/node/perf_hooks/test-monitorEventLoopDelay.test.js`
- [x] Run adapted Node.js test
`test/js/node/test/sequential/test-performance-eventloopdelay-adapted.test.js`
- [x] Verify proper error handling for invalid arguments
- [x] Confirm delay measurements are recorded correctly

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-09-06 00:31:32 -07:00
Ciro Spaciari
48b0b7fe6d fix(Bun.SQL) test failure (#22438)
### What does this PR do?

### How did you verify your code works?
2025-09-05 21:51:00 -07:00
Meghan Denny
e0cbef0dce Delete test/js/node/test/parallel/test-net-allow-half-open.js 2025-09-05 20:50:33 -07:00
Ciro Spaciari
14832c5547 fix(CI) update cert in harness (#22440)
### What does this PR do?
update harness.ts
### How did you verify your code works?
CI

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-09-05 20:42:25 -07:00
36 changed files with 1532 additions and 691 deletions

View File

@@ -1,506 +0,0 @@
packages/bun-usockets/src/crypto/root_certs.cpp
packages/bun-usockets/src/crypto/sni_tree.cpp
src/bake/BakeGlobalObject.cpp
src/bake/BakeProduction.cpp
src/bake/BakeSourceProvider.cpp
src/bun.js/bindings/ActiveDOMCallback.cpp
src/bun.js/bindings/AsymmetricKeyValue.cpp
src/bun.js/bindings/AsyncContextFrame.cpp
src/bun.js/bindings/Base64Helpers.cpp
src/bun.js/bindings/bindings.cpp
src/bun.js/bindings/blob.cpp
src/bun.js/bindings/bun-simdutf.cpp
src/bun.js/bindings/bun-spawn.cpp
src/bun.js/bindings/BunClientData.cpp
src/bun.js/bindings/BunCommonStrings.cpp
src/bun.js/bindings/BunDebugger.cpp
src/bun.js/bindings/BunGCOutputConstraint.cpp
src/bun.js/bindings/BunGlobalScope.cpp
src/bun.js/bindings/BunHttp2CommonStrings.cpp
src/bun.js/bindings/BunInjectedScriptHost.cpp
src/bun.js/bindings/BunInspector.cpp
src/bun.js/bindings/BunJSCEventLoop.cpp
src/bun.js/bindings/BunObject.cpp
src/bun.js/bindings/BunPlugin.cpp
src/bun.js/bindings/BunProcess.cpp
src/bun.js/bindings/BunString.cpp
src/bun.js/bindings/BunWorkerGlobalScope.cpp
src/bun.js/bindings/c-bindings.cpp
src/bun.js/bindings/CallSite.cpp
src/bun.js/bindings/CallSitePrototype.cpp
src/bun.js/bindings/CatchScopeBinding.cpp
src/bun.js/bindings/CodeCoverage.cpp
src/bun.js/bindings/ConsoleObject.cpp
src/bun.js/bindings/Cookie.cpp
src/bun.js/bindings/CookieMap.cpp
src/bun.js/bindings/coroutine.cpp
src/bun.js/bindings/CPUFeatures.cpp
src/bun.js/bindings/decodeURIComponentSIMD.cpp
src/bun.js/bindings/DOMException.cpp
src/bun.js/bindings/DOMFormData.cpp
src/bun.js/bindings/DOMURL.cpp
src/bun.js/bindings/DOMWrapperWorld.cpp
src/bun.js/bindings/DoubleFormatter.cpp
src/bun.js/bindings/EncodeURIComponent.cpp
src/bun.js/bindings/EncodingTables.cpp
src/bun.js/bindings/ErrorCode.cpp
src/bun.js/bindings/ErrorStackFrame.cpp
src/bun.js/bindings/ErrorStackTrace.cpp
src/bun.js/bindings/EventLoopTaskNoContext.cpp
src/bun.js/bindings/ExposeNodeModuleGlobals.cpp
src/bun.js/bindings/ffi.cpp
src/bun.js/bindings/helpers.cpp
src/bun.js/bindings/highway_strings.cpp
src/bun.js/bindings/HTMLEntryPoint.cpp
src/bun.js/bindings/ImportMetaObject.cpp
src/bun.js/bindings/inlines.cpp
src/bun.js/bindings/InspectorBunFrontendDevServerAgent.cpp
src/bun.js/bindings/InspectorHTTPServerAgent.cpp
src/bun.js/bindings/InspectorLifecycleAgent.cpp
src/bun.js/bindings/InspectorTestReporterAgent.cpp
src/bun.js/bindings/InternalForTesting.cpp
src/bun.js/bindings/InternalModuleRegistry.cpp
src/bun.js/bindings/IPC.cpp
src/bun.js/bindings/isBuiltinModule.cpp
src/bun.js/bindings/JS2Native.cpp
src/bun.js/bindings/JSBigIntBinding.cpp
src/bun.js/bindings/JSBuffer.cpp
src/bun.js/bindings/JSBufferEncodingType.cpp
src/bun.js/bindings/JSBufferList.cpp
src/bun.js/bindings/JSBundlerPlugin.cpp
src/bun.js/bindings/JSBunRequest.cpp
src/bun.js/bindings/JSCommonJSExtensions.cpp
src/bun.js/bindings/JSCommonJSModule.cpp
src/bun.js/bindings/JSCTaskScheduler.cpp
src/bun.js/bindings/JSCTestingHelpers.cpp
src/bun.js/bindings/JSDOMExceptionHandling.cpp
src/bun.js/bindings/JSDOMFile.cpp
src/bun.js/bindings/JSDOMGlobalObject.cpp
src/bun.js/bindings/JSDOMWrapper.cpp
src/bun.js/bindings/JSDOMWrapperCache.cpp
src/bun.js/bindings/JSEnvironmentVariableMap.cpp
src/bun.js/bindings/JSFFIFunction.cpp
src/bun.js/bindings/JSMockFunction.cpp
src/bun.js/bindings/JSNextTickQueue.cpp
src/bun.js/bindings/JSNodePerformanceHooksHistogram.cpp
src/bun.js/bindings/JSNodePerformanceHooksHistogramConstructor.cpp
src/bun.js/bindings/JSNodePerformanceHooksHistogramPrototype.cpp
src/bun.js/bindings/JSPropertyIterator.cpp
src/bun.js/bindings/JSS3File.cpp
src/bun.js/bindings/JSSecrets.cpp
src/bun.js/bindings/JSSocketAddressDTO.cpp
src/bun.js/bindings/JSStringDecoder.cpp
src/bun.js/bindings/JSWrappingFunction.cpp
src/bun.js/bindings/JSX509Certificate.cpp
src/bun.js/bindings/JSX509CertificateConstructor.cpp
src/bun.js/bindings/JSX509CertificatePrototype.cpp
src/bun.js/bindings/linux_perf_tracing.cpp
src/bun.js/bindings/MarkedArgumentBufferBinding.cpp
src/bun.js/bindings/MarkingConstraint.cpp
src/bun.js/bindings/ModuleLoader.cpp
src/bun.js/bindings/napi_external.cpp
src/bun.js/bindings/napi_finalizer.cpp
src/bun.js/bindings/napi_handle_scope.cpp
src/bun.js/bindings/napi_type_tag.cpp
src/bun.js/bindings/napi.cpp
src/bun.js/bindings/NapiClass.cpp
src/bun.js/bindings/NapiRef.cpp
src/bun.js/bindings/NapiWeakValue.cpp
src/bun.js/bindings/ncrpyto_engine.cpp
src/bun.js/bindings/ncrypto.cpp
src/bun.js/bindings/node/crypto/CryptoDhJob.cpp
src/bun.js/bindings/node/crypto/CryptoGenDhKeyPair.cpp
src/bun.js/bindings/node/crypto/CryptoGenDsaKeyPair.cpp
src/bun.js/bindings/node/crypto/CryptoGenEcKeyPair.cpp
src/bun.js/bindings/node/crypto/CryptoGenKeyPair.cpp
src/bun.js/bindings/node/crypto/CryptoGenNidKeyPair.cpp
src/bun.js/bindings/node/crypto/CryptoGenRsaKeyPair.cpp
src/bun.js/bindings/node/crypto/CryptoHkdf.cpp
src/bun.js/bindings/node/crypto/CryptoKeygen.cpp
src/bun.js/bindings/node/crypto/CryptoKeys.cpp
src/bun.js/bindings/node/crypto/CryptoPrimes.cpp
src/bun.js/bindings/node/crypto/CryptoSignJob.cpp
src/bun.js/bindings/node/crypto/CryptoUtil.cpp
src/bun.js/bindings/node/crypto/JSCipher.cpp
src/bun.js/bindings/node/crypto/JSCipherConstructor.cpp
src/bun.js/bindings/node/crypto/JSCipherPrototype.cpp
src/bun.js/bindings/node/crypto/JSDiffieHellman.cpp
src/bun.js/bindings/node/crypto/JSDiffieHellmanConstructor.cpp
src/bun.js/bindings/node/crypto/JSDiffieHellmanGroup.cpp
src/bun.js/bindings/node/crypto/JSDiffieHellmanGroupConstructor.cpp
src/bun.js/bindings/node/crypto/JSDiffieHellmanGroupPrototype.cpp
src/bun.js/bindings/node/crypto/JSDiffieHellmanPrototype.cpp
src/bun.js/bindings/node/crypto/JSECDH.cpp
src/bun.js/bindings/node/crypto/JSECDHConstructor.cpp
src/bun.js/bindings/node/crypto/JSECDHPrototype.cpp
src/bun.js/bindings/node/crypto/JSHash.cpp
src/bun.js/bindings/node/crypto/JSHmac.cpp
src/bun.js/bindings/node/crypto/JSKeyObject.cpp
src/bun.js/bindings/node/crypto/JSKeyObjectConstructor.cpp
src/bun.js/bindings/node/crypto/JSKeyObjectPrototype.cpp
src/bun.js/bindings/node/crypto/JSPrivateKeyObject.cpp
src/bun.js/bindings/node/crypto/JSPrivateKeyObjectConstructor.cpp
src/bun.js/bindings/node/crypto/JSPrivateKeyObjectPrototype.cpp
src/bun.js/bindings/node/crypto/JSPublicKeyObject.cpp
src/bun.js/bindings/node/crypto/JSPublicKeyObjectConstructor.cpp
src/bun.js/bindings/node/crypto/JSPublicKeyObjectPrototype.cpp
src/bun.js/bindings/node/crypto/JSSecretKeyObject.cpp
src/bun.js/bindings/node/crypto/JSSecretKeyObjectConstructor.cpp
src/bun.js/bindings/node/crypto/JSSecretKeyObjectPrototype.cpp
src/bun.js/bindings/node/crypto/JSSign.cpp
src/bun.js/bindings/node/crypto/JSVerify.cpp
src/bun.js/bindings/node/crypto/KeyObject.cpp
src/bun.js/bindings/node/crypto/node_crypto_binding.cpp
src/bun.js/bindings/node/http/JSConnectionsList.cpp
src/bun.js/bindings/node/http/JSConnectionsListConstructor.cpp
src/bun.js/bindings/node/http/JSConnectionsListPrototype.cpp
src/bun.js/bindings/node/http/JSHTTPParser.cpp
src/bun.js/bindings/node/http/JSHTTPParserConstructor.cpp
src/bun.js/bindings/node/http/JSHTTPParserPrototype.cpp
src/bun.js/bindings/node/http/NodeHTTPParser.cpp
src/bun.js/bindings/node/NodeTimers.cpp
src/bun.js/bindings/NodeAsyncHooks.cpp
src/bun.js/bindings/NodeDirent.cpp
src/bun.js/bindings/NodeFetch.cpp
src/bun.js/bindings/NodeFSStatBinding.cpp
src/bun.js/bindings/NodeFSStatFSBinding.cpp
src/bun.js/bindings/NodeHTTP.cpp
src/bun.js/bindings/NodeTimerObject.cpp
src/bun.js/bindings/NodeTLS.cpp
src/bun.js/bindings/NodeURL.cpp
src/bun.js/bindings/NodeValidator.cpp
src/bun.js/bindings/NodeVM.cpp
src/bun.js/bindings/NodeVMModule.cpp
src/bun.js/bindings/NodeVMScript.cpp
src/bun.js/bindings/NodeVMSourceTextModule.cpp
src/bun.js/bindings/NodeVMSyntheticModule.cpp
src/bun.js/bindings/NoOpForTesting.cpp
src/bun.js/bindings/ObjectBindings.cpp
src/bun.js/bindings/objects.cpp
src/bun.js/bindings/OsBinding.cpp
src/bun.js/bindings/Path.cpp
src/bun.js/bindings/ProcessBindingBuffer.cpp
src/bun.js/bindings/ProcessBindingConstants.cpp
src/bun.js/bindings/ProcessBindingFs.cpp
src/bun.js/bindings/ProcessBindingHTTPParser.cpp
src/bun.js/bindings/ProcessBindingNatives.cpp
src/bun.js/bindings/ProcessBindingTTYWrap.cpp
src/bun.js/bindings/ProcessBindingUV.cpp
src/bun.js/bindings/ProcessIdentifier.cpp
src/bun.js/bindings/RegularExpression.cpp
src/bun.js/bindings/S3Error.cpp
src/bun.js/bindings/ScriptExecutionContext.cpp
src/bun.js/bindings/SecretsDarwin.cpp
src/bun.js/bindings/SecretsLinux.cpp
src/bun.js/bindings/SecretsWindows.cpp
src/bun.js/bindings/Serialization.cpp
src/bun.js/bindings/ServerRouteList.cpp
src/bun.js/bindings/spawn.cpp
src/bun.js/bindings/SQLClient.cpp
src/bun.js/bindings/sqlite/JSSQLStatement.cpp
src/bun.js/bindings/StringBuilderBinding.cpp
src/bun.js/bindings/stripANSI.cpp
src/bun.js/bindings/Strong.cpp
src/bun.js/bindings/TextCodec.cpp
src/bun.js/bindings/TextCodecCJK.cpp
src/bun.js/bindings/TextCodecReplacement.cpp
src/bun.js/bindings/TextCodecSingleByte.cpp
src/bun.js/bindings/TextCodecUserDefined.cpp
src/bun.js/bindings/TextCodecWrapper.cpp
src/bun.js/bindings/TextEncoding.cpp
src/bun.js/bindings/TextEncodingRegistry.cpp
src/bun.js/bindings/Uint8Array.cpp
src/bun.js/bindings/Undici.cpp
src/bun.js/bindings/URLDecomposition.cpp
src/bun.js/bindings/URLSearchParams.cpp
src/bun.js/bindings/UtilInspect.cpp
src/bun.js/bindings/v8/node.cpp
src/bun.js/bindings/v8/shim/Function.cpp
src/bun.js/bindings/v8/shim/FunctionTemplate.cpp
src/bun.js/bindings/v8/shim/GlobalInternals.cpp
src/bun.js/bindings/v8/shim/Handle.cpp
src/bun.js/bindings/v8/shim/HandleScopeBuffer.cpp
src/bun.js/bindings/v8/shim/InternalFieldObject.cpp
src/bun.js/bindings/v8/shim/Map.cpp
src/bun.js/bindings/v8/shim/ObjectTemplate.cpp
src/bun.js/bindings/v8/shim/Oddball.cpp
src/bun.js/bindings/v8/shim/TaggedPointer.cpp
src/bun.js/bindings/v8/v8_api_internal.cpp
src/bun.js/bindings/v8/v8_internal.cpp
src/bun.js/bindings/v8/V8Array.cpp
src/bun.js/bindings/v8/V8Boolean.cpp
src/bun.js/bindings/v8/V8Context.cpp
src/bun.js/bindings/v8/V8EscapableHandleScope.cpp
src/bun.js/bindings/v8/V8EscapableHandleScopeBase.cpp
src/bun.js/bindings/v8/V8External.cpp
src/bun.js/bindings/v8/V8Function.cpp
src/bun.js/bindings/v8/V8FunctionCallbackInfo.cpp
src/bun.js/bindings/v8/V8FunctionTemplate.cpp
src/bun.js/bindings/v8/V8HandleScope.cpp
src/bun.js/bindings/v8/V8Isolate.cpp
src/bun.js/bindings/v8/V8Local.cpp
src/bun.js/bindings/v8/V8Maybe.cpp
src/bun.js/bindings/v8/V8Number.cpp
src/bun.js/bindings/v8/V8Object.cpp
src/bun.js/bindings/v8/V8ObjectTemplate.cpp
src/bun.js/bindings/v8/V8String.cpp
src/bun.js/bindings/v8/V8Template.cpp
src/bun.js/bindings/v8/V8Value.cpp
src/bun.js/bindings/Weak.cpp
src/bun.js/bindings/webcore/AbortController.cpp
src/bun.js/bindings/webcore/AbortSignal.cpp
src/bun.js/bindings/webcore/ActiveDOMObject.cpp
src/bun.js/bindings/webcore/BroadcastChannel.cpp
src/bun.js/bindings/webcore/BunBroadcastChannelRegistry.cpp
src/bun.js/bindings/webcore/CloseEvent.cpp
src/bun.js/bindings/webcore/CommonAtomStrings.cpp
src/bun.js/bindings/webcore/ContextDestructionObserver.cpp
src/bun.js/bindings/webcore/CustomEvent.cpp
src/bun.js/bindings/webcore/CustomEventCustom.cpp
src/bun.js/bindings/webcore/DOMJITHelpers.cpp
src/bun.js/bindings/webcore/ErrorCallback.cpp
src/bun.js/bindings/webcore/ErrorEvent.cpp
src/bun.js/bindings/webcore/Event.cpp
src/bun.js/bindings/webcore/EventContext.cpp
src/bun.js/bindings/webcore/EventDispatcher.cpp
src/bun.js/bindings/webcore/EventEmitter.cpp
src/bun.js/bindings/webcore/EventFactory.cpp
src/bun.js/bindings/webcore/EventListenerMap.cpp
src/bun.js/bindings/webcore/EventNames.cpp
src/bun.js/bindings/webcore/EventPath.cpp
src/bun.js/bindings/webcore/EventTarget.cpp
src/bun.js/bindings/webcore/EventTargetConcrete.cpp
src/bun.js/bindings/webcore/EventTargetFactory.cpp
src/bun.js/bindings/webcore/FetchHeaders.cpp
src/bun.js/bindings/webcore/HeaderFieldTokenizer.cpp
src/bun.js/bindings/webcore/HTTPHeaderField.cpp
src/bun.js/bindings/webcore/HTTPHeaderIdentifiers.cpp
src/bun.js/bindings/webcore/HTTPHeaderMap.cpp
src/bun.js/bindings/webcore/HTTPHeaderNames.cpp
src/bun.js/bindings/webcore/HTTPHeaderStrings.cpp
src/bun.js/bindings/webcore/HTTPHeaderValues.cpp
src/bun.js/bindings/webcore/HTTPParsers.cpp
src/bun.js/bindings/webcore/IdentifierEventListenerMap.cpp
src/bun.js/bindings/webcore/InternalWritableStream.cpp
src/bun.js/bindings/webcore/JSAbortAlgorithm.cpp
src/bun.js/bindings/webcore/JSAbortController.cpp
src/bun.js/bindings/webcore/JSAbortSignal.cpp
src/bun.js/bindings/webcore/JSAbortSignalCustom.cpp
src/bun.js/bindings/webcore/JSAddEventListenerOptions.cpp
src/bun.js/bindings/webcore/JSBroadcastChannel.cpp
src/bun.js/bindings/webcore/JSByteLengthQueuingStrategy.cpp
src/bun.js/bindings/webcore/JSCallbackData.cpp
src/bun.js/bindings/webcore/JSCloseEvent.cpp
src/bun.js/bindings/webcore/JSCookie.cpp
src/bun.js/bindings/webcore/JSCookieMap.cpp
src/bun.js/bindings/webcore/JSCountQueuingStrategy.cpp
src/bun.js/bindings/webcore/JSCustomEvent.cpp
src/bun.js/bindings/webcore/JSDOMBindingInternalsBuiltins.cpp
src/bun.js/bindings/webcore/JSDOMBuiltinConstructorBase.cpp
src/bun.js/bindings/webcore/JSDOMConstructorBase.cpp
src/bun.js/bindings/webcore/JSDOMConvertDate.cpp
src/bun.js/bindings/webcore/JSDOMConvertNumbers.cpp
src/bun.js/bindings/webcore/JSDOMConvertStrings.cpp
src/bun.js/bindings/webcore/JSDOMConvertWebGL.cpp
src/bun.js/bindings/webcore/JSDOMException.cpp
src/bun.js/bindings/webcore/JSDOMFormData.cpp
src/bun.js/bindings/webcore/JSDOMGuardedObject.cpp
src/bun.js/bindings/webcore/JSDOMIterator.cpp
src/bun.js/bindings/webcore/JSDOMOperation.cpp
src/bun.js/bindings/webcore/JSDOMPromise.cpp
src/bun.js/bindings/webcore/JSDOMPromiseDeferred.cpp
src/bun.js/bindings/webcore/JSDOMURL.cpp
src/bun.js/bindings/webcore/JSErrorCallback.cpp
src/bun.js/bindings/webcore/JSErrorEvent.cpp
src/bun.js/bindings/webcore/JSErrorEventCustom.cpp
src/bun.js/bindings/webcore/JSErrorHandler.cpp
src/bun.js/bindings/webcore/JSEvent.cpp
src/bun.js/bindings/webcore/JSEventCustom.cpp
src/bun.js/bindings/webcore/JSEventDOMJIT.cpp
src/bun.js/bindings/webcore/JSEventEmitter.cpp
src/bun.js/bindings/webcore/JSEventEmitterCustom.cpp
src/bun.js/bindings/webcore/JSEventInit.cpp
src/bun.js/bindings/webcore/JSEventListener.cpp
src/bun.js/bindings/webcore/JSEventListenerOptions.cpp
src/bun.js/bindings/webcore/JSEventModifierInit.cpp
src/bun.js/bindings/webcore/JSEventTarget.cpp
src/bun.js/bindings/webcore/JSEventTargetCustom.cpp
src/bun.js/bindings/webcore/JSEventTargetNode.cpp
src/bun.js/bindings/webcore/JSFetchHeaders.cpp
src/bun.js/bindings/webcore/JSMessageChannel.cpp
src/bun.js/bindings/webcore/JSMessageChannelCustom.cpp
src/bun.js/bindings/webcore/JSMessageEvent.cpp
src/bun.js/bindings/webcore/JSMessageEventCustom.cpp
src/bun.js/bindings/webcore/JSMessagePort.cpp
src/bun.js/bindings/webcore/JSMessagePortCustom.cpp
src/bun.js/bindings/webcore/JSMIMEBindings.cpp
src/bun.js/bindings/webcore/JSMIMEParams.cpp
src/bun.js/bindings/webcore/JSMIMEType.cpp
src/bun.js/bindings/webcore/JSPerformance.cpp
src/bun.js/bindings/webcore/JSPerformanceEntry.cpp
src/bun.js/bindings/webcore/JSPerformanceEntryCustom.cpp
src/bun.js/bindings/webcore/JSPerformanceMark.cpp
src/bun.js/bindings/webcore/JSPerformanceMarkOptions.cpp
src/bun.js/bindings/webcore/JSPerformanceMeasure.cpp
src/bun.js/bindings/webcore/JSPerformanceMeasureOptions.cpp
src/bun.js/bindings/webcore/JSPerformanceObserver.cpp
src/bun.js/bindings/webcore/JSPerformanceObserverCallback.cpp
src/bun.js/bindings/webcore/JSPerformanceObserverCustom.cpp
src/bun.js/bindings/webcore/JSPerformanceObserverEntryList.cpp
src/bun.js/bindings/webcore/JSPerformanceResourceTiming.cpp
src/bun.js/bindings/webcore/JSPerformanceServerTiming.cpp
src/bun.js/bindings/webcore/JSPerformanceTiming.cpp
src/bun.js/bindings/webcore/JSReadableByteStreamController.cpp
src/bun.js/bindings/webcore/JSReadableStream.cpp
src/bun.js/bindings/webcore/JSReadableStreamBYOBReader.cpp
src/bun.js/bindings/webcore/JSReadableStreamBYOBRequest.cpp
src/bun.js/bindings/webcore/JSReadableStreamDefaultController.cpp
src/bun.js/bindings/webcore/JSReadableStreamDefaultReader.cpp
src/bun.js/bindings/webcore/JSReadableStreamSink.cpp
src/bun.js/bindings/webcore/JSReadableStreamSource.cpp
src/bun.js/bindings/webcore/JSReadableStreamSourceCustom.cpp
src/bun.js/bindings/webcore/JSStructuredSerializeOptions.cpp
src/bun.js/bindings/webcore/JSTextDecoderStream.cpp
src/bun.js/bindings/webcore/JSTextEncoder.cpp
src/bun.js/bindings/webcore/JSTextEncoderStream.cpp
src/bun.js/bindings/webcore/JSTransformStream.cpp
src/bun.js/bindings/webcore/JSTransformStreamDefaultController.cpp
src/bun.js/bindings/webcore/JSURLSearchParams.cpp
src/bun.js/bindings/webcore/JSWasmStreamingCompiler.cpp
src/bun.js/bindings/webcore/JSWebSocket.cpp
src/bun.js/bindings/webcore/JSWorker.cpp
src/bun.js/bindings/webcore/JSWorkerOptions.cpp
src/bun.js/bindings/webcore/JSWritableStream.cpp
src/bun.js/bindings/webcore/JSWritableStreamDefaultController.cpp
src/bun.js/bindings/webcore/JSWritableStreamDefaultWriter.cpp
src/bun.js/bindings/webcore/JSWritableStreamSink.cpp
src/bun.js/bindings/webcore/MessageChannel.cpp
src/bun.js/bindings/webcore/MessageEvent.cpp
src/bun.js/bindings/webcore/MessagePort.cpp
src/bun.js/bindings/webcore/MessagePortChannel.cpp
src/bun.js/bindings/webcore/MessagePortChannelProvider.cpp
src/bun.js/bindings/webcore/MessagePortChannelProviderImpl.cpp
src/bun.js/bindings/webcore/MessagePortChannelRegistry.cpp
src/bun.js/bindings/webcore/NetworkLoadMetrics.cpp
src/bun.js/bindings/webcore/Performance.cpp
src/bun.js/bindings/webcore/PerformanceEntry.cpp
src/bun.js/bindings/webcore/PerformanceMark.cpp
src/bun.js/bindings/webcore/PerformanceMeasure.cpp
src/bun.js/bindings/webcore/PerformanceObserver.cpp
src/bun.js/bindings/webcore/PerformanceObserverEntryList.cpp
src/bun.js/bindings/webcore/PerformanceResourceTiming.cpp
src/bun.js/bindings/webcore/PerformanceServerTiming.cpp
src/bun.js/bindings/webcore/PerformanceTiming.cpp
src/bun.js/bindings/webcore/PerformanceUserTiming.cpp
src/bun.js/bindings/webcore/ReadableStream.cpp
src/bun.js/bindings/webcore/ReadableStreamDefaultController.cpp
src/bun.js/bindings/webcore/ReadableStreamSink.cpp
src/bun.js/bindings/webcore/ReadableStreamSource.cpp
src/bun.js/bindings/webcore/ResourceTiming.cpp
src/bun.js/bindings/webcore/RFC7230.cpp
src/bun.js/bindings/webcore/SerializedScriptValue.cpp
src/bun.js/bindings/webcore/ServerTiming.cpp
src/bun.js/bindings/webcore/ServerTimingParser.cpp
src/bun.js/bindings/webcore/StructuredClone.cpp
src/bun.js/bindings/webcore/TextEncoder.cpp
src/bun.js/bindings/webcore/WebCoreTypedArrayController.cpp
src/bun.js/bindings/webcore/WebSocket.cpp
src/bun.js/bindings/webcore/Worker.cpp
src/bun.js/bindings/webcore/WritableStream.cpp
src/bun.js/bindings/webcrypto/CommonCryptoDERUtilities.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithm.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmAES_CBC.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmAES_CBCOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmAES_CFB.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmAES_CFBOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmAES_CTR.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmAES_CTROpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmAES_GCM.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmAES_GCMOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmAES_KW.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmAES_KWOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmECDH.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmECDHOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmECDSA.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmECDSAOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmEd25519.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmHKDF.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmHKDFOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmHMAC.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmHMACOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmPBKDF2.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmPBKDF2OpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistry.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmRegistryOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_OAEP.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_OAEPOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSS.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmRSA_PSSOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmRSAES_PKCS1_v1_5.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmRSAES_PKCS1_v1_5OpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmRSASSA_PKCS1_v1_5OpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA1.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA224.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA256.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA384.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmSHA512.cpp
src/bun.js/bindings/webcrypto/CryptoAlgorithmX25519.cpp
src/bun.js/bindings/webcrypto/CryptoDigest.cpp
src/bun.js/bindings/webcrypto/CryptoKey.cpp
src/bun.js/bindings/webcrypto/CryptoKeyAES.cpp
src/bun.js/bindings/webcrypto/CryptoKeyEC.cpp
src/bun.js/bindings/webcrypto/CryptoKeyECOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoKeyHMAC.cpp
src/bun.js/bindings/webcrypto/CryptoKeyOKP.cpp
src/bun.js/bindings/webcrypto/CryptoKeyOKPOpenSSL.cpp
src/bun.js/bindings/webcrypto/CryptoKeyRaw.cpp
src/bun.js/bindings/webcrypto/CryptoKeyRSA.cpp
src/bun.js/bindings/webcrypto/CryptoKeyRSAComponents.cpp
src/bun.js/bindings/webcrypto/CryptoKeyRSAOpenSSL.cpp
src/bun.js/bindings/webcrypto/JSAesCbcCfbParams.cpp
src/bun.js/bindings/webcrypto/JSAesCtrParams.cpp
src/bun.js/bindings/webcrypto/JSAesGcmParams.cpp
src/bun.js/bindings/webcrypto/JSAesKeyParams.cpp
src/bun.js/bindings/webcrypto/JSCryptoAesKeyAlgorithm.cpp
src/bun.js/bindings/webcrypto/JSCryptoAlgorithmParameters.cpp
src/bun.js/bindings/webcrypto/JSCryptoEcKeyAlgorithm.cpp
src/bun.js/bindings/webcrypto/JSCryptoHmacKeyAlgorithm.cpp
src/bun.js/bindings/webcrypto/JSCryptoKey.cpp
src/bun.js/bindings/webcrypto/JSCryptoKeyAlgorithm.cpp
src/bun.js/bindings/webcrypto/JSCryptoKeyPair.cpp
src/bun.js/bindings/webcrypto/JSCryptoKeyUsage.cpp
src/bun.js/bindings/webcrypto/JSCryptoRsaHashedKeyAlgorithm.cpp
src/bun.js/bindings/webcrypto/JSCryptoRsaKeyAlgorithm.cpp
src/bun.js/bindings/webcrypto/JSEcdhKeyDeriveParams.cpp
src/bun.js/bindings/webcrypto/JSEcdsaParams.cpp
src/bun.js/bindings/webcrypto/JSEcKeyParams.cpp
src/bun.js/bindings/webcrypto/JSHkdfParams.cpp
src/bun.js/bindings/webcrypto/JSHmacKeyParams.cpp
src/bun.js/bindings/webcrypto/JSJsonWebKey.cpp
src/bun.js/bindings/webcrypto/JSPbkdf2Params.cpp
src/bun.js/bindings/webcrypto/JSRsaHashedImportParams.cpp
src/bun.js/bindings/webcrypto/JSRsaHashedKeyGenParams.cpp
src/bun.js/bindings/webcrypto/JSRsaKeyGenParams.cpp
src/bun.js/bindings/webcrypto/JSRsaOaepParams.cpp
src/bun.js/bindings/webcrypto/JSRsaOtherPrimesInfo.cpp
src/bun.js/bindings/webcrypto/JSRsaPssParams.cpp
src/bun.js/bindings/webcrypto/JSSubtleCrypto.cpp
src/bun.js/bindings/webcrypto/JSX25519Params.cpp
src/bun.js/bindings/webcrypto/OpenSSLUtilities.cpp
src/bun.js/bindings/webcrypto/PhonyWorkQueue.cpp
src/bun.js/bindings/webcrypto/SerializedCryptoKeyWrapOpenSSL.cpp
src/bun.js/bindings/webcrypto/SubtleCrypto.cpp
src/bun.js/bindings/workaround-missing-symbols.cpp
src/bun.js/bindings/wtf-bindings.cpp
src/bun.js/bindings/ZigGeneratedCode.cpp
src/bun.js/bindings/ZigGlobalObject.cpp
src/bun.js/bindings/ZigSourceProvider.cpp
src/bun.js/modules/NodeModuleModule.cpp
src/bun.js/modules/NodeTTYModule.cpp
src/bun.js/modules/NodeUtilTypesModule.cpp
src/bun.js/modules/ObjectModule.cpp
src/deps/libuwsockets.cpp
src/io/io_darwin.cpp
src/vm/Semaphore.cpp
src/vm/SigintWatcher.cpp

View File

@@ -31,6 +31,9 @@ pub const All = struct {
immediate_ref_count: i32 = 0,
uv_idle: if (Environment.isWindows) uv.uv_idle_t else void = if (Environment.isWindows) std.mem.zeroes(uv.uv_idle_t),
// Event loop delay monitoring (not exposed to JS)
event_loop_delay: EventLoopDelayMonitor = .{},
// We split up the map here to avoid storing an extra "repeat" boolean
maps: struct {
setTimeout: TimeoutMap = .{},
@@ -597,6 +600,8 @@ pub const WTFTimer = @import("./Timer/WTFTimer.zig");
pub const DateHeaderTimer = @import("./Timer/DateHeaderTimer.zig");
pub const EventLoopDelayMonitor = @import("./Timer/EventLoopDelayMonitor.zig");
pub const internal_bindings = struct {
/// Node.js has some tests that check whether timers fire at the right time. They check this
/// with the internal binding `getLibuvNow()`, which returns an integer in milliseconds. This

View File

@@ -0,0 +1,83 @@
const EventLoopDelayMonitor = @This();
/// We currently only globally share the same instance, which is kept alive by
/// the existence of the src/js/internal/perf_hooks/monitorEventLoopDelay.ts
/// function's scope.
///
/// I don't think having a single event loop delay monitor histogram instance
/// /will cause any issues? Let's find out.
js_histogram: jsc.JSValue = jsc.JSValue.zero,
event_loop_timer: jsc.API.Timer.EventLoopTimer = .{
.next = .epoch,
.tag = .EventLoopDelayMonitor,
},
resolution_ms: i32 = 10,
last_fire_ns: u64 = 0,
enabled: bool = false,
pub fn enable(this: *EventLoopDelayMonitor, vm: *VirtualMachine, histogram: jsc.JSValue, resolution_ms: i32) void {
if (this.enabled) return;
this.js_histogram = histogram;
this.resolution_ms = resolution_ms;
this.enabled = true;
// Schedule timer
const now = bun.timespec.now();
this.event_loop_timer.next = now.addMs(@intCast(resolution_ms));
vm.timer.insert(&this.event_loop_timer);
}
pub fn disable(this: *EventLoopDelayMonitor, vm: *VirtualMachine) void {
if (!this.enabled) return;
this.enabled = false;
this.js_histogram = jsc.JSValue.zero;
this.last_fire_ns = 0;
vm.timer.remove(&this.event_loop_timer);
}
pub fn isEnabled(this: *const EventLoopDelayMonitor) bool {
return this.enabled and this.js_histogram != jsc.JSValue.zero;
}
pub fn onFire(this: *EventLoopDelayMonitor, vm: *VirtualMachine, now: *const bun.timespec) void {
if (!this.enabled or this.js_histogram == jsc.JSValue.zero) {
return;
}
const now_ns = now.ns();
if (this.last_fire_ns > 0) {
const expected_ns = @as(u64, @intCast(this.resolution_ms)) *| 1_000_000;
const actual_ns = now_ns - this.last_fire_ns;
if (actual_ns > expected_ns) {
const delay_ns = @as(i64, @intCast(actual_ns -| expected_ns));
JSNodePerformanceHooksHistogram_recordDelay(this.js_histogram, delay_ns);
}
}
this.last_fire_ns = now_ns;
// Reschedule
this.event_loop_timer.next = now.addMs(@intCast(this.resolution_ms));
vm.timer.insert(&this.event_loop_timer);
}
// Record delay to histogram
extern fn JSNodePerformanceHooksHistogram_recordDelay(histogram: jsc.JSValue, delay_ns: i64) void;
// Export functions for C++
export fn Timer_enableEventLoopDelayMonitoring(vm: *VirtualMachine, histogram: jsc.JSValue, resolution_ms: i32) void {
vm.timer.event_loop_delay.enable(vm, histogram, resolution_ms);
}
export fn Timer_disableEventLoopDelayMonitoring(vm: *VirtualMachine) void {
vm.timer.event_loop_delay.disable(vm);
}
const bun = @import("bun");
const jsc = bun.jsc;
const VirtualMachine = jsc.VirtualMachine;

View File

@@ -68,6 +68,7 @@ pub const Tag = if (Environment.isWindows) enum {
DevServerMemoryVisualizerTick,
AbortSignalTimeout,
DateHeaderTimer,
EventLoopDelayMonitor,
pub fn Type(comptime T: Tag) type {
return switch (T) {
@@ -92,6 +93,7 @@ pub const Tag = if (Environment.isWindows) enum {
=> bun.bake.DevServer,
.AbortSignalTimeout => jsc.WebCore.AbortSignal.Timeout,
.DateHeaderTimer => jsc.API.Timer.DateHeaderTimer,
.EventLoopDelayMonitor => jsc.API.Timer.EventLoopDelayMonitor,
};
}
} else enum {
@@ -114,6 +116,7 @@ pub const Tag = if (Environment.isWindows) enum {
DevServerMemoryVisualizerTick,
AbortSignalTimeout,
DateHeaderTimer,
EventLoopDelayMonitor,
pub fn Type(comptime T: Tag) type {
return switch (T) {
@@ -137,6 +140,7 @@ pub const Tag = if (Environment.isWindows) enum {
=> bun.bake.DevServer,
.AbortSignalTimeout => jsc.WebCore.AbortSignal.Timeout,
.DateHeaderTimer => jsc.API.Timer.DateHeaderTimer,
.EventLoopDelayMonitor => jsc.API.Timer.EventLoopDelayMonitor,
};
}
};
@@ -213,6 +217,11 @@ pub fn fire(self: *Self, now: *const timespec, vm: *VirtualMachine) Arm {
date_header_timer.run(vm);
return .disarm;
},
.EventLoopDelayMonitor => {
const monitor = @as(*jsc.API.Timer.EventLoopDelayMonitor, @fieldParentPtr("event_loop_timer", self));
monitor.onFire(vm, now);
return .disarm;
},
inline else => |t| {
if (@FieldType(t.Type(), "event_loop_timer") != Self) {
@compileError(@typeName(t.Type()) ++ " has wrong type for 'event_loop_timer'");

View File

@@ -1066,6 +1066,7 @@ pub const WindowsSpawnOptions = struct {
verbatim_arguments: bool = false,
hide_window: bool = true,
loop: jsc.EventLoopHandle = undefined,
shell_enabled: bool = false,
};
pub const Stdio = union(enum) {
@@ -1528,6 +1529,39 @@ pub fn spawnProcessPosix(
unreachable;
}
fn isWindowsBatchFile(path: []const u8) bool {
if (path.len < 4) return false;
// Find the last dot to get the extension
var i = path.len;
while (i > 0) : (i -= 1) {
if (path[i - 1] == '.') break;
}
if (i == 0) return false;
const ext = path[i - 1 ..];
// Check for .bat or .cmd extensions (case-insensitive)
if (ext.len == 4) {
// .bat
if ((ext[1] == 'b' or ext[1] == 'B') and
(ext[2] == 'a' or ext[2] == 'A') and
(ext[3] == 't' or ext[3] == 'T'))
{
return true;
}
// .cmd
if ((ext[1] == 'c' or ext[1] == 'C') and
(ext[2] == 'm' or ext[2] == 'M') and
(ext[3] == 'd' or ext[3] == 'D'))
{
return true;
}
}
return false;
}
pub fn spawnProcessWindows(
options: *const WindowsSpawnOptions,
argv: [*:null]?[*:0]const u8,
@@ -1541,6 +1575,13 @@ pub fn spawnProcessWindows(
uv_process_options.args = argv;
uv_process_options.env = envp;
uv_process_options.file = options.argv0 orelse argv[0].?;
// Security check: prevent direct execution of batch files without shell
// This prevents command injection vulnerabilities (similar to CVE-2024-27980)
const file_path = std.mem.sliceTo(uv_process_options.file, 0);
if (isWindowsBatchFile(file_path) and !options.windows.shell_enabled) {
return .{ .err = bun.sys.Error.fromCode(.INVAL, .uv_spawn) };
}
uv_process_options.exit_cb = &Process.onExitUV;
var stack_allocator = std.heap.stackFallback(8192, bun.default_allocator);
const allocator = stack_allocator.get();

View File

@@ -1028,6 +1028,7 @@ pub fn spawnMaybeSync(
var windows_hide: bool = false;
var windows_verbatim_arguments: bool = false;
var windows_shell_enabled: bool = false;
var abort_signal: ?*jsc.WebCore.AbortSignal = null;
defer {
// Ensure we clean it up on error.
@@ -1214,6 +1215,12 @@ pub fn spawnMaybeSync(
windows_verbatim_arguments = val.asBoolean();
}
}
if (try args.get(globalThis, "windowsShellEnabled")) |val| {
if (val.isBoolean()) {
windows_shell_enabled = val.asBoolean();
}
}
}
if (try args.get(globalThis, "timeout")) |timeout_value| brk: {
@@ -1376,6 +1383,7 @@ pub fn spawnMaybeSync(
.windows = if (Environment.isWindows) .{
.hide_window = windows_hide,
.verbatim_arguments = windows_verbatim_arguments,
.shell_enabled = windows_shell_enabled,
.loop = jsc.EventLoopHandle.init(jsc_vm),
},
};

View File

@@ -48,6 +48,9 @@ JSC_DECLARE_HOST_FUNCTION(jsNodePerformanceHooksHistogramProtoFuncPercentile);
JSC_DECLARE_HOST_FUNCTION(jsNodePerformanceHooksHistogramProtoFuncPercentileBigInt);
JSC_DECLARE_HOST_FUNCTION(jsFunction_createHistogram);
JSC_DECLARE_HOST_FUNCTION(jsFunction_monitorEventLoopDelay);
JSC_DECLARE_HOST_FUNCTION(jsFunction_enableEventLoopDelay);
JSC_DECLARE_HOST_FUNCTION(jsFunction_disableEventLoopDelay);
class HistogramData {
public:

View File

@@ -1,5 +1,6 @@
#include "ErrorCode.h"
#include "JSDOMExceptionHandling.h"
#include "NodeValidator.h"
#include "root.h"
#include "JSNodePerformanceHooksHistogramPrototype.h"
@@ -140,6 +141,20 @@ JSC_DEFINE_HOST_FUNCTION(jsNodePerformanceHooksHistogramProtoFuncReset, (JSGloba
return JSValue::encode(jsUndefined());
}
static double toPercentile(JSC::ThrowScope& scope, JSGlobalObject* globalObject, JSValue value)
{
Bun::V::validateNumber(scope, globalObject, value, "percentile"_s, jsNumber(0), jsNumber(100));
RETURN_IF_EXCEPTION(scope, {});
// TODO: rewrite validateNumber to return the validated value.
double percentile = value.toNumber(globalObject);
scope.assertNoException();
if (percentile <= 0 || percentile > 100 || std::isnan(percentile)) {
Bun::ERR::OUT_OF_RANGE(scope, globalObject, "percentile"_s, "> 0 && <= 100"_s, value);
return {};
}
return percentile;
}
JSC_DEFINE_HOST_FUNCTION(jsNodePerformanceHooksHistogramProtoFuncPercentile, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
@@ -156,12 +171,8 @@ JSC_DEFINE_HOST_FUNCTION(jsNodePerformanceHooksHistogramProtoFuncPercentile, (JS
return {};
}
double percentile = callFrame->uncheckedArgument(0).toNumber(globalObject);
double percentile = toPercentile(scope, globalObject, callFrame->uncheckedArgument(0));
RETURN_IF_EXCEPTION(scope, {});
if (percentile <= 0 || percentile > 100 || std::isnan(percentile)) {
Bun::ERR::OUT_OF_RANGE(scope, globalObject, "percentile"_s, "> 0 && <= 100"_s, jsNumber(percentile));
return {};
}
return JSValue::encode(jsNumber(static_cast<double>(thisObject->getPercentile(percentile))));
}
@@ -182,12 +193,8 @@ JSC_DEFINE_HOST_FUNCTION(jsNodePerformanceHooksHistogramProtoFuncPercentileBigIn
return {};
}
double percentile = callFrame->uncheckedArgument(0).toNumber(globalObject);
double percentile = toPercentile(scope, globalObject, callFrame->uncheckedArgument(0));
RETURN_IF_EXCEPTION(scope, {});
if (percentile <= 0 || percentile > 100 || std::isnan(percentile)) {
Bun::ERR::OUT_OF_RANGE(scope, globalObject, "percentile"_s, "> 0 && <= 100"_s, jsNumber(percentile));
return {};
}
RELEASE_AND_RETURN(scope, JSValue::encode(JSBigInt::createFrom(globalObject, thisObject->getPercentile(percentile))));
}
@@ -415,4 +422,107 @@ JSC_DEFINE_HOST_FUNCTION(jsFunction_createHistogram, (JSGlobalObject * globalObj
return JSValue::encode(histogram);
}
// Extern declarations for Timer.zig
extern "C" void Timer_enableEventLoopDelayMonitoring(void* vm, JSC::EncodedJSValue histogram, int32_t resolution);
extern "C" void Timer_disableEventLoopDelayMonitoring(void* vm);
// Create histogram for event loop delay monitoring
JSC_DEFINE_HOST_FUNCTION(jsFunction_monitorEventLoopDelay, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
int32_t resolution = 10; // default 10ms
if (callFrame->argumentCount() > 0) {
resolution = callFrame->argument(0).toInt32(globalObject);
RETURN_IF_EXCEPTION(scope, {});
if (resolution < 1) {
throwRangeError(globalObject, scope, "Resolution must be >= 1"_s);
return JSValue::encode(jsUndefined());
}
}
// Create histogram with range for event loop delays (1ns to 1 hour)
auto* zigGlobalObject = defaultGlobalObject(globalObject);
Structure* structure = zigGlobalObject->m_JSNodePerformanceHooksHistogramClassStructure.get(zigGlobalObject);
RETURN_IF_EXCEPTION(scope, {});
JSNodePerformanceHooksHistogram* histogram = JSNodePerformanceHooksHistogram::create(
vm, structure, globalObject,
1, // lowest: 1 nanosecond
3600000000000LL, // highest: 1 hour in nanoseconds
3 // figures: 3 significant digits
);
RETURN_IF_EXCEPTION(scope, {});
return JSValue::encode(histogram);
}
// Enable event loop delay monitoring
JSC_DEFINE_HOST_FUNCTION(jsFunction_enableEventLoopDelay, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (callFrame->argumentCount() < 2) {
throwTypeError(globalObject, scope, "Missing arguments"_s);
return JSValue::encode(jsUndefined());
}
JSValue histogramValue = callFrame->argument(0);
JSNodePerformanceHooksHistogram* histogram = jsDynamicCast<JSNodePerformanceHooksHistogram*>(histogramValue);
if (!histogram) {
throwTypeError(globalObject, scope, "Invalid histogram"_s);
return JSValue::encode(jsUndefined());
}
int32_t resolution = callFrame->argument(1).toInt32(globalObject);
RETURN_IF_EXCEPTION(scope, {});
// Reset histogram data on enable
histogram->reset();
// Enable the event loop delay monitor in Timer.zig
Timer_enableEventLoopDelayMonitoring(bunVM(globalObject), JSValue::encode(histogram), resolution);
RELEASE_AND_RETURN(scope, JSValue::encode(jsUndefined()));
}
// Disable event loop delay monitoring
JSC_DEFINE_HOST_FUNCTION(jsFunction_disableEventLoopDelay, (JSGlobalObject * globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
if (callFrame->argumentCount() < 1) {
throwTypeError(globalObject, scope, "Missing histogram argument"_s);
return JSValue::encode(jsUndefined());
}
JSValue histogramValue = callFrame->argument(0);
JSNodePerformanceHooksHistogram* histogram = jsDynamicCast<JSNodePerformanceHooksHistogram*>(histogramValue);
if (!histogram) {
throwTypeError(globalObject, scope, "Invalid histogram"_s);
return JSValue::encode(jsUndefined());
}
// Call into Zig to disable monitoring
Timer_disableEventLoopDelayMonitoring(bunVM(globalObject));
return JSValue::encode(jsUndefined());
}
// Extern function for Zig to record delays
extern "C" void JSNodePerformanceHooksHistogram_recordDelay(JSC::EncodedJSValue histogram, int64_t delay_ns)
{
if (!histogram || delay_ns <= 0) return;
auto* hist = jsCast<JSNodePerformanceHooksHistogram*>(JSValue::decode(histogram));
hist->record(delay_ns);
}
} // namespace Bun

View File

@@ -0,0 +1 @@
CLAUDE.md

View File

@@ -0,0 +1,326 @@
# V8 C++ API Implementation Guide
This directory contains Bun's implementation of the V8 C++ API on top of JavaScriptCore. This allows native Node.js modules that use V8 APIs to work with Bun.
## Architecture Overview
Bun implements V8 APIs by creating a compatibility layer that:
- Maps V8's `Local<T>` handles to JSC's `JSValue` system
- Uses handle scopes to manage memory lifetimes similar to V8
- Provides V8-compatible object layouts that inline V8 functions can read
- Manages tagged pointers for efficient value representation
For detailed background, see the blog series:
- [Part 1: Introduction and challenges](https://bun.sh/blog/how-bun-supports-v8-apis-without-using-v8-part-1.md)
- [Part 2: Memory layout and object representation](https://bun.sh/blog/how-bun-supports-v8-apis-without-using-v8-part-2.md)
- [Part 3: Garbage collection and primitives](https://bun.sh/blog/how-bun-supports-v8-apis-without-using-v8-part-3.md)
## Directory Structure
```
src/bun.js/bindings/v8/
├── v8.h # Main header with V8_UNIMPLEMENTED macro
├── v8_*.h # V8 compatibility headers
├── V8*.h # V8 class headers (Number, String, Object, etc.)
├── V8*.cpp # V8 class implementations
├── shim/ # Internal implementation details
│ ├── Handle.h # Handle and ObjectLayout implementation
│ ├── HandleScopeBuffer.h # Handle scope memory management
│ ├── TaggedPointer.h # V8-style tagged pointer implementation
│ ├── Map.h # V8 Map objects for inline function compatibility
│ ├── GlobalInternals.h # V8 global state management
│ ├── InternalFieldObject.h # Objects with internal fields
│ └── Oddball.h # Primitive values (undefined, null, true, false)
├── node.h # Node.js module registration compatibility
└── real_v8.h # Includes real V8 headers when needed
```
## Implementing New V8 APIs
### 1. Create Header and Implementation Files
Create `V8NewClass.h`:
```cpp
#pragma once
#include "v8.h"
#include "V8Local.h"
#include "V8Isolate.h"
namespace v8 {
class NewClass : public Data {
public:
BUN_EXPORT static Local<NewClass> New(Isolate* isolate, /* parameters */);
BUN_EXPORT /* return_type */ SomeMethod() const;
// Add other methods as needed
};
} // namespace v8
```
Create `V8NewClass.cpp`:
```cpp
#include "V8NewClass.h"
#include "V8HandleScope.h"
#include "v8_compatibility_assertions.h"
ASSERT_V8_TYPE_LAYOUT_MATCHES(v8::NewClass)
namespace v8 {
Local<NewClass> NewClass::New(Isolate* isolate, /* parameters */)
{
// Implementation - typically:
// 1. Create JSC value
// 2. Get current handle scope
// 3. Create local handle
return isolate->currentHandleScope()->createLocal<NewClass>(isolate->vm(), /* JSC value */);
}
/* return_type */ NewClass::SomeMethod() const
{
// Implementation - typically:
// 1. Convert this Local to JSValue via localToJSValue()
// 2. Perform JSC operations
// 3. Return converted result
auto jsValue = localToJSValue();
// ... JSC operations ...
return /* result */;
}
} // namespace v8
```
### 2. Add Symbol Exports
For each new C++ method, you must add the mangled symbol names to multiple files:
#### a. Add to `src/napi/napi.zig`
Find the `V8API` struct (around line 1801) and add entries for both GCC/Clang and MSVC:
```zig
const V8API = if (!bun.Environment.isWindows) struct {
// ... existing functions ...
pub extern fn _ZN2v88NewClass3NewEPNS_7IsolateE/* parameters */() *anyopaque;
pub extern fn _ZNK2v88NewClass10SomeMethodEv() *anyopaque;
} else struct {
// ... existing functions ...
pub extern fn @"?New@NewClass@v8@@SA?AV?$Local@VNewClass@v8@@@2@PEAVIsolate@2@/* parameters */@Z"() *anyopaque;
pub extern fn @"?SomeMethod@NewClass@v8@@QEBA/* return_type */XZ"() *anyopaque;
};
```
**To get the correct mangled names:**
For **GCC/Clang** (Unix):
```bash
# Build your changes first
bun bd --help # This compiles your code
# Extract symbols
nm build/CMakeFiles/bun-debug.dir/src/bun.js/bindings/v8/V8NewClass.cpp.o | grep "T _ZN2v8"
```
For **MSVC** (Windows):
```powershell
# Use the provided PowerShell script in the comments:
dumpbin .\build\CMakeFiles\bun-debug.dir\src\bun.js\bindings\v8\V8NewClass.cpp.obj /symbols | where-object { $_.Contains(' v8::') } | foreach-object { (($_ -split "\|")[1] -split " ")[1] } | ForEach-Object { "extern fn @`"${_}`"() *anyopaque;" }
```
#### b. Add to Symbol Files
Add to `src/symbols.txt` (without leading underscore):
```
_ZN2v88NewClass3NewEPNS_7IsolateE...
_ZNK2v88NewClass10SomeMethodEv
```
Add to `src/symbols.dyn` (with leading underscore and semicolons):
```
{
__ZN2v88NewClass3NewEPNS_7IsolateE...;
__ZNK2v88NewClass10SomeMethodEv;
}
```
**Note:** `src/symbols.def` is Windows-only and typically doesn't contain V8 symbols.
### 3. Add Tests
Create tests in `test/v8/v8-module/main.cpp`:
```cpp
void test_new_class_feature(const FunctionCallbackInfo<Value> &info) {
Isolate* isolate = info.GetIsolate();
// Test your new V8 API
Local<NewClass> obj = NewClass::New(isolate, /* parameters */);
auto result = obj->SomeMethod();
// Print results for comparison with Node.js
std::cout << "Result: " << result << std::endl;
info.GetReturnValue().Set(Undefined(isolate));
}
```
Add the test to the registration section:
```cpp
void Init(Local<Object> exports, Local<Value> module, Local<Context> context) {
// ... existing functions ...
NODE_SET_METHOD(exports, "test_new_class_feature", test_new_class_feature);
}
```
Add test case to `test/v8/v8.test.ts`:
```typescript
describe("NewClass", () => {
it("can use new feature", async () => {
await checkSameOutput("test_new_class_feature", []);
});
});
```
### 4. Handle Special Cases
#### Objects with Internal Fields
If implementing objects that need internal fields, extend `InternalFieldObject`:
```cpp
// In your .h file
class MyObject : public InternalFieldObject {
// ... implementation
};
```
#### Primitive Values
For primitive values, ensure they work with the `Oddball` system in `shim/Oddball.h`.
#### Template Classes
For `ObjectTemplate` or `FunctionTemplate` implementations, see existing patterns in `V8ObjectTemplate.cpp` and `V8FunctionTemplate.cpp`.
## Memory Management Guidelines
### Handle Scopes
- All V8 values must be created within an active handle scope
- Use `isolate->currentHandleScope()->createLocal<T>()` to create handles
- Handle scopes automatically clean up when destroyed
### JSC Integration
- Use `localToJSValue()` to convert V8 handles to JSC values
- Use `JSC::WriteBarrier` for heap-allocated references
- Implement `visitChildren()` for custom heap objects
### Tagged Pointers
- Small integers (±2^31) are stored directly as Smis
- Objects use pointer tagging with map pointers
- Doubles are stored in object layouts with special maps
## Testing Strategy
### Comprehensive Testing
The V8 test suite compares output between Node.js and Bun for the same C++ code:
1. **Install Phase**: Sets up identical module builds for Node.js and Bun
2. **Build Phase**: Compiles native modules using node-gyp
3. **Test Phase**: Runs identical C++ functions and compares output
### Test Categories
- **Primitives**: undefined, null, booleans, numbers, strings
- **Objects**: creation, property access, internal fields
- **Arrays**: creation, length, iteration, element access
- **Functions**: callbacks, templates, argument handling
- **Memory**: handle scopes, garbage collection, external data
- **Advanced**: templates, inheritance, error handling
### Adding New Tests
1. Add C++ test function to `test/v8/v8-module/main.cpp`
2. Register function in the module exports
3. Add test case to `test/v8/v8.test.ts` using `checkSameOutput()`
4. Run with: `bun bd test test/v8/v8.test.ts -t "your test name"`
## Debugging Tips
### Build and Test
```bash
# Build debug version (takes ~5 minutes)
bun bd --help
# Run V8 tests
bun bd test test/v8/v8.test.ts
# Run specific test
bun bd test test/v8/v8.test.ts -t "can create small integer"
```
### Common Issues
**Symbol Not Found**: Ensure mangled names are correctly added to `napi.zig` and symbol files.
**Segmentation Fault**: Usually indicates inline V8 functions are reading incorrect memory layouts. Check `Map` setup and `ObjectLayout` structure.
**GC Issues**: Objects being freed prematurely. Ensure proper `WriteBarrier` usage and `visitChildren()` implementation.
**Type Mismatches**: Use `v8_compatibility_assertions.h` macros to verify type layouts match V8 expectations.
### Debug Logging
Use `V8_UNIMPLEMENTED()` macro for functions not yet implemented:
```cpp
void MyClass::NotYetImplemented() {
V8_UNIMPLEMENTED();
}
```
## Advanced Topics
### Inline Function Compatibility
Many V8 functions are inline and compiled into native modules. The memory layout must exactly match what these functions expect:
- Objects start with tagged pointer to `Map`
- Maps have instance type at offset 12
- Handle scopes store tagged pointers
- Primitive values at fixed global offsets
### Cross-Platform Considerations
- Symbol mangling differs between GCC/Clang and MSVC
- Handle calling conventions (JSC uses System V on Unix)
- Ensure `BUN_EXPORT` visibility on all public functions
- Test on all target platforms via CI
## Contributing
When contributing V8 API implementations:
1. **Follow existing patterns** in similar classes
2. **Add comprehensive tests** that compare with Node.js
3. **Update all symbol files** with correct mangled names
4. **Document any special behavior** or limitations
For questions about V8 API implementation, refer to the blog series linked above or examine existing implementations in this directory.

View File

@@ -380,6 +380,8 @@ pub const Command = struct {
runtime_options: RuntimeOptions = .{},
filters: []const []const u8 = &.{},
workspaces: bool = false,
if_present: bool = false,
preloads: []const string = &.{},
has_loaded_global_config: bool = false,
@@ -815,7 +817,7 @@ pub const Command = struct {
const ctx = try Command.init(allocator, log, .RunCommand);
ctx.args.target = .bun;
if (ctx.filters.len > 0) {
if (ctx.filters.len > 0 or ctx.workspaces) {
FilterRun.runScriptsWithFilter(ctx) catch |err| {
Output.prettyErrorln("<r><red>error<r>: {s}", .{@errorName(err)});
Global.exit(1);
@@ -854,7 +856,7 @@ pub const Command = struct {
};
ctx.args.target = .bun;
if (ctx.filters.len > 0) {
if (ctx.filters.len > 0 or ctx.workspaces) {
FilterRun.runScriptsWithFilter(ctx) catch |err| {
Output.prettyErrorln("<r><red>error<r>: {s}", .{@errorName(err)});
Global.exit(1);

View File

@@ -116,6 +116,7 @@ pub const auto_or_run_params = [_]ParamType{
clap.parseParam("-F, --filter <STR>... Run a script in all workspace packages matching the pattern") catch unreachable,
clap.parseParam("-b, --bun Force a script or package to use Bun's runtime instead of Node.js (via symlinking node)") catch unreachable,
clap.parseParam("--shell <STR> Control the shell used for package.json scripts. Supports either 'bun' or 'system'") catch unreachable,
clap.parseParam("--workspaces Run a script in all workspace packages (from the \"workspaces\" field in package.json)") catch unreachable,
};
pub const auto_only_params = [_]ParamType{
@@ -387,6 +388,8 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
if (cmd == .RunCommand or cmd == .AutoCommand) {
ctx.filters = args.options("--filter");
ctx.workspaces = args.flag("--workspaces");
ctx.if_present = args.flag("--if-present");
if (args.option("--elide-lines")) |elide_lines| {
if (elide_lines.len > 0) {

View File

@@ -433,7 +433,15 @@ pub fn runScriptsWithFilter(ctx: Command.Context) !noreturn {
const fsinstance = try bun.fs.FileSystem.init(null);
// these things are leaked because we are going to exit
var filter_instance = try FilterArg.FilterSet.init(ctx.allocator, ctx.filters, fsinstance.top_level_dir);
// When --workspaces is set, we want to match all workspace packages
// Otherwise use the provided filters
var filters_to_use = ctx.filters;
if (ctx.workspaces) {
// Use "*" as filter to match all packages in the workspace
filters_to_use = &.{"*"};
}
var filter_instance = try FilterArg.FilterSet.init(ctx.allocator, filters_to_use, fsinstance.top_level_dir);
var patterns = std.ArrayList([]u8).init(ctx.allocator);
// Find package.json at workspace root
@@ -453,6 +461,11 @@ pub fn runScriptsWithFilter(ctx: Command.Context) !noreturn {
const dirpath = std.fs.path.dirname(package_json_path) orelse Global.crash();
const path = bun.strings.withoutTrailingSlash(dirpath);
// When using --workspaces, skip the root package to prevent recursion
if (ctx.workspaces and strings.eql(path, resolve_root)) {
continue;
}
const pkgjson = bun.PackageJSON.parse(&this_transpiler.resolver, dirpath, .invalid, null, .include_scripts, .main) orelse {
Output.warn("Failed to read package.json\n", .{});
continue;
@@ -465,8 +478,15 @@ pub fn runScriptsWithFilter(ctx: Command.Context) !noreturn {
const PATH = try RunCommand.configurePathForRunWithPackageJsonDir(ctx, dirpath, &this_transpiler, null, dirpath, ctx.debug.run_in_bun);
for (&[3][]const u8{ pre_script_name, script_name, post_script_name }) |name| {
const original_content = pkgscripts.get(name) orelse continue;
for (&[3][]const u8{ pre_script_name, script_name, post_script_name }, 0..) |name, i| {
const original_content = pkgscripts.get(name) orelse {
if (i == 1 and ctx.workspaces and !ctx.if_present) {
Output.errGeneric("Missing '{s}' script at '{s}'", .{ script_name, path });
Global.exit(1);
}
continue;
};
var copy_script_capacity: usize = original_content.len;
for (ctx.passthrough) |part| copy_script_capacity += 1 + part.len;
@@ -500,7 +520,15 @@ pub fn runScriptsWithFilter(ctx: Command.Context) !noreturn {
}
if (scripts.items.len == 0) {
Output.prettyErrorln("<r><red>error<r>: No packages matched the filter", .{});
if (ctx.if_present) {
// Exit silently with success when --if-present is set
Global.exit(0);
}
if (ctx.workspaces) {
Output.errGeneric("No workspace packages have script \"{s}\"", .{script_name});
} else {
Output.errGeneric("No packages matched the filter", .{});
}
Global.exit(1);
}
@@ -648,6 +676,7 @@ const bun = @import("bun");
const Environment = bun.Environment;
const Global = bun.Global;
const Output = bun.Output;
const strings = bun.strings;
const transpiler = bun.transpiler;
const CLI = bun.cli;

1
src/js/AGENTS.md Symbolic link
View File

@@ -0,0 +1 @@
CLAUDE.md

104
src/js/CLAUDE.md Normal file
View File

@@ -0,0 +1,104 @@
# JavaScript Builtins in Bun
Write JS builtins for Bun's Node.js compatibility and APIs. Run `bun bd` after changes.
## Directory Structure
- `builtins/` - Individual functions (`*CodeGenerator(vm)` in C++)
- `node/` - Node.js modules (`node:fs`, `node:path`)
- `bun/` - Bun modules (`bun:ffi`, `bun:sqlite`)
- `thirdparty/` - NPM replacements (`ws`, `node-fetch`)
- `internal/` - Internal modules
## Writing Modules
Modules are NOT ES modules:
```typescript
const EventEmitter = require("node:events"); // String literals only
const { validateFunction } = require("internal/validators");
export default {
myFunction() {
if (!$isCallable(callback)) {
throw $ERR_INVALID_ARG_TYPE("cb", "function", callback);
}
},
};
```
## Writing Builtin Functions
```typescript
export function initializeReadableStream(
this: ReadableStream,
underlyingSource,
strategy,
) {
if (!$isObject(underlyingSource)) {
throw new TypeError(
"ReadableStream constructor takes an object as first argument",
);
}
$putByIdDirectPrivate(this, "state", $streamReadable);
}
```
C++ access:
```cpp
object->putDirectBuiltinFunction(vm, globalObject, identifier,
readableStreamInitializeReadableStreamCodeGenerator(vm), 0);
```
## $ Globals and Special Syntax
**CRITICAL**: Use `.$call` and `.$apply`, never `.call` or `.apply`:
```typescript
// ✗ WRONG - User can tamper
callback.call(undefined, arg1);
fn.apply(undefined, args);
// ✓ CORRECT - Tamper-proof
callback.$call(undefined, arg1);
fn.$apply(undefined, args);
// $ prefix for private APIs
const arr = $Array.from(...); // Private globals
map.$set(key, value); // Private methods
const newArr = $newArrayWithSize(5); // JSC intrinsics
$debug("Module loaded:", name); // Debug (stripped in release)
$assert(condition, "message"); // Assertions (stripped in release)
```
## Validation and Errors
```typescript
const { validateFunction } = require("internal/validators");
function myAPI(callback) {
if (!$isCallable(callback)) {
throw $ERR_INVALID_ARG_TYPE("callback", "function", callback);
}
}
```
## Build Process
`Source TS/JS → Preprocessor → Bundler → C++ Headers`
1. Assign numeric IDs (A-Z sorted)
2. Replace `$` with `__intrinsic__`, `require("x")` with `$requireId(n)`
3. Bundle, convert `export default` to `return`
4. Replace `__intrinsic__` with `@`, inline into C++
ModuleLoader.zig loads modules by numeric ID via `InternalModuleRegistry.cpp`.
## Key Rules
- Use `.$call`/`.$apply` not `.call`/`.apply`
- String literal `require()` only
- Export via `export default {}`
- Use JSC intrinsics for performance
- Run `bun bd` after changes

View File

@@ -0,0 +1,71 @@
// Internal module for monitorEventLoopDelay implementation
const { validateObject, validateInteger } = require("internal/validators");
// Private C++ bindings for event loop delay monitoring
const cppMonitorEventLoopDelay = $newCppFunction(
"JSNodePerformanceHooksHistogramPrototype.cpp",
"jsFunction_monitorEventLoopDelay",
1,
) as (resolution: number) => import("node:perf_hooks").RecordableHistogram;
const cppEnableEventLoopDelay = $newCppFunction(
"JSNodePerformanceHooksHistogramPrototype.cpp",
"jsFunction_enableEventLoopDelay",
2,
) as (histogram: import("node:perf_hooks").RecordableHistogram, resolution: number) => void;
const cppDisableEventLoopDelay = $newCppFunction(
"JSNodePerformanceHooksHistogramPrototype.cpp",
"jsFunction_disableEventLoopDelay",
1,
) as (histogram: import("node:perf_hooks").RecordableHistogram) => void;
// IntervalHistogram wrapper class for event loop delay monitoring
let eventLoopDelayHistogram: import("node:perf_hooks").RecordableHistogram | undefined;
let enabled = false;
let resolution = 10;
function enable() {
if (enabled) {
return false;
}
enabled = true;
cppEnableEventLoopDelay(eventLoopDelayHistogram!, resolution);
return true;
}
function disable() {
if (!enabled) {
return false;
}
enabled = false;
cppDisableEventLoopDelay(eventLoopDelayHistogram!);
return true;
}
function monitorEventLoopDelay(options?: { resolution?: number }) {
if (options !== undefined) {
validateObject(options, "options");
}
resolution = 10;
let resolutionOption = options?.resolution;
if (typeof resolutionOption !== "undefined") {
validateInteger(resolutionOption, "options.resolution", 1);
resolution = resolutionOption;
}
if (!eventLoopDelayHistogram) {
eventLoopDelayHistogram = cppMonitorEventLoopDelay(resolution);
$putByValDirect(eventLoopDelayHistogram, "enable", enable);
$putByValDirect(eventLoopDelayHistogram, "disable", disable);
$putByValDirect(eventLoopDelayHistogram, Symbol.dispose, disable);
}
return eventLoopDelayHistogram;
}
export default monitorEventLoopDelay;

View File

@@ -392,7 +392,7 @@ class PooledMySQLConnection {
// remove from ready connections if its there
this.adapter.readyConnections.delete(this);
const queries = new Set(this.queries);
this.queries.clear();
this.queries?.clear?.();
this.queryCount = 0;
this.flags &= ~PooledConnectionFlags.reserved;

View File

@@ -409,7 +409,7 @@ class PooledPostgresConnection {
// remove from ready connections if its there
this.adapter.readyConnections?.delete(this);
const queries = new Set(this.queries);
this.queries.clear();
this.queries?.clear?.();
this.queryCount = 0;
this.flags &= ~PooledConnectionFlags.reserved;

View File

@@ -1007,6 +1007,7 @@ function normalizeSpawnArguments(file, args, options) {
file,
windowsHide: !!options.windowsHide,
windowsVerbatimArguments: !!windowsVerbatimArguments,
windowsShellEnabled: !!options.shell,
argv0: options.argv0,
};
}
@@ -1333,6 +1334,7 @@ class ChildProcess extends EventEmitter {
cwd: options.cwd || undefined,
env: env,
detached: typeof detachedOption !== "undefined" ? !!detachedOption : false,
windowsShellEnabled: !!options.windowsShellEnabled,
onExit: (handle, exitCode, signalCode, err) => {
this.#handle = handle;
this.pid = this.#handle.pid;
@@ -1513,6 +1515,73 @@ class ChildProcess extends EventEmitter {
unref() {
if (this.#handle) this.#handle.unref();
}
// Static initializer to make stdio properties enumerable on the prototype
// This fixes libraries like tinyspawn that use Object.assign(promise, childProcess)
static {
Object.defineProperties(this.prototype, {
stdin: {
get: function () {
const value = (this.#stdin ??= this.#getBunSpawnIo(0, this.#encoding, false));
// Define as own enumerable property on first access
Object.defineProperty(this, "stdin", {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
return value;
},
enumerable: true,
configurable: true,
},
stdout: {
get: function () {
const value = (this.#stdout ??= this.#getBunSpawnIo(1, this.#encoding, false));
// Define as own enumerable property on first access
Object.defineProperty(this, "stdout", {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
return value;
},
enumerable: true,
configurable: true,
},
stderr: {
get: function () {
const value = (this.#stderr ??= this.#getBunSpawnIo(2, this.#encoding, false));
// Define as own enumerable property on first access
Object.defineProperty(this, "stderr", {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
return value;
},
enumerable: true,
configurable: true,
},
stdio: {
get: function () {
const value = (this.#stdioObject ??= this.#createStdioObject());
// Define as own enumerable property on first access
Object.defineProperty(this, "stdio", {
value: value,
enumerable: true,
configurable: true,
writable: true,
});
return value;
},
enumerable: true,
configurable: true,
},
});
}
}
//------------------------------------------------------------------------------

View File

@@ -1,12 +1,6 @@
// Hardcoded module "node:perf_hooks"
const { throwNotImplemented } = require("internal/shared");
const createFunctionThatMasqueradesAsUndefined = $newCppFunction(
"ZigGlobalObject.cpp",
"jsFunctionCreateFunctionThatMasqueradesAsUndefined",
2,
);
const cppCreateHistogram = $newCppFunction("JSNodePerformanceHooksHistogram.cpp", "jsFunction_createHistogram", 3) as (
min: number,
max: number,
@@ -178,8 +172,10 @@ export default {
PerformanceObserver,
PerformanceObserverEntryList,
PerformanceNodeTiming,
// TODO: node:perf_hooks.monitorEventLoopDelay -- https://github.com/oven-sh/bun/issues/17650
monitorEventLoopDelay: createFunctionThatMasqueradesAsUndefined("", 0),
monitorEventLoopDelay: function monitorEventLoopDelay(options?: { resolution?: number }) {
const impl = require("internal/perf_hooks/monitorEventLoopDelay");
return impl(options);
},
createHistogram: function createHistogram(options?: {
lowest?: number | bigint;
highest?: number | bigint;

View File

@@ -76,7 +76,7 @@ function getHeapStatistics() {
// -- Copied from Node:
does_zap_garbage: 0,
number_of_native_contexts: 1,
number_of_native_contexts: stats.globalObjectCount,
number_of_detached_contexts: 0,
total_global_handles_size: 8192,
used_global_handles_size: 2208,

View File

@@ -1,5 +1,3 @@
import type * as s from "stream";
// Users may override the global fetch implementation, so we need to ensure these are the originals.
const bindings = $cpp("NodeFetch.cpp", "createNodeFetchInternalBinding");
const WebResponse: typeof globalThis.Response = bindings[0];
@@ -147,22 +145,16 @@ class Request extends WebRequest {
* like `.json()` or `.text()`, which is faster in Bun's native fetch, vs `node-fetch` going
* through `node:http`, a node stream, then processing the data.
*/
async function fetch(url: any, init?: RequestInit & { body?: any }) {
// input node stream -> web stream
let body: s.Readable | undefined = init?.body;
if (body) {
const chunks: any = [];
const { Readable } = require("node:stream");
if (body instanceof Readable) {
// TODO: Bun fetch() doesn't support ReadableStream at all.
for await (const chunk of body) {
chunks.push(chunk);
}
init = { ...init, body: new Blob(chunks) };
}
}
async function fetch(
// eslint-disable-next-line no-unused-vars
url: any,
const response = await nativeFetch(url, init);
// eslint-disable-next-line no-unused-vars
init?: RequestInit & { body?: any },
) {
// Since `body` accepts async iterables
// We don't need to convert the Readable body into a ReadableStream.
const response = await nativeFetch.$apply(undefined, arguments);
Object.setPrototypeOf(response, ResponsePrototype);
return response;
}

View File

@@ -1,7 +1,7 @@
import { describe, expect, test } from "bun:test";
import { execSync } from "child_process";
import { promises as fs } from "fs";
import { bunEnv, bunExe, isWindows, tempDirWithFiles } from "harness";
import { bunEnv, bunExe, isWindows, tempDir } from "harness";
import { join } from "path";
// Helper to ensure executable cleanup
@@ -18,11 +18,11 @@ function cleanup(outfile: string) {
describe.skipIf(!isWindows)("Windows compile metadata", () => {
describe("CLI flags", () => {
test("all metadata flags via CLI", async () => {
const dir = tempDirWithFiles("windows-metadata-cli", {
using dir = tempDir("windows-metadata-cli", {
"app.js": `console.log("Test app with metadata");`,
});
const outfile = join(dir, "app-with-metadata.exe");
const outfile = join(String(dir), "app-with-metadata.exe");
await using _cleanup = cleanup(outfile);
await using proc = Bun.spawn({
@@ -30,7 +30,7 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
bunExe(),
"build",
"--compile",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
@@ -78,11 +78,11 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
});
test("partial metadata flags", async () => {
const dir = tempDirWithFiles("windows-metadata-partial", {
using dir = tempDir("windows-metadata-partial", {
"app.js": `console.log("Partial metadata test");`,
});
const outfile = join(dir, "app-partial.exe");
const outfile = join(String(dir), "app-partial.exe");
await using _cleanup = cleanup(outfile);
await using proc = Bun.spawn({
@@ -90,7 +90,7 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
bunExe(),
"build",
"--compile",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
@@ -122,12 +122,12 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
});
test("windows flags without --compile should error", async () => {
const dir = tempDirWithFiles("windows-no-compile", {
using dir = tempDir("windows-no-compile", {
"app.js": `console.log("test");`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "build", join(dir, "app.js"), "--windows-title", "Should Fail"],
cmd: [bunExe(), "build", join(String(dir), "app.js"), "--windows-title", "Should Fail"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -140,7 +140,7 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
});
test("windows flags with non-Windows target should error", async () => {
const dir = tempDirWithFiles("windows-wrong-target", {
using dir = tempDir("windows-wrong-target", {
"app.js": `console.log("test");`,
});
@@ -151,7 +151,7 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
"--compile",
"--target",
"bun-linux-x64",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--windows-title",
"Should Fail",
],
@@ -170,13 +170,13 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
describe("Bun.build() API", () => {
test("all metadata via Bun.build()", async () => {
const dir = tempDirWithFiles("windows-metadata-api", {
using dir = tempDir("windows-metadata-api", {
"app.js": `console.log("API metadata test");`,
});
const result = await Bun.build({
entrypoints: [join(dir, "app.js")],
outdir: dir,
entrypoints: [join(String(dir), "app.js")],
outdir: String(dir),
compile: {
target: "bun-windows-x64",
outfile: "app-api.exe",
@@ -217,13 +217,13 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
});
test("partial metadata via Bun.build()", async () => {
const dir = tempDirWithFiles("windows-metadata-api-partial", {
using dir = tempDir("windows-metadata-api-partial", {
"app.js": `console.log("Partial API test");`,
});
const result = await Bun.build({
entrypoints: [join(dir, "app.js")],
outdir: dir,
entrypoints: [join(String(dir), "app.js")],
outdir: String(dir),
compile: {
target: "bun-windows-x64",
outfile: "partial-api.exe",
@@ -254,12 +254,12 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
});
test("relative outdir with compile", async () => {
const dir = tempDirWithFiles("windows-relative-outdir", {
using dir = tempDir("windows-relative-outdir", {
"app.js": `console.log("Relative outdir test");`,
});
const result = await Bun.build({
entrypoints: [join(dir, "app.js")],
entrypoints: [join(String(dir), "app.js")],
outdir: "./out",
compile: {
target: "bun-windows-x64",
@@ -290,14 +290,23 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
];
test.each(testVersionFormats)("version format: $input", async ({ input, expected }) => {
const dir = tempDirWithFiles(`windows-version-${input.replace(/\./g, "-")}`, {
using dir = tempDir(`windows-version-${input.replace(/\./g, "-")}`, {
"app.js": `console.log("Version test");`,
});
const outfile = join(dir, "version-test.exe");
const outfile = join(String(dir), "version-test.exe");
await using proc = Bun.spawn({
cmd: [bunExe(), "build", "--compile", join(dir, "app.js"), "--outfile", outfile, "--windows-version", input],
cmd: [
bunExe(),
"build",
"--compile",
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-version",
input,
],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
@@ -314,7 +323,7 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
});
test("invalid version format should error gracefully", async () => {
const dir = tempDirWithFiles("windows-invalid-version", {
using dir = tempDir("windows-invalid-version", {
"app.js": `console.log("Invalid version test");`,
});
@@ -332,9 +341,9 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
bunExe(),
"build",
"--compile",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--outfile",
join(dir, "test.exe"),
join(String(dir), "test.exe"),
"--windows-version",
version,
],
@@ -351,11 +360,11 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
describe("Original Filename removal", () => {
test("Original Filename field should be empty", async () => {
const dir = tempDirWithFiles("windows-original-filename", {
using dir = tempDir("windows-original-filename", {
"app.js": `console.log("Original filename test");`,
});
const outfile = join(dir, "test-original.exe");
const outfile = join(String(dir), "test-original.exe");
await using _cleanup = cleanup(outfile);
await using proc = Bun.spawn({
@@ -363,7 +372,7 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
bunExe(),
"build",
"--compile",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
@@ -394,11 +403,11 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
});
test("Original Filename should be empty even with all metadata set", async () => {
const dir = tempDirWithFiles("windows-original-filename-full", {
using dir = tempDir("windows-original-filename-full", {
"app.js": `console.log("Full metadata test");`,
});
const outfile = join(dir, "full-metadata.exe");
const outfile = join(String(dir), "full-metadata.exe");
await using _cleanup = cleanup(outfile);
await using proc = Bun.spawn({
@@ -406,7 +415,7 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
bunExe(),
"build",
"--compile",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
@@ -453,19 +462,19 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
describe("Edge cases", () => {
test("long strings in metadata", async () => {
const dir = tempDirWithFiles("windows-long-strings", {
using dir = tempDir("windows-long-strings", {
"app.js": `console.log("Long strings test");`,
});
const longString = Buffer.alloc(255, "A").toString();
const outfile = join(dir, "long-strings.exe");
const outfile = join(String(dir), "long-strings.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
@@ -486,18 +495,18 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
});
test("special characters in metadata", async () => {
const dir = tempDirWithFiles("windows-special-chars", {
using dir = tempDir("windows-special-chars", {
"app.js": `console.log("Special chars test");`,
});
const outfile = join(dir, "special-chars.exe");
const outfile = join(String(dir), "special-chars.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
@@ -535,18 +544,18 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
});
test("unicode in metadata", async () => {
const dir = tempDirWithFiles("windows-unicode", {
using dir = tempDir("windows-unicode", {
"app.js": `console.log("Unicode test");`,
});
const outfile = join(dir, "unicode.exe");
const outfile = join(String(dir), "unicode.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
@@ -571,11 +580,11 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
});
test("empty strings in metadata", async () => {
const dir = tempDirWithFiles("windows-empty-strings", {
using dir = tempDir("windows-empty-strings", {
"app.js": `console.log("Empty strings test");`,
});
const outfile = join(dir, "empty.exe");
const outfile = join(String(dir), "empty.exe");
await using _cleanup = cleanup(outfile);
// Empty strings should be treated as not provided
@@ -584,7 +593,7 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
bunExe(),
"build",
"--compile",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-title",
@@ -607,18 +616,18 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
describe("Combined with other compile options", () => {
test("metadata with --windows-hide-console", async () => {
const dir = tempDirWithFiles("windows-metadata-hide-console", {
using dir = tempDir("windows-metadata-hide-console", {
"app.js": `console.log("Hidden console test");`,
});
const outfile = join(dir, "hidden-with-metadata.exe");
const outfile = join(String(dir), "hidden-with-metadata.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-hide-console",
@@ -679,23 +688,23 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
0x00, // Offset
]);
const dir = tempDirWithFiles("windows-metadata-icon", {
using dir = tempDir("windows-metadata-icon", {
"app.js": `console.log("Icon test");`,
"icon.ico": icoHeader,
});
const outfile = join(dir, "icon-with-metadata.exe");
const outfile = join(String(dir), "icon-with-metadata.exe");
await using proc = Bun.spawn({
cmd: [
bunExe(),
"build",
"--compile",
join(dir, "app.js"),
join(String(dir), "app.js"),
"--outfile",
outfile,
"--windows-icon",
join(dir, "icon.ico"),
join(String(dir), "icon.ico"),
"--windows-title",
"App with Icon",
"--windows-version",
@@ -709,23 +718,21 @@ describe.skipIf(!isWindows)("Windows compile metadata", () => {
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Icon might fail but metadata should still work
if (exitCode === 0) {
const exists = await Bun.file(outfile).exists();
expect(exists).toBe(true);
const exists = await Bun.file(outfile).exists();
expect(exists).toBe(true);
const getMetadata = (field: string) => {
try {
return execSync(`powershell -Command "(Get-ItemProperty '${outfile}').VersionInfo.${field}"`, {
encoding: "utf8",
}).trim();
} catch {
return "";
}
};
const getMetadata = (field: string) => {
try {
return execSync(`powershell -Command "(Get-ItemProperty '${outfile}').VersionInfo.${field}"`, {
encoding: "utf8",
}).trim();
} catch {
return "";
}
};
expect(getMetadata("ProductName")).toBe("App with Icon");
expect(getMetadata("ProductVersion")).toBe("2.0.0.0");
}
expect(getMetadata("ProductName")).toBe("App with Icon");
expect(getMetadata("ProductVersion")).toBe("2.0.0.0");
});
});
});

View File

@@ -0,0 +1,103 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDirWithFiles } from "harness";
test("bun run --workspaces runs script in all workspace packages", async () => {
const dir = tempDirWithFiles("workspaces-test", {
"package.json": JSON.stringify({
name: "root",
workspaces: ["packages/*"],
scripts: {
test: "echo root test",
},
}),
"packages/a/package.json": JSON.stringify({
name: "a",
scripts: {
test: "echo package a test",
},
}),
"packages/b/package.json": JSON.stringify({
name: "b",
scripts: {
test: "echo package b test",
},
}),
});
const proc = Bun.spawn({
cmd: [bunExe(), "run", "--workspaces", "test"],
env: bunEnv,
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(0);
expect(stdout).toContain("package a test");
expect(stdout).toContain("package b test");
// Root should not be included when using --workspaces
expect(stdout).not.toContain("root test");
});
test("bun run --workspaces --if-present succeeds when script is missing", async () => {
const dir = tempDirWithFiles("workspaces-if-present", {
"package.json": JSON.stringify({
name: "root",
workspaces: ["packages/*"],
}),
"packages/a/package.json": JSON.stringify({
name: "a",
scripts: {
test: "echo package a test",
},
}),
"packages/b/package.json": JSON.stringify({
name: "b",
// No test script
}),
});
const proc = Bun.spawn({
cmd: [bunExe(), "run", "--workspaces", "--if-present", "test"],
env: bunEnv,
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(0);
expect(stdout).toContain("package a test");
// Should not fail for package b
});
test("bun run --workspaces fails when no packages have the script", async () => {
const dir = tempDirWithFiles("workspaces-no-script", {
"package.json": JSON.stringify({
name: "root",
workspaces: ["packages/*"],
}),
"packages/a/package.json": JSON.stringify({
name: "a",
}),
"packages/b/package.json": JSON.stringify({
name: "b",
}),
});
const proc = Bun.spawn({
cmd: [bunExe(), "run", "--workspaces", "nonexistent"],
env: bunEnv,
cwd: dir,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(1);
expect(stderr).toContain("No workspace packages have script");
});

View File

@@ -1301,11 +1301,15 @@ export const expiredTls = Object.freeze({
passphrase: "1234",
});
// openssl x509 -enddate -noout -in
// notAfter=Sep 5 23:27:34 2025 GMT
// openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
// -keyout localhost.key \
// -out localhost.crt \
// -subj "/C=US/ST=CA/L=San Francisco/O=Oven/OU=Team Bun/CN=server-bun" \
// -addext "subjectAltName = DNS:localhost,IP:127.0.0.1,IP:::1"
// notAfter=Sep 4 03:00:49 2035 GMT
export const tls = Object.freeze({
cert: "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIUHaenuNcUAu0tjDZGpc7fK4EX78gwDQYJKoZIhvcNAQEL\nBQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh\nbmNpc2NvMQ0wCwYDVQQKDARPdmVuMREwDwYDVQQLDAhUZWFtIEJ1bjETMBEGA1UE\nAwwKc2VydmVyLWJ1bjAeFw0yMzA5MDYyMzI3MzRaFw0yNTA5MDUyMzI3MzRaMGkx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj\nbzENMAsGA1UECgwET3ZlbjERMA8GA1UECwwIVGVhbSBCdW4xEzARBgNVBAMMCnNl\ncnZlci1idW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+7odzr3yI\nYewRNRGIubF5hzT7Bym2dDab4yhaKf5drL+rcA0J15BM8QJ9iSmL1ovg7x35Q2MB\nKw3rl/Yyy3aJS8whZTUze522El72iZbdNbS+oH6GxB2gcZB6hmUehPjHIUH4icwP\ndwVUeR6fB7vkfDddLXe0Tb4qsO1EK8H0mr5PiQSXfj39Yc1QHY7/gZ/xeSrt/6yn\n0oH9HbjF2XLSL2j6cQPKEayartHN0SwzwLi0eWSzcziVPSQV7c6Lg9UuIHbKlgOF\nzDpcp1p1lRqv2yrT25im/dS6oy9XX+p7EfZxqeqpXX2fr5WKxgnzxI3sW93PG8FU\nIDHtnUsoHX3RAgMBAAGjTzBNMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQ\nAAAAAAAAAAAAAAAAAAAAATAdBgNVHQ4EFgQUF3y/su4J/8ScpK+rM2LwTct6EQow\nDQYJKoZIhvcNAQELBQADggEBAGWGWp59Bmrk3Gt0bidFLEbvlOgGPWCT9ZrJUjgc\nhY44E+/t4gIBdoKOSwxo1tjtz7WsC2IYReLTXh1vTsgEitk0Bf4y7P40+pBwwZwK\naeIF9+PC6ZoAkXGFRoyEalaPVQDBg/DPOMRG9OH0lKfen9OGkZxmmjRLJzbyfAhU\noI/hExIjV8vehcvaJXmkfybJDYOYkN4BCNqPQHNf87ZNdFCb9Zgxwp/Ou+47J5k4\n5plQ+K7trfKXG3ABMbOJXNt1b0sH8jnpAsyHY4DLEQqxKYADbXsr3YX/yy6c0eOo\nX2bHGD1+zGsb7lGyNyoZrCZ0233glrEM4UxmvldBcWwOWfk=\n-----END CERTIFICATE-----\n",
key: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+7odzr3yIYewR\nNRGIubF5hzT7Bym2dDab4yhaKf5drL+rcA0J15BM8QJ9iSmL1ovg7x35Q2MBKw3r\nl/Yyy3aJS8whZTUze522El72iZbdNbS+oH6GxB2gcZB6hmUehPjHIUH4icwPdwVU\neR6fB7vkfDddLXe0Tb4qsO1EK8H0mr5PiQSXfj39Yc1QHY7/gZ/xeSrt/6yn0oH9\nHbjF2XLSL2j6cQPKEayartHN0SwzwLi0eWSzcziVPSQV7c6Lg9UuIHbKlgOFzDpc\np1p1lRqv2yrT25im/dS6oy9XX+p7EfZxqeqpXX2fr5WKxgnzxI3sW93PG8FUIDHt\nnUsoHX3RAgMBAAECggEAAckMqkn+ER3c7YMsKRLc5bUE9ELe+ftUwfA6G+oXVorn\nE+uWCXGdNqI+TOZkQpurQBWn9IzTwv19QY+H740cxo0ozZVSPE4v4czIilv9XlVw\n3YCNa2uMxeqp76WMbz1xEhaFEgn6ASTVf3hxYJYKM0ljhPX8Vb8wWwlLONxr4w4X\nOnQAB5QE7i7LVRsQIpWKnGsALePeQjzhzUZDhz0UnTyGU6GfC+V+hN3RkC34A8oK\njR3/Wsjahev0Rpb+9Pbu3SgTrZTtQ+srlRrEsDG0wVqxkIk9ueSMOHlEtQ7zYZsk\nlX59Bb8LHNGQD5o+H1EDaC6OCsgzUAAJtDRZsPiZEQKBgQDs+YtVsc9RDMoC0x2y\nlVnP6IUDXt+2UXndZfJI3YS+wsfxiEkgK7G3AhjgB+C+DKEJzptVxP+212hHnXgr\n1gfW/x4g7OWBu4IxFmZ2J/Ojor+prhHJdCvD0VqnMzauzqLTe92aexiexXQGm+WW\nwRl3YZLmkft3rzs3ZPhc1G2X9QKBgQDOQq3rrxcvxSYaDZAb+6B/H7ZE4natMCiz\nLx/cWT8n+/CrJI2v3kDfdPl9yyXIOGrsqFgR3uhiUJnz+oeZFFHfYpslb8KvimHx\nKI+qcVDcprmYyXj2Lrf3fvj4pKorc+8TgOBDUpXIFhFDyM+0DmHLfq+7UqvjU9Hs\nkjER7baQ7QKBgQDTh508jU/FxWi9RL4Jnw9gaunwrEt9bxUc79dp+3J25V+c1k6Q\nDPDBr3mM4PtYKeXF30sBMKwiBf3rj0CpwI+W9ntqYIwtVbdNIfWsGtV8h9YWHG98\nJ9q5HLOS9EAnogPuS27walj7wL1k+NvjydJ1of+DGWQi3aQ6OkMIegap0QKBgBlR\nzCHLa5A8plG6an9U4z3Xubs5BZJ6//QHC+Uzu3IAFmob4Zy+Lr5/kITlpCyw6EdG\n3xDKiUJQXKW7kluzR92hMCRnVMHRvfYpoYEtydxcRxo/WS73SzQBjTSQmicdYzLE\ntkLtZ1+ZfeMRSpXy0gR198KKAnm0d2eQBqAJy0h9AoGBAM80zkd+LehBKq87Zoh7\ndtREVWslRD1C5HvFcAxYxBybcKzVpL89jIRGKB8SoZkF7edzhqvVzAMP0FFsEgCh\naClYGtO+uo+B91+5v2CCqowRJUGfbFOtCuSPR7+B3LDK8pkjK2SQ0mFPUfRA5z0z\nNVWtC0EYNBTRkqhYtqr3ZpUc\n-----END PRIVATE KEY-----\n",
cert: "-----BEGIN CERTIFICATE-----\nMIID4jCCAsqgAwIBAgIUcaRq6J/YF++Bo01Zc+HeQvCbnWMwDQYJKoZIhvcNAQEL\nBQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh\nbmNpc2NvMQ0wCwYDVQQKDARPdmVuMREwDwYDVQQLDAhUZWFtIEJ1bjETMBEGA1UE\nAwwKc2VydmVyLWJ1bjAeFw0yNTA5MDYwMzAwNDlaFw0zNTA5MDQwMzAwNDlaMGkx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj\nbzENMAsGA1UECgwET3ZlbjERMA8GA1UECwwIVGVhbSBCdW4xEzARBgNVBAMMCnNl\ncnZlci1idW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDlYzosgRgX\nHL6vMh1V0ERFhsvlZrtRojSw6tafr3SQBphU793/rGiYZlL/lJ9HIlLkx9JMbuTj\nNm5U2eRwHiTQIeWD4aCIESwPlkdaVYtC+IOj55bJN8xNa7h5GyJwF7PnPetAsKyE\n8DMBn1gKMhaIis7HHOUtk4/K3Y4peU44d04z0yPt6JtY5Sbvi1E7pGX6T/2c9sHs\ndIDeDctWnewpXXs8zkAla0KNWQfpDnpS53wxAfStTA4lSrA9daxC7hZopQlLxFIb\nJk+0BLbEsXtrJ54T5iguHk+2MDVAy4MOqP9XbKV7eGHk73l6+CSwmHyHBxh4ChxR\nQeT5BP0MUTn1AgMBAAGjgYEwfzAdBgNVHQ4EFgQUw7nEnh4uOdZVZUapQzdAUaVa\nAn0wHwYDVR0jBBgwFoAUw7nEnh4uOdZVZUapQzdAUaVaAn0wDwYDVR0TAQH/BAUw\nAwEB/zAsBgNVHREEJTAjgglsb2NhbGhvc3SHBH8AAAGHEAAAAAAAAAAAAAAAAAAA\nAAEwDQYJKoZIhvcNAQELBQADggEBAEA8r1fvDLMSCb8bkAURpFk8chn8pl5MChzT\nYUDaLdCCBjPXJkSXNdyuwS+T/ljAGyZbW5xuDccCNKltawO4CbyEXUEZbYr3w9eq\nj8uqymJPhFf0O1rKOI2han5GBCgHwG13QwKI+4uu7390nD+TlzLOhxFfvOG7OadH\nQNMNLNyldgF4Nb8vWdz0FtQiGUIrO7iq4LFhhd1lCxe0q+FAYSEYcc74WtF/Yo8V\nJQauXuXyoP5FqLzNt/yeNQhceyIXJGKCsjr5/bASBmVlCwgRfsD3jpG37L8YCJs1\nL4WEikcY4Lzb2NF9e94IyZdQsRqd9DFBF5zP013MSUiuhiow32k=\n-----END CERTIFICATE-----\n",
key: "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDlYzosgRgXHL6v\nMh1V0ERFhsvlZrtRojSw6tafr3SQBphU793/rGiYZlL/lJ9HIlLkx9JMbuTjNm5U\n2eRwHiTQIeWD4aCIESwPlkdaVYtC+IOj55bJN8xNa7h5GyJwF7PnPetAsKyE8DMB\nn1gKMhaIis7HHOUtk4/K3Y4peU44d04z0yPt6JtY5Sbvi1E7pGX6T/2c9sHsdIDe\nDctWnewpXXs8zkAla0KNWQfpDnpS53wxAfStTA4lSrA9daxC7hZopQlLxFIbJk+0\nBLbEsXtrJ54T5iguHk+2MDVAy4MOqP9XbKV7eGHk73l6+CSwmHyHBxh4ChxRQeT5\nBP0MUTn1AgMBAAECggEABtPvC5uVGr0DjQX2GxONsK8cOxoVec7U+C4pUMwBcXcM\nyjxwlHdujpi/IDXtjsm+A2rSPu2vGPdKDfMFanPvPxW/Ne99noc6U0VzHsR8lnP8\nwSB328nyJhzOeyZcXk9KTtgIPF7156gZsJLsZTNL+ej90i3xQWvKxCxXmrLuad5O\nz/TrgZkC6wC3fgj1d3e8bMljQ7tLxbshJMYVI5o6RFTxy84DLI+rlvPkf7XbiMPf\n2lsm4jcJKvfx+164HZJ9QVlx8ncqOHAnGvxb2xHHfqv4JAbz615t7yRvtaw4Paj5\n6kQSf0VWnsVzgxNJWvnUZym/i/Qf5nQafjChCyKOEQKBgQD9f4SkvJrp/mFKWLHd\nkDvRpSIIltfJsa5KShn1IHsQXFwc0YgyP4SKQb3Ckv+/9UFHK9EzM+WlPxZi7ZOS\nhsWhIfkI4c4ORpxUQ+hPi0K2k+HIY7eYyONqDAzw5PGkKBo3mSGMHDXYywSqexhB\nCCMHuHdMhwyHdz4PWYOK3C2VMQKBgQDnpsrHK7lM9aVb8wNhTokbK5IlTSzH/5oJ\nlAVu6G6H3tM5YQeoDXztbZClvrvKU8DU5UzwaC+8AEWQwaram29QIDpAI3nVQQ0k\ndmHHp/pCeADdRG2whaGcl418UJMMv8AUpWTRm+kVLTLqfTHBC0ji4NlCQMHCUCfd\nU8TeUi5QBQKBgQDvJNd7mboDOUmLG7VgMetc0Y4T0EnuKsMjrlhimau/OYJkZX84\n+BcPXwmnf4nqC3Lzs3B9/12L0MJLvZjUSHQ0mJoZOPxtF0vvasjEEbp0B3qe0wOn\nDQ0NRCUJNNKJbJOfE8VEKnDZ/lx+f/XXk9eINwvElDrLqUBQtr+TxjbyYQKBgAxQ\nlZ8Y9/TbajsFJDzcC/XhzxckjyjisbGoqNFIkfevJNN8EQgiD24f0Py+swUChtHK\njtiI8WCxMwGLCiYs9THxRKd8O1HW73fswy32BBvcfU9F//7OW9UTSXY+YlLfLrrq\nP/3UqAN0L6y/kxGMJAfLpEEdaC+IS1Y8yc531/ZxAoGASYiasDpePtmzXklDxk3h\njEw64QAdXK2p/xTMjSeTtcqJ7fvaEbg+Mfpxq0mdTjfbTdR9U/nzAkwS7OoZZ4Du\nueMVls0IVqcNnBtikG8wgdxN27b5JPXS+GzQ0zDSpWFfRPZiIh37BAXr0D1voluJ\nrEHkcals6p7hL98BoxjFIvA=\n-----END PRIVATE KEY-----\n",
});
export const invalidTls = Object.freeze({

View File

@@ -1,6 +1,6 @@
import type { Server, ServerWebSocket, Socket } from "bun";
import { describe, expect, test } from "bun:test";
import { bunEnv, bunExe, rejectUnauthorizedScope, tempDirWithFiles } from "harness";
import { bunEnv, bunExe, rejectUnauthorizedScope, tempDirWithFiles, tls } from "harness";
import path from "path";
describe("Server", () => {
@@ -405,10 +405,7 @@ describe("Server", () => {
test("handshake failures should not impact future connections", async () => {
using server = Bun.serve({
tls: {
cert: "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIUHaenuNcUAu0tjDZGpc7fK4EX78gwDQYJKoZIhvcNAQEL\nBQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh\nbmNpc2NvMQ0wCwYDVQQKDARPdmVuMREwDwYDVQQLDAhUZWFtIEJ1bjETMBEGA1UE\nAwwKc2VydmVyLWJ1bjAeFw0yMzA5MDYyMzI3MzRaFw0yNTA5MDUyMzI3MzRaMGkx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj\nbzENMAsGA1UECgwET3ZlbjERMA8GA1UECwwIVGVhbSBCdW4xEzARBgNVBAMMCnNl\ncnZlci1idW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+7odzr3yI\nYewRNRGIubF5hzT7Bym2dDab4yhaKf5drL+rcA0J15BM8QJ9iSmL1ovg7x35Q2MB\nKw3rl/Yyy3aJS8whZTUze522El72iZbdNbS+oH6GxB2gcZB6hmUehPjHIUH4icwP\ndwVUeR6fB7vkfDddLXe0Tb4qsO1EK8H0mr5PiQSXfj39Yc1QHY7/gZ/xeSrt/6yn\n0oH9HbjF2XLSL2j6cQPKEayartHN0SwzwLi0eWSzcziVPSQV7c6Lg9UuIHbKlgOF\nzDpcp1p1lRqv2yrT25im/dS6oy9XX+p7EfZxqeqpXX2fr5WKxgnzxI3sW93PG8FU\nIDHtnUsoHX3RAgMBAAGjTzBNMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQ\nAAAAAAAAAAAAAAAAAAAAATAdBgNVHQ4EFgQUF3y/su4J/8ScpK+rM2LwTct6EQow\nDQYJKoZIhvcNAQELBQADggEBAGWGWp59Bmrk3Gt0bidFLEbvlOgGPWCT9ZrJUjgc\nhY44E+/t4gIBdoKOSwxo1tjtz7WsC2IYReLTXh1vTsgEitk0Bf4y7P40+pBwwZwK\naeIF9+PC6ZoAkXGFRoyEalaPVQDBg/DPOMRG9OH0lKfen9OGkZxmmjRLJzbyfAhU\noI/hExIjV8vehcvaJXmkfybJDYOYkN4BCNqPQHNf87ZNdFCb9Zgxwp/Ou+47J5k4\n5plQ+K7trfKXG3ABMbOJXNt1b0sH8jnpAsyHY4DLEQqxKYADbXsr3YX/yy6c0eOo\nX2bHGD1+zGsb7lGyNyoZrCZ0233glrEM4UxmvldBcWwOWfk=\n-----END CERTIFICATE-----\n",
key: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+7odzr3yIYewR\nNRGIubF5hzT7Bym2dDab4yhaKf5drL+rcA0J15BM8QJ9iSmL1ovg7x35Q2MBKw3r\nl/Yyy3aJS8whZTUze522El72iZbdNbS+oH6GxB2gcZB6hmUehPjHIUH4icwPdwVU\neR6fB7vkfDddLXe0Tb4qsO1EK8H0mr5PiQSXfj39Yc1QHY7/gZ/xeSrt/6yn0oH9\nHbjF2XLSL2j6cQPKEayartHN0SwzwLi0eWSzcziVPSQV7c6Lg9UuIHbKlgOFzDpc\np1p1lRqv2yrT25im/dS6oy9XX+p7EfZxqeqpXX2fr5WKxgnzxI3sW93PG8FUIDHt\nnUsoHX3RAgMBAAECggEAAckMqkn+ER3c7YMsKRLc5bUE9ELe+ftUwfA6G+oXVorn\nE+uWCXGdNqI+TOZkQpurQBWn9IzTwv19QY+H740cxo0ozZVSPE4v4czIilv9XlVw\n3YCNa2uMxeqp76WMbz1xEhaFEgn6ASTVf3hxYJYKM0ljhPX8Vb8wWwlLONxr4w4X\nOnQAB5QE7i7LVRsQIpWKnGsALePeQjzhzUZDhz0UnTyGU6GfC+V+hN3RkC34A8oK\njR3/Wsjahev0Rpb+9Pbu3SgTrZTtQ+srlRrEsDG0wVqxkIk9ueSMOHlEtQ7zYZsk\nlX59Bb8LHNGQD5o+H1EDaC6OCsgzUAAJtDRZsPiZEQKBgQDs+YtVsc9RDMoC0x2y\nlVnP6IUDXt+2UXndZfJI3YS+wsfxiEkgK7G3AhjgB+C+DKEJzptVxP+212hHnXgr\n1gfW/x4g7OWBu4IxFmZ2J/Ojor+prhHJdCvD0VqnMzauzqLTe92aexiexXQGm+WW\nwRl3YZLmkft3rzs3ZPhc1G2X9QKBgQDOQq3rrxcvxSYaDZAb+6B/H7ZE4natMCiz\nLx/cWT8n+/CrJI2v3kDfdPl9yyXIOGrsqFgR3uhiUJnz+oeZFFHfYpslb8KvimHx\nKI+qcVDcprmYyXj2Lrf3fvj4pKorc+8TgOBDUpXIFhFDyM+0DmHLfq+7UqvjU9Hs\nkjER7baQ7QKBgQDTh508jU/FxWi9RL4Jnw9gaunwrEt9bxUc79dp+3J25V+c1k6Q\nDPDBr3mM4PtYKeXF30sBMKwiBf3rj0CpwI+W9ntqYIwtVbdNIfWsGtV8h9YWHG98\nJ9q5HLOS9EAnogPuS27walj7wL1k+NvjydJ1of+DGWQi3aQ6OkMIegap0QKBgBlR\nzCHLa5A8plG6an9U4z3Xubs5BZJ6//QHC+Uzu3IAFmob4Zy+Lr5/kITlpCyw6EdG\n3xDKiUJQXKW7kluzR92hMCRnVMHRvfYpoYEtydxcRxo/WS73SzQBjTSQmicdYzLE\ntkLtZ1+ZfeMRSpXy0gR198KKAnm0d2eQBqAJy0h9AoGBAM80zkd+LehBKq87Zoh7\ndtREVWslRD1C5HvFcAxYxBybcKzVpL89jIRGKB8SoZkF7edzhqvVzAMP0FFsEgCh\naClYGtO+uo+B91+5v2CCqowRJUGfbFOtCuSPR7+B3LDK8pkjK2SQ0mFPUfRA5z0z\nNVWtC0EYNBTRkqhYtqr3ZpUc\n-----END PRIVATE KEY-----\n",
},
tls,
fetch() {
return new Response("Hello");
},

View File

@@ -90,3 +90,71 @@ test("node-fetch uses node streams instead of web streams", async () => {
expect(Buffer.concat(chunks).toString()).toBe("hello world");
}
});
test("node-fetch request body streams properly", async () => {
let responseResolve;
const responsePromise = new Promise(resolve => {
responseResolve = resolve;
});
let receivedChunks = [];
let requestBodyComplete = false;
using server = Bun.serve({
port: 0,
async fetch(req, server) {
const reader = req.body.getReader();
// Read first chunk
const { value: firstChunk } = await reader.read();
receivedChunks.push(firstChunk);
// Signal that response can be sent
responseResolve();
// Continue reading remaining chunks
let result;
while (!(result = await reader.read()).done) {
receivedChunks.push(result.value);
}
requestBodyComplete = true;
return new Response("response sent");
},
});
const requestBody = new stream.Readable({
read() {
// Will be controlled manually
},
});
// Start the fetch request
const fetchPromise = fetch2(server.url.href, {
body: requestBody,
method: "POST",
});
// Send first chunk
requestBody.push("first chunk");
// Wait for response to be available (server has read first chunk)
await responsePromise;
// Response is available, but request body should still be streaming
expect(requestBodyComplete).toBe(false);
// Send more data after response is available
requestBody.push("second chunk");
requestBody.push("third chunk");
requestBody.push(null); // End the stream
// Now wait for the fetch to complete
const result = await fetchPromise;
expect(await result.text()).toBe("response sent");
// Verify all chunks were received
const allData = Buffer.concat(receivedChunks).toString();
expect(allData).toBe("first chunksecond chunkthird chunk");
expect(requestBodyComplete).toBe(true);
});

View File

@@ -2,8 +2,6 @@ import { expect, test } from "bun:test";
import perf from "perf_hooks";
test("stubs", () => {
expect(!!perf.monitorEventLoopDelay).toBeFalse();
expect(() => perf.monitorEventLoopDelay()).toThrow();
expect(perf.performance.nodeTiming).toBeObject();
expect(perf.performance.now()).toBeNumber();

View File

@@ -1,47 +0,0 @@
'use strict';
const common = require('../common');
const assert = require('assert');
const net = require('net');
{
const server = net.createServer(common.mustCall((socket) => {
socket.end(Buffer.alloc(1024));
})).listen(0, common.mustCall(() => {
const socket = net.connect(server.address().port);
assert.strictEqual(socket.allowHalfOpen, false);
socket.resume();
socket.on('end', common.mustCall(() => {
process.nextTick(() => {
// Ensure socket is not destroyed straight away
// without proper shutdown.
assert(!socket.destroyed);
server.close();
});
}));
socket.on('finish', common.mustCall(() => {
assert(!socket.destroyed);
}));
socket.on('close', common.mustCall());
}));
}
{
const server = net.createServer(common.mustCall((socket) => {
socket.end(Buffer.alloc(1024));
})).listen(0, common.mustCall(() => {
const socket = net.connect(server.address().port);
assert.strictEqual(socket.allowHalfOpen, false);
socket.resume();
socket.on('end', common.mustCall(() => {
assert(!socket.destroyed);
}));
socket.end('asd');
socket.on('finish', common.mustCall(() => {
assert(!socket.destroyed);
}));
socket.on('close', common.mustCall(() => {
server.close();
}));
}));
}

View File

@@ -0,0 +1,110 @@
// Flags: --expose-gc --expose-internals
'use strict';
const common = require('../common');
const assert = require('assert');
const os = require('os');
const {
monitorEventLoopDelay
} = require('perf_hooks');
const sleep = typeof Bun === 'object' ? Bun.sleepSync : require('internal/util').sleep;
{
const histogram = monitorEventLoopDelay();
assert(histogram);
assert(histogram.enable());
assert(!histogram.enable());
histogram.reset();
assert(histogram.disable());
assert(!histogram.disable());
}
{
[null, 'a', 1, false, Infinity].forEach((i) => {
assert.throws(
() => monitorEventLoopDelay(i),
{
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE'
}
);
});
[null, 'a', false, {}, []].forEach((i) => {
assert.throws(
() => monitorEventLoopDelay({ resolution: i }),
{
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE'
}
);
});
[-1, 0, 2 ** 53, Infinity].forEach((i) => {
assert.throws(
() => monitorEventLoopDelay({ resolution: i }),
{
name: 'RangeError',
code: 'ERR_OUT_OF_RANGE'
}
);
});
}
{
const s390x = os.arch() === 's390x';
const histogram = monitorEventLoopDelay({ resolution: 1 });
histogram.enable();
let m = 5;
if (s390x) {
m = m * 2;
}
function spinAWhile() {
sleep(1000);
if (--m > 0) {
setTimeout(spinAWhile, common.platformTimeout(500));
} else {
histogram.disable();
// The values are non-deterministic, so we just check that a value is
// present, as opposed to a specific value.
assert(histogram.min > 0);
assert(histogram.max > 0);
assert(histogram.stddev > 0);
assert(histogram.mean > 0);
assert(histogram.percentiles.size > 0);
for (let n = 1; n < 100; n = n + 0.1) {
assert(histogram.percentile(n) >= 0);
}
histogram.reset();
assert.strictEqual(histogram.min, 9223372036854776000);
assert.strictEqual(histogram.max, 0);
assert(Number.isNaN(histogram.stddev));
assert(Number.isNaN(histogram.mean));
assert.strictEqual(histogram.percentiles.size, 1);
['a', false, {}, []].forEach((i) => {
assert.throws(
() => histogram.percentile(i),
{
name: 'TypeError',
code: 'ERR_INVALID_ARG_TYPE'
}
);
});
[-1, 0, 101, NaN].forEach((i) => {
assert.throws(
() => histogram.percentile(i),
{
name: 'RangeError',
code: 'ERR_OUT_OF_RANGE'
}
);
});
}
}
spinAWhile();
}
// Make sure that the histogram instances can be garbage-collected without
// and not just implicitly destroyed when the Environment is torn down.
process.on('exit', global.gc);

View File

@@ -236,23 +236,23 @@ for (const { name, connect } of tests) {
});
expect(cert.subjectaltname).toBe("DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1");
expect(cert.infoAccess).toBeUndefined();
expect(cert.ca).toBeFalse();
expect(cert.ca).toBe(true);
expect(cert.bits).toBe(2048);
expect(cert.modulus).toBe(
"beee8773af7c8861ec11351188b9b1798734fb0729b674369be3285a29fe5dacbfab700d09d7904cf1027d89298bd68be0ef1df94363012b0deb97f632cb76894bcc216535337b9db6125ef68996dd35b4bea07e86c41da071907a86651e84f8c72141f889cc0f770554791e9f07bbe47c375d2d77b44dbe2ab0ed442bc1f49abe4f8904977e3dfd61cd501d8eff819ff1792aedffaca7d281fd1db8c5d972d22f68fa7103ca11ac9aaed1cdd12c33c0b8b47964b37338953d2415edce8b83d52e2076ca960385cc3a5ca75a75951aafdb2ad3db98a6fdd4baa32f575fea7b11f671a9eaa95d7d9faf958ac609f3c48dec5bddcf1bc1542031ed9d4b281d7dd1",
"e5633a2c8118171cbeaf321d55d0444586cbe566bb51a234b0ead69faf7490069854efddffac68986652ff949f472252e4c7d24c6ee4e3366e54d9e4701e24d021e583e1a088112c0f96475a558b42f883a3e796c937cc4d6bb8791b227017b3e73deb40b0ac84f033019f580a3216888acec71ce52d938fcadd8e29794e38774e33d323ede89b58e526ef8b513ba465fa4ffd9cf6c1ec7480de0dcb569dec295d7b3cce40256b428d5907e90e7a52e77c3101f4ad4c0e254ab03d75ac42ee1668a5094bc4521b264fb404b6c4b17b6b279e13e6282e1e4fb6303540cb830ea8ff576ca57b7861e4ef797af824b0987c870718780a1c5141e4f904fd0c5139f5",
);
expect(cert.exponent).toBe("0x10001");
expect(cert.pubkey).toBeInstanceOf(Buffer);
expect(cert.valid_from).toBe("Sep 6 23:27:34 2023 GMT"); // yes this space is intentional
expect(cert.valid_to).toBe("Sep 5 23:27:34 2025 GMT");
expect(cert.fingerprint).toBe("E3:90:9C:A8:AB:80:48:37:8D:CE:11:64:45:3A:EB:AD:C8:3C:B3:5C");
expect(cert.valid_from).toBe("Sep 6 03:00:49 2025 GMT"); // yes this space is intentional
expect(cert.valid_to).toBe("Sep 4 03:00:49 2035 GMT");
expect(cert.fingerprint).toBe("D2:5E:B9:AD:8B:48:3B:7A:35:D3:1A:45:BD:32:AC:AD:55:4A:BA:AD");
expect(cert.fingerprint256).toBe(
"53:DD:15:78:60:FD:66:8C:43:9E:19:7E:CF:2C:AF:49:3C:D1:11:EC:61:2D:F5:DC:1D:0A:FA:CD:12:F9:F8:E0",
"85:F4:47:0C:6D:D8:DE:C8:68:77:7C:5E:3F:9B:56:A6:D3:69:C7:C2:1A:E8:B8:F8:1C:16:1D:04:78:A0:E9:91",
);
expect(cert.fingerprint512).toBe(
"2D:31:CB:D2:A0:CA:E5:D4:B5:59:11:48:4B:BC:65:11:4F:AB:02:24:59:D8:73:43:2F:9A:31:92:BC:AF:26:66:CD:DB:8B:03:74:0C:C1:84:AF:54:2D:7C:FD:EF:07:6E:85:66:98:6B:82:4F:A5:72:97:A2:19:8C:7B:57:D6:15",
"CE:00:17:97:29:5E:1C:7E:59:86:8D:1F:F0:F4:AF:A0:B0:10:F2:2E:0E:79:D1:32:D0:44:F9:B4:3A:DE:D5:83:A9:15:0E:E4:47:24:D4:2A:10:FB:21:BE:3A:38:21:FC:40:20:B3:BC:52:64:F7:38:93:EF:C9:3F:C8:57:89:31",
);
expect(cert.serialNumber).toBe("1da7a7b8d71402ed2d8c3646a5cedf2b8117efc8");
expect(cert.serialNumber).toBe("71a46ae89fd817ef81a34d5973e1de42f09b9d63");
expect(cert.raw).toBeInstanceOf(Buffer);
} finally {
socket.end();

View File

@@ -316,24 +316,24 @@ describe("tls.createServer", () => {
ST: "CA",
});
expect(cert.ca).toBeFalse();
expect(cert.ca).toBe(true);
expect(cert.bits).toBe(2048);
expect(cert.modulus).toBe(
"beee8773af7c8861ec11351188b9b1798734fb0729b674369be3285a29fe5dacbfab700d09d7904cf1027d89298bd68be0ef1df94363012b0deb97f632cb76894bcc216535337b9db6125ef68996dd35b4bea07e86c41da071907a86651e84f8c72141f889cc0f770554791e9f07bbe47c375d2d77b44dbe2ab0ed442bc1f49abe4f8904977e3dfd61cd501d8eff819ff1792aedffaca7d281fd1db8c5d972d22f68fa7103ca11ac9aaed1cdd12c33c0b8b47964b37338953d2415edce8b83d52e2076ca960385cc3a5ca75a75951aafdb2ad3db98a6fdd4baa32f575fea7b11f671a9eaa95d7d9faf958ac609f3c48dec5bddcf1bc1542031ed9d4b281d7dd1",
"e5633a2c8118171cbeaf321d55d0444586cbe566bb51a234b0ead69faf7490069854efddffac68986652ff949f472252e4c7d24c6ee4e3366e54d9e4701e24d021e583e1a088112c0f96475a558b42f883a3e796c937cc4d6bb8791b227017b3e73deb40b0ac84f033019f580a3216888acec71ce52d938fcadd8e29794e38774e33d323ede89b58e526ef8b513ba465fa4ffd9cf6c1ec7480de0dcb569dec295d7b3cce40256b428d5907e90e7a52e77c3101f4ad4c0e254ab03d75ac42ee1668a5094bc4521b264fb404b6c4b17b6b279e13e6282e1e4fb6303540cb830ea8ff576ca57b7861e4ef797af824b0987c870718780a1c5141e4f904fd0c5139f5",
);
expect(cert.exponent).toBe("0x10001");
expect(cert.pubkey).toBeInstanceOf(Buffer);
// yes these spaces are intentional
expect(cert.valid_from).toBe("Sep 6 23:27:34 2023 GMT");
expect(cert.valid_to).toBe("Sep 5 23:27:34 2025 GMT");
expect(cert.fingerprint).toBe("E3:90:9C:A8:AB:80:48:37:8D:CE:11:64:45:3A:EB:AD:C8:3C:B3:5C");
expect(cert.valid_from).toBe("Sep 6 03:00:49 2025 GMT");
expect(cert.valid_to).toBe("Sep 4 03:00:49 2035 GMT");
expect(cert.fingerprint).toBe("D2:5E:B9:AD:8B:48:3B:7A:35:D3:1A:45:BD:32:AC:AD:55:4A:BA:AD");
expect(cert.fingerprint256).toBe(
"53:DD:15:78:60:FD:66:8C:43:9E:19:7E:CF:2C:AF:49:3C:D1:11:EC:61:2D:F5:DC:1D:0A:FA:CD:12:F9:F8:E0",
"85:F4:47:0C:6D:D8:DE:C8:68:77:7C:5E:3F:9B:56:A6:D3:69:C7:C2:1A:E8:B8:F8:1C:16:1D:04:78:A0:E9:91",
);
expect(cert.fingerprint512).toBe(
"2D:31:CB:D2:A0:CA:E5:D4:B5:59:11:48:4B:BC:65:11:4F:AB:02:24:59:D8:73:43:2F:9A:31:92:BC:AF:26:66:CD:DB:8B:03:74:0C:C1:84:AF:54:2D:7C:FD:EF:07:6E:85:66:98:6B:82:4F:A5:72:97:A2:19:8C:7B:57:D6:15",
"CE:00:17:97:29:5E:1C:7E:59:86:8D:1F:F0:F4:AF:A0:B0:10:F2:2E:0E:79:D1:32:D0:44:F9:B4:3A:DE:D5:83:A9:15:0E:E4:47:24:D4:2A:10:FB:21:BE:3A:38:21:FC:40:20:B3:BC:52:64:F7:38:93:EF:C9:3F:C8:57:89:31",
);
expect(cert.serialNumber).toBe("1da7a7b8d71402ed2d8c3646a5cedf2b8117efc8");
expect(cert.serialNumber).toBe("71a46ae89fd817ef81a34d5973e1de42f09b9d63");
expect(cert.raw).toBeInstanceOf(Buffer);
client?.end();

View File

@@ -1,7 +1,7 @@
const server = require("https").createServer(
{
cert: "-----BEGIN CERTIFICATE-----\nMIIDrzCCApegAwIBAgIUHaenuNcUAu0tjDZGpc7fK4EX78gwDQYJKoZIhvcNAQEL\nBQAwaTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1TYW4gRnJh\nbmNpc2NvMQ0wCwYDVQQKDARPdmVuMREwDwYDVQQLDAhUZWFtIEJ1bjETMBEGA1UE\nAwwKc2VydmVyLWJ1bjAeFw0yMzA5MDYyMzI3MzRaFw0yNTA5MDUyMzI3MzRaMGkx\nCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNj\nbzENMAsGA1UECgwET3ZlbjERMA8GA1UECwwIVGVhbSBCdW4xEzARBgNVBAMMCnNl\ncnZlci1idW4wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC+7odzr3yI\nYewRNRGIubF5hzT7Bym2dDab4yhaKf5drL+rcA0J15BM8QJ9iSmL1ovg7x35Q2MB\nKw3rl/Yyy3aJS8whZTUze522El72iZbdNbS+oH6GxB2gcZB6hmUehPjHIUH4icwP\ndwVUeR6fB7vkfDddLXe0Tb4qsO1EK8H0mr5PiQSXfj39Yc1QHY7/gZ/xeSrt/6yn\n0oH9HbjF2XLSL2j6cQPKEayartHN0SwzwLi0eWSzcziVPSQV7c6Lg9UuIHbKlgOF\nzDpcp1p1lRqv2yrT25im/dS6oy9XX+p7EfZxqeqpXX2fr5WKxgnzxI3sW93PG8FU\nIDHtnUsoHX3RAgMBAAGjTzBNMCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQ\nAAAAAAAAAAAAAAAAAAAAATAdBgNVHQ4EFgQUF3y/su4J/8ScpK+rM2LwTct6EQow\nDQYJKoZIhvcNAQELBQADggEBAGWGWp59Bmrk3Gt0bidFLEbvlOgGPWCT9ZrJUjgc\nhY44E+/t4gIBdoKOSwxo1tjtz7WsC2IYReLTXh1vTsgEitk0Bf4y7P40+pBwwZwK\naeIF9+PC6ZoAkXGFRoyEalaPVQDBg/DPOMRG9OH0lKfen9OGkZxmmjRLJzbyfAhU\noI/hExIjV8vehcvaJXmkfybJDYOYkN4BCNqPQHNf87ZNdFCb9Zgxwp/Ou+47J5k4\n5plQ+K7trfKXG3ABMbOJXNt1b0sH8jnpAsyHY4DLEQqxKYADbXsr3YX/yy6c0eOo\nX2bHGD1+zGsb7lGyNyoZrCZ0233glrEM4UxmvldBcWwOWfk=\n-----END CERTIFICATE-----\n",
key: "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC+7odzr3yIYewR\nNRGIubF5hzT7Bym2dDab4yhaKf5drL+rcA0J15BM8QJ9iSmL1ovg7x35Q2MBKw3r\nl/Yyy3aJS8whZTUze522El72iZbdNbS+oH6GxB2gcZB6hmUehPjHIUH4icwPdwVU\neR6fB7vkfDddLXe0Tb4qsO1EK8H0mr5PiQSXfj39Yc1QHY7/gZ/xeSrt/6yn0oH9\nHbjF2XLSL2j6cQPKEayartHN0SwzwLi0eWSzcziVPSQV7c6Lg9UuIHbKlgOFzDpc\np1p1lRqv2yrT25im/dS6oy9XX+p7EfZxqeqpXX2fr5WKxgnzxI3sW93PG8FUIDHt\nnUsoHX3RAgMBAAECggEAAckMqkn+ER3c7YMsKRLc5bUE9ELe+ftUwfA6G+oXVorn\nE+uWCXGdNqI+TOZkQpurQBWn9IzTwv19QY+H740cxo0ozZVSPE4v4czIilv9XlVw\n3YCNa2uMxeqp76WMbz1xEhaFEgn6ASTVf3hxYJYKM0ljhPX8Vb8wWwlLONxr4w4X\nOnQAB5QE7i7LVRsQIpWKnGsALePeQjzhzUZDhz0UnTyGU6GfC+V+hN3RkC34A8oK\njR3/Wsjahev0Rpb+9Pbu3SgTrZTtQ+srlRrEsDG0wVqxkIk9ueSMOHlEtQ7zYZsk\nlX59Bb8LHNGQD5o+H1EDaC6OCsgzUAAJtDRZsPiZEQKBgQDs+YtVsc9RDMoC0x2y\nlVnP6IUDXt+2UXndZfJI3YS+wsfxiEkgK7G3AhjgB+C+DKEJzptVxP+212hHnXgr\n1gfW/x4g7OWBu4IxFmZ2J/Ojor+prhHJdCvD0VqnMzauzqLTe92aexiexXQGm+WW\nwRl3YZLmkft3rzs3ZPhc1G2X9QKBgQDOQq3rrxcvxSYaDZAb+6B/H7ZE4natMCiz\nLx/cWT8n+/CrJI2v3kDfdPl9yyXIOGrsqFgR3uhiUJnz+oeZFFHfYpslb8KvimHx\nKI+qcVDcprmYyXj2Lrf3fvj4pKorc+8TgOBDUpXIFhFDyM+0DmHLfq+7UqvjU9Hs\nkjER7baQ7QKBgQDTh508jU/FxWi9RL4Jnw9gaunwrEt9bxUc79dp+3J25V+c1k6Q\nDPDBr3mM4PtYKeXF30sBMKwiBf3rj0CpwI+W9ntqYIwtVbdNIfWsGtV8h9YWHG98\nJ9q5HLOS9EAnogPuS27walj7wL1k+NvjydJ1of+DGWQi3aQ6OkMIegap0QKBgBlR\nzCHLa5A8plG6an9U4z3Xubs5BZJ6//QHC+Uzu3IAFmob4Zy+Lr5/kITlpCyw6EdG\n3xDKiUJQXKW7kluzR92hMCRnVMHRvfYpoYEtydxcRxo/WS73SzQBjTSQmicdYzLE\ntkLtZ1+ZfeMRSpXy0gR198KKAnm0d2eQBqAJy0h9AoGBAM80zkd+LehBKq87Zoh7\ndtREVWslRD1C5HvFcAxYxBybcKzVpL89jIRGKB8SoZkF7edzhqvVzAMP0FFsEgCh\naClYGtO+uo+B91+5v2CCqowRJUGfbFOtCuSPR7+B3LDK8pkjK2SQ0mFPUfRA5z0z\nNVWtC0EYNBTRkqhYtqr3ZpUc\n-----END PRIVATE KEY-----\n",
cert: process.env.SERVER_CERT,
key: process.env.SERVER_KEY,
rejectUnauthorized: false,
hostname: "localhost",
minVersion: "TLSv1.2",

View File

@@ -1,5 +1,6 @@
import type { Subprocess } from "bun";
import { afterAll, beforeAll, expect, it } from "bun:test";
import { bunEnv, tls } from "harness";
import type { IncomingMessage } from "http";
import { join } from "path";
let url: URL;
@@ -9,6 +10,11 @@ beforeAll(async () => {
stdout: "pipe",
stderr: "inherit",
stdin: "ignore",
env: {
...bunEnv,
SERVER_CERT: tls.cert,
SERVER_KEY: tls.key,
},
});
const { value } = await process.stdout.getReader().read();
url = new URL(new TextDecoder().decode(value));

View File

@@ -0,0 +1,63 @@
// Regression test for https://github.com/microlinkhq/youtube-dl-exec/issues/246
// Child process stdio properties should be enumerable for Object.assign() compatibility
import { expect, test } from "bun:test";
import { spawn } from "child_process";
test("child process stdio properties should be enumerable for Object.assign()", () => {
const child = spawn(process.execPath, ["-e", 'console.log("hello")']);
// The real issue: stdio properties must be enumerable for Object.assign() to work
// This is what libraries like tinyspawn depend on
expect(Object.keys(child)).toContain("stdin");
expect(Object.keys(child)).toContain("stdout");
expect(Object.keys(child)).toContain("stderr");
expect(Object.keys(child)).toContain("stdio");
// Property descriptors should show enumerable: true
for (const key of ["stdin", "stdout", "stderr", "stdio"] as const) {
expect(Object.getOwnPropertyDescriptor(child, key)?.enumerable).toBe(true);
}
});
test("Object.assign should copy child process stdio properties", () => {
const child = spawn(process.execPath, ["-e", 'console.log("hello")']);
// This is what tinyspawn does: Object.assign(promise, childProcess)
const merged = {};
Object.assign(merged, child);
// The merged object should have the stdio properties
expect(merged.stdout).toBeTruthy();
expect(merged.stderr).toBeTruthy();
expect(merged.stdin).toBeTruthy();
expect(merged.stdio).toBeTruthy();
// Should maintain stream functionality
expect(typeof merged.stdout.pipe).toBe("function");
expect(typeof merged.stdout.on).toBe("function");
});
test("tinyspawn-like library usage should work", () => {
// Simulate the exact pattern from tinyspawn library
let childProcess;
const promise = new Promise(resolve => {
childProcess = spawn(process.execPath, ["-e", 'console.log("test")']);
childProcess.on("exit", () => resolve(childProcess));
});
// This is the critical line that was failing in Bun
const subprocess = Object.assign(promise, childProcess);
// Should have stdio properties immediately after Object.assign
expect(subprocess.stdout).toBeTruthy();
expect(subprocess.stderr).toBeTruthy();
expect(subprocess.stdin).toBeTruthy();
// Should still be a Promise
expect(subprocess instanceof Promise).toBe(true);
// Should have stream methods available
expect(typeof subprocess.stdout.pipe).toBe("function");
expect(typeof subprocess.stdout.on).toBe("function");
});

View File

@@ -0,0 +1,185 @@
import { describe, expect, test } from "bun:test";
import { bunEnv, tempDir } from "harness";
import { exec, execSync, spawn, spawnSync } from "node:child_process";
import { writeFileSync } from "node:fs";
import { join } from "node:path";
describe("Batch file execution security on Windows", () => {
// Only run these tests on Windows
if (process.platform !== "win32") {
test.skip("Windows-only test", () => {});
return;
}
test("should prevent direct execution of .bat files without shell option", () => {
using dir = tempDir("batch-security");
const batFile = join(String(dir), "test.bat");
writeFileSync(batFile, "@echo test output");
// This should throw an error
expect(() => {
spawnSync(batFile, [], { env: bunEnv });
}).toThrow();
// Try with spawn (async)
const child = spawn(batFile, [], { env: bunEnv });
return new Promise((resolve, reject) => {
child.on("error", err => {
expect(err.code).toBe("EINVAL");
resolve();
});
child.on("exit", () => {
reject(new Error("Process should not have executed"));
});
});
});
test("should prevent direct execution of .cmd files without shell option", () => {
using dir = tempDir("batch-security");
const cmdFile = join(String(dir), "test.cmd");
writeFileSync(cmdFile, "@echo test output");
// This should throw an error
expect(() => {
spawnSync(cmdFile, [], { env: bunEnv });
}).toThrow();
// Try with spawn (async)
const child = spawn(cmdFile, [], { env: bunEnv });
return new Promise((resolve, reject) => {
child.on("error", err => {
expect(err.code).toBe("EINVAL");
resolve();
});
child.on("exit", () => {
reject(new Error("Process should not have executed"));
});
});
});
test("should allow execution of .bat files with shell: true", () => {
using dir = tempDir("batch-security");
const batFile = join(String(dir), "test.bat");
writeFileSync(batFile, "@echo test output");
// This should work
const result = spawnSync(batFile, [], {
shell: true,
encoding: "utf8",
env: bunEnv,
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("test output");
});
test("should allow execution of .cmd files with shell: true", () => {
using dir = tempDir("batch-security");
const cmdFile = join(String(dir), "test.cmd");
writeFileSync(cmdFile, "@echo test output");
// This should work
const result = spawnSync(cmdFile, [], {
shell: true,
encoding: "utf8",
env: bunEnv,
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("test output");
});
test("should prevent command injection in batch file arguments without shell", () => {
using dir = tempDir("batch-security");
const batFile = join(String(dir), "test.bat");
writeFileSync(batFile, "@echo %1");
// This should throw an error (batch files can't be executed without shell)
expect(() => {
spawnSync(batFile, ["&calc.exe"], { env: bunEnv });
}).toThrow();
// Also test with quotes
expect(() => {
spawnSync(batFile, ['"&calc.exe'], { env: bunEnv });
}).toThrow();
});
test("exec and execSync should work with batch files (they use shell by default)", () => {
using dir = tempDir("batch-security");
const batFile = join(String(dir), "test.bat");
writeFileSync(batFile, "@echo exec test");
// execSync uses shell by default
const result = execSync(`"${batFile}"`, {
encoding: "utf8",
env: bunEnv,
});
expect(result).toContain("exec test");
// exec uses shell by default
return new Promise((resolve, reject) => {
exec(`"${batFile}"`, { env: bunEnv }, (error, stdout, stderr) => {
if (error) {
reject(error);
} else {
expect(stdout).toContain("exec test");
resolve();
}
});
});
});
test("should handle case-insensitive batch file extensions", () => {
using dir = tempDir("batch-security");
const extensions = [".BAT", ".bAt", ".BaT", ".CMD", ".cMd", ".CmD"];
for (const ext of extensions) {
const file = join(String(dir), `test${ext}`);
writeFileSync(file, "@echo test");
// Should throw without shell
expect(() => {
spawnSync(file, [], { env: bunEnv });
}).toThrow();
// Should work with shell
const result = spawnSync(file, [], {
shell: true,
encoding: "utf8",
env: bunEnv,
});
expect(result.status).toBe(0);
}
});
test("should allow normal executables without shell", () => {
// Test that normal executables still work
const result = spawnSync("cmd.exe", ["/c", "echo", "test"], {
encoding: "utf8",
env: bunEnv,
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("test");
});
test("should check the actual file being executed, not arguments", () => {
using dir = tempDir("batch-security");
const batFile = join(String(dir), "test.bat");
writeFileSync(batFile, "@echo test");
// Even if we have .bat in arguments, should work if the executable is not a batch file
const result = spawnSync("cmd.exe", ["/c", "echo", "test.bat"], {
encoding: "utf8",
env: bunEnv,
});
expect(result.status).toBe(0);
expect(result.stdout).toContain("test.bat");
});
});