mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
Merge branch 'main' into dylan/nohoist
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -231,23 +231,67 @@ const myPlugin: BunPlugin = {
|
||||
### onResolve
|
||||
|
||||
<Tabs>
|
||||
<Tab title="options">- 🟢 `filter` - 🟢 `namespace`</Tab>
|
||||
<Tab title="options">
|
||||
|
||||
- 🟢 `filter`
|
||||
- 🟢 `namespace`
|
||||
|
||||
</Tab>
|
||||
<Tab title="arguments">
|
||||
- 🟢 `path` - 🟢 `importer` - 🔴 `namespace` - 🔴 `resolveDir` - 🔴 `kind` - 🔴 `pluginData`
|
||||
|
||||
- 🟢 `path`
|
||||
- 🟢 `importer`
|
||||
- 🔴 `namespace`
|
||||
- 🔴 `resolveDir`
|
||||
- 🔴 `kind`
|
||||
- 🔴 `pluginData`
|
||||
|
||||
</Tab>
|
||||
<Tab title="results">
|
||||
- 🟢 `namespace` - 🟢 `path` - 🔴 `errors` - 🔴 `external` - 🔴 `pluginData` - 🔴 `pluginName` - 🔴 `sideEffects` -
|
||||
🔴 `suffix` - 🔴 `warnings` - 🔴 `watchDirs` - 🔴 `watchFiles`
|
||||
|
||||
- 🟢 `namespace`
|
||||
- 🟢 `path`
|
||||
- 🔴 `errors`
|
||||
- 🔴 `external`
|
||||
- 🔴 `pluginData`
|
||||
- 🔴 `pluginName`
|
||||
- 🔴 `sideEffects`
|
||||
- 🔴 `suffix`
|
||||
- 🔴 `warnings`
|
||||
- 🔴 `watchDirs`
|
||||
- 🔴 `watchFiles`
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
### onLoad
|
||||
|
||||
<Tabs>
|
||||
<Tab title="options">- 🟢 `filter` - 🟢 `namespace`</Tab>
|
||||
<Tab title="arguments">- 🟢 `path` - 🔴 `namespace` - 🔴 `suffix` - 🔴 `pluginData`</Tab>
|
||||
<Tab title="options">
|
||||
|
||||
- 🟢 `filter`
|
||||
- 🟢 `namespace`
|
||||
|
||||
</Tab>
|
||||
<Tab title="arguments">
|
||||
|
||||
- 🟢 `path`
|
||||
- 🔴 `namespace`
|
||||
- 🔴 `suffix`
|
||||
- 🔴 `pluginData`
|
||||
|
||||
</Tab>
|
||||
<Tab title="results">
|
||||
- 🟢 `contents` - 🟢 `loader` - 🔴 `errors` - 🔴 `pluginData` - 🔴 `pluginName` - 🔴 `resolveDir` - 🔴 `warnings` -
|
||||
🔴 `watchDirs` - 🔴 `watchFiles`
|
||||
|
||||
- 🟢 `contents`
|
||||
- 🟢 `loader`
|
||||
- 🔴 `errors`
|
||||
- 🔴 `pluginData`
|
||||
- 🔴 `pluginName`
|
||||
- 🔴 `resolveDir`
|
||||
- 🔴 `warnings`
|
||||
- 🔴 `watchDirs`
|
||||
- 🔴 `watchFiles`
|
||||
|
||||
</Tab>
|
||||
</Tabs>
|
||||
|
||||
@@ -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.<br/><br/>`js<br/>import pkg from "./package.json";<br/>pkg.name; // => "my-package"<br/>` |
|
||||
| `.jsonc` | JSON with comments. Files are parsed and inlined into the bundle as a JavaScript object.<br/><br/>`js<br/>import config from "./config.jsonc";<br/>config.name; // => "my-config"<br/>` |
|
||||
| `.toml` | TOML files are parsed and inlined into the bundle as a JavaScript object.<br/><br/>`js<br/>import config from "./bunfig.toml";<br/>config.logLevel; // => "debug"<br/>` |
|
||||
| `.yaml` `.yml` | YAML files are parsed and inlined into the bundle as a JavaScript object.<br/><br/>`js<br/>import config from "./config.yaml";<br/>config.name; // => "my-app"<br/>` |
|
||||
| `.txt` | The contents of the text file are read and inlined into the bundle as a string.<br/><br/>`js<br/>import contents from "./file.txt";<br/>console.log(contents); // => "Hello, world!"<br/>` |
|
||||
| `.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[];
|
||||
|
||||
@@ -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",
|
||||
};
|
||||
```
|
||||
|
||||
<Note>
|
||||
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.
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
### `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.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```yaml Input
|
||||
name: John Doe
|
||||
age: 35
|
||||
email: johndoe@example.com
|
||||
```
|
||||
|
||||
```js Output
|
||||
export default {
|
||||
name: "John Doe",
|
||||
age: 35,
|
||||
email: "johndoe@example.com",
|
||||
};
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
---
|
||||
|
||||
### `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.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -132,9 +132,7 @@ CMD ["bun", "index.ts"]
|
||||
<Note>
|
||||
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.
|
||||
|
||||
</Note>
|
||||
|
||||
@@ -159,8 +157,7 @@ Make sure you're in the directory containing your `Dockerfile`, then deploy dire
|
||||
|
||||
<Note>
|
||||
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.
|
||||
</Note>
|
||||
|
||||
```bash terminal icon="terminal"
|
||||
|
||||
@@ -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.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -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/).
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
<root>
|
||||
├── README.md
|
||||
├── bun.lock
|
||||
|
||||
@@ -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"
|
||||
```
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<Accordion title="Low-level details of CommonJS interop in Bun">
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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" },
|
||||
|
||||
26
packages/bun-types/bun.d.ts
vendored
26
packages/bun-types/bun.d.ts
vendored
@@ -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<string, Record<string, string>>;
|
||||
|
||||
/**
|
||||
* `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 }
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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});
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
21
src/fd.zig
21
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,
|
||||
|
||||
38
src/sys.zig
38
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");
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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`;
|
||||
|
||||
166
test/regression/issue/24575.test.ts
Normal file
166
test/regression/issue/24575.test.ts
Normal file
@@ -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<void>();
|
||||
|
||||
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<void>();
|
||||
|
||||
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<void>();
|
||||
|
||||
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;
|
||||
});
|
||||
Reference in New Issue
Block a user