Skip to content

Commit d742e7d

Browse files
fix(init): expose freshly-installed rustup to the same dot init run
The rustup installer drops binaries in $CARGO_HOME/bin and edits shell rc files, neither of which reaches the running dot process. The next two steps (Rust nightly, rust-src) then fail with `rustup: command not found` on a clean machine. Prepend $CARGO_HOME/bin to process.env.PATH after rustup install so the rest of `dot init` can resolve rustup immediately.
1 parent 1f64f04 commit d742e7d

3 files changed

Lines changed: 64 additions & 3 deletions

File tree

.changeset/init-rustup-path.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"playground-cli": patch
3+
---
4+
5+
Fix `dot init` failing on a clean macOS/Linux machine where `rustup` isn't yet installed. The rustup installer writes its binaries to `~/.cargo/bin` and adds that directory to PATH only via shell rc files, which doesn't reach the running `dot` process. The next two steps (`Rust nightly`, `rust-src`) then fail with `bash: rustup: command not found`. After installing rustup we now prepend `$CARGO_HOME/bin` (default `~/.cargo/bin`) to `process.env.PATH` so the rest of `dot init` can resolve `rustup` immediately.

src/utils/toolchain.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
2+
import { prependPath } from "./toolchain.js";
3+
4+
describe("prependPath", () => {
5+
let originalPath: string | undefined;
6+
7+
beforeEach(() => {
8+
originalPath = process.env.PATH;
9+
});
10+
11+
afterEach(() => {
12+
if (originalPath === undefined) delete process.env.PATH;
13+
else process.env.PATH = originalPath;
14+
});
15+
16+
it("prepends the directory when not already present", () => {
17+
process.env.PATH = "/usr/bin:/bin";
18+
prependPath("/Users/me/.cargo/bin");
19+
expect(process.env.PATH).toBe("/Users/me/.cargo/bin:/usr/bin:/bin");
20+
});
21+
22+
it("is a no-op when the directory is already on PATH", () => {
23+
process.env.PATH = "/Users/me/.cargo/bin:/usr/bin";
24+
prependPath("/Users/me/.cargo/bin");
25+
expect(process.env.PATH).toBe("/Users/me/.cargo/bin:/usr/bin");
26+
});
27+
28+
it("handles an empty PATH", () => {
29+
process.env.PATH = "";
30+
prependPath("/Users/me/.cargo/bin");
31+
expect(process.env.PATH).toBe("/Users/me/.cargo/bin");
32+
});
33+
34+
it("handles an unset PATH", () => {
35+
delete process.env.PATH;
36+
prependPath("/Users/me/.cargo/bin");
37+
expect(process.env.PATH).toBe("/Users/me/.cargo/bin");
38+
});
39+
});

src/utils/toolchain.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ function run(cmd: string, opts?: { shell?: string }): Promise<string> {
1414
});
1515
}
1616

17+
/**
18+
* Prepend `dir` to `process.env.PATH` if not already present. Lets a step that
19+
* just installed a binary expose it to the rest of `dot init` without waiting
20+
* for a shell restart.
21+
*/
22+
export function prependPath(dir: string): void {
23+
const segments = (process.env.PATH ?? "").split(":").filter(Boolean);
24+
if (segments.includes(dir)) return;
25+
process.env.PATH = process.env.PATH ? `${dir}:${process.env.PATH}` : dir;
26+
}
27+
1728
export async function commandExists(cmd: string): Promise<boolean> {
1829
if (!/^[a-zA-Z0-9_-]+$/.test(cmd)) {
1930
throw new Error(`Invalid command name: ${cmd}`);
@@ -80,11 +91,17 @@ export const TOOL_STEPS: ToolStep[] = [
8091
{
8192
name: "rustup",
8293
check: () => commandExists("rustup"),
83-
install: (onData) =>
84-
runPiped(
94+
install: async (onData) => {
95+
await runPiped(
8596
'curl --proto "=https" --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y',
8697
onData,
87-
),
98+
);
99+
// rustup-init writes binaries to $CARGO_HOME/bin (default ~/.cargo/bin)
100+
// and updates shell rc files, but those edits don't reach the running
101+
// dot process. Prepend the bin dir so the very next step in this same
102+
// `dot init` can resolve `rustup`.
103+
prependPath(resolve(process.env.CARGO_HOME ?? `${homedir()}/.cargo`, "bin"));
104+
},
88105
manualHint: "https://rustup.rs",
89106
},
90107
{

0 commit comments

Comments
 (0)