Skip to content

refactor(toolchain): extract post-install path-update logic for unit testability #115

@ottovlotto

Description

@ottovlotto

Why

paritytech/playground-app#118 was a path-config bug: `dot init` installed rustup, but the new `~/.cargo/bin` was not applied to the running init process, so the next step (`rustup toolchain install nightly`) failed with `rustup: command not found` on a fresh machine.

The bug went undetected by the e2e suite because every CI runner already has rustup on PATH from its base image — init's "install rustup" branch never runs in CI.

The follow-up (#114) added a daily-cron container smoke test that runs init in a fresh `ubuntu:22.04` to catch this class of bug. That works, but it gives a slow signal (minutes per run) and only fires nightly. The fast complement is a unit test, but `src/utils/toolchain.ts` doesn't currently expose a seam for one — install and path patching are fused inside per-tool `install` shell pipelines.

What

Refactor `src/utils/toolchain.ts` so the path-update logic is a pure function that takes the install result and the current env, and returns the env to use for subsequent steps. Roughly:

```ts
// Today (sketch):
{
name: "rustup",
check: () => commandExists("rustup"),
install: (onData) => runPiped('curl ... | sh -s -- -y', onData),
manualHint: "https://rustup.rs\",
}

// After:
{
name: "rustup",
check: () => commandExists("rustup"),
install: (onData) => runPiped('curl ... | sh -s -- -y', onData),
/**
* Pure: given the current env after install, return the env the
* next step should run with. For rustup that means prepending
* $HOME/.cargo/bin to PATH if it isn't already there.
*/
patchEnv: (env) => ({
...env,
PATH: prependPath(env.PATH, `${env.HOME}/.cargo/bin`),
}),
manualHint: "https://rustup.rs\",
}
```

Then the orchestrator threads the env through:

```ts
let env = { ...process.env };
for (const step of TOOL_STEPS) {
if (await step.check(env)) continue;
await step.install(env, onData);
if (step.patchEnv) env = step.patchEnv(env);
}
```

Acceptance criteria

  • `patchEnv` (or whatever it ends up being called) is a pure function with no I/O.
  • Unit test in `src/utils/toolchain.test.ts`: given an env where `PATH=/usr/bin` and `HOME=/root`, after the rustup step `patchEnv` returns an env whose `PATH` starts with `/root/.cargo/bin`. (The exact [BUG] Apps uploaded as moddable with private repos trigger failure #118 regression — landing in milliseconds on every PR.)
  • Equivalent unit tests for the foundry, IPFS, and cdm steps.
  • The orchestrator path-threads the patched env through subsequent `check` and `install` calls. (`check` will need to take an `env` parameter and pass it down to `commandExists`.)
  • The cold-start container smoke test (`init-cold-smoke` in `.github/workflows/e2e.yml`) keeps passing — this refactor is meant to add fast feedback, not replace the slow end-to-end signal.

Out of scope

  • Changing what gets installed or how installs are wired up.
  • The TUI rendering (`src/commands/init/InitScreen.tsx`).

Notes

  • The orchestrator should fail loudly if a `check` returns true after `patchEnv` but the same `check` returned false before `install` — that's a smoke signal that env patching went the wrong direction.
  • Don't `process.env = ...` mutate the global. Pass env down explicitly. Tests need to be hermetic.

Metadata

Metadata

Assignees

Labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions