diff options
-rw-r--r-- | docs/api/http.md | 107 | ||||
-rw-r--r-- | docs/api/streams.md | 6 | ||||
-rw-r--r-- | docs/cli/install.md | 52 | ||||
-rw-r--r-- | docs/cli/test.md | 18 | ||||
-rw-r--r-- | docs/nav.ts | 11 | ||||
-rw-r--r-- | docs/project/development.md | 31 | ||||
-rw-r--r-- | docs/runtime/nodejs-apis.md | 6 | ||||
-rw-r--r-- | docs/runtime/typescript.md | 11 | ||||
-rw-r--r-- | docs/test/lifecycle.md | 2 | ||||
-rw-r--r-- | docs/test/mocks.md | 55 | ||||
-rw-r--r-- | docs/test/time.md | 14 | ||||
-rw-r--r-- | docs/test/writing.md | 82 | ||||
-rw-r--r-- | packages/bun-types/tests/serve.test-d.ts | 13 |
13 files changed, 355 insertions, 53 deletions
diff --git a/docs/api/http.md b/docs/api/http.md index aed9da27c..8520604e8 100644 --- a/docs/api/http.md +++ b/docs/api/http.md @@ -67,7 +67,7 @@ Bun.serve({ fetch(req) { throw new Error("woops!"); }, - error(error: Error) { + error(error) { return new Response(`<pre>${error}\n${error.stack}</pre>`, { headers: { "Content-Type": "text/html", @@ -95,37 +95,37 @@ server.stop(); ## TLS -Bun supports TLS out of the box, powered by [OpenSSL](https://www.openssl.org/). Enable TLS by passing in a value for `key` and `cert`; both are required to enable TLS. If needed, supply a `passphrase` to decrypt the `keyFile`. +Bun supports TLS out of the box, powered by [BoringSSL](https://boringssl.googlesource.com/boringssl). Enable TLS by passing in a value for `key` and `cert`; both are required to enable TLS. -```ts -Bun.serve({ - fetch(req) { - return new Response("Hello!!!"); - }, - - // can be string, BunFile, TypedArray, Buffer, or array thereof - key: Bun.file("./key.pem"), - cert: Bun.file("./cert.pem"), +```ts-diff + Bun.serve({ + fetch(req) { + return new Response("Hello!!!"); + }, - // passphrase, only required if key is encrypted - passphrase: "super-secret", -}); ++ tls: { ++ key: Bun.file("./key.pem"), ++ cert: Bun.file("./cert.pem"), ++ } + }); ``` -The `key` and `cert` fields expect the _contents_ of your TLS key and certificate. This can be a string, `BunFile`, `TypedArray`, or `Buffer`. +The `key` and `cert` fields expect the _contents_ of your TLS key and certificate, _not a path to it_. This can be a string, `BunFile`, `TypedArray`, or `Buffer`. ```ts Bun.serve({ fetch() {}, - // BunFile - key: Bun.file("./key.pem"), - // Buffer - key: fs.readFileSync("./key.pem"), - // string - key: fs.readFileSync("./key.pem", "utf8"), - // array of above - key: [Bun.file('./key1.pem'), Bun.file('./key2.pem')], + tls: { + // BunFile + key: Bun.file("./key.pem"), + // Buffer + key: fs.readFileSync("./key.pem"), + // string + key: fs.readFileSync("./key.pem", "utf8"), + // array of above + key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")], + }, }); ``` @@ -135,17 +135,35 @@ Bun.serve({ {% /callout %} +If your private key is encrypted with a passphrase, provide a value for `passphrase` to decrypt it. + +```ts-diff + Bun.serve({ + fetch(req) { + return new Response("Hello!!!"); + }, + + tls: { + key: Bun.file("./key.pem"), + cert: Bun.file("./cert.pem"), ++ passphrase: "my-secret-passphrase", + } + }); +``` + Optionally, you can override the trusted CA certificates by passing a value for `ca`. By default, the server will trust the list of well-known CAs curated by Mozilla. When `ca` is specified, the Mozilla list is overwritten. -```ts -Bun.serve({ - fetch(req) { - return new Response("Hello!!!"); - }, - key: Bun.file("./key.pem"), // path to TLS key - cert: Bun.file("./cert.pem"), // path to TLS cert - ca: Bun.file("./ca.pem"), // path to root CA certificate -}); +```ts-diff + Bun.serve({ + fetch(req) { + return new Response("Hello!!!"); + }, + tls: { + key: Bun.file("./key.pem"), // path to TLS key + cert: Bun.file("./cert.pem"), // path to TLS cert ++ ca: Bun.file("./ca.pem"), // path to root CA certificate + } + }); ``` To override Diffie-Helman parameters: @@ -153,7 +171,10 @@ To override Diffie-Helman parameters: ```ts Bun.serve({ // ... - dhParamsFile: "./dhparams.pem", // path to Diffie Helman parameters + tls: { + // other config + dhParamsFile: "/path/to/dhparams.pem", // path to Diffie Helman parameters + }, }); ``` @@ -274,11 +295,21 @@ interface Bun { port?: number; development?: boolean; error?: (error: Error) => Response | Promise<Response>; - keyFile?: string; - certFile?: string; - caFile?: string; - dhParamsFile?: string; - passphrase?: string; + tls?: { + key?: + | string + | TypedArray + | BunFile + | Array<string | TypedArray | BunFile>; + cert?: + | string + | TypedArray + | BunFile + | Array<string | TypedArray | BunFile>; + ca?: string | TypedArray | BunFile | Array<string | TypedArray | BunFile>; + passphrase?: string; + dhParamsFile?: string; + }; maxRequestBodySize?: number; lowMemoryMode?: boolean; }): Server; diff --git a/docs/api/streams.md b/docs/api/streams.md index 7f3e3bcb4..210090927 100644 --- a/docs/api/streams.md +++ b/docs/api/streams.md @@ -28,8 +28,6 @@ for await (const chunk of stream) { } ``` -For a more complete discusson of streams in Bun, see [API > Streams](/docs/api/streams). - ## Direct `ReadableStream` Bun implements an optimized version of `ReadableStream` that avoid unnecessary data copying & queue management logic. With a traditional `ReadableStream`, chunks of data are _enqueued_. Each chunk is copied into a queue, where it sits until the stream is ready to send more data. @@ -154,7 +152,9 @@ export class ArrayBufferSink { stream?: boolean; }): void; - write(chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer): number; + write( + chunk: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, + ): number; /** * Flush the internal buffer * diff --git a/docs/cli/install.md b/docs/cli/install.md index 4489a0d4a..f7b081ba8 100644 --- a/docs/cli/install.md +++ b/docs/cli/install.md @@ -49,7 +49,7 @@ To install in production mode (i.e. without `devDependencies`): $ bun install --production ``` -To install dependencies without allowing changes to lockfile (useful on CI): +To install with reproducible dependencies, use `--frozen-lockfile`. If your `package.json` disagrees with `bun.lockb`, Bun will exit with an error. This is useful for production builds and CI environments. ```bash $ bun install --frozen-lockfile @@ -124,6 +124,26 @@ To add a package as an optional dependency (`"optionalDependencies"`): $ bun add --optional lodash ``` +To add a package and pin to the resolved version, use `--exact`. This will resolve the version of the package and add it to your `package.json` with an exact version number instead of a version range. + +```bash +$ bun add react --exact +``` + +This will add the following to your `package.json`: + +```jsonc +{ + "dependencies": { + // without --exact + "react": "^18.2.0", // this matches >= 18.2.0 < 19.0.0 + + // with --exact + "react": "18.2.0" // this matches only 18.2.0 exactly + } +} +``` + To install a package globally: ```bash @@ -206,6 +226,36 @@ In addition, the `--save` flag can be used to add `cool-pkg` to the `dependencie } ``` +## Trusted dependencies + +Unlike other npm clients, Bun does not execute arbitrary lifecycle scripts for installed dependencies, such as `postinstall`. These scripts represent a potential security risk, as they can execute arbitrary code on your machine. + +<!-- Bun maintains an allow-list of popular packages containing `postinstall` scripts that are known to be safe. To run lifecycle scripts for packages that aren't on this list, add the package to `trustedDependencies` in your package.json. --> + +To tell Bun to allow lifecycle scripts for a particular package, add the package to `trustedDependencies` in your package.json. + +```json-diff + { + "name": "my-app", + "version": "1.0.0", ++ "trustedDependencies": { ++ "my-trusted-package": "*" ++ } + } +``` + +Bun reads this field and will run lifecycle scripts for `my-trusted-package`. If you specify a version range, Bun will only execute lifecycle scripts if the resolved package version matches the range. + +```json +{ + "name": "my-app", + "version": "1.0.0", + "trustedDependencies": { + "my-trusted-package": "^1.0.0" + } +} +``` + ## Git dependencies To add a dependency from a git repository: diff --git a/docs/cli/test.md b/docs/cli/test.md index d19a45a12..7af8dcc20 100644 --- a/docs/cli/test.md +++ b/docs/cli/test.md @@ -65,6 +65,24 @@ $ bun test --preload ./setup.ts See [Test > Lifecycle](/docs/test/lifecycle) for complete documentation. +## Mocks + +Create mocks with the `mock` function. Mocks are automatically reset between tests. + +``` +import { test, expect, mock } from "bun:test"; +const random = mock(() => Math.random()); + +test("random", async () => { + const val = random(); + expect(val).toBeGreaterThan(0); + expect(random).toHaveBeenCalled(); + expect(random).toHaveBeenCalledTimes(1); +}); +``` + +See [Test > Mocks](/docs/test/mocks) for complete documentation. + ## Snapshot testing Snapshots are supported by `bun test`. See [Test > Snapshots](/docs/test/snapshots) for complete documentation. diff --git a/docs/nav.ts b/docs/nav.ts index 2832a53cf..5b35036d3 100644 --- a/docs/nav.ts +++ b/docs/nav.ts @@ -135,13 +135,13 @@ export default { description: "Install all dependencies with `bun install`, or manage dependencies with `bun add` and `bun remove`.", }), - page("install/workspaces", "Workspaces", { - description: "Bun's package manager supports workspaces and mono-repo development workflows.", - }), page("install/cache", "Global cache", { description: "Bun's package manager installs all packages into a shared global cache to avoid redundant re-downloads.", }), + page("install/workspaces", "Workspaces", { + description: "Bun's package manager supports workspaces and mono-repo development workflows.", + }), page("install/lockfile", "Lockfile", { description: "Bun's binary lockfile `bun.lockb` tracks your resolved dependency ytrr, making future installs fast and repeatable.", @@ -190,10 +190,13 @@ export default { page("test/lifecycle", "Lifecycle hooks", { description: "Add lifecycle hooks to your tests that run before/after each test or test run", }), + page("test/mocks", "Mocks", { + description: "Mocks functions and track method calls", + }), page("test/snapshots", "Snapshots", { description: "Add lifecycle hooks to your tests that run before/after each test or test run", }), - page("test/time", "Time", { + page("test/time", "Dates and times", { description: "Control the date & time in your tests for more reliable and deterministic tests", }), page("test/dom", "DOM testing", { diff --git a/docs/project/development.md b/docs/project/development.md index d21641aff..e50f53215 100644 --- a/docs/project/development.md +++ b/docs/project/development.md @@ -217,6 +217,37 @@ You'll need a very recent version of Valgrind due to DWARF 5 debug symbols. You $ valgrind --fair-sched=try --track-origins=yes bun-debug <args> ``` +## Updating `WebKit` + +The Bun team will occasionally bump the version of WebKit used in Bun. When this happens, you may see something like this with you run `git status`. + +```bash +$ git status +On branch my-branch +Changes not staged for commit: + (use "git add <file>..." to update what will be committed) + (use "git restore <file>..." to discard changes in working directory) + modified: src/bun.js/WebKit (new commits) +``` + +For performance reasons, `bun submodule update` does not automatically update the WebKit submodule. To update, run the following commands from the root of the Bun repo: + +```bash +$ bun install +$ make bindings +``` + +<!-- Check the [Bun repo](https://github.com/oven-sh/bun/tree/main/src/bun.js) to get the hash of the commit of WebKit is currently being used. + +{% image width="270" src="https://github.com/oven-sh/bun/assets/3084745/51730b73-89ef-4358-9a41-9563a60a54be" /%} --> + +<!-- +```bash +$ cd src/bun.js/WebKit +$ git fetch +$ git checkout <hash> +``` --> + ## Troubleshooting ### libarchive diff --git a/docs/runtime/nodejs-apis.md b/docs/runtime/nodejs-apis.md index ac42559a0..593954aae 100644 --- a/docs/runtime/nodejs-apis.md +++ b/docs/runtime/nodejs-apis.md @@ -51,7 +51,7 @@ This page is updated regularly to reflect compatibility status of the latest ver - {% anchor id="node_crypto" %} [`node:crypto`](https://nodejs.org/api/crypto.html) {% /anchor %} - 🟡 -- Missing `crypto.Certificate` `crypto.ECDH` `crypto.KeyObject` `crypto.X509Certificate` `crypto.checkPrime{Sync}` `crypto.createPrivateKey` `crypto.createPublicKey` `crypto.createSecretKey` `crypto.diffieHellman` `crypto.generateKey{Sync}` `crypto.generateKeyPair{Sync}` `crypto.generatePrime{Sync}` `crypto.getCipherInfo` `crypto.getCurves` `crypto.{get|set}Fips` `crypto.hkdf` `crypto.hkdfSync` `crypto.randomInt` `crypto.secureHeapUsed` `crypto.setEngine` `crypto.sign` `crypto.verify` +- Missing `crypto.Certificate` `crypto.ECDH` `crypto.KeyObject` `crypto.X509Certificate` `crypto.checkPrime{Sync}` `crypto.createPrivateKey` `crypto.createPublicKey` `crypto.createSecretKey` `crypto.diffieHellman` `crypto.generateKey{Sync}` `crypto.generateKeyPair{Sync}` `crypto.generatePrime{Sync}` `crypto.getCipherInfo` `crypto.getCurves` `crypto.{get|set}Fips` `crypto.hkdf` `crypto.hkdfSync` `crypto.secureHeapUsed` `crypto.setEngine` `crypto.sign` `crypto.verify` --- @@ -87,7 +87,7 @@ This page is updated regularly to reflect compatibility status of the latest ver - {% anchor id="node_fs" %} [`node:fs`](https://nodejs.org/api/fs.html) {% /anchor %} - 🟡 -- Missing `fs.fdatasync{Sync}` `fs.opendir{Sync}` `fs.readv{Sync}` `fs.{watch|watchFile|unwatchFile}` `fs.writev{Sync}`. +- Missing `fs.fdatasync{Sync}` `fs.opendir{Sync}` `fs.{watchFile|unwatchFile}` `fs.{cp|cpSync}`. --- @@ -558,7 +558,7 @@ The table below lists all globals implemented by Node.js and Bun's current compa - {% anchor id="node_require" %} [`require()`](https://nodejs.org/api/globals.html#require) {% /anchor %} - 🟢 -- Fully implemented. +- Fully implemented, as well as [`require.main`](https://nodejs.org/api/modules.html#requiremain), [`require.cache`](https://nodejs.org/api/modules.html#requirecache), and [`require.resolve`](https://nodejs.org/api/modules.html#requireresolverequest-options) --- diff --git a/docs/runtime/typescript.md b/docs/runtime/typescript.md index d466bb016..b79b1ec6d 100644 --- a/docs/runtime/typescript.md +++ b/docs/runtime/typescript.md @@ -93,6 +93,17 @@ These are the recommended `compilerOptions` for a Bun project. } ``` +### Add DOM types + +Settings `"types": ["bun-types"]` means TypeScript will ignore other global type definitions, including `lib: ["dom"]`. To add DOM types into your project, add the following [triple-slash directives](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html) at the top of any TypeScript file in your project. + +```ts +/// <reference lib="dom" /> +/// <reference lib="dom.iterable" /> +``` + +The same applies to other global type definition _libs_ like `webworker`. + ## Path mapping When resolving modules, Bun's runtime respects path mappings defined in [`compilerOptions.paths`](https://www.typescriptlang.org/tsconfig#paths) in your `tsconfig.json`. No other runtime does this. diff --git a/docs/test/lifecycle.md b/docs/test/lifecycle.md index 176c476de..fd804c9bb 100644 --- a/docs/test/lifecycle.md +++ b/docs/test/lifecycle.md @@ -70,7 +70,7 @@ afterAll(() => { Then use `--preload` to run the setup script before any test files. ```ts -bun test --preload ./setup.ts +$ bun test --preload ./setup.ts ``` To avoid typing `--preload` every time you run tests, it can be added to your `bunfig.toml`: diff --git a/docs/test/mocks.md b/docs/test/mocks.md new file mode 100644 index 000000000..31b5dab41 --- /dev/null +++ b/docs/test/mocks.md @@ -0,0 +1,55 @@ +Create mocks with the `mock` function. + +```ts +import { test, expect, mock } from "bun:test"; +const random = mock(() => Math.random()); + +test("random", async () => { + const val = random(); + expect(val).toBeGreaterThan(0); + expect(random).toHaveBeenCalled(); + expect(random).toHaveBeenCalledTimes(1); +}); +``` + +The result of `mock()` is a new function that's been decorated with some additional properties. + +```ts +import { mock } from "bun:test"; +const random = mock((multiplier: number) => multiplier * Math.random()); + +random(2); +random(10); + +random.mock.calls; +// [[ 2 ], [ 10 ]] + +random.mock.results; +// [ +// { type: "return", value: 0.6533907460954099 }, +// { type: "return", value: 0.6452713933037312 } +// ] +``` + +## `.spyOn()` + +It's possible to track calls to a function without replacing it with a mock. Use `spyOn()` to create a spy; these spies can be passed to `.toHaveBeenCalled()` and `.toHaveBeenCalledTimes()`. + +```ts +import { test, expect, spyOn } from "bun:test"; + +const ringo = { + name: "Ringo", + sayHi() { + console.log(`Hello I'm ${this.name}`); + }, +}; + +const spy = spyOn(ringo, "sayHi"); + +test("spyon", () => { + expect(spy).toHaveBeenCalledTimes(0); + ringo.sayHi(); + expect(spy).toHaveBeenCalledTimes(1); +}); +``` diff --git a/docs/test/time.md b/docs/test/time.md index ef741fc6f..4a0f98407 100644 --- a/docs/test/time.md +++ b/docs/test/time.md @@ -51,7 +51,9 @@ test("unlike in jest", () => { }); ``` -Note that we have not implemented builtin support for mocking timers yet, but this is on the roadmap. +{% callout %} +**Timers** — Note that we have not implemented builtin support for mocking timers yet, but this is on the roadmap. +{% /callout %} ### Reset the system time @@ -74,7 +76,13 @@ test("it was 2020, for a moment.", () => { ## Set the time zone -To change the time zone, either pass the `$TZ` environment variable to `bun test`, or set `process.env.TZ` at runtime: +To change the time zone, either pass the `$TZ` environment variable to `bun test`. + +```sh +TZ=America/Los_Angeles bun test +``` + +Or set `process.env.TZ` at runtime: ```ts import { test, expect } from "bun:test"; @@ -88,7 +96,7 @@ test("Welcome to California!", () => { }); test("Welcome to New York!", () => { - // Unlike in jest, you can set the timezone multiple times at runtime and it will work. + // Unlike in Jest, you can set the timezone multiple times at runtime and it will work. process.env.TZ = "America/New_York"; expect(new Date().getTimezoneOffset()).toBe(240); expect(new Intl.DateTimeFormat().resolvedOptions().timeZone).toBe( diff --git a/docs/test/writing.md b/docs/test/writing.md index 029418a48..6a29bf81f 100644 --- a/docs/test/writing.md +++ b/docs/test/writing.md @@ -63,6 +63,21 @@ test("2 * 2", done => { }); ``` +## Timeouts + +Optionally specify a per-test timeout in milliseconds by passing a number as the third argument to `test`. + +```ts +import { test } from "bun:test"; + +test.skip("wat", async () => { + const data = await slowOperation(); + expect(data).toBe(42); +}, 500); // test must run in <500ms +``` + +## `test.skip` + Skip individual tests with `test.skip`. These tests will not be run. ```ts @@ -74,6 +89,8 @@ test.skip("wat", () => { }); ``` +## `test.todo` + Mark a test as a todo with `test.todo`. These tests _will_ be run, and the test runner will expect them to fail. If they pass, you will be prompted to mark it as a regular test. ```ts @@ -84,6 +101,71 @@ test.todo("fix this", () => { }); ``` +To exlusively run tests marked as _todo_, use `bun test --todo`. + +```sh +$ bun test --todo +``` + +## `test.only` + +To run a particular test or suite of tests use `test.only()` or `describe.only()`. Once declared, running `bun test --skip` will only execute tests/suites that have been marked with `.only()`. + +```ts +import { test, describe } from "bun:test"; + +test("test #1", () => { + // does not run +}); + +test.only("test #2", () => { + // runs +}); + +describe.only("only", () => { + test("test #3", () => { + // runs + }); +}); +``` + +The following command will only execute tests #2 and #3. + +```sh +$ bun test --only +``` + +## `test.if` + +To run a test conditionally, use `test.if()`. The test will run if the condition is truthy. This is particularly useful for tests that should only run on specific architectures or operating systems. + +```ts +test.if(Math.random() > 0.5)("runs half the time", () => { + // ... +}); +``` + +```ts +test.if(Math.random() > 0.5)("runs half the time", () => { + // ... +}); + +const macOS = process.arch === "darwin"; +test.if(macOS)("runs on macOS", () => { + // runs if macOS +}); +``` + +To instead skip a test based on some condition, use `test.skipIf()` or `describe.skipIf()`. + +```ts +const macOS = process.arch === "darwin"; + +test.skipIf(macOS)("runs on non-macOS", () => { + // runs if *not* macOS +}); +``` + ## Matchers Bun implements the following matchers. Full Jest compatibility is on the roadmap; track progress [here](https://github.com/oven-sh/bun/issues/1825). diff --git a/packages/bun-types/tests/serve.test-d.ts b/packages/bun-types/tests/serve.test-d.ts index 2477433dc..4ba3144b8 100644 --- a/packages/bun-types/tests/serve.test-d.ts +++ b/packages/bun-types/tests/serve.test-d.ts @@ -79,4 +79,17 @@ Bun.serve<User>({ }, }); +Bun.serve({ + fetch(req) { + throw new Error("woops!"); + }, + error(error) { + return new Response(`<pre>${error}\n${error.stack}</pre>`, { + headers: { + "Content-Type": "text/html", + }, + }); + }, +}); + export {}; |