Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 39 additions & 3 deletions packages/bun-types/bun.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9808,12 +9808,26 @@ declare module "bun" {
* The structure of Bun's lockfile, `bun.lock`
*/
type BunLockFile = {
lockfileVersion: 0 | 1 | 2;
/**
* Version of the file format. A release of Bun that does not know a lockfile's version cannot read it.
*
* - `0`: workspace entries in {@link BunLockFile.packages} carry a trailing object with the workspace's dependencies.
* - `1`: workspace entries are `["name@workspace:path"]` only.
* - `2`: same content as `1`. Bun reads it more strictly, for example an npm package resolved to a tarball
* outside the configured registry must carry an integrity hash.
* - `3`: {@link BunLockFile.overrides} holds nested or version-scoped rules, see {@link BunLockFileScopedOverrides}.
*/
lockfileVersion: 0 | 1 | 2 | 3;
workspaces: {
[workspace: string]: BunLockFileWorkspacePackage;
};
/** @see https://bun.com/docs/install/overrides */
overrides?: Record<string, string>;
/**
* A string value applies to every dependency on the package named by the key.
* An object value holds the rules scoped to the package selected by the key, see {@link BunLockFileScopedOverrides}.
*
* @see https://bun.com/docs/install/overrides
*/
overrides?: Record<string, string | BunLockFileScopedOverrides>;
/** @see https://bun.com/docs/install/patch */
patchedDependencies?: Record<string, string>;
/** @see https://bun.com/docs/install/lifecycle#trusteddependencies */
Expand Down Expand Up @@ -9853,6 +9867,28 @@ declare module "bun" {
};
};

/**
* The object form of a {@link BunLockFile.overrides} value. Bun writes it for the nested
* (`"parent": { "child": "1.0.0" }`, `"parent>child"`) and version-scoped (`"pkg@1"`) rules of
* `package.json`.
*
* The key in `overrides` selects the package these rules are scoped to, as `name` or `name@range`.
* Inside the object, `"."` is the rule for the selected package itself and every other key, again
* `name` or `name@range`, is the rule for that dependency of the selected package. Every value is a
* dependency specifier: rules nest one level deep only.
*
* @example
* ```jsonc
* "overrides": {
* "no-deps": "1.0.0",
* "no-deps@1": { ".": "1.1.0" },
* "one-dep": { "no-deps": "2.0.0" },
* "one-range-dep": { "no-deps": "1.0.1", "no-deps@1": "2.0.0" },
* }
* ```
*/
type BunLockFileScopedOverrides = Record<string, string>;

type BunLockFileBasePackageInfo = {
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
Expand Down
73 changes: 73 additions & 0 deletions test/integration/bun-types/bun-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,79 @@ describe("@types/bun integration test", () => {
});
});

describe("BunLockFile", () => {
test("accepts lockfileVersion 3 and the object form of overrides", async () => {
const checkDir = join(TEMP_DIR, "bun-lock-check");
const tsconfig = structuredClone(sourceTsconfig);
tsconfig.include = ["bun-lock.ts"];
tsconfig.compilerOptions.typeRoots = [join(BASE_FIXTURE_DIR, "node_modules", "@types")];
await mkdir(checkDir, { recursive: true });
await makeTree(checkDir, {
"tsconfig.json": JSON.stringify(tsconfig, null, 2),
// The overrides are what bun install writes for nested and version-scoped rules
// (test/cli/install/nested-overrides.test.ts). No bun.lock exists in checkDir: the
// import is typed by the "*/bun.lock" module declaration in extensions.d.ts.
"bun-lock.ts": `import type { BunLockFile, BunLockFileScopedOverrides } from "bun";
import imported from "./bun.lock";

({
lockfileVersion: 3,
workspaces: { "": { name: "app", dependencies: { "one-dep": "1.0.0" } } },
overrides: {
"no-deps": "1.0.0",
"no-deps@1": { ".": "1.1.0" },
"one-dep": { "no-deps": "2.0.0" },
"one-range-dep": { "no-deps": "1.0.1", "no-deps@1": "2.0.0" },
},
packages: {},
}) satisfies BunLockFile;

({ ".": "1.1.0", "no-deps@1": "2.0.0" }) satisfies BunLockFileScopedOverrides;

declare const parsed: BunLockFile;
if (parsed.lockfileVersion === 3) parsed.lockfileVersion satisfies 3;
if (imported.lockfileVersion === 3) imported.lockfileVersion satisfies 3;

for (const rule of Object.values(parsed.overrides ?? {})) {
if (typeof rule === "string") continue;
rule satisfies BunLockFileScopedOverrides;
for (const specifier of Object.values(rule)) specifier satisfies string;
}

({
lockfileVersion: 3,
workspaces: {},
overrides: {
// @ts-expect-error rules nest one level deep only
"one-dep": { "no-deps": { ".": "2.0.0" } },
},
packages: {},
}) satisfies BunLockFile;

({
// @ts-expect-error lockfileVersion is a closed set of known versions, not number
lockfileVersion: 4,
workspaces: {},
packages: {},
}) satisfies BunLockFile;`,
});

await using proc = Bun.spawn({
cmd: [bunExe(), join(BASE_FIXTURE_DIR, "node_modules", "typescript", "bin", "tsc"), "-p", "."],
env: bunEnv,
cwd: checkDir,
stdout: "pipe",
stderr: "pipe",
});

const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);

expect(stderr.trim()).toBe("");
expect(stdout.trim()).toBe("");
expect(exitCode).toBe(0);
});
});

describe("Test Globals", () => {
const code = `
const test_shouldBeAFunction: Function = test;
Expand Down