diff --git a/.buildkite/Dockerfile b/.buildkite/Dockerfile
index 55e45ccdff..033aec633d 100644
--- a/.buildkite/Dockerfile
+++ b/.buildkite/Dockerfile
@@ -133,6 +133,20 @@ RUN ARCH=$(if [ "$TARGETARCH" = "arm64" ]; then echo "arm64"; else echo "amd64";
RUN mkdir -p /var/cache/buildkite-agent /var/log/buildkite-agent /var/run/buildkite-agent /etc/buildkite-agent /var/lib/buildkite-agent/cache/bun
+# The following is necessary to configure buildkite to use a stable
+# checkout directory. sccache hashes absolute paths into its cache keys,
+# so if buildkite uses a different checkout path each time (which it does
+# by default), sccache will be useless.
+RUN mkdir -p -m 755 /var/lib/buildkite-agent/hooks && \
+ cat <<'EOF' > /var/lib/buildkite-agent/hooks/environment
+#!/bin/sh
+set -efu
+
+export BUILDKITE_BUILD_CHECKOUT_PATH=/var/lib/buildkite-agent/build
+EOF
+
+RUN chmod 744 /var/lib/buildkite-agent/hooks/environment
+
COPY ../*/agent.mjs /var/bun/scripts/
ENV BUN_INSTALL_CACHE=/var/lib/buildkite-agent/cache/bun
diff --git a/.prettierignore b/.prettierignore
index 040f49fb17..548265d59b 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -9,3 +9,6 @@ test/snippets
test/js/node/test
test/napi/node-napi-tests
bun.lock
+
+# the output codeblocks need to stay minified
+docs/bundler/minifier.mdx
diff --git a/README.md b/README.md
index 61733ac8e8..3c845722d1 100644
--- a/README.md
+++ b/README.md
@@ -230,7 +230,7 @@ bun upgrade --canary
- Ecosystem
- [Use React and JSX](https://bun.com/guides/ecosystem/react)
- - [Use EdgeDB with Bun](https://bun.com/guides/ecosystem/edgedb)
+ - [Use Gel with Bun](https://bun.com/guides/ecosystem/gel)
- [Use Prisma with Bun](https://bun.com/guides/ecosystem/prisma)
- [Add Sentry to a Bun app](https://bun.com/guides/ecosystem/sentry)
- [Create a Discord bot](https://bun.com/guides/ecosystem/discordjs)
diff --git a/cmake/tools/SetupSccache.cmake b/cmake/tools/SetupSccache.cmake
index 6736f0706e..cb4b5aa750 100644
--- a/cmake/tools/SetupSccache.cmake
+++ b/cmake/tools/SetupSccache.cmake
@@ -10,12 +10,8 @@ set(SCCACHE_SHARED_CACHE_BUCKET "bun-build-sccache-store")
function(check_aws_credentials OUT_VAR)
# Install dependencies first
execute_process(
- COMMAND
- ${BUN_EXECUTABLE}
- install
- --frozen-lockfile
- WORKING_DIRECTORY
- ${CMAKE_SOURCE_DIR}/scripts/build-cache
+ COMMAND ${BUN_EXECUTABLE} install --frozen-lockfile
+ WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/scripts/build-cache
RESULT_VARIABLE INSTALL_EXIT_CODE
OUTPUT_VARIABLE INSTALL_OUTPUT
ERROR_VARIABLE INSTALL_ERROR
@@ -106,26 +102,22 @@ function(sccache_configure_environment_developer)
endif()
endfunction()
-function(sccache_configure)
- find_command(VARIABLE SCCACHE_PROGRAM COMMAND sccache REQUIRED ${CI})
- if(NOT SCCACHE_PROGRAM)
- message(WARNING "sccache not found. Your builds will be slower.")
- return()
- endif()
+find_command(VARIABLE SCCACHE_PROGRAM COMMAND sccache REQUIRED ${CI})
+if(NOT SCCACHE_PROGRAM)
+ message(WARNING "sccache not found. Your builds will be slower.")
+ return()
+endif()
- set(SCCACHE_ARGS CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER)
- foreach(arg ${SCCACHE_ARGS})
- setx(${arg} ${SCCACHE_PROGRAM})
- list(APPEND CMAKE_ARGS -D${arg}=${${arg}})
- endforeach()
+set(SCCACHE_ARGS CMAKE_C_COMPILER_LAUNCHER CMAKE_CXX_COMPILER_LAUNCHER)
+foreach(arg ${SCCACHE_ARGS})
+ setx(${arg} ${SCCACHE_PROGRAM})
+ list(APPEND CMAKE_ARGS -D${arg}=${${arg}})
+endforeach()
- setenv(SCCACHE_LOG "info")
+setenv(SCCACHE_LOG "info")
- if (CI)
- sccache_configure_environment_ci()
- else()
- sccache_configure_environment_developer()
- endif()
-endfunction()
-
-sccache_configure()
+if (CI)
+ sccache_configure_environment_ci()
+else()
+ sccache_configure_environment_developer()
+endif()
diff --git a/docs/bundler/css.mdx b/docs/bundler/css.mdx
index 628a9fa4b8..730332f173 100644
--- a/docs/bundler/css.mdx
+++ b/docs/bundler/css.mdx
@@ -34,7 +34,7 @@ By default, Bun's CSS bundler targets the following browsers:
The CSS Nesting specification allows you to write more concise and intuitive stylesheets by nesting selectors inside one another. Instead of repeating parent selectors across your CSS file, you can write child styles directly within their parent blocks.
-```css title="styles.css" icon="file-code"
+```scss title="styles.css" icon="file-code"
/* With nesting */
.card {
background: white;
@@ -100,7 +100,7 @@ This compiles to:
The `color-mix()` function gives you an easy way to blend two colors together according to a specified ratio in a chosen color space. This powerful feature lets you create color variations without manually calculating the resulting values.
-```css title="styles.css" icon="file-code"
+```scss title="styles.css" icon="file-code"
.button {
/* Mix blue and red in the RGB color space with a 30/70 proportion */
background-color: color-mix(in srgb, blue 30%, red);
diff --git a/docs/bundler/esbuild.mdx b/docs/bundler/esbuild.mdx
index d9cd38efd7..10d6ae7591 100644
--- a/docs/bundler/esbuild.mdx
+++ b/docs/bundler/esbuild.mdx
@@ -231,23 +231,67 @@ const myPlugin: BunPlugin = {
### onResolve
- - π’ `filter` - π’ `namespace`
+
+
+ - π’ `filter`
+ - π’ `namespace`
+
+
- - π’ `path` - π’ `importer` - π΄ `namespace` - π΄ `resolveDir` - π΄ `kind` - π΄ `pluginData`
+
+ - π’ `path`
+ - π’ `importer`
+ - π΄ `namespace`
+ - π΄ `resolveDir`
+ - π΄ `kind`
+ - π΄ `pluginData`
+
- - π’ `namespace` - π’ `path` - π΄ `errors` - π΄ `external` - π΄ `pluginData` - π΄ `pluginName` - π΄ `sideEffects` -
- π΄ `suffix` - π΄ `warnings` - π΄ `watchDirs` - π΄ `watchFiles`
+
+ - π’ `namespace`
+ - π’ `path`
+ - π΄ `errors`
+ - π΄ `external`
+ - π΄ `pluginData`
+ - π΄ `pluginName`
+ - π΄ `sideEffects`
+ - π΄ `suffix`
+ - π΄ `warnings`
+ - π΄ `watchDirs`
+ - π΄ `watchFiles`
+
### onLoad
- - π’ `filter` - π’ `namespace`
- - π’ `path` - π΄ `namespace` - π΄ `suffix` - π΄ `pluginData`
+
+
+ - π’ `filter`
+ - π’ `namespace`
+
+
+
+
+ - π’ `path`
+ - π΄ `namespace`
+ - π΄ `suffix`
+ - π΄ `pluginData`
+
+
- - π’ `contents` - π’ `loader` - π΄ `errors` - π΄ `pluginData` - π΄ `pluginName` - π΄ `resolveDir` - π΄ `warnings` -
- π΄ `watchDirs` - π΄ `watchFiles`
+
+ - π’ `contents`
+ - π’ `loader`
+ - π΄ `errors`
+ - π΄ `pluginData`
+ - π΄ `pluginName`
+ - π΄ `resolveDir`
+ - π΄ `warnings`
+ - π΄ `watchDirs`
+ - π΄ `watchFiles`
+
diff --git a/docs/bundler/index.mdx b/docs/bundler/index.mdx
index 66696f0151..6204c42bca 100644
--- a/docs/bundler/index.mdx
+++ b/docs/bundler/index.mdx
@@ -160,8 +160,12 @@ Like the Bun runtime, the bundler supports an array of file types out of the box
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `.js` `.jsx` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` | Uses Bun's built-in transpiler to parse the file and transpile TypeScript/JSX syntax to vanilla JavaScript. The bundler executes a set of default transforms including dead code elimination and tree shaking. At the moment Bun does not attempt to down-convert syntax; if you use recently ECMAScript syntax, that will be reflected in the bundled code. |
| `.json` | JSON files are parsed and inlined into the bundle as a JavaScript object.
`js
import pkg from "./package.json";
pkg.name; // => "my-package"
` |
+| `.jsonc` | JSON with comments. Files are parsed and inlined into the bundle as a JavaScript object.
`js
import config from "./config.jsonc";
config.name; // => "my-config"
` |
| `.toml` | TOML files are parsed and inlined into the bundle as a JavaScript object.
`js
import config from "./bunfig.toml";
config.logLevel; // => "debug"
` |
+| `.yaml` `.yml` | YAML files are parsed and inlined into the bundle as a JavaScript object.
`js
import config from "./config.yaml";
config.name; // => "my-app"
` |
| `.txt` | The contents of the text file are read and inlined into the bundle as a string.
`js
import contents from "./file.txt";
console.log(contents); // => "Hello, world!"
` |
+| `.html` | HTML files are processed and any referenced assets (scripts, stylesheets, images) are bundled. |
+| `.css` | CSS files are bundled together into a single `.css` file in the output directory. |
| `.node` `.wasm` | These files are supported by the Bun runtime, but during bundling they are treated as assets. |
### Assets
@@ -1462,7 +1466,21 @@ interface BuildArtifact extends Blob {
sourcemap: BuildArtifact | null;
}
-type Loader = "js" | "jsx" | "ts" | "tsx" | "json" | "toml" | "file" | "napi" | "wasm" | "text";
+type Loader =
+ | "js"
+ | "jsx"
+ | "ts"
+ | "tsx"
+ | "css"
+ | "json"
+ | "jsonc"
+ | "toml"
+ | "yaml"
+ | "text"
+ | "file"
+ | "napi"
+ | "wasm"
+ | "html";
interface BuildOutput {
outputs: BuildArtifact[];
diff --git a/docs/bundler/loaders.mdx b/docs/bundler/loaders.mdx
index a610186f58..89e8824679 100644
--- a/docs/bundler/loaders.mdx
+++ b/docs/bundler/loaders.mdx
@@ -7,7 +7,7 @@ The Bun bundler implements a set of default loaders out of the box.
> As a rule of thumb: **the bundler and the runtime both support the same set of file types out of the box.**
-`.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` `.jsx` `.toml` `.json` `.txt` `.wasm` `.node` `.html`
+`.js` `.cjs` `.mjs` `.mts` `.cts` `.ts` `.tsx` `.jsx` `.css` `.json` `.jsonc` `.toml` `.yaml` `.yml` `.txt` `.wasm` `.node` `.html` `.sh`
Bun uses the file extension to determine which built-in loader should be used to parse the file. Every loader has a name, such as `js`, `tsx`, or `json`. These names are used when building plugins that extend Bun with custom loaders.
@@ -85,7 +85,7 @@ If a `.json` file is passed as an entrypoint to the bundler, it will be converte
}
```
-```ts Output
+```js Output
export default {
name: "John Doe",
age: 35,
@@ -97,7 +97,33 @@ export default {
---
-### toml
+### `jsonc`
+
+**JSON with Comments loader.** Default for `.jsonc`.
+
+JSONC (JSON with Comments) files can be directly imported. Bun will parse them, stripping out comments and trailing commas.
+
+```js
+import config from "./config.jsonc";
+console.log(config);
+```
+
+During bundling, the parsed JSONC is inlined into the bundle as a JavaScript object, identical to the `json` loader.
+
+```js
+var config = {
+ option: "value",
+};
+```
+
+
+ Bun automatically uses the `jsonc` loader for `tsconfig.json`, `jsconfig.json`, `package.json`, and `bun.lock` files,
+ allowing comments and trailing commas in these files.
+
+
+---
+
+### `toml`
**TOML loader.** Default for `.toml`.
@@ -131,7 +157,7 @@ age = 35
email = "johndoe@example.com"
```
-```ts Output
+```js Output
export default {
name: "John Doe",
age: 35,
@@ -143,7 +169,53 @@ export default {
---
-### text
+### `yaml`
+
+**YAML loader.** Default for `.yaml` and `.yml`.
+
+YAML files can be directly imported. Bun will parse them with its fast native YAML parser.
+
+```js
+import config from "./config.yaml";
+console.log(config);
+
+// via import attribute:
+import data from "./data.txt" with { type: "yaml" };
+```
+
+During bundling, the parsed YAML is inlined into the bundle as a JavaScript object.
+
+```js
+var config = {
+ name: "my-app",
+ version: "1.0.0",
+ // ...other fields
+};
+```
+
+If a `.yaml` or `.yml` file is passed as an entrypoint, it will be converted to a `.js` module that `export default`s the parsed object.
+
+
+
+```yaml Input
+name: John Doe
+age: 35
+email: johndoe@example.com
+```
+
+```js Output
+export default {
+ name: "John Doe",
+ age: 35,
+ email: "johndoe@example.com",
+};
+```
+
+
+
+---
+
+### `text`
**Text loader.** Default for `.txt`.
@@ -173,7 +245,7 @@ If a `.txt` file is passed as an entrypoint, it will be converted to a `.js` mod
Hello, world!
```
-```ts Output
+```js Output
export default "Hello, world!";
```
@@ -181,7 +253,7 @@ export default "Hello, world!";
---
-### napi
+### `napi`
**Native addon loader.** Default for `.node`.
@@ -196,7 +268,7 @@ console.log(addon);
---
-### sqlite
+### `sqlite`
**SQLite loader.** Requires `with { "type": "sqlite" }` import attribute.
@@ -226,7 +298,9 @@ Otherwise, the database to embed is copied into the `outdir` with a hashed filen
---
-### html
+### `html`
+
+**HTML loader.** Default for `.html`.
The `html` loader processes HTML files and bundles any referenced assets. It will:
@@ -301,7 +375,27 @@ The `html` loader behaves differently depending on how it's used:
---
-### sh
+### `css`
+
+**CSS loader.** Default for `.css`.
+
+CSS files can be directly imported. The bundler will parse and bundle CSS files, handling `@import` statements and `url()` references.
+
+```js
+import "./styles.css";
+```
+
+During bundling, all imported CSS files are bundled together into a single `.css` file in the output directory.
+
+```css
+.my-class {
+ background: url("./image.png");
+}
+```
+
+---
+
+### `sh`
**Bun Shell loader.** Default for `.sh` files.
@@ -313,7 +407,7 @@ bun run ./script.sh
---
-### file
+### `file`
**File loader.** Default for all unrecognized file types.
diff --git a/docs/bundler/minifier.mdx b/docs/bundler/minifier.mdx
index 56a004e295..2cf4866030 100644
--- a/docs/bundler/minifier.mdx
+++ b/docs/bundler/minifier.mdx
@@ -103,13 +103,13 @@ Renames local variables and function names to shorter identifiers using frequenc
Converts boolean literals to shorter expressions.
```ts Input
-true;
-false;
+true
+false
```
-```ts Output
-!0;
-!1;
+```js Output
+!0
+!1
```
### Boolean algebra optimizations
@@ -119,21 +119,21 @@ false;
Simplifies boolean expressions using logical rules.
```ts Input
-!!x;
-x === true;
-x && true;
-x || false;
-!true;
-!false;
+!!x
+x === true
+x && true
+x || false
+!true
+!false
```
-```ts Output
-x;
-x;
-x;
-x;
-!1;
-!0;
+```js Output
+x
+x
+x
+x
+!1
+!0
```
### Undefined shortening
@@ -143,13 +143,13 @@ x;
Replaces `undefined` with shorter equivalent.
```ts Input
-undefined;
+undefined
let x = undefined;
```
-```ts Output
-void 0;
-let x = void 0;
+```js Output
+void 0
+let x=void 0;
```
### Undefined equality optimization
@@ -159,13 +159,13 @@ let x = void 0;
Optimizes loose equality checks with undefined.
```ts Input
-x == undefined;
-x != undefined;
+x == undefined
+x != undefined
```
-```ts Output
-x == null;
-x != null;
+```js Output
+x == null
+x != null
```
### Infinity shortening
@@ -175,11 +175,13 @@ x != null;
Converts Infinity to mathematical expressions.
```ts Input
-Infinity - Infinity;
+Infinity
+-Infinity
```
-```ts Output
-1 / 0 - 1 / 0;
+```js Output
+1/0
+-1/0
```
### Typeof optimizations
@@ -189,25 +191,25 @@ Infinity - Infinity;
Optimizes typeof comparisons and evaluates constant typeof expressions.
```ts Input
-typeof x === "undefined";
-typeof x !== "undefined";
-typeof require;
-typeof null;
-typeof true;
-typeof 123;
-typeof "str";
-typeof 123n;
+typeof x === 'undefined'
+typeof x !== 'undefined'
+typeof require
+typeof null
+typeof true
+typeof 123
+typeof "str"
+typeof 123n
```
-```ts Output
-typeof x > "u";
-typeof x < "u";
-("function");
-("object");
-("boolean");
-("number");
-("string");
-("bigint");
+```js Output
+typeof x>'u'
+typeof x<'u'
+"function"
+"object"
+"boolean"
+"number"
+"string"
+"bigint"
```
### Number formatting
@@ -217,17 +219,19 @@ typeof x < "u";
Formats numbers in the most compact representation.
```ts Input
-10000;
-100000;
-1000000;
-1.0 - 42.0;
+10000
+100000
+1000000
+1.0
+-42.0
```
-```ts Output
-1e4;
-1e5;
-1e6;
-1 - 42;
+```js Output
+1e4
+1e5
+1e6
+1
+-42
```
### Arithmetic constant folding
@@ -237,21 +241,21 @@ Formats numbers in the most compact representation.
Evaluates arithmetic operations at compile time.
```ts Input
-1 + 2;
-10 - 5;
-3 * 4;
-10 / 2;
-10 % 3;
-2 ** 3;
+1 + 2
+10 - 5
+3 * 4
+10 / 2
+10 % 3
+2 ** 3
```
-```ts Output
-3;
-5;
-12;
-5;
-1;
-8;
+```js Output
+3
+5
+12
+5
+1
+8
```
### Bitwise constant folding
@@ -261,20 +265,21 @@ Evaluates arithmetic operations at compile time.
Evaluates bitwise operations at compile time.
```ts Input
-5 & 3;
-5 | 3;
-5 ^ 3;
-8 << 2;
-32 >> 2;
-~5;
+5 & 3
+5 | 3
+5 ^ 3
+8 << 2
+32 >> 2
+~5
```
-```ts Output
-1;
-7;
-6;
-32;
-8 - 6;
+```js Output
+1
+7
+6
+32
+8
+-6
```
### String concatenation
@@ -284,15 +289,15 @@ Evaluates bitwise operations at compile time.
Combines string literals at compile time.
```ts Input
-"a" + "b";
-"x" + 123;
-"foo" + "bar" + "baz";
+"a" + "b"
+"x" + 123
+"foo" + "bar" + "baz"
```
-```ts Output
-"ab";
-"x123";
-"foobarbaz";
+```js Output
+"ab"
+"x123"
+"foobarbaz"
```
### String indexing
@@ -302,13 +307,13 @@ Combines string literals at compile time.
Evaluates string character access at compile time.
```ts Input
-"foo"[2];
-"hello"[0];
+"foo"[2]
+"hello"[0]
```
-```ts Output
-"o";
-"h";
+```js Output
+"o"
+"h"
```
### Template literal folding
@@ -318,12 +323,13 @@ Evaluates string character access at compile time.
Evaluates template literals with constant expressions.
```ts Input
-`a${123}b``result: ${5 + 10}`;
+`a${123}b`
+`result: ${5 + 10}`
```
-```ts Output
-"a123b";
-"result: 15";
+```js Output
+"a123b"
+"result: 15"
```
### Template literal to string conversion
@@ -333,13 +339,14 @@ Evaluates template literals with constant expressions.
Converts simple template literals to regular strings.
```ts Input
-`Hello World``Line 1
-Line 2`;
+`Hello World`
+`Line 1
+Line 2`
```
-```ts Output
-"Hello World";
-"Line 1\nLine 2";
+```js Output
+"Hello World"
+"Line 1\nLine 2"
```
### String quote optimization
@@ -349,14 +356,15 @@ Line 2`;
Chooses the optimal quote character to minimize escapes.
```ts Input
-"It's a string";
-'He said "hello"'`Simple string`;
+"It's a string"
+'He said "hello"'
+`Simple string`
```
-```ts Output
-"It's a string";
-'He said "hello"';
-"Simple string";
+```js Output
+"It's a string"
+'He said "hello"'
+"Simple string"
```
### Array spread inlining
@@ -370,8 +378,9 @@ Inlines array spread operations with constant arrays.
[...[a, b]]
```
-```ts Output
-[1, 2, 3, 4][(a, b)];
+```js Output
+[1,2,3,4]
+[a,b]
```
### Array indexing
@@ -386,10 +395,10 @@ Evaluates constant array access at compile time.
['a', , 'c'][1]
```
-```ts Output
-x;
-("b");
-void 0;
+```js Output
+x
+'b'
+void 0
```
### Property access optimization
@@ -399,17 +408,17 @@ void 0;
Converts bracket notation to dot notation when possible.
```ts Input
-obj["property"];
-obj["validName"];
-obj["123"];
-obj["invalid-name"];
+obj["property"]
+obj["validName"]
+obj["123"]
+obj["invalid-name"]
```
-```ts Output
-obj.property;
-obj.validName;
-obj["123"];
-obj["invalid-name"];
+```js Output
+obj.property
+obj.validName
+obj["123"]
+obj["invalid-name"]
```
### Comparison folding
@@ -419,19 +428,19 @@ obj["invalid-name"];
Evaluates constant comparisons at compile time.
```ts Input
-3 < 5;
-5 > 3;
-3 <= 3;
-5 >= 6;
-"a" < "b";
+3 < 5
+5 > 3
+3 <= 3
+5 >= 6
+"a" < "b"
```
-```ts Output
-!0;
-!0;
-!0;
-!1;
-!0;
+```js Output
+!0
+!0
+!0
+!1
+!0
```
### Logical operation folding
@@ -441,17 +450,17 @@ Evaluates constant comparisons at compile time.
Simplifies logical operations with constant values.
```ts Input
-true && x;
-false && x;
-true || x;
-false || x;
+true && x
+false && x
+true || x
+false || x
```
-```ts Output
-x;
-!1;
-!0;
-x;
+```js Output
+x
+!1
+!0
+x
```
### Nullish coalescing folding
@@ -461,15 +470,15 @@ x;
Evaluates nullish coalescing with known values.
```ts Input
-null ?? x;
-undefined ?? x;
-42 ?? x;
+null ?? x
+undefined ?? x
+42 ?? x
```
-```ts Output
-x;
-x;
-42;
+```js Output
+x
+x
+42
```
### Comma expression simplification
@@ -479,12 +488,13 @@ x;
Removes side-effect-free expressions from comma sequences.
```ts Input
-(0, x)(123, "str", x);
+(0, x)
+(123, "str", x)
```
-```ts Output
-x;
-x;
+```js Output
+x
+x
```
### Ternary conditional folding
@@ -494,17 +504,17 @@ x;
Evaluates conditional expressions with constant conditions.
```ts Input
-true ? a : b;
-false ? a : b;
-x ? true : false;
-x ? false : true;
+true ? a : b
+false ? a : b
+x ? true : false
+x ? false : true
```
-```ts Output
-a;
-b;
-x;
-!x;
+```js Output
+a
+b
+x ? !0 : !1
+x ? !1 : !0
```
### Unary expression folding
@@ -514,17 +524,22 @@ x;
Simplifies unary operations.
```ts Input
-+123 + "123" - -x;
-~~x;
-!!x;
++123
++"123"
+-(-x)
+~~x
+!!x
```
-```ts Output
-123;
-123;
-x;
-x;
-x;
+```js Output
+123
+123
+123
+123
+x
+~~x
+!!x
+x
```
### Double negation removal
@@ -534,13 +549,13 @@ x;
Removes unnecessary double negations.
```ts Input
-!!x;
-!!!x;
+!!x
+!!!x
```
-```ts Output
-x;
-!x;
+```js Output
+x
+!x
```
### If statement optimization
@@ -552,18 +567,15 @@ Optimizes if statements with constant conditions.
```ts Input
if (true) x;
if (false) x;
-if (x) {
- a;
-}
-if (x) {
-} else y;
+if (x) { a; }
+if (x) {} else y;
```
-```ts Output
+```js Output
x;
// removed
-if (x) a;
-if (!x) y;
+if(x)a;
+if(!x)y;
```
### Dead code elimination
@@ -582,10 +594,8 @@ function foo() {
}
```
-```ts Output
-function foo() {
- return x;
-}
+```js Output
+function foo(){return x}
```
### Unreachable branch removal
@@ -600,7 +610,7 @@ while (false) {
}
```
-```ts Output
+```js Output
// removed entirely
```
@@ -611,13 +621,12 @@ while (false) {
Removes empty blocks and unnecessary braces.
```ts Input
-{
-}
-if (x) {
-}
+{ }
+if (x) { }
```
-```ts Output
+```js Output
+;
// removed
```
@@ -633,8 +642,8 @@ if (condition) {
}
```
-```ts Output
-if (condition) doSomething();
+```js Output
+if(condition)doSomething();
```
### TypeScript enum inlining
@@ -644,16 +653,12 @@ if (condition) doSomething();
Inlines TypeScript enum values at compile time.
```ts Input
-enum Color {
- Red,
- Green,
- Blue,
-}
+enum Color { Red, Green, Blue }
const x = Color.Red;
```
-```ts Output
-const x = 0;
+```js Output
+const x=0;
```
### Pure annotation support
@@ -667,7 +672,7 @@ const x = /*@__PURE__*/ expensive();
// If x is unused...
```
-```ts Output
+```js Output
// removed entirely
```
@@ -684,11 +689,8 @@ function calculateSum(firstNumber, secondNumber) {
}
```
-```ts Output
-function a(b, c) {
- const d = b + c;
- return d;
-}
+```js Output
+function a(b,c){const d=b+c;return d}
```
**Naming strategy:**
@@ -713,16 +715,13 @@ Removes all unnecessary whitespace.
```ts Input
function add(a, b) {
- return a + b;
+ return a + b;
}
let x = 10;
```
-```ts Output
-function add(a, b) {
- return a + b;
-}
-let x = 10;
+```js Output
+function add(a,b){return a+b;}let x=10;
```
### Semicolon optimization
@@ -737,10 +736,8 @@ let b = 2;
return a + b;
```
-```ts Output
-let a = 1;
-let b = 2;
-return a + b;
+```js Output
+let a=1;let b=2;return a+b
```
### Operator spacing removal
@@ -750,15 +747,15 @@ return a + b;
Removes spaces around operators.
```ts Input
-a + b;
-x = y * z;
-(foo && bar) || baz;
+a + b
+x = y * z
+foo && bar || baz
```
-```ts Output
-a + b;
-x = y * z;
-(foo && bar) || baz;
+```js Output
+a+b
+x=y*z
+foo&&bar||baz
```
### Comment removal
@@ -771,14 +768,12 @@ Removes comments except important license comments.
// This comment is removed
/* So is this */
/*! But this license comment is kept */
-function test() {
- /* inline comment */
-}
+function test() { /* inline comment */ }
```
-```ts Output
+```js Output
/*! But this license comment is kept */
-function test() {}
+function test(){}
```
### Object and array formatting
@@ -789,15 +784,14 @@ Removes whitespace in object and array literals.
```ts Input
const obj = {
- name: "John",
- age: 30,
+ name: "John",
+ age: 30
};
const arr = [1, 2, 3];
```
-```ts Output
-const obj = { name: "John", age: 30 };
-const arr = [1, 2, 3];
+```js Output
+const obj={name:"John",age:30};const arr=[1,2,3];
```
### Control flow formatting
@@ -808,16 +802,15 @@ Removes whitespace in control structures.
```ts Input
if (condition) {
- doSomething();
+ doSomething();
}
for (let i = 0; i < 10; i++) {
- console.log(i);
+ console.log(i);
}
```
-```ts Output
-if (condition) doSomething();
-for (let i = 0; i < 10; i++) console.log(i);
+```js Output
+if(condition)doSomething();for(let i=0;i<10;i++)console.log(i);
```
### Function formatting
@@ -828,16 +821,13 @@ Removes whitespace in function declarations.
```ts Input
function myFunction(param1, param2) {
- return param1 + param2;
+ return param1 + param2;
}
const arrow = (a, b) => a + b;
```
-```ts Output
-function myFunction(a, b) {
- return a + b;
-}
-const arrow = (a, b) => a + b;
+```js Output
+function myFunction(a,b){return a+b}const arrow=(a,b)=>a+b;
```
### Parentheses minimization
@@ -847,14 +837,15 @@ const arrow = (a, b) => a + b;
Only adds parentheses when necessary for operator precedence.
```ts Input
-(a + b) * c;
-a + (b * c)(x);
+(a + b) * c
+a + (b * c)
+((x))
```
-```ts Output
-(a + b) * c;
-a + b * c;
-x;
+```js Output
+(a+b)*c
+a+b*c
+x
```
### Property mangling
@@ -863,12 +854,12 @@ x;
Renames object properties to shorter names when configured.
-```ts#input.ts (with property mangling enabled)
+```ts Input
obj.longPropertyName
```
-```ts Output
-obj.a;
+```js Output (with property mangling enabled)
+obj.a
```
### Template literal value folding
@@ -878,15 +869,19 @@ obj.a;
Converts non-string interpolated values to strings and folds them into the template.
```ts Input
-`hello ${123}``value: ${true}``result: ${null}``status: ${undefined}``big: ${10n}`;
+`hello ${123}`
+`value: ${true}`
+`result: ${null}`
+`status: ${undefined}`
+`big: ${10n}`
```
-```ts Output
-"hello 123";
-"value: true";
-"result: null";
-"status: undefined";
-"big: 10";
+```js Output
+"hello 123"
+"value: true"
+"result: null"
+"status: undefined"
+"big: 10"
```
### String length constant folding
@@ -896,13 +891,13 @@ Converts non-string interpolated values to strings and folds them into the templ
Evaluates `.length` property on string literals at compile time.
```ts Input
-"hello world".length;
-"test".length;
+"hello world".length
+"test".length
```
-```ts Output
-11;
-4;
+```js Output
+11
+4
```
### Constructor call simplification
@@ -912,22 +907,19 @@ Evaluates `.length` property on string literals at compile time.
Simplifies constructor calls for built-in types.
```ts Input
-new Object();
-new Object(null);
-new Object({ a: 1 });
-new Array();
-new Array(x, y);
+new Object()
+new Object(null)
+new Object({a: 1})
+new Array()
+new Array(x, y)
```
-```ts Output
-{
-}
-{
-}
-{
- a: 1;
-}
-[][(x, y)];
+```js Output
+{}
+{}
+{a:1}
+[]
+[x,y]
```
### Single property object inlining
@@ -937,11 +929,11 @@ new Array(x, y);
Inlines property access for objects with a single property.
```ts Input
-({ fn: () => console.log("hi") }).fn();
+({fn: () => console.log('hi')}).fn()
```
-```ts Output
-(() => console.log("hi"))();
+```js Output
+(() => console.log('hi'))()
```
### String charCodeAt constant folding
@@ -951,13 +943,13 @@ Inlines property access for objects with a single property.
Evaluates `charCodeAt()` on string literals for ASCII characters.
```ts Input
-"hello".charCodeAt(1);
-"A".charCodeAt(0);
+"hello".charCodeAt(1)
+"A".charCodeAt(0)
```
-```ts Output
-101;
-65;
+```js Output
+101
+65
```
### Void 0 equality to null equality
@@ -967,13 +959,13 @@ Evaluates `charCodeAt()` on string literals for ASCII characters.
Converts loose equality checks with `void 0` to `null` since they're equivalent.
```ts Input
-x == void 0;
-x != void 0;
+x == void 0
+x != void 0
```
-```ts Output
-x == null;
-x != null;
+```js Output
+x == null
+x != null
```
### Negation operator optimization
@@ -983,12 +975,13 @@ x != null;
Moves negation operator through comma expressions.
```ts Input
--(a, b) - (x, y, z);
+-(a, b)
+-(x, y, z)
```
-```ts Output
-(a, -b);
-(x, y, -z);
+```js Output
+a,-b
+x,y,-z
```
### Import.meta property inlining
@@ -998,17 +991,17 @@ Moves negation operator through comma expressions.
Inlines `import.meta` properties at build time when values are known.
```ts Input
-import.meta.dir;
-import.meta.file;
-import.meta.path;
-import.meta.url;
+import.meta.dir
+import.meta.file
+import.meta.path
+import.meta.url
```
-```ts Output
-"/path/to/directory";
-"filename.js";
-"/full/path/to/file.js";
-"file:///full/path/to/file.js";
+```js Output
+"/path/to/directory"
+"filename.js"
+"/full/path/to/file.js"
+"file:///full/path/to/file.js"
```
### Variable declaration merging
@@ -1024,11 +1017,9 @@ const c = 3;
const d = 4;
```
-```ts Output
-let a = 1,
- b = 2;
-const c = 3,
- d = 4;
+```js Output
+let a=1,b=2;
+const c=3,d=4;
```
### Expression statement merging
@@ -1043,8 +1034,8 @@ console.log(2);
console.log(3);
```
-```ts Output
-(console.log(1), console.log(2), console.log(3));
+```js Output
+console.log(1),console.log(2),console.log(3);
```
### Return statement merging
@@ -1058,8 +1049,8 @@ console.log(x);
return y;
```
-```ts Output
-return (console.log(x), y);
+```js Output
+return console.log(x),y;
```
### Throw statement merging
@@ -1073,8 +1064,8 @@ console.log(x);
throw new Error();
```
-```ts Output
-throw (console.log(x), new Error());
+```js Output
+throw(console.log(x),new Error());
```
### TypeScript enum cross-module inlining
@@ -1083,7 +1074,8 @@ throw (console.log(x), new Error());
Inlines enum values across module boundaries.
-```ts#input.ts (lib.ts)
+```ts Input
+// lib.ts
export enum Color { Red, Green, Blue }
// Input (main.ts)
@@ -1091,8 +1083,8 @@ import { Color } from './lib';
const x = Color.Red;
```
-```ts Output
-const x = 0;
+```js Output
+const x=0;
```
### Computed property enum inlining
@@ -1102,14 +1094,12 @@ const x = 0;
Inlines enum values used as computed object properties.
```ts Input
-enum Keys {
- FOO = "foo",
-}
-const obj = { [Keys.FOO]: value };
+enum Keys { FOO = 'foo' }
+const obj = { [Keys.FOO]: value }
```
-```ts Output
-const obj = { foo: value };
+```js Output
+const obj={foo:value}
```
### String number to numeric index
@@ -1119,13 +1109,13 @@ const obj = { foo: value };
Converts string numeric property access to numeric index.
```ts Input
-obj["0"];
-arr["5"];
+obj["0"]
+arr["5"]
```
-```ts Output
-obj[0];
-arr[5];
+```js Output
+obj[0]
+arr[5]
```
### Arrow function body shortening
@@ -1135,17 +1125,13 @@ arr[5];
Uses expression body syntax when an arrow function only returns a value.
```ts Input
-() => {
- return x;
-};
-a => {
- return a + 1;
-};
+() => { return x; }
+(a) => { return a + 1; }
```
-```ts Output
-() => x;
-a => a + 1;
+```js Output
+() => x
+a => a + 1
```
### Object property shorthand
@@ -1159,13 +1145,9 @@ Uses shorthand syntax when property name and value identifier match.
{ name: name, age: age }
```
-```ts Output
-{
- (x, y);
-}
-{
- (name, age);
-}
+```js Output
+{ x, y }
+{ name, age }
```
### Method shorthand
@@ -1181,7 +1163,7 @@ Uses method shorthand syntax in object literals.
}
```
-```ts Output
+```js Output
{
foo() {},
async bar() {}
@@ -1201,10 +1183,8 @@ function test() {
}
```
-```ts Output
-function test() {
- return x;
-}
+```js Output
+function test(){return x}
```
### Drop console calls
@@ -1219,10 +1199,10 @@ console.warn("warning");
x = console.error("error");
```
-```ts Output
+```js Output
void 0;
void 0;
-x = void 0;
+x=void 0;
```
### Drop custom function calls
@@ -1231,12 +1211,12 @@ x = void 0;
Removes calls to specified global functions or methods.
-```ts#input.ts with --drop=assert
+```ts Input
assert(condition);
obj.assert(test);
```
-```ts Output
+```js Output with --drop=assert
void 0;
void 0;
```
@@ -1268,7 +1248,7 @@ This preserves the `.name` property on functions and classes while still minifyi
Using all three minification modes together:
-```ts#input.ts (158 bytes)
+```ts input.ts (158 bytes)
const myVariable = 42;
const myFunction = () => {
@@ -1280,7 +1260,7 @@ const myFunction = () => {
const output = myFunction();
```
-```ts#output.ts
+```js output.js
// Output with --minify (49 bytes, 69% reduction)
const a=42,b=()=>{const c=!0,d=void 0;return c?a:d},e=b();
```
diff --git a/docs/bundler/plugins.mdx b/docs/bundler/plugins.mdx
index 49caa67bae..58908e352f 100644
--- a/docs/bundler/plugins.mdx
+++ b/docs/bundler/plugins.mdx
@@ -42,7 +42,21 @@ type PluginBuilder = {
config: BuildConfig;
};
-type Loader = "js" | "jsx" | "ts" | "tsx" | "css" | "json" | "toml";
+type Loader =
+ | "js"
+ | "jsx"
+ | "ts"
+ | "tsx"
+ | "json"
+ | "jsonc"
+ | "toml"
+ | "yaml"
+ | "file"
+ | "napi"
+ | "wasm"
+ | "text"
+ | "css"
+ | "html";
```
## Usage
diff --git a/docs/docs.json b/docs/docs.json
index 38fd681f92..cfb3924cc3 100644
--- a/docs/docs.json
+++ b/docs/docs.json
@@ -354,7 +354,7 @@
"/guides/ecosystem/discordjs",
"/guides/ecosystem/docker",
"/guides/ecosystem/drizzle",
- "/guides/ecosystem/edgedb",
+ "/guides/ecosystem/gel",
"/guides/ecosystem/elysia",
"/guides/ecosystem/express",
"/guides/ecosystem/hono",
diff --git a/docs/guides/deployment/google-cloud-run.mdx b/docs/guides/deployment/google-cloud-run.mdx
index 153f61fc69..4e6aac5182 100644
--- a/docs/guides/deployment/google-cloud-run.mdx
+++ b/docs/guides/deployment/google-cloud-run.mdx
@@ -132,9 +132,7 @@ CMD ["bun", "index.ts"]
Make sure that the start command corresponds to your application's entry point. This can also be `CMD ["bun", "run", "start"]` if you have a start script in your `package.json`.
- This image installs dependencies and runs your app with Bun inside a container. If your app doesn't have dependencies, you can omit the `RUN bun install --production --frozen-lockfile` line.
-
- This image installs dependencies and runs your app with Bun inside a container. If your app doesn't have dependencies, you can omit the `RUN bun install --production --frozen-lockfile` line.
+This image installs dependencies and runs your app with Bun inside a container. If your app doesn't have dependencies, you can omit the `RUN bun install --production --frozen-lockfile` line.
@@ -159,8 +157,7 @@ Make sure you're in the directory containing your `Dockerfile`, then deploy dire
Update the `--region` flag to your preferred region. You can also omit this flag to get an interactive prompt to
- select a region. Update the `--region` flag to your preferred region. You can also omit this flag to get an
- interactive prompt to select a region.
+ select a region.
```bash terminal icon="terminal"
diff --git a/docs/guides/ecosystem/edgedb.mdx b/docs/guides/ecosystem/gel.mdx
similarity index 63%
rename from docs/guides/ecosystem/edgedb.mdx
rename to docs/guides/ecosystem/gel.mdx
index 6b621f9139..c26a6bfa64 100644
--- a/docs/guides/ecosystem/edgedb.mdx
+++ b/docs/guides/ecosystem/gel.mdx
@@ -1,23 +1,27 @@
---
-title: Use EdgeDB with Bun
-sidebarTitle: EdgeDB with Bun
+title: Use Gel with Bun
+sidebarTitle: Gel with Bun
mode: center
---
-EdgeDB is a graph-relational database powered by Postgres under the hood. It provides a declarative schema language, migrations system, and object-oriented query language, in addition to supporting raw SQL queries. It solves the object-relational mapping problem at the database layer, eliminating the need for an ORM library in your application code.
+Gel (formerly EdgeDB) is a graph-relational database powered by Postgres under the hood. It provides a declarative schema language, migrations system, and object-oriented query language, in addition to supporting raw SQL queries. It solves the object-relational mapping problem at the database layer, eliminating the need for an ORM library in your application code.
---
-First, [install EdgeDB](https://www.edgedb.com/install) if you haven't already.
+First, [install Gel](https://docs.geldata.com/learn/installation) if you haven't already.
```sh Linux/macOS terminal icon="terminal"
-curl --proto '=https' --tlsv1.2 -sSf https://sh.edgedb.com | sh
+curl https://www.geldata.com/sh --proto "=https" -sSf1 | sh
```
```sh Windows terminal icon="windows"
-iwr https://ps1.edgedb.com -useb | iex
+irm https://www.geldata.com/ps1 | iex
+```
+
+```sh Homebrew terminal icon="terminal"
+brew install geldata/tap/gel-cli
```
@@ -34,35 +38,35 @@ bun init -y
---
-We'll use the EdgeDB CLI to initialize an EdgeDB instance for our project. This creates an `edgedb.toml` file in our project root.
+We'll use the Gel CLI to initialize a Gel instance for our project. This creates a `gel.toml` file in our project root.
```sh terminal icon="terminal"
-edgedb project init
+gel project init
```
```txt
-No `edgedb.toml` found in `/Users/colinmcd94/Documents/bun/fun/examples/my-edgedb-app` or above
+No `gel.toml` found in `/Users/colinmcd94/Documents/bun/fun/examples/my-gel-app` or above
Do you want to initialize a new project? [Y/n]
> Y
-Specify the name of EdgeDB instance to use with this project [default: my_edgedb_app]:
-> my_edgedb_app
-Checking EdgeDB versions...
-Specify the version of EdgeDB to use with this project [default: x.y]:
+Specify the name of Gel instance to use with this project [default: my_gel_app]:
+> my_gel_app
+Checking Gel versions...
+Specify the version of Gel to use with this project [default: x.y]:
> x.y
-βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
-β Project directory β /Users/colinmcd94/Documents/bun/fun/examples/my-edgedb-app β
-β Project config β /Users/colinmcd94/Documents/bun/fun/examples/my-edgedb-app/edgedb.toml β
-β Schema dir (empty) β /Users/colinmcd94/Documents/bun/fun/examples/my-edgedb-app/dbschema β
-β Installation method β portable package β
-β Version β x.y+6d5921b β
-β Instance name β my_edgedb_app β
-βββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+βββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
+β Project directory β /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app β
+β Project config β /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app/gel.tomlβ
+β Schema dir (empty) β /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app/dbschemaβ
+β Installation method β portable package β
+β Version β x.y+6d5921b β
+β Instance name β my_gel_app β
+βββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Version x.y+6d5921b is already downloaded
-Initializing EdgeDB instance...
+Initializing Gel instance...
Applying migrations...
Everything is up to date. Revision initial
Project initialized.
-To connect to my_edgedb_app, run `edgedb`
+To connect to my_gel_app, run `gel`
```
---
@@ -70,8 +74,8 @@ To connect to my_edgedb_app, run `edgedb`
To see if the database is running, let's open a REPL and run a simple query.
```sh terminal icon="terminal"
-edgedb
-edgedb> select 1 + 1;
+gel
+gel> select 1 + 1;
```
```txt
@@ -81,12 +85,12 @@ edgedb> select 1 + 1;
Then run `\quit` to exit the REPL.
```sh terminal icon="terminal"
-edgedb> \quit
+gel> \quit
```
---
-With the project initialized, we can define a schema. The `edgedb project init` command already created a `dbschema/default.esdl` file to contain our schema.
+With the project initialized, we can define a schema. The `gel project init` command already created a `dbschema/default.esdl` file to contain our schema.
```txt File Tree icon="folder-tree"
dbschema
@@ -112,15 +116,15 @@ module default {
Then generate and apply an initial migration.
```sh terminal icon="terminal"
-edgedb migration create
+gel migration create
```
```txt
-Created /Users/colinmcd94/Documents/bun/fun/examples/my-edgedb-app/dbschema/migrations/00001.edgeql, id: m1uwekrn4ni4qs7ul7hfar4xemm5kkxlpswolcoyqj3xdhweomwjrq
+Created /Users/colinmcd94/Documents/bun/fun/examples/my-gel-app/dbschema/migrations/00001.edgeql, id: m1uwekrn4ni4qs7ul7hfar4xemm5kkxlpswolcoyqj3xdhweomwjrq
```
```sh terminal icon="terminal"
-edgedb migrate
+gel migrate
```
```txt
@@ -129,11 +133,11 @@ Applied m1uwekrn4ni4qs7ul7hfar4xemm5kkxlpswolcoyqj3xdhweomwjrq (00001.edgeql)
---
-With our schema applied, let's execute some queries using EdgeDB's JavaScript client library. We'll install the client library and EdgeDB's codegen CLI, and create a `seed.ts`.file.
+With our schema applied, let's execute some queries using Gel's JavaScript client library. We'll install the client library and Gel's codegen CLI, and create a `seed.ts`.file.
```sh terminal icon="terminal"
-bun add edgedb
-bun add -D @edgedb/generate
+bun add gel
+bun add -D @gel/generate
touch seed.ts
```
@@ -144,7 +148,7 @@ Paste the following code into `seed.ts`.
The client auto-connects to the database. We insert a couple movies using the `.execute()` method. We will use EdgeQL's `for` expression to turn this bulk insert into a single optimized query.
```ts seed.ts icon="/icons/typescript.svg"
-import { createClient } from "edgedb";
+import { createClient } from "gel";
const client = createClient();
@@ -184,10 +188,10 @@ Seeding complete.
---
-EdgeDB implements a number of code generation tools for TypeScript. To query our newly seeded database in a typesafe way, we'll use `@edgedb/generate` to code-generate the EdgeQL query builder.
+Gel implements a number of code generation tools for TypeScript. To query our newly seeded database in a typesafe way, we'll use `@gel/generate` to code-generate the EdgeQL query builder.
```sh terminal icon="terminal"
-bunx @edgedb/generate edgeql-js
+bunx @gel/generate edgeql-js
```
```txt
@@ -213,7 +217,7 @@ the query builder directory? The following line will be added:
In `index.ts`, we can import the generated query builder from `./dbschema/edgeql-js` and write a simple select query.
```ts index.ts icon="/icons/typescript.svg"
-import { createClient } from "edgedb";
+import { createClient } from "gel";
import e from "./dbschema/edgeql-js";
const client = createClient();
@@ -254,4 +258,4 @@ bun run index.ts
---
-For complete documentation, refer to the [EdgeDB docs](https://www.edgedb.com/docs).
+For complete documentation, refer to the [Gel docs](https://docs.geldata.com/).
diff --git a/docs/guides/test/snapshot.mdx b/docs/guides/test/snapshot.mdx
index 1141c42f21..df16c0f73d 100644
--- a/docs/guides/test/snapshot.mdx
+++ b/docs/guides/test/snapshot.mdx
@@ -64,10 +64,10 @@ Later, when this test file is executed again, Bun will read the snapshot file an
```sh terminal icon="terminal"
bun test
-bun test v1.3.2 (9c68abdb)
```
```txt
+bun test v1.3.2 (9c68abdb)
test/snap.test.ts:
β snapshot [1.05ms]
@@ -83,10 +83,10 @@ To update snapshots, use the `--update-snapshots` flag.
```sh terminal icon="terminal"
bun test --update-snapshots
-bun test v1.3.2 (9c68abdb)
```
```txt
+bun test v1.3.2 (9c68abdb)
test/snap.test.ts:
β snapshot [0.86ms]
diff --git a/docs/pm/cli/pm.mdx b/docs/pm/cli/pm.mdx
index b5683f8c52..d2f1cca971 100644
--- a/docs/pm/cli/pm.mdx
+++ b/docs/pm/cli/pm.mdx
@@ -303,7 +303,7 @@ scripts[test:watch] # bracket for special chars
Examples:
```bash terminal icon="terminal"
-# set
+# get
bun pm pkg get name # single property
bun pm pkg get name version # multiple properties
bun pm pkg get # entire package.json
diff --git a/docs/pm/workspaces.mdx b/docs/pm/workspaces.mdx
index 3d220aa1e7..6570597dd4 100644
--- a/docs/pm/workspaces.mdx
+++ b/docs/pm/workspaces.mdx
@@ -7,8 +7,7 @@ Bun supports [`workspaces`](https://docs.npmjs.com/cli/v9/using-npm/workspaces?v
It's common for a monorepo to have the following structure:
-```
-tree
+```txt File Tree icon="folder-tree"
βββ README.md
βββ bun.lock
diff --git a/docs/runtime/bunfig.mdx b/docs/runtime/bunfig.mdx
index fef0c10d19..57fb1805f0 100644
--- a/docs/runtime/bunfig.mdx
+++ b/docs/runtime/bunfig.mdx
@@ -401,6 +401,7 @@ Environment variable: `BUN_INSTALL_BIN`
```toml title="bunfig.toml" icon="settings"
# where globally-installed package bins are linked
+[install]
globalBinDir = "~/.bun/bin"
```
diff --git a/docs/runtime/module-resolution.mdx b/docs/runtime/module-resolution.mdx
index 2dcd123621..a43843eec6 100644
--- a/docs/runtime/module-resolution.mdx
+++ b/docs/runtime/module-resolution.mdx
@@ -276,7 +276,7 @@ await Bun.build({
## Path re-mapping
-In the spirit of treating TypeScript as a first-class citizen, the Bun runtime will re-map import paths according to the [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig#paths) field in `tsconfig.json`. This is a major divergence from Node.js, which doesn't support any form of import path re-mapping.
+Bun supports import path re-mapping through TypeScript's [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig#paths) in `tsconfig.json`, which works well with editors. If you aren't a TypeScript user, you can achieve the same behavior by using a [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) in your project root.
```json tsconfig.json icon="file-json"
{
@@ -289,7 +289,16 @@ In the spirit of treating TypeScript as a first-class citizen, the Bun runtime w
}
```
-If you aren't a TypeScript user, you can create a [`jsconfig.json`](https://code.visualstudio.com/docs/languages/jsconfig) in your project root to achieve the same behavior.
+Bun also supports [Node.js-style subpath imports](https://nodejs.org/api/packages.html#subpath-imports) in `package.json`, where mapped paths must start with `#`. This approach doesnβt work as well with editors, but both options can be used together.
+
+```json package.json icon="file-json"
+{
+ "imports": {
+ "#config": "./config.ts", // map specifier to file
+ "#components/*": "./components/*" // wildcard matching
+ }
+}
+```
diff --git a/docs/runtime/plugins.mdx b/docs/runtime/plugins.mdx
index a64af263a6..aff34d67a5 100644
--- a/docs/runtime/plugins.mdx
+++ b/docs/runtime/plugins.mdx
@@ -42,7 +42,21 @@ type PluginBuilder = {
config: BuildConfig;
};
-type Loader = "js" | "jsx" | "ts" | "tsx" | "css" | "json" | "toml";
+type Loader =
+ | "js"
+ | "jsx"
+ | "ts"
+ | "tsx"
+ | "json"
+ | "jsonc"
+ | "toml"
+ | "yaml"
+ | "file"
+ | "napi"
+ | "wasm"
+ | "text"
+ | "css"
+ | "html";
```
## Usage
diff --git a/docs/runtime/s3.mdx b/docs/runtime/s3.mdx
index 992293f85e..e4831efc7b 100644
--- a/docs/runtime/s3.mdx
+++ b/docs/runtime/s3.mdx
@@ -697,7 +697,7 @@ To list some or all (up to 1,000) objects in a bucket, you can use the `S3Client
```ts s3.ts icon="/icons/typescript.svg" highlight={12, 15-20, 24-29}
import { S3Client } from "bun";
-const credentials = { ... }
+const credentials = {
accessKeyId: "your-access-key",
secretAccessKey: "your-secret-key",
bucket: "my-bucket",
diff --git a/docs/snippets/guides.jsx b/docs/snippets/guides.jsx
index ce3c7a7f8a..248814ef79 100644
--- a/docs/snippets/guides.jsx
+++ b/docs/snippets/guides.jsx
@@ -123,7 +123,7 @@ export const GuidesList = () => {
title: "Ecosystem",
icon: "puzzle",
items: [
- { title: "Use EdgeDB with Bun", href: "/guides/ecosystem/edgedb" },
+ { title: "Use Gel with Bun", href: "/guides/ecosystem/gel" },
{ title: "Use Prisma ORM with Bun", href: "/guides/ecosystem/prisma" },
{ title: "Use Prisma Postgres with Bun", href: "/guides/ecosystem/prisma-postgres" },
{ title: "Create a Discord bot", href: "/guides/ecosystem/discordjs" },
diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts
index 6d66e097c5..a4d84f4352 100644
--- a/packages/bun-types/bun.d.ts
+++ b/packages/bun-types/bun.d.ts
@@ -4041,7 +4041,21 @@ declare module "bun" {
| "browser";
/** https://bun.com/docs/bundler/loaders */
- type Loader = "js" | "jsx" | "ts" | "tsx" | "json" | "toml" | "file" | "napi" | "wasm" | "text" | "css" | "html";
+ type Loader =
+ | "js"
+ | "jsx"
+ | "ts"
+ | "tsx"
+ | "json"
+ | "jsonc"
+ | "toml"
+ | "yaml"
+ | "file"
+ | "napi"
+ | "wasm"
+ | "text"
+ | "css"
+ | "html";
interface PluginConstraints {
/**
@@ -6430,6 +6444,16 @@ declare module "bun" {
/** @see https://bun.com/docs/install/catalogs */
catalogs?: Record>;
+ /**
+ * `0` / `undefined` for projects created before v1.3.2, `1` for projects created after.
+ *
+ * ---
+ * Right now this only changes the default [install linker strategy](https://bun.com/docs/pm/cli/install#isolated-installs):
+ * - With `0`, the linker is hoisted.
+ * - With `1`, the linker is isolated for workspaces and hoisted for single-package projects.
+ */
+ configVersion?: 0 | 1;
+
/**
* ```
* INFO = { prod/dev/optional/peer dependencies, os, cpu, libc (TODO), bin, binDir }
diff --git a/packages/bun-usockets/src/crypto/openssl.c b/packages/bun-usockets/src/crypto/openssl.c
index 60e1900da5..348819d0e8 100644
--- a/packages/bun-usockets/src/crypto/openssl.c
+++ b/packages/bun-usockets/src/crypto/openssl.c
@@ -635,7 +635,7 @@ ssl_on_writable(struct us_internal_ssl_socket_t *s) {
(struct us_internal_ssl_socket_context_t *)us_socket_context(0, &s->s);
// if this one fails to write data, it sets ssl_read_wants_write again
- s = (struct us_internal_ssl_socket_t *)context->sc.on_data(&s->s, 0,
+ s = (struct us_internal_ssl_socket_t *)context->sc.on_data(&s->s, "",
0); // cast here!
}
// Do not call on_writable if the socket is closed.
diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh
index ead3de99c8..7bc2a53ad6 100755
--- a/scripts/bootstrap.sh
+++ b/scripts/bootstrap.sh
@@ -1392,6 +1392,10 @@ create_buildkite_user() {
create_file "$file"
done
+ # The following is necessary to configure buildkite to use a stable
+ # checkout directory. sccache hashes absolute paths into its cache keys,
+ # so if buildkite uses a different checkout path each time (which it does
+ # by default), sccache will be useless.
local opts=$-
set -ef
diff --git a/src/bake/DevServer/SerializedFailure.zig b/src/bake/DevServer/SerializedFailure.zig
index d14af1c133..9ee733aa89 100644
--- a/src/bake/DevServer/SerializedFailure.zig
+++ b/src/bake/DevServer/SerializedFailure.zig
@@ -104,29 +104,6 @@ pub const ErrorKind = enum(u8) {
js_aggregate,
};
-pub fn initFromJs(dev: *DevServer, owner: Owner, value: JSValue) !SerializedFailure {
- {
- _ = value;
- @panic("TODO");
- }
- // Avoid small re-allocations without requesting so much from the heap
- var sfb = std.heap.stackFallback(65536, dev.allocator());
- var payload = std.array_list.Managed(u8).initCapacity(sfb.get(), 65536) catch
- unreachable; // enough space
- const w = payload.writer();
-
- try w.writeInt(u32, @bitCast(owner.encode()), .little);
- // try writeJsValue(value);
-
- // Avoid-recloning if it is was moved to the hap
- const data = if (payload.items.ptr == &sfb.buffer)
- try dev.allocator().dupe(u8, payload.items)
- else
- payload.items;
-
- return .{ .data = data };
-}
-
pub fn initFromLog(
dev: *DevServer,
owner: Owner,
diff --git a/src/bake/DevServer/SourceMapStore.zig b/src/bake/DevServer/SourceMapStore.zig
index ea65007624..26a41954e1 100644
--- a/src/bake/DevServer/SourceMapStore.zig
+++ b/src/bake/DevServer/SourceMapStore.zig
@@ -273,7 +273,11 @@ pub const Entry = struct {
.none => {
// NOTE: It is too late to compute the line count since the bundled text may
// have been freed already. For example, a HMR chunk is never persisted.
- @panic("Missing internal precomputed line count.");
+ // We could return an error here but what would be a better behavior for renderJSON and renderMappings?
+ // This is a dev server, crashing is not a good DX, we could fail the request but that's not a good DX either.
+ if (bun.Environment.enable_logs) {
+ mapLog("Skipping source map entry with missing line count at index {d}", .{i});
+ }
},
};
}
diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig
index e5746333bc..b766a6f7a1 100644
--- a/src/bun.js/api/bun/socket.zig
+++ b/src/bun.js/api/bun/socket.zig
@@ -1357,6 +1357,10 @@ pub fn NewSocket(comptime ssl: bool) type {
return .js_undefined;
}
+ pub fn getFD(this: *This, _: *jsc.JSGlobalObject) JSValue {
+ return this.socket.fd().toJSWithoutMakingLibUVOwned();
+ }
+
pub fn getBytesWritten(this: *This, _: *jsc.JSGlobalObject) JSValue {
return jsc.JSValue.jsNumber(this.bytes_written + this.buffered_data_for_node_net.len);
}
diff --git a/src/bun.js/api/bun/socket/Listener.zig b/src/bun.js/api/bun/socket/Listener.zig
index fbb6c69c5e..420347cb38 100644
--- a/src/bun.js/api/bun/socket/Listener.zig
+++ b/src/bun.js/api/bun/socket/Listener.zig
@@ -523,6 +523,19 @@ pub fn getPort(this: *Listener, _: *jsc.JSGlobalObject) JSValue {
return JSValue.jsNumber(this.connection.host.port);
}
+pub fn getFD(this: *Listener, _: *jsc.JSGlobalObject) JSValue {
+ switch (this.listener) {
+ .uws => |uws_listener| {
+ switch (this.ssl) {
+ inline else => |ssl| {
+ return uws_listener.socket(ssl).fd().toJSWithoutMakingLibUVOwned();
+ },
+ }
+ },
+ else => return JSValue.jsNumber(-1),
+ }
+}
+
pub fn ref(this: *Listener, globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!JSValue {
const this_value = callframe.this();
if (this.listener == .none) return .js_undefined;
diff --git a/src/bun.js/api/sockets.classes.ts b/src/bun.js/api/sockets.classes.ts
index 13d89453ff..ef001397c7 100644
--- a/src/bun.js/api/sockets.classes.ts
+++ b/src/bun.js/api/sockets.classes.ts
@@ -21,6 +21,7 @@ function generate(ssl) {
fn: "pauseFromJS",
length: 0,
},
+
getTLSFinishedMessage: {
fn: "getTLSFinishedMessage",
length: 0,
@@ -91,6 +92,9 @@ function generate(ssl) {
bytesWritten: {
getter: "getBytesWritten",
},
+ fd: {
+ getter: "getFD",
+ },
setNoDelay: {
fn: "setNoDelay",
length: 1,
@@ -263,7 +267,9 @@ export default [
fn: "unref",
length: 0,
},
-
+ fd: {
+ getter: "getFD",
+ },
port: {
getter: "getPort",
},
diff --git a/src/cli/run_command.zig b/src/cli/run_command.zig
index 2d099b25dd..956dd9f851 100644
--- a/src/cli/run_command.zig
+++ b/src/cli/run_command.zig
@@ -1053,9 +1053,6 @@ pub const RunCommand = struct {
bun.copy(u8, path_buf[dir_slice.len..], base);
path_buf[dir_slice.len + base.len] = 0;
const slice = path_buf[0 .. dir_slice.len + base.len :0];
- if (Environment.isWindows) {
- @panic("TODO");
- }
if (!(bun.sys.isExecutableFilePath(slice))) continue;
// we need to dupe because the string pay point to a pointer that only exists in the current scope
_ = try results.getOrPut(this_transpiler.fs.filename_store.append(@TypeOf(base), base) catch continue);
diff --git a/src/deps/uws/ListenSocket.zig b/src/deps/uws/ListenSocket.zig
index 334bc98e6c..98f93d6977 100644
--- a/src/deps/uws/ListenSocket.zig
+++ b/src/deps/uws/ListenSocket.zig
@@ -11,6 +11,9 @@ pub const ListenSocket = opaque {
pub fn getSocket(this: *ListenSocket) *uws.us_socket_t {
return @ptrCast(this);
}
+ pub fn socket(this: *ListenSocket, comptime is_ssl: bool) uws.NewSocketHandler(is_ssl) {
+ return uws.NewSocketHandler(is_ssl).from(this.getSocket());
+ }
};
const c = struct {
diff --git a/src/deps/uws/socket.zig b/src/deps/uws/socket.zig
index 971f08c495..bab59f189c 100644
--- a/src/deps/uws/socket.zig
+++ b/src/deps/uws/socket.zig
@@ -183,7 +183,7 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type {
const res = Fields.onData(
getValue(socket),
TLSSocket.from(socket),
- buf.?[0..@as(usize, @intCast(len))],
+ if (buf) |data_ptr| data_ptr[0..@as(usize, @intCast(len))] else "",
);
if (@TypeOf(res) != void) res catch |err| switch (err) {
error.JSTerminated => return null, // TODO: declare throw scope
@@ -295,16 +295,29 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type {
}
pub inline fn fd(this: ThisSocket) bun.FileDescriptor {
- if (comptime is_ssl) {
- @compileError("SSL sockets do not have a file descriptor accessible this way");
- }
const socket = this.socket.get() orelse return bun.invalid_fd;
-
- // on windows uSockets exposes SOCKET
- return if (comptime Environment.isWindows)
- .fromNative(@ptrCast(socket.getNativeHandle(is_ssl).?))
- else
- .fromNative(@intCast(@intFromPtr(socket.getNativeHandle(is_ssl))));
+ if (comptime is_ssl) {
+ if (socket.getNativeHandle(is_ssl)) |handle| {
+ const ssl_ptr: *BoringSSL.SSL = @as(*BoringSSL.SSL, @ptrCast(handle));
+ const fd_value = BoringSSL.SSL_get_fd(ssl_ptr);
+ if (fd_value == -1) {
+ return bun.invalid_fd;
+ }
+ return if (Environment.isWindows)
+ .fromNative(@ptrFromInt(@as(usize, @intCast(fd_value))))
+ else
+ .fromNative(fd_value);
+ }
+ return bun.invalid_fd;
+ }
+ if (socket.getNativeHandle(is_ssl)) |handle| {
+ // on windows uSockets exposes SOCKET
+ return if (comptime Environment.isWindows)
+ .fromNative(@ptrCast(handle))
+ else
+ .fromNative(@intCast(@intFromPtr(handle)));
+ }
+ return bun.invalid_fd;
}
pub fn markNeedsMoreForSendfile(this: ThisSocket) void {
@@ -731,7 +744,7 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type {
const res = Fields.onData(
getValue(socket),
SocketHandlerType.from(socket),
- buf.?[0..@as(usize, @intCast(len))],
+ if (buf) |data_ptr| data_ptr[0..@as(usize, @intCast(len))] else "",
);
if (@TypeOf(res) != void) res catch |err| switch (err) {
error.JSTerminated => return null, // TODO: declare throw scope
@@ -897,7 +910,7 @@ pub fn NewSocketHandler(comptime is_ssl: bool) type {
const res = Fields.onData(
getValue(socket),
ThisSocket.from(socket),
- buf.?[0..@as(usize, @intCast(len))],
+ if (buf) |data_ptr| data_ptr[0..@as(usize, @intCast(len))] else "",
);
if (@TypeOf(res) != void) res catch |err| switch (err) {
error.JSTerminated => return null, // TODO: declare throw scope
diff --git a/src/deps/uws/us_socket_t.zig b/src/deps/uws/us_socket_t.zig
index a67efb9286..7261178814 100644
--- a/src/deps/uws/us_socket_t.zig
+++ b/src/deps/uws/us_socket_t.zig
@@ -18,7 +18,7 @@ pub const us_socket_t = opaque {
if (ip_addr) |ip| {
bun.assert(ip.len < max_i32);
- _ = c.us_socket_open(ssl, this, @intFromBool(is_client), ip.ptr, @intCast(ip.len));
+ _ = c.us_socket_open(ssl, this, @intFromBool(is_client), ip.ptr, @intCast(@min(ip.len, std.math.maxInt(i32))));
} else {
_ = c.us_socket_open(ssl, this, @intFromBool(is_client), null, 0);
}
@@ -66,7 +66,7 @@ pub const us_socket_t = opaque {
/// Returned slice is a view into `buf`.
pub fn localAddress(this: *us_socket_t, ssl: bool, buf: []u8) ![]const u8 {
- var length: i32 = @intCast(buf.len);
+ var length: i32 = @intCast(@min(buf.len, std.math.maxInt(i32)));
c.us_socket_local_address(@intFromBool(ssl), this, buf.ptr, &length);
if (length < 0) {
@@ -81,7 +81,7 @@ pub const us_socket_t = opaque {
/// Returned slice is a view into `buf`. On error, `errno` should be set
pub fn remoteAddress(this: *us_socket_t, ssl: bool, buf: []u8) ![]const u8 {
- var length: i32 = @intCast(buf.len);
+ var length: i32 = @intCast(@min(buf.len, std.math.maxInt(i32)));
c.us_socket_remote_address(@intFromBool(ssl), this, buf.ptr, &length);
if (length < 0) {
@@ -95,11 +95,11 @@ pub const us_socket_t = opaque {
}
pub fn setTimeout(this: *us_socket_t, ssl: bool, seconds: u32) void {
- c.us_socket_timeout(@intFromBool(ssl), this, @intCast(seconds));
+ c.us_socket_timeout(@intFromBool(ssl), this, seconds);
}
pub fn setLongTimeout(this: *us_socket_t, ssl: bool, minutes: u32) void {
- c.us_socket_long_timeout(@intFromBool(ssl), this, @intCast(minutes));
+ c.us_socket_long_timeout(@intFromBool(ssl), this, minutes);
}
pub fn setNodelay(this: *us_socket_t, enabled: bool) void {
@@ -109,7 +109,7 @@ pub const us_socket_t = opaque {
/// Returns error code. `0` on success. error codes depend on platform an
/// configured event loop.
pub fn setKeepalive(this: *us_socket_t, enabled: bool, delay: u32) i32 {
- return c.us_socket_keepalive(this, @intFromBool(enabled), @intCast(delay));
+ return c.us_socket_keepalive(this, @intFromBool(enabled), delay);
}
pub fn getNativeHandle(this: *us_socket_t, ssl: bool) ?*anyopaque {
@@ -127,14 +127,14 @@ pub const us_socket_t = opaque {
}
pub fn write(this: *us_socket_t, ssl: bool, data: []const u8) i32 {
- const rc = c.us_socket_write(@intFromBool(ssl), this, data.ptr, @intCast(data.len));
+ const rc = c.us_socket_write(@intFromBool(ssl), this, data.ptr, @intCast(@min(data.len, std.math.maxInt(i32))));
debug("us_socket_write({p}, {d}) = {d}", .{ this, data.len, rc });
return rc;
}
pub fn writeFd(this: *us_socket_t, data: []const u8, file_descriptor: bun.FD) i32 {
if (bun.Environment.isWindows) @compileError("TODO: implement writeFd on Windows");
- const rc = c.us_socket_ipc_write_fd(this, data.ptr, @intCast(data.len), file_descriptor.native());
+ const rc = c.us_socket_ipc_write_fd(this, data.ptr, @intCast(@min(data.len, std.math.maxInt(i32))), file_descriptor.native());
debug("us_socket_ipc_write_fd({p}, {d}, {d}) = {d}", .{ this, data.len, file_descriptor.native(), rc });
return rc;
}
@@ -147,7 +147,7 @@ pub const us_socket_t = opaque {
pub fn rawWrite(this: *us_socket_t, ssl: bool, data: []const u8) i32 {
debug("us_socket_raw_write({p}, {d})", .{ this, data.len });
- return c.us_socket_raw_write(@intFromBool(ssl), this, data.ptr, @intCast(data.len));
+ return c.us_socket_raw_write(@intFromBool(ssl), this, data.ptr, @intCast(@min(data.len, std.math.maxInt(i32))));
}
pub fn flush(this: *us_socket_t, ssl: bool) void {
diff --git a/src/fd.zig b/src/fd.zig
index ea11cedbd6..75eab577cf 100644
--- a/src/fd.zig
+++ b/src/fd.zig
@@ -346,6 +346,9 @@ pub const FD = packed struct(backing_int) {
/// After calling, the input file descriptor is no longer valid and must not be used.
/// If an error is thrown, the file descriptor is cleaned up for you.
pub fn toJS(any_fd: FD, global: *jsc.JSGlobalObject) JSValue {
+ if (!any_fd.isValid()) {
+ return JSValue.jsNumberFromInt32(-1);
+ }
const uv_owned_fd = any_fd.makeLibUVOwned() catch {
any_fd.close();
return global.throwValue((jsc.SystemError{
@@ -356,6 +359,24 @@ pub const FD = packed struct(backing_int) {
return JSValue.jsNumberFromInt32(uv_owned_fd.uv());
}
+ /// Convert an FD to a JavaScript number without transferring ownership to libuv.
+ /// Unlike toJS(), this does not call makeLibUVOwned() on Windows, so the caller
+ /// retains ownership and must close the FD themselves.
+ /// Returns -1 for invalid file descriptors.
+ /// On Windows: returns Uint64 for system handles, Int32 for uv file descriptors.
+ pub fn toJSWithoutMakingLibUVOwned(any_fd: FD) JSValue {
+ if (!any_fd.isValid()) {
+ return JSValue.jsNumberFromInt32(-1);
+ }
+ if (Environment.isWindows) {
+ return switch (any_fd.kind) {
+ .system => JSValue.jsNumberFromUint64(@intCast(any_fd.value.as_system)),
+ .uv => JSValue.jsNumberFromInt32(any_fd.value.as_uv),
+ };
+ }
+ return JSValue.jsNumberFromInt32(any_fd.value.as_system);
+ }
+
pub const Stdio = enum(u8) {
std_in = 0,
std_out = 1,
diff --git a/src/sys.zig b/src/sys.zig
index c9aff7d40d..a2b860fed3 100644
--- a/src/sys.zig
+++ b/src/sys.zig
@@ -3430,29 +3430,23 @@ pub fn isExecutableFileOSPath(path: bun.OSPathSliceZ) bool {
if (comptime Environment.isWindows) {
// Rationale: `GetBinaryTypeW` does not work on .cmd files.
- // Windows does not have executable permission like posix does, instead we
- // can just look at the file extension to determine executable status.
- @compileError("Do not use isExecutableFilePath on Windows");
+ // SaferiIsExecutableFileType works on .cmd files.
+ // The following file name extensions are examples of executable file types. This is not a complete list.
+ // .bat
+ // .cmd
+ // .com
+ // .exe
+ // .js
+ // .lnk
+ // .pif
+ // .pl
+ // .shs
+ // .url
+ // .vbs
+ // The security policy Microsoft Management Console (MMC) snap-in (Secpol.msc) controls which extensions are considered executable file types.
- // var out: windows.DWORD = 0;
- // const rc = kernel32.GetBinaryTypeW(path, &out);
-
- // const result = if (rc == windows.FALSE)
- // false
- // else switch (out) {
- // kernel32.SCS_32BIT_BINARY,
- // kernel32.SCS_64BIT_BINARY,
- // kernel32.SCS_DOS_BINARY,
- // kernel32.SCS_OS216_BINARY,
- // kernel32.SCS_PIF_BINARY,
- // kernel32.SCS_POSIX_BINARY,
- // => true,
- // else => false,
- // };
-
- // log("GetBinaryTypeW({f}) = {d}. isExecutable={}", .{ bun.fmt.utf16(path), out, result });
-
- // return result;
+ // we pass false to include .exe files (see https://learn.microsoft.com/en-us/windows/win32/api/winsafer/nf-winsafer-saferiisexecutablefiletype)
+ return bun.windows.SaferiIsExecutableFileType(path, w.FALSE) != w.FALSE;
}
@compileError("TODO: isExecutablePath");
diff --git a/src/windows.zig b/src/windows.zig
index d358b7a5ad..a909b11858 100644
--- a/src/windows.zig
+++ b/src/windows.zig
@@ -150,7 +150,7 @@ pub extern "kernel32" fn SetCurrentDirectoryW(
) callconv(.winapi) win32.BOOL;
pub const SetCurrentDirectory = SetCurrentDirectoryW;
pub extern "ntdll" fn RtlNtStatusToDosError(win32.NTSTATUS) callconv(.winapi) Win32Error;
-
+pub extern "advapi32" fn SaferiIsExecutableFileType(szFullPathname: win32.LPCWSTR, bFromShellExecute: win32.BOOLEAN) callconv(.winapi) win32.BOOL;
// This was originally copied from Zig's standard library
/// Codes are from https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d
pub const Win32Error = enum(u16) {
diff --git a/test/cli/bun.test.ts b/test/cli/bun.test.ts
index c6dd62858f..cf2b66794c 100644
--- a/test/cli/bun.test.ts
+++ b/test/cli/bun.test.ts
@@ -59,7 +59,16 @@ describe("bun", () => {
);
});
});
-
+ describe("getcompletes", () => {
+ test("getcompletes should not panic and should not be empty", () => {
+ const { stdout, exitCode } = spawnSync({
+ cmd: [bunExe(), "getcompletes"],
+ env: bunEnv,
+ });
+ expect(exitCode).toBe(0);
+ expect(stdout.toString()).not.toBeEmpty();
+ });
+ });
describe("test command line arguments", () => {
test("test --config, issue #4128", () => {
const path = `${tmpdir()}/bunfig-${Date.now()}.toml`;
diff --git a/test/regression/issue/24575.test.ts b/test/regression/issue/24575.test.ts
new file mode 100644
index 0000000000..005de73ed0
--- /dev/null
+++ b/test/regression/issue/24575.test.ts
@@ -0,0 +1,166 @@
+// https://github.com/oven-sh/bun/pull/24575
+// Tests that socket._handle.fd property is available
+import { expect, test } from "bun:test";
+import net from "node:net";
+import tls from "node:tls";
+
+test("socket._handle.fd should be accessible on TCP sockets", async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+
+ let serverFd: number | undefined;
+ let clientFd: number | undefined;
+
+ const server = net.createServer(socket => {
+ // Server-side socket should have _handle.fd
+ expect(socket._handle).toBeDefined();
+ expect(socket._handle.fd).toBeTypeOf("number");
+ expect(socket._handle.fd).toBeGreaterThan(0);
+ serverFd = socket._handle.fd;
+
+ socket.end(`server fd: ${socket._handle.fd}`);
+ });
+
+ server.listen(0, "127.0.0.1", () => {
+ const client = net.connect({
+ host: "127.0.0.1",
+ port: (server.address() as any).port,
+ });
+
+ client.on("connect", () => {
+ // Client-side socket should have _handle.fd
+ expect(client._handle).toBeDefined();
+ expect(client._handle.fd).toBeTypeOf("number");
+ expect(client._handle.fd).toBeGreaterThan(0);
+ clientFd = client._handle.fd;
+ });
+
+ client.on("data", data => {
+ const response = data.toString();
+ expect(response).toStartWith("server fd: ");
+
+ // Verify we got valid fds
+ expect(serverFd).toBeTypeOf("number");
+ expect(clientFd).toBeTypeOf("number");
+ expect(serverFd).toBeGreaterThan(0);
+ expect(clientFd).toBeGreaterThan(0);
+
+ // Server and client should have different fds
+ expect(serverFd).not.toBe(clientFd);
+
+ server.close();
+ resolve();
+ });
+
+ client.on("error", reject);
+ });
+
+ server.on("error", reject);
+
+ await promise;
+});
+
+test("socket._handle.fd should remain consistent during connection lifetime", async () => {
+ const { promise, resolve, reject } = Promise.withResolvers();
+
+ const server = net.createServer(socket => {
+ const initialFd = socket._handle.fd;
+
+ // Send multiple messages to ensure fd doesn't change
+ socket.write("message1\n");
+ expect(socket._handle.fd).toBe(initialFd);
+
+ socket.write("message2\n");
+ expect(socket._handle.fd).toBe(initialFd);
+
+ socket.end("message3\n");
+ expect(socket._handle.fd).toBe(initialFd);
+ });
+
+ server.listen(0, "127.0.0.1", () => {
+ const client = net.connect({
+ host: "127.0.0.1",
+ port: (server.address() as any).port,
+ });
+
+ let initialClientFd: number;
+ let buffer = "";
+
+ client.on("connect", () => {
+ initialClientFd = client._handle.fd;
+ expect(initialClientFd).toBeGreaterThan(0);
+ });
+
+ client.on("data", data => {
+ buffer += data.toString();
+ // Fd should remain consistent across multiple data events
+ expect(client._handle.fd).toBe(initialClientFd);
+ });
+
+ client.on("end", () => {
+ // Verify we received all messages
+ expect(buffer).toBe("message1\nmessage2\nmessage3\n");
+ server.close();
+ resolve();
+ });
+
+ client.on("error", reject);
+ });
+
+ server.on("error", reject);
+
+ await promise;
+});
+
+test("socket._handle.fd should be accessible on TLS sockets", async () => {
+ const { tls: tlsCert } = await import("harness");
+ const { promise, resolve, reject } = Promise.withResolvers();
+
+ let serverFd: number | undefined;
+ let clientFd: number | undefined;
+
+ const server = tls.createServer(tlsCert, socket => {
+ // Server-side TLS socket should have _handle.fd
+ expect(socket._handle).toBeDefined();
+ expect(socket._handle.fd).toBeTypeOf("number");
+ // TLS sockets should have a valid fd (may be -1 on some platforms/states)
+ expect(typeof socket._handle.fd).toBe("number");
+ serverFd = socket._handle.fd;
+
+ socket.end(`server fd: ${socket._handle.fd}`);
+ });
+
+ server.listen(0, "127.0.0.1", () => {
+ const client = tls.connect({
+ host: "127.0.0.1",
+ port: (server.address() as any).port,
+ rejectUnauthorized: false,
+ });
+
+ client.on("secureConnect", () => {
+ // Client-side TLS socket should have _handle.fd
+ expect(client._handle).toBeDefined();
+ expect(client._handle.fd).toBeTypeOf("number");
+ // TLS sockets should have a valid fd (may be -1 on some platforms/states)
+ expect(typeof client._handle.fd).toBe("number");
+ clientFd = client._handle.fd;
+ });
+
+ client.on("data", data => {
+ const response = data.toString();
+ expect(response).toMatch(/server fd: -?\d+/);
+
+ // Verify we got valid fds (number type, even if -1)
+ expect(serverFd).toBeTypeOf("number");
+ expect(clientFd).toBeTypeOf("number");
+
+ server.close();
+ resolve();
+ });
+
+ client.on("error", reject);
+ });
+
+ server.on("error", reject);
+
+ await promise;
+});