-
Notifications
You must be signed in to change notification settings - Fork 5k
install(bin): support cross-volume Windows global installs #30154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
c67d424
install(bin): support cross-volume Windows global installs
robobun 6390df6
[autofix.ci] apply automated fixes
autofix-ci[bot] e844625
install(bin): handle drive-root bin dirs + address review feedback
robobun 1fe22e8
test(30129): assert install stderr stays clean of 'error:' marker
robobun 4551282
ci: retrigger — build 50974 hit CI-wide agent-pool outage (66 expired…
robobun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| // https://github.com/oven-sh/bun/issues/30129 | ||
| // | ||
| // On Windows, `bun install -g <pkg>` panicked at `install/bin.zig:738` with | ||
| // "Internal assertion failure" when `BUN_INSTALL_BIN` lived on a different | ||
| // physical drive from the global package store. `createWindowsShim` assumed | ||
| // the stored `bin_path` could always be expressed as a `..\`-relative walk | ||
| // from the shim's `.bin` directory. Windows cannot produce such a walk | ||
| // between two volumes, so `path.relative` returned the absolute target | ||
| // instead and the assertion fired — after the `.bunx` file had already been | ||
| // opened with O_TRUNC, leaving an empty 0-byte `.bunx` behind. | ||
| // | ||
| // The same assertion family also fires on POSIX (`createSymlink`) and on | ||
| // Windows (`createWindowsShim`) for the *zero-`..`* shape, where a package's | ||
| // `bin` field resolves to a file *inside* the sibling `.bin` directory. That | ||
| // is the shape this test exercises on both Linux and Windows CI. Without the | ||
| // fix, `bun install` panics during bin linking and exits non-zero before any | ||
| // node_modules artefacts land on disk. | ||
| import { expect, test } from "bun:test"; | ||
| import { bunEnv, bunExe, isWindows, tempDir } from "harness"; | ||
| import { existsSync, readlinkSync, statSync } from "node:fs"; | ||
| import { join } from "node:path"; | ||
|
|
||
| test("bun install does not panic when bin target's relative path has no `..` prefix", { timeout: 60_000 }, async () => { | ||
| using dir = tempDir("issue-30129", { | ||
| "package.json": JSON.stringify({ | ||
| name: "root", | ||
| version: "1.0.0", | ||
| dependencies: { weird: "file:./pkg" }, | ||
| }), | ||
| "pkg/package.json": JSON.stringify({ | ||
| name: "weird", | ||
| version: "1.0.0", | ||
| bin: "../.bin/foo.js", | ||
| }), | ||
| "pkg/index.js": "module.exports = 1;\n", | ||
| // Pre-create the .bin directory with the file `bin` points at so that | ||
| // `abs_target` resolves to `<node_modules>/.bin/foo.js`. The relative | ||
| // path from `<node_modules>/.bin` to that target is just `foo.js`, | ||
| // which is the shape that trips the overly strict assertion on both | ||
| // POSIX (`createSymlink`) and Windows (`createWindowsShim`). | ||
| "node_modules/.bin/foo.js": "#!/usr/bin/env node\nprocess.exit(0);\n", | ||
| }); | ||
|
|
||
| await using proc = Bun.spawn({ | ||
| cmd: [bunExe(), "install"], | ||
| cwd: String(dir), | ||
| env: bunEnv, | ||
| stdout: "pipe", | ||
| stderr: "pipe", | ||
| }); | ||
|
|
||
| const [, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); | ||
|
|
||
| // Bun prints "error:" for any user-facing install failure. The fix must | ||
| // let the install complete cleanly — no error markers in stderr. | ||
| expect(stderr).not.toContain("error:"); | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| // The package itself must be extracted. | ||
| const pkgJson = join(String(dir), "node_modules", "weird", "package.json"); | ||
| expect(statSync(pkgJson).isFile()).toBe(true); | ||
|
|
||
| // The lockfile must have been written to disk. Without the fix the install | ||
| // panics before saving the lockfile, so the file simply doesn't exist. | ||
| const lockfile = existsSync(join(String(dir), "bun.lock")) | ||
| ? join(String(dir), "bun.lock") | ||
| : join(String(dir), "bun.lockb"); | ||
| expect(existsSync(lockfile)).toBe(true); | ||
|
|
||
| // Bin-link artefacts differ by platform: | ||
| // - POSIX: a symlink at `.bin/weird` pointing at the target relative to | ||
| // `.bin`. For our fixture that path is just `foo.js`. | ||
| // - Windows: a `.exe` + `.bunx` shim pair. The `.bunx` file encodes the | ||
| // bin path as a UTF-16LE prefix terminated by a `"\0` sequence; for | ||
| // this fixture it must resolve to `<node_modules>\.bin\foo.js` at | ||
| // runtime, which in the parent-of-`.bin`-anchored form is | ||
| // `.bin\foo.js`. | ||
| if (isWindows) { | ||
| const exe = join(String(dir), "node_modules", ".bin", "weird.exe"); | ||
| const bunx = join(String(dir), "node_modules", ".bin", "weird.bunx"); | ||
| expect(statSync(exe).isFile()).toBe(true); | ||
| expect(statSync(bunx).isFile()).toBe(true); | ||
|
|
||
| // `.bunx` must not be a truncated zero-byte file left behind by a | ||
| // panic between `O_TRUNC` and the metadata write. | ||
| expect(statSync(bunx).size).toBeGreaterThan(0); | ||
|
|
||
| const bunxBytes = await Bun.file(bunx).bytes(); | ||
| const decoded = new TextDecoder("utf-16le").decode(bunxBytes); | ||
| const terminator = decoded.indexOf('"\0'); | ||
| expect(terminator).toBeGreaterThan(0); | ||
| expect(decoded.slice(0, terminator)).toBe(".bin\\foo.js"); | ||
| } else { | ||
|
robobun marked this conversation as resolved.
|
||
| const link = join(String(dir), "node_modules", ".bin", "weird"); | ||
| expect(readlinkSync(link)).toBe("foo.js"); | ||
| } | ||
|
|
||
| expect(exitCode).toBe(0); | ||
| }); | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.