Compare commits

...

29 Commits

Author SHA1 Message Date
autofix-ci[bot]
93bbf7b7f2 [autofix.ci] apply automated fixes 2025-09-08 02:32:07 +00:00
Claude Bot
58ed44ff06 Fix node_modules stats counting during scanning phase
CRITICAL FIX: Files in node_modules are now correctly counted ONLY in
node_modules stats, not double-counted in language categories.

Previous bug:
- Files were added to language stats (JS/TS/etc) unconditionally
- THEN checked if in node_modules and added there too
- This caused massive overcounting and wrong percentages

Fixed by:
- Check if file is in node_modules FIRST
- If in node_modules, ONLY add to node_modules stats
- If NOT in node_modules, add to appropriate language/module stats

Also:
- Removed all the hacky percentage-based calculations
- Show raw stats as collected during scanning
- Your Code = Total - node_modules (simple subtraction)
- Fixed column widths to handle large numbers properly

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-08 02:29:42 +00:00
Claude Bot
ecc969c5a7 Remove high coupling warning from stats output
🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-08 01:48:49 +00:00
autofix-ci[bot]
607b7fa84c [autofix.ci] apply automated fixes 2025-09-08 01:43:29 +00:00
Claude Bot
bb924092ad Make stats table more compact and informative
- Reduced column widths to minimum needed
- Category column sized to fit longest category name (16 chars)
- Added all requested categories:
  - JavaScript, TypeScript
  - React Components (when detected)
  - Stylesheets
  - CommonJS Modules, ECMA Modules
  - node_modules (separate from source)
  - Your Code (excluding node_modules)
  - Tests
  - All Code (grand total)
- All counts properly exclude node_modules except for node_modules and All Code rows
- Added high coupling warning when imports/file > 10

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-08 01:41:32 +00:00
Claude Bot
5d78352248 Improve stats table formatting and terminology
- Replace 'Methods' with 'Functions' throughout
- Change F/C (Functions per Class) to F/M (Functions per Module)
- Use beautiful unicode box-drawing characters for table
- Switch from f32 to f64 for better precision
- Add chart emoji to code ratio display

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-08 00:44:18 +00:00
autofix-ci[bot]
09062a8ebe [autofix.ci] apply automated fixes 2025-09-08 00:35:15 +00:00
Claude Bot
b8f5b494ba Fix stats command to suppress module resolution errors
- Continue parsing files despite module resolution errors
- Suppress all error output during stats collection
- Remove unnecessary summary text output
- Stats now silently handles missing dependencies and continues

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-08 00:33:06 +00:00
autofix-ci[bot]
baed5516e0 [autofix.ci] apply automated fixes 2025-09-07 23:51:23 +00:00
Claude Bot
138858da1a Improve bun stats command with AST-based counting and cleaner output
- Use parsed AST to accurately count functions/classes instead of string searching
  - Count s_function, s_class statements
  - Check s_local declarations for function/class values
  - Check s_expr for function/class expressions
  - Check s_export_default for exported functions/classes
- Ignore module resolution and syntax errors so stats collection continues
- Remove unnecessary output text for cleaner, Rails-like formatting
- Add M/C (Methods per Class) and LOC/M (Lines per Method) metrics
- Safely handle CSS files and empty AST parts
- Fix ASAN errors by properly checking parts.len before access
2025-09-07 23:49:01 +00:00
autofix-ci[bot]
5bc237352b [autofix.ci] apply automated fixes 2025-09-07 13:56:55 +00:00
Claude Bot
d3cc0813a5 fix: correct bounds checking logic to prevent index out of bounds
- Use >= instead of < for bounds checking (array indices are 0-based)
- Add comprehensive bounds checks for both sources and loaders arrays
- Check bounds before accessing any array element
- This prevents crashes when processing files with mismatched array sizes
2025-09-07 13:54:43 +00:00
autofix-ci[bot]
6898e8a76e [autofix.ci] apply automated fixes 2025-09-07 13:26:44 +00:00
Claude Bot
5be0ade599 fix: change target to .bun and add error logging for resolver failures
- Use .bun target instead of .browser to properly resolve test files and Bun-specific imports
- Add error logging to show bundler errors when files fail to resolve
- This allows the stats command to work with test files and Bun modules
2025-09-07 13:24:14 +00:00
Claude Bot
33aa520794 refactor: remove Buntastic rating system, keep speed metric
Keep the clean speed metric showing LOC analyzed and time taken,
but remove the rating system to keep the feature focused.
2025-09-07 13:04:03 +00:00
Claude Bot
b2a6c0ae44 feat: add BUNTASTIC rating system to bun stats
- Add speed flex message showing LOC analyzed and time taken
- Add Bun API usage detection and rating system (1-10)
- Show encouraging messages based on Bun API usage
- Track imports of 'bun' and 'bun:*' modules
- Detect usage of Bun.* in source code

The rating system encourages developers to use more Bun APIs
by giving them a fun score and helpful suggestions.
2025-09-07 12:52:19 +00:00
Claude Bot
b1ce142e29 fix: improve bun stats implementation
- Fix type mismatches in stats_command.zig
- Add proper handling for virtual and builtin files
- Skip runtime file (index 0) in statistics
- Handle bounds checking for AST arrays
- Fix export count calculation for named exports

The stats command now correctly processes files using the bundler's
DependencyScanner and generates accurate statistics.
2025-09-07 12:34:06 +00:00
Claude Bot
5e8e8423f1 “wip” 2025-09-07 12:08:33 +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
35 changed files with 2179 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

@@ -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

@@ -91,6 +91,7 @@ pub const PackCommand = @import("./cli/pack_command.zig").PackCommand;
pub const AuditCommand = @import("./cli/audit_command.zig").AuditCommand;
pub const InitCommand = @import("./cli/init_command.zig").InitCommand;
pub const WhyCommand = @import("./cli/why_command.zig").WhyCommand;
pub const StatsCommand = @import("./cli/stats_command.zig").StatsCommand;
pub const Arguments = @import("./cli/Arguments.zig");
@@ -380,6 +381,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,
@@ -587,6 +590,7 @@ pub const Command = struct {
RootCommandMatcher.case("prune") => .ReservedCommand,
RootCommandMatcher.case("list") => .ReservedCommand,
RootCommandMatcher.case("why") => .WhyCommand,
RootCommandMatcher.case("stats") => .StatsCommand,
RootCommandMatcher.case("-e") => .AutoCommand,
@@ -758,6 +762,11 @@ pub const Command = struct {
try WhyCommand.exec(ctx);
return;
},
.StatsCommand => {
const ctx = try Command.init(allocator, log, .StatsCommand);
try StatsCommand.exec(ctx);
return;
},
.BunxCommand => {
const ctx = try Command.init(allocator, log, .BunxCommand);
@@ -815,7 +824,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 +863,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);
@@ -931,6 +940,7 @@ pub const Command = struct {
PublishCommand,
AuditCommand,
WhyCommand,
StatsCommand,
/// Used by crash reports.
///
@@ -968,6 +978,7 @@ pub const Command = struct {
.PublishCommand => 'k',
.AuditCommand => 'A',
.WhyCommand => 'W',
.StatsCommand => 'S',
};
}
@@ -1280,6 +1291,19 @@ pub const Command = struct {
Output.pretty(intro_text, .{});
Output.flush();
},
.StatsCommand => {
const intro_text =
\\<b>Usage<r>: <b><green>bun stats<r>
\\Generate a comprehensive code statistics report for your project
\\
\\Shows file counts, lines of code, imports/exports, and more.
\\Analyzes JavaScript, TypeScript, CSS, and JSON files.
\\
;
Output.pretty(intro_text, .{});
Output.flush();
},
else => {
HelpCommand.printWithReason(.explicit);
},

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) {
@@ -1111,6 +1114,14 @@ pub fn parse(allocator: std.mem.Allocator, ctx: Command.Context, comptime cmd: C
entry_points = entry_points[1..];
}
},
.StatsCommand => {
if (entry_points.len > 0 and strings.eqlComptime(
entry_points[0],
"stats",
)) {
entry_points = entry_points[1..];
}
},
else => {},
}

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;

726
src/cli/stats_command.zig Normal file
View File

@@ -0,0 +1,726 @@
// Custom bundler generation that continues despite errors
fn generateForStats(
transpiler_: *transpiler.Transpiler,
alloc: std.mem.Allocator,
event_loop: bun.jsc.AnyEventLoop,
scanner: *BundleV2.DependenciesScanner,
) !void {
var this = BundleV2.init(
transpiler_,
null,
alloc,
event_loop,
false, // no hot reload
null,
.init(),
) catch {
// Even initialization errors should be ignored
return;
};
this.unique_key = bundle_v2.generateUniqueKey();
// Clear any initialization errors
this.transpiler.log.msgs.clearRetainingCapacity();
this.transpiler.log.errors = 0;
this.transpiler.log.warnings = 0;
// Enqueue entry points but continue even if there are errors
this.enqueueEntryPoints(.normal, this.transpiler.options.entry_points) catch {
// Continue anyway
};
// Clear errors again
this.transpiler.log.msgs.clearRetainingCapacity();
this.transpiler.log.errors = 0;
this.transpiler.log.warnings = 0;
// Wait for parsing to complete
this.waitForParse();
// Clear errors again
this.transpiler.log.msgs.clearRetainingCapacity();
this.transpiler.log.errors = 0;
this.transpiler.log.warnings = 0;
// Scan for secondary paths
this.scanForSecondaryPaths();
// Process server components but ignore errors
this.processServerComponentManifestFiles() catch {};
// Find reachable files
const reachable_files = this.findReachableFiles() catch {
// Return empty if we can't find reachable files
return;
};
// Call the scanner with whatever we managed to collect
this.getAllDependencies(reachable_files, scanner) catch {
// Continue even if dependency scanning fails
};
}
pub const StatsCommand = struct {
const FileStats = struct {
files: u32 = 0,
lines: u32 = 0,
loc: u32 = 0,
imports: u32 = 0,
exports: u32 = 0,
classes: u32 = 0,
functions: u32 = 0,
components: u32 = 0,
avg_size: u32 = 0,
};
const CategoryStats = struct {
typescript: FileStats = .{},
javascript: FileStats = .{},
jsx: FileStats = .{},
commonjs: FileStats = .{},
esmodules: FileStats = .{},
css: FileStats = .{},
json: FileStats = .{},
tests: FileStats = .{},
node_modules: FileStats = .{},
workspace_packages: std.StringHashMap(FileStats),
total: FileStats = .{},
components: u32 = 0,
};
const StatsContext = struct {
stats: CategoryStats,
allocator: std.mem.Allocator,
workspace_packages: [][]const u8,
};
fn countLinesAndLOC(content: []const u8) struct { lines: u32, loc: u32 } {
var lines: u32 = if (content.len > 0) 1 else 0;
var loc: u32 = 0;
var i: usize = 0;
var line_start: usize = 0;
var in_block_comment = false;
while (i < content.len) : (i += 1) {
if (content[i] == '\n') {
const line = content[line_start..i];
const trimmed = std.mem.trim(u8, line, " \t\r");
// Check for block comments
if (std.mem.indexOf(u8, trimmed, "/*") != null) {
in_block_comment = true;
}
if (std.mem.indexOf(u8, trimmed, "*/") != null) {
in_block_comment = false;
} else if (trimmed.len > 0 and
!in_block_comment and
!std.mem.startsWith(u8, trimmed, "//"))
{
loc += 1;
}
lines += 1;
line_start = i + 1;
}
}
// Handle last line without newline
if (line_start < content.len) {
const line = content[line_start..];
const trimmed = std.mem.trim(u8, line, " \t\r");
if (trimmed.len > 0 and !in_block_comment and !std.mem.startsWith(u8, trimmed, "//")) {
loc += 1;
}
}
return .{ .lines = lines, .loc = loc };
}
fn addStats(dest: *FileStats, src: *const FileStats) void {
dest.files += src.files;
dest.lines += src.lines;
dest.loc += src.loc;
dest.imports += src.imports;
dest.exports += src.exports;
dest.classes += src.classes;
dest.functions += src.functions;
dest.components += src.components;
// Recalculate average
if (dest.files > 0) {
dest.avg_size = dest.loc / dest.files;
}
}
fn printTable(stats: *const CategoryStats, workspace_package_names: []const []const u8) void {
_ = workspace_package_names;
// Table with proper column widths for large numbers
Output.pretty("┌{s:─<18}┬{s:─<8}┬{s:─<10}┬{s:─<10}┬{s:─<8}┬{s:─<5}┐\n", .{ "", "", "", "", "", "" });
Output.pretty("│ {s:<16} │ {s:>6} │ {s:>8} │ {s:>8} │ {s:>6} │ {s:>3} │\n", .{ "Category", "Files", "Lines", "LOC", "Funcs", "F/M" });
Output.pretty("├{s:─<18}┼{s:─<8}┼{s:─<10}┼{s:─<10}┼{s:─<8}┼{s:─<5}┤\n", .{ "", "", "", "", "", "" });
const printRow = struct {
fn print(name: []const u8, s: *const FileStats) void {
const functions_per_module: f64 = if (s.files > 0) @as(f64, @floatFromInt(s.functions)) / @as(f64, @floatFromInt(s.files)) else 0;
Output.pretty("│ {s:<16} │ {d:>6} │ {d:>8} │ {d:>8} │ {d:>6} │ {d:>3.0} │\n", .{
name,
s.files,
s.lines,
s.loc,
s.functions,
functions_per_module,
});
}
}.print;
// Language breakdown (all files)
if (stats.javascript.files > 0) {
printRow("JavaScript", &stats.javascript);
}
if (stats.jsx.files > 0) {
printRow("JSX", &stats.jsx);
}
if (stats.typescript.files > 0) {
printRow("TypeScript", &stats.typescript);
}
// Assets
if (stats.json.files > 0) {
printRow("JSON", &stats.json);
}
if (stats.css.files > 0) {
printRow("Stylesheets", &stats.css);
}
// Module Systems (all files)
if (stats.commonjs.files > 0) {
printRow("CommonJS Modules", &stats.commonjs);
}
if (stats.esmodules.files > 0) {
printRow("ECMA Modules", &stats.esmodules);
}
// Separator before summary sections
Output.pretty("├{s:─<18}┼{s:─<8}┼{s:─<10}┼{s:─<10}┼{s:─<8}┼{s:─<5}┤\n", .{ "", "", "", "", "", "" });
// Dependencies
if (stats.node_modules.files > 0) {
printRow("node_modules", &stats.node_modules);
}
// Your code (everything except node_modules)
const your_code = FileStats{
.files = stats.total.files -| stats.node_modules.files,
.lines = stats.total.lines -| stats.node_modules.lines,
.loc = stats.total.loc -| stats.node_modules.loc,
.functions = stats.total.functions -| stats.node_modules.functions,
.imports = stats.total.imports -| stats.node_modules.imports,
.exports = stats.total.exports -| stats.node_modules.exports,
.classes = stats.total.classes -| stats.node_modules.classes,
.components = stats.components,
.avg_size = 0,
};
printRow("Your Code", &your_code);
// Tests
if (stats.tests.files > 0) {
printRow("Tests", &stats.tests);
}
// All code
printRow("All Code", &stats.total);
Output.pretty("└{s:─<18}┴{s:─<8}┴{s:─<10}┴{s:─<10}┴{s:─<8}┴{s:─<5}┘\n", .{ "", "", "", "", "", "" });
// Print interesting metrics
Output.pretty("\n📊 Insights:\n", .{});
const code_loc = your_code.loc -| stats.tests.loc;
const test_loc = stats.tests.loc;
// Test coverage
if (code_loc > 0 and test_loc > 0) {
const coverage = (@as(f64, @floatFromInt(test_loc)) / @as(f64, @floatFromInt(code_loc + test_loc))) * 100.0;
Output.pretty(" • Test coverage: {d:.1}%\n", .{coverage});
}
// TypeScript adoption (excluding node_modules)
const ts_files = stats.typescript.files;
const js_files = stats.javascript.files + stats.jsx.files;
if (ts_files > 0 and js_files > 0) {
const ts_adoption = (@as(f64, @floatFromInt(ts_files)) / @as(f64, @floatFromInt(ts_files + js_files))) * 100.0;
Output.pretty(" • TypeScript: {d:.1}%\n", .{ts_adoption});
}
// ES Modules adoption (excluding node_modules)
if (stats.esmodules.files > 0 and stats.commonjs.files > 0) {
const esm_adoption = (@as(f64, @floatFromInt(stats.esmodules.files)) / @as(f64, @floatFromInt(stats.esmodules.files + stats.commonjs.files))) * 100.0;
Output.pretty(" • ES Modules: {d:.1}%\n", .{esm_adoption});
}
// Average file size
if (your_code.files > 0) {
const avg_size = @as(f64, @floatFromInt(your_code.loc)) / @as(f64, @floatFromInt(your_code.files));
Output.pretty(" • Avg file size: {d:.0} LOC\n", .{avg_size});
}
// Average function size
if (your_code.functions > 0) {
const avg_func_size = @as(f64, @floatFromInt(your_code.loc)) / @as(f64, @floatFromInt(your_code.functions));
Output.pretty(" • Avg function: {d:.0} LOC\n", .{avg_func_size});
}
// Dependency weight
if (stats.node_modules.files > 0 and your_code.files > 0) {
const dep_ratio = @as(f64, @floatFromInt(stats.node_modules.loc)) / @as(f64, @floatFromInt(your_code.loc));
Output.pretty(" • Dependency weight: {d:.1}x your code\n", .{dep_ratio});
}
}
fn printSummary(stats: *const CategoryStats, workspace_count: usize, reachable_count: usize, source_size: u64, elapsed_ms: u64) void {
_ = workspace_count;
_ = reachable_count;
_ = source_size;
_ = elapsed_ms;
_ = stats;
// All summary output has been removed - table is sufficient
}
fn getWorkspacePackages(allocator: std.mem.Allocator) ![][]const u8 {
// For now, return empty list
// TODO: Parse package.json properly
return allocator.alloc([]const u8, 0) catch &.{};
}
fn onStatsCollect(
ctx_: *anyopaque,
result: *BundleV2.DependenciesScanner.Result,
) anyerror!void {
const ctx = @as(*StatsContext, @ptrCast(@alignCast(ctx_)));
const bundle = result.bundle_v2;
// Access the parsed graph data
const graph = &bundle.graph;
const ast_data = &graph.ast;
// Get the MultiArrayList slices
const sources = graph.input_files.items(.source);
const loaders = graph.input_files.items(.loader);
const import_records = ast_data.items(.import_records);
const exports_kind = ast_data.items(.exports_kind);
const named_exports = ast_data.items(.named_exports);
const export_star_import_records = ast_data.items(.export_star_import_records);
const parts_list = ast_data.items(.parts);
// Process each reachable file
for (result.reachable_files) |source_index| {
const index = source_index.get();
// Comprehensive bounds checking for all arrays
if (index >= sources.len or index >= loaders.len) continue;
// Skip the runtime file (index 0)
if (index == 0) continue;
const source = sources[index];
const loader = loaders[index];
const imports = if (index >= import_records.len) ImportRecord.List{} else import_records[index];
const export_kind = if (index >= exports_kind.len) .none else exports_kind[index];
// Only access named_exports and export_stars for non-CSS files
const is_css = loader == .css;
const named_exports_count: u32 = if (is_css or index >= named_exports.len) 0 else @intCast(named_exports[index].count());
const export_stars_count: u32 = if (is_css or index >= export_star_import_records.len) 0 else @intCast(export_star_import_records[index].len);
// Get source content and path
const source_contents = source.contents;
const path_text = source.path.text;
// Skip virtual files and bun: files
if (strings.hasPrefixComptime(path_text, "bun:") or
strings.hasPrefixComptime(path_text, "node:") or
strings.hasPrefixComptime(path_text, "<") or
strings.eqlComptime(path_text, "bun")) continue;
// Count lines and LOC
const line_stats = countLinesAndLOC(source_contents);
// Categorize file
const is_test = std.mem.indexOf(u8, path_text, ".test.") != null or
std.mem.indexOf(u8, path_text, ".spec.") != null or
std.mem.indexOf(u8, path_text, "__tests__") != null;
const is_node_modules = std.mem.indexOf(u8, path_text, "node_modules") != null;
// Determine workspace package
var workspace_pkg: ?[]const u8 = null;
for (ctx.workspace_packages) |pkg_name| {
if (std.mem.indexOf(u8, path_text, pkg_name) != null and !is_node_modules) {
workspace_pkg = pkg_name;
break;
}
}
// Count imports and exports
const import_count: u32 = @intCast(imports.len);
const export_count: u32 = named_exports_count + export_stars_count;
// Count classes and functions using the parsed AST (for non-CSS files)
var class_count: u32 = 0;
var function_count: u32 = 0;
// Only access parts for non-CSS files
// When parts.len == 0, it means the AST is invalid/failed to parse
// Skip files that failed to parse or have empty ASTs
if (!is_css and index < parts_list.len and parts_list[index].len > 0) {
// Try to safely access the parts
const parts = parts_list[index].slice();
// Iterate through all parts in the file
for (parts) |part| {
// Iterate through all statements in the part
for (part.stmts) |stmt| {
switch (stmt.data) {
// Direct function declarations
.s_function => {
function_count += 1;
},
// Direct class declarations
.s_class => {
class_count += 1;
},
// Local variable declarations (const/let/var)
.s_local => |local| {
// Check each declaration's value
for (local.decls.slice()) |decl| {
if (decl.value) |value_expr| {
switch (value_expr.data) {
.e_function => function_count += 1,
.e_arrow => function_count += 1,
.e_class => class_count += 1,
else => {},
}
}
}
},
// Expression statements (e.g., anonymous functions)
.s_expr => |expr_stmt| {
switch (expr_stmt.value.data) {
.e_function => function_count += 1,
.e_arrow => function_count += 1,
.e_class => class_count += 1,
// Check for assignments that might contain functions/classes
.e_binary => |binary| {
if (binary.op == .bin_assign) {
switch (binary.right.data) {
.e_function => function_count += 1,
.e_arrow => function_count += 1,
.e_class => class_count += 1,
else => {},
}
}
},
else => {},
}
},
// Export statements might also contain functions/classes
.s_export_default => |export_default| {
switch (export_default.value) {
.stmt => |export_stmt| {
switch (export_stmt.data) {
.s_function => function_count += 1,
.s_class => class_count += 1,
else => {},
}
},
.expr => |export_expr| {
switch (export_expr.data) {
.e_function => function_count += 1,
.e_arrow => function_count += 1,
.e_class => class_count += 1,
else => {},
}
},
}
},
else => {},
}
}
}
}
var file_stats = FileStats{
.files = 1,
.lines = line_stats.lines,
.loc = line_stats.loc,
.imports = import_count,
.exports = export_count,
.classes = class_count,
.functions = function_count,
.components = 0,
.avg_size = line_stats.loc,
};
// Determine module type from exports_kind
const is_commonjs = export_kind == .cjs;
const is_esm = export_kind == .esm;
// Check categories first - node_modules files should ONLY count in node_modules
if (is_node_modules) {
// Files in node_modules only count towards node_modules stats
file_stats.imports = 0;
file_stats.exports = 0;
addStats(&ctx.stats.node_modules, &file_stats);
} else {
// Files NOT in node_modules get categorized normally
switch (loader) {
.tsx, .ts => {
addStats(&ctx.stats.typescript, &file_stats);
if (is_commonjs) {
addStats(&ctx.stats.commonjs, &file_stats);
} else if (is_esm) {
addStats(&ctx.stats.esmodules, &file_stats);
}
},
.jsx => {
addStats(&ctx.stats.jsx, &file_stats);
if (is_commonjs) {
addStats(&ctx.stats.commonjs, &file_stats);
} else if (is_esm) {
addStats(&ctx.stats.esmodules, &file_stats);
}
},
.js => {
addStats(&ctx.stats.javascript, &file_stats);
if (is_commonjs) {
addStats(&ctx.stats.commonjs, &file_stats);
} else if (is_esm) {
addStats(&ctx.stats.esmodules, &file_stats);
}
},
.css => {
file_stats.imports = 0;
file_stats.exports = 0;
addStats(&ctx.stats.css, &file_stats);
},
.json => {
file_stats.imports = 0;
file_stats.exports = 0;
addStats(&ctx.stats.json, &file_stats);
},
else => {},
}
// Check if it's a test file
if (is_test) {
addStats(&ctx.stats.tests, &file_stats);
} else if (workspace_pkg) |pkg| {
if (ctx.stats.workspace_packages.getPtr(pkg)) |pkg_stats| {
addStats(pkg_stats, &file_stats);
}
}
}
// No need to track components
// Always add to total
addStats(&ctx.stats.total, &file_stats);
}
}
fn findAllJSFiles(allocator: std.mem.Allocator, dir_path: []const u8) ![][]const u8 {
var files = std.ArrayList([]const u8).init(allocator);
errdefer {
for (files.items) |file| {
allocator.free(file);
}
files.deinit();
}
// Simple recursive directory walker
var stack = std.ArrayList([]const u8).init(allocator);
defer {
for (stack.items) |item| {
allocator.free(item);
}
stack.deinit();
}
try stack.append(try allocator.dupe(u8, dir_path));
while (stack.items.len > 0) {
const current_dir = stack.pop() orelse break;
defer allocator.free(current_dir);
// Skip node_modules and hidden directories
if (std.mem.indexOf(u8, current_dir, "node_modules") != null or
std.mem.indexOf(u8, current_dir, "/.git") != null or
std.mem.indexOf(u8, current_dir, "/.next") != null) continue;
var dir = std.fs.openDirAbsolute(current_dir, .{ .iterate = true }) catch |err| {
if (err == error.NotDir or err == error.FileNotFound) continue;
return err;
};
defer dir.close();
var iter = dir.iterate();
while (try iter.next()) |entry| {
const full_path = try std.fs.path.join(allocator, &.{ current_dir, entry.name });
switch (entry.kind) {
.directory => {
// Add directory to stack for processing
try stack.append(full_path);
},
.file => {
// Check if it's a JS/TS/JSON/CSS file
const ext = std.fs.path.extension(entry.name);
const is_js_file = std.mem.eql(u8, ext, ".js") or
std.mem.eql(u8, ext, ".jsx") or
std.mem.eql(u8, ext, ".ts") or
std.mem.eql(u8, ext, ".tsx") or
std.mem.eql(u8, ext, ".mjs") or
std.mem.eql(u8, ext, ".cjs") or
std.mem.eql(u8, ext, ".mts") or
std.mem.eql(u8, ext, ".cts") or
std.mem.eql(u8, ext, ".json") or
std.mem.eql(u8, ext, ".css");
if (is_js_file) {
try files.append(full_path);
} else {
allocator.free(full_path);
}
},
else => {
allocator.free(full_path);
},
}
}
}
return files.toOwnedSlice();
}
pub fn exec(ctx: Command.Context) !void {
Global.configureAllocator(.{ .long_running = true });
const allocator = ctx.allocator;
const start_time = std.time.nanoTimestamp();
// Set up the bundler context to be as permissive as possible
ctx.args.target = .bun; // Use bun target to resolve test files and Bun-specific imports
ctx.args.packages = .bundle; // Bundle mode to analyze all files
ctx.args.ignore_dce_annotations = true; // Ignore DCE annotations that might cause errors
// Get workspace packages
const workspace_packages = try getWorkspacePackages(allocator);
defer {
for (workspace_packages) |pkg| {
allocator.free(pkg);
}
allocator.free(workspace_packages);
}
// Initialize stats context
var stats_ctx = StatsContext{
.stats = CategoryStats{
.workspace_packages = std.StringHashMap(FileStats).init(allocator),
},
.allocator = allocator,
.workspace_packages = workspace_packages,
};
defer stats_ctx.stats.workspace_packages.deinit();
// Initialize workspace package stats
for (workspace_packages) |pkg| {
try stats_ctx.stats.workspace_packages.put(pkg, FileStats{});
}
// Set up transpiler with suppressed error logging
var silent_log = Logger.Log.init(allocator);
silent_log.level = .err; // Highest level to minimize processing
var this_transpiler = try transpiler.Transpiler.init(allocator, &silent_log, ctx.args, null);
// Handle entry points based on user input
var allocated_entry_points: ?[][]const u8 = null;
defer if (allocated_entry_points) |entry_points| {
for (entry_points) |entry| {
allocator.free(entry);
}
allocator.free(entry_points);
};
if (ctx.args.entry_points.len > 0) {
// User provided entry points - use them directly
this_transpiler.options.entry_points = ctx.args.entry_points;
} else {
// No entry points provided - walk directory to find all JS/TS files
const cwd = try std.process.getCwdAlloc(allocator);
defer allocator.free(cwd);
allocated_entry_points = try findAllJSFiles(allocator, cwd);
this_transpiler.options.entry_points = allocated_entry_points.?;
}
this_transpiler.options.output_dir = ""; // No output needed
this_transpiler.options.write = false; // Don't write files
this_transpiler.configureLinker();
try this_transpiler.configureDefines();
// Set up the dependencies scanner to collect stats
var scanner = BundleV2.DependenciesScanner{
.ctx = &stats_ctx,
.entry_points = this_transpiler.options.entry_points,
.onFetch = onStatsCollect,
};
// Run the bundler to parse all files
if (this_transpiler.options.entry_points.len == 0) {
Output.prettyErrorln("<red>error<r>: No files found to analyze", .{});
return;
}
// No "Analyzing X files..." message - just start processing
// Clear any messages and reset error count
this_transpiler.log.msgs.clearRetainingCapacity();
this_transpiler.log.errors = 0;
this_transpiler.log.warnings = 0;
// Use our custom generation function that doesn't stop on errors
try generateForStats(
&this_transpiler,
allocator,
bun.jsc.AnyEventLoop.init(allocator),
&scanner,
);
// Calculate elapsed time
const end_time = std.time.nanoTimestamp();
const elapsed_ns = @as(u64, @intCast(end_time - start_time));
const elapsed_ms = elapsed_ns / std.time.ns_per_ms;
// Print results
printTable(&stats_ctx.stats, workspace_packages);
printSummary(&stats_ctx.stats, workspace_packages.len, 0, 0, elapsed_ms);
}
};
const Logger = @import("../logger.zig");
const options = @import("../options.zig");
const std = @import("std");
const transpiler = @import("../transpiler.zig");
const Command = @import("../cli.zig").Command;
const ImportRecord = @import("../import_record.zig").ImportRecord;
const bundle_v2 = @import("../bundler/bundle_v2.zig");
const BundleV2 = bundle_v2.BundleV2;
const bun = @import("bun");
const Global = bun.Global;
const Output = bun.Output;
const ast = bun.ast;
const strings = bun.strings;

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

@@ -1513,6 +1513,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

@@ -0,0 +1,127 @@
import { expect, test } from "bun:test";
import { bunEnv, bunExe, tempDir } from "harness";
test("bun stats - basic functionality", async () => {
using dir = tempDir("stats-test", {
"index.js": `console.log("hello");`,
"utils.mjs": `export const add = (a, b) => a + b;`,
"config.json": `{"name": "test"}`,
"styles.css": `body { color: red; }`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "stats"],
env: bunEnv,
cwd: String(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(stderr).toBe("");
// Check that the output contains expected sections
expect(stdout).toContain("JavaScript");
expect(stdout).toContain("ES modules");
expect(stdout).toContain("CSS");
expect(stdout).toContain("JSON");
expect(stdout).toContain("Total");
expect(stdout).toContain("Code LOC:");
expect(stdout).toContain("Bundled Size (est.):");
});
test("bun stats - with TypeScript files", async () => {
using dir = tempDir("stats-ts-test", {
"index.ts": `const msg: string = "hello";\nconsole.log(msg);`,
"types.d.ts": `export interface User { name: string; }`,
"test.spec.ts": `import { test } from "bun:test";\ntest("sample", () => {});`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "stats"],
env: bunEnv,
cwd: String(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(stderr).toBe("");
// Check TypeScript stats
expect(stdout).toContain("TypeScript");
expect(stdout).toContain("Tests");
expect(stdout).toContain("Test LOC:");
});
test("bun stats - handles CommonJS and ES modules", async () => {
using dir = tempDir("stats-modules-test", {
"cjs-module.js": `module.exports = { foo: 'bar' };`,
"esm-module.mjs": `export default { foo: 'bar' };`,
"mixed.js": `const lib = require('./lib');\nexport { lib };`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "stats"],
env: bunEnv,
cwd: String(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(stderr).toBe("");
// Check module type detection
expect(stdout).toContain("CommonJS modules");
expect(stdout).toContain("ES modules");
});
test("bun stats - counts imports and exports", async () => {
using dir = tempDir("stats-imports-test", {
"module.js": `
import React from 'react';
import { useState } from 'react';
import './styles.css';
export default App;
export { helper };
`,
});
await using proc = Bun.spawn({
cmd: [bunExe(), "stats"],
env: bunEnv,
cwd: String(dir),
stdout: "pipe",
stderr: "pipe",
});
const [stdout] = await Promise.all([proc.stdout.text(), proc.exited]);
// Should count imports and exports - check the table contains expected values
expect(stdout).toContain("| 3 | 2 |");
});
test("bun stats --help", async () => {
await using proc = Bun.spawn({
cmd: [bunExe(), "stats", "--help"],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
expect(exitCode).toBe(0);
expect(stderr).toBe("");
expect(stdout).toContain("Usage:");
expect(stdout).toContain("bun stats");
expect(stdout).toContain("Generate a comprehensive code statistics report");
});

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");
});