Compare commits

...

2 Commits

Author SHA1 Message Date
autofix-ci[bot]
109923d660 [autofix.ci] apply automated fixes 2026-01-29 07:37:07 +00:00
Jarred Sumner
b57f3bfec1 fix(install): resolve dist-tag dependencies to local workspace packages
When a workspace package has the same name as an npm package and a
dependency uses a dist-tag specifier (e.g. "latest" or ""), bun install
would incorrectly fetch from the npm registry instead of symlinking to
the local workspace. This extends the workspace resolution logic in both
the lockfile package resolver and the enqueue step to handle dist-tag
versions the same way as other version specifiers, preferring local
workspace packages when `link_workspace_packages` is enabled.
2026-01-29 08:35:11 +01:00
7 changed files with 322 additions and 385 deletions

View File

@@ -1354,22 +1354,22 @@ In the JavaScript API, `metafile` accepts several forms:
```ts title="build.ts" icon="/icons/typescript.svg"
// Boolean — include metafile in the result object
await Bun.build({
entrypoints: ['./src/index.ts'],
outdir: './dist',
entrypoints: ["./src/index.ts"],
outdir: "./dist",
metafile: true,
});
// String — write JSON metafile to a specific path
await Bun.build({
entrypoints: ['./src/index.ts'],
outdir: './dist',
entrypoints: ["./src/index.ts"],
outdir: "./dist",
metafile: "./dist/meta.json",
});
// Object — specify separate paths for JSON and markdown output
await Bun.build({
entrypoints: ['./src/index.ts'],
outdir: './dist',
entrypoints: ["./src/index.ts"],
outdir: "./dist",
metafile: {
json: "./dist/meta.json",
markdown: "./dist/meta.md",

View File

@@ -254,12 +254,12 @@ bun --cpu-prof --cpu-prof-name my-profile.cpuprofile script.js
bun --cpu-prof --cpu-prof-dir ./profiles script.js
```
| Flag | Description |
| ---------------------------- | -------------------------------------------------------- |
| Flag | Description |
| ---------------------------- | ----------------------------------------------------------- |
| `--cpu-prof` | Generate a `.cpuprofile` JSON file (Chrome DevTools format) |
| `--cpu-prof-md` | Generate a markdown CPU profile (grep/LLM-friendly) |
| `--cpu-prof-name <filename>` | Set output filename |
| `--cpu-prof-dir <dir>` | Set output directory |
| `--cpu-prof-md` | Generate a markdown CPU profile (grep/LLM-friendly) |
| `--cpu-prof-name <filename>` | Set output filename |
| `--cpu-prof-dir <dir>` | Set output directory |
## Heap profiling
@@ -288,9 +288,9 @@ bun --heap-prof --heap-prof-name my-snapshot.heapsnapshot script.js
bun --heap-prof --heap-prof-dir ./profiles script.js
```
| Flag | Description |
| ----------------------------- | --------------------------------------------------------- |
| `--heap-prof` | Generate a V8 `.heapsnapshot` file on exit |
| `--heap-prof-md` | Generate a markdown heap profile on exit |
| `--heap-prof-name <filename>` | Set output filename |
| `--heap-prof-dir <dir>` | Set output directory |
| Flag | Description |
| ----------------------------- | ------------------------------------------ |
| `--heap-prof` | Generate a V8 `.heapsnapshot` file on exit |
| `--heap-prof-md` | Generate a markdown heap profile on exit |
| `--heap-prof-name <filename>` | Set output filename |
| `--heap-prof-dir <dir>` | Set output directory |

View File

@@ -902,19 +902,19 @@ Bun.wrapAnsi("\u001b[31mThe quick brown fox jumps over the lazy dog\u001b[0m", 2
```ts
Bun.wrapAnsi("Hello World", 5, {
hard: true, // Break words that exceed column width (default: false)
wordWrap: true, // Wrap at word boundaries (default: true)
trim: true, // Trim leading/trailing whitespace per line (default: true)
hard: true, // Break words that exceed column width (default: false)
wordWrap: true, // Wrap at word boundaries (default: true)
trim: true, // Trim leading/trailing whitespace per line (default: true)
ambiguousIsNarrow: true, // Treat ambiguous-width characters as narrow (default: true)
});
```
| Option | Default | Description |
| --- | --- | --- |
| `hard` | `false` | If `true`, break words in the middle if they exceed the column width. |
| `wordWrap` | `true` | If `true`, wrap at word boundaries. If `false`, only break at explicit newlines. |
| `trim` | `true` | If `true`, trim leading and trailing whitespace from each line. |
| `ambiguousIsNarrow` | `true` | If `true`, treat ambiguous-width Unicode characters as 1 column wide. If `false`, treat them as 2 columns wide. |
| Option | Default | Description |
| ------------------- | ------- | --------------------------------------------------------------------------------------------------------------- |
| `hard` | `false` | If `true`, break words in the middle if they exceed the column width. |
| `wordWrap` | `true` | If `true`, wrap at word boundaries. If `false`, only break at explicit newlines. |
| `trim` | `true` | If `true`, trim leading and trailing whitespace from each line. |
| `ambiguousIsNarrow` | `true` | If `true`, treat ambiguous-width Unicode characters as 1 column wide. If `false`, treat them as 2 columns wide. |
TypeScript definition:

View File

@@ -1611,6 +1611,31 @@ fn getOrPutResolvedPackage(
}
}
}
} else if (version.tag == .dist_tag) {
if (this.options.link_workspace_packages) {
const workspace_path = if (this.lockfile.workspace_paths.count() > 0)
this.lockfile.workspace_paths.get(name_hash)
else
null;
if (workspace_path != null) {
const root_package = this.lockfile.rootPackage() orelse break :resolve_from_workspace;
const root_dependencies = root_package.dependencies.get(this.lockfile.buffers.dependencies.items);
const root_resolutions = root_package.resolutions.get(this.lockfile.buffers.resolutions.items);
for (root_dependencies, root_resolutions) |root_dep, workspace_package_id| {
if (workspace_package_id != invalid_package_id and
root_dep.version.tag == .workspace and
root_dep.name_hash == name_hash)
{
successFn(this, dependency_id, workspace_package_id);
return .{
.package = this.lockfile.packages.get(workspace_package_id),
.is_first_time = false,
};
}
}
}
}
}
}

View File

@@ -1121,6 +1121,24 @@ pub fn Package(comptime SemverIntType: type) type {
}
}
},
.dist_tag => {
if (workspace_path != null and pm.options.link_workspace_packages) {
const path = workspace_path.?.sliced(buf);
if (Dependency.parseWithTag(
allocator,
external_alias.value,
external_alias.hash,
path.slice,
.workspace,
&path,
log,
pm,
)) |dep| {
dependency_version.tag = dep.tag;
dependency_version.value = dep.value;
}
}
},
.workspace => workspace: {
if (workspace_path) |path| {
if (workspace_range) |range| {

View File

@@ -738,6 +738,252 @@ exports[`dependency on workspace without version in package.json: version: *-pre
}"
`;
exports[`dependency on workspace without version in package.json: version: latest 1`] = `
"{
"format": "v3",
"meta_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"package_index": {
"no-deps": 2,
"foo": 0,
"bar": 1
},
"trees": [
{
"id": 0,
"path": "node_modules",
"depth": 0,
"dependencies": {
"bar": {
"id": 0,
"package_id": 1
},
"no-deps": {
"id": 1,
"package_id": 2
}
}
}
],
"dependencies": [
{
"name": "bar",
"literal": "",
"workspace": "packages/bar",
"package_id": 1,
"behavior": {
"workspace": true
},
"id": 0
},
{
"name": "no-deps",
"literal": "",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"workspace": true
},
"id": 1
},
{
"name": "no-deps",
"literal": "latest",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"prod": true
},
"id": 2
}
],
"packages": [
{
"id": 0,
"name": "foo",
"name_hash": "14841791273925386894",
"resolution": {
"tag": "root",
"value": "",
"resolved": ""
},
"dependencies": [
0,
1
],
"integrity": null,
"man_dir": "",
"origin": "local",
"bin": null,
"scripts": {}
},
{
"id": 1,
"name": "bar",
"name_hash": "11592711315645265694",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/bar",
"resolved": "workspace:packages/bar"
},
"dependencies": [
2
],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 2,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/mono",
"resolved": "workspace:packages/mono"
},
"dependencies": [],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
}
],
"workspace_paths": {
"11592711315645265694": "packages/bar",
"5128161233225832376": "packages/mono"
},
"workspace_versions": {
"11592711315645265694": "1.0.0"
}
}"
`;
exports[`dependency on workspace without version in package.json: version: 1`] = `
"{
"format": "v3",
"meta_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"package_index": {
"no-deps": 2,
"foo": 0,
"bar": 1
},
"trees": [
{
"id": 0,
"path": "node_modules",
"depth": 0,
"dependencies": {
"bar": {
"id": 0,
"package_id": 1
},
"no-deps": {
"id": 1,
"package_id": 2
}
}
}
],
"dependencies": [
{
"name": "bar",
"literal": "",
"workspace": "packages/bar",
"package_id": 1,
"behavior": {
"workspace": true
},
"id": 0
},
{
"name": "no-deps",
"literal": "",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"workspace": true
},
"id": 1
},
{
"name": "no-deps",
"literal": "",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"prod": true
},
"id": 2
}
],
"packages": [
{
"id": 0,
"name": "foo",
"name_hash": "14841791273925386894",
"resolution": {
"tag": "root",
"value": "",
"resolved": ""
},
"dependencies": [
0,
1
],
"integrity": null,
"man_dir": "",
"origin": "local",
"bin": null,
"scripts": {}
},
{
"id": 1,
"name": "bar",
"name_hash": "11592711315645265694",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/bar",
"resolved": "workspace:packages/bar"
},
"dependencies": [
2
],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 2,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/mono",
"resolved": "workspace:packages/mono"
},
"dependencies": [],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
}
],
"workspace_paths": {
"11592711315645265694": "packages/bar",
"5128161233225832376": "packages/mono"
},
"workspace_versions": {
"11592711315645265694": "1.0.0"
}
}"
`;
exports[`dependency on workspace without version in package.json: version: 1 1`] = `
"{
"format": "v3",
@@ -1674,327 +1920,12 @@ exports[`dependency on workspace without version in package.json: version: *+bui
}"
`;
exports[`dependency on workspace without version in package.json: version: latest 1`] = `
"{
"format": "v3",
"meta_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"package_index": {
"no-deps": [
2,
3
],
"foo": 0,
"bar": 1
},
"trees": [
{
"id": 0,
"path": "node_modules",
"depth": 0,
"dependencies": {
"bar": {
"id": 0,
"package_id": 1
},
"no-deps": {
"id": 1,
"package_id": 2
}
}
},
{
"id": 1,
"path": "node_modules/bar/node_modules",
"depth": 1,
"dependencies": {
"no-deps": {
"id": 2,
"package_id": 3
}
}
}
],
"dependencies": [
{
"name": "bar",
"literal": "",
"workspace": "packages/bar",
"package_id": 1,
"behavior": {
"workspace": true
},
"id": 0
},
{
"name": "no-deps",
"literal": "",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"workspace": true
},
"id": 1
},
{
"name": "no-deps",
"literal": "latest",
"dist_tag": {
"name": "no-deps",
"tag": "no-deps"
},
"package_id": 3,
"behavior": {
"prod": true
},
"id": 2
}
],
"packages": [
{
"id": 0,
"name": "foo",
"name_hash": "14841791273925386894",
"resolution": {
"tag": "root",
"value": "",
"resolved": ""
},
"dependencies": [
0,
1
],
"integrity": null,
"man_dir": "",
"origin": "local",
"bin": null,
"scripts": {}
},
{
"id": 1,
"name": "bar",
"name_hash": "11592711315645265694",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/bar",
"resolved": "workspace:packages/bar"
},
"dependencies": [
2
],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 2,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/mono",
"resolved": "workspace:packages/mono"
},
"dependencies": [],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 3,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "npm",
"value": "2.0.0",
"resolved": "http://localhost:1234/no-deps/-/no-deps-2.0.0.tgz"
},
"dependencies": [],
"integrity": "sha512-W3duJKZPcMIG5rA1io5cSK/bhW9rWFz+jFxZsKS/3suK4qHDkQNxUTEXee9/hTaAoDCeHWQqogukWYKzfr6X4g==",
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
}
],
"workspace_paths": {
"11592711315645265694": "packages/bar",
"5128161233225832376": "packages/mono"
},
"workspace_versions": {
"11592711315645265694": "1.0.0"
}
}"
`;
exports[`dependency on workspace without version in package.json: version: 1`] = `
"{
"format": "v3",
"meta_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"package_index": {
"no-deps": [
2,
3
],
"foo": 0,
"bar": 1
},
"trees": [
{
"id": 0,
"path": "node_modules",
"depth": 0,
"dependencies": {
"bar": {
"id": 0,
"package_id": 1
},
"no-deps": {
"id": 1,
"package_id": 2
}
}
},
{
"id": 1,
"path": "node_modules/bar/node_modules",
"depth": 1,
"dependencies": {
"no-deps": {
"id": 2,
"package_id": 3
}
}
}
],
"dependencies": [
{
"name": "bar",
"literal": "",
"workspace": "packages/bar",
"package_id": 1,
"behavior": {
"workspace": true
},
"id": 0
},
{
"name": "no-deps",
"literal": "",
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"workspace": true
},
"id": 1
},
{
"name": "no-deps",
"literal": "",
"dist_tag": {
"name": "no-deps",
"tag": "no-deps"
},
"package_id": 3,
"behavior": {
"prod": true
},
"id": 2
}
],
"packages": [
{
"id": 0,
"name": "foo",
"name_hash": "14841791273925386894",
"resolution": {
"tag": "root",
"value": "",
"resolved": ""
},
"dependencies": [
0,
1
],
"integrity": null,
"man_dir": "",
"origin": "local",
"bin": null,
"scripts": {}
},
{
"id": 1,
"name": "bar",
"name_hash": "11592711315645265694",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/bar",
"resolved": "workspace:packages/bar"
},
"dependencies": [
2
],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 2,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "workspace",
"value": "workspace:packages/mono",
"resolved": "workspace:packages/mono"
},
"dependencies": [],
"integrity": null,
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 3,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "npm",
"value": "2.0.0",
"resolved": "http://localhost:1234/no-deps/-/no-deps-2.0.0.tgz"
},
"dependencies": [],
"integrity": "sha512-W3duJKZPcMIG5rA1io5cSK/bhW9rWFz+jFxZsKS/3suK4qHDkQNxUTEXee9/hTaAoDCeHWQqogukWYKzfr6X4g==",
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
}
],
"workspace_paths": {
"11592711315645265694": "packages/bar",
"5128161233225832376": "packages/mono"
},
"workspace_versions": {
"11592711315645265694": "1.0.0"
}
}"
`;
exports[`dependency on same name as workspace and dist-tag: with version 1`] = `
"{
"format": "v3",
"meta_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"package_index": {
"no-deps": [
2,
3
],
"no-deps": 2,
"foo": 0,
"bar": 1
},
@@ -2013,17 +1944,6 @@ exports[`dependency on same name as workspace and dist-tag: with version 1`] = `
"package_id": 2
}
}
},
{
"id": 1,
"path": "node_modules/bar/node_modules",
"depth": 1,
"dependencies": {
"no-deps": {
"id": 2,
"package_id": 3
}
}
}
],
"dependencies": [
@@ -2050,11 +1970,8 @@ exports[`dependency on same name as workspace and dist-tag: with version 1`] = `
{
"name": "no-deps",
"literal": "latest",
"dist_tag": {
"name": "no-deps",
"tag": "no-deps"
},
"package_id": 3,
"workspace": "packages/mono",
"package_id": 2,
"behavior": {
"prod": true
},
@@ -2114,22 +2031,6 @@ exports[`dependency on same name as workspace and dist-tag: with version 1`] = `
"origin": "npm",
"bin": null,
"scripts": {}
},
{
"id": 3,
"name": "no-deps",
"name_hash": "5128161233225832376",
"resolution": {
"tag": "npm",
"value": "2.0.0",
"resolved": "http://localhost:1234/no-deps/-/no-deps-2.0.0.tgz"
},
"dependencies": [],
"integrity": "sha512-W3duJKZPcMIG5rA1io5cSK/bhW9rWFz+jFxZsKS/3suK4qHDkQNxUTEXee9/hTaAoDCeHWQqogukWYKzfr6X4g==",
"man_dir": "",
"origin": "npm",
"bin": null,
"scripts": {}
}
],
"workspace_paths": {

View File

@@ -66,17 +66,10 @@ test("dependency on workspace without version in package.json", async () => {
"kjwoehcojrgjoj", // dist-tag does not exist, should choose local workspace
"*.1.*",
"*-pre",
"latest", // dist-tag exists, should choose local workspace
"", // empty string is treated as "latest" dist tag, should choose local workspace
];
const shouldNotWork: string[] = [
"1",
"1.*",
"1.1.*",
"1.1.0",
"*-pre+build",
"*+build",
"latest", // dist-tag exists, should choose package from npm
"",
];
const shouldNotWork: string[] = ["1", "1.*", "1.1.*", "1.1.0", "*-pre+build", "*+build"];
for (const version of shouldWork) {
writeFileSync(
@@ -213,7 +206,7 @@ test("dependency on same name as workspace and dist-tag", async () => {
expect(out.replace(/\s*\[[0-9\.]+m?s\]\s*$/, "").split(/\r?\n/)).toEqual([
expect.stringContaining("bun install v1."),
"",
"3 packages installed",
"2 packages installed",
]);
});