Skip to content
Merged
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
31 changes: 24 additions & 7 deletions apps/repo_management/lib/mix/tasks/stokowski.ex
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ defmodule Mix.Tasks.Stokowski do
ensure_vendor!(vendor)
validate_api_key!(workflow)

resolved_runners = resolve_configured_runners!(workflow, root)
mise = mise_executable!()
resolved_runners = resolve_configured_runners!(workflow, root, mise)
uv = executable!("uv")

runner_executables =
Enum.map(resolved_runners, fn {_runner, {executable, _version}} -> executable end)
path_executables =
[mise | Enum.map(resolved_runners, fn {_runner, {executable, _version}} -> executable end)]

Enum.each(resolved_runners, fn {runner, {executable, version}} ->
Mix.shell().info("Using #{String.capitalize(runner)} #{version} at #{executable}")
Expand All @@ -48,7 +49,7 @@ defmodule Mix.Tasks.Stokowski do
try do
System.cmd(uv, command_args,
cd: root,
env: [{"PATH", prepend_path(runner_executables)}],
env: [{"PATH", prepend_path(path_executables)}],
stderr_to_stdout: true,
into: IO.stream(:stdio, :line)
)
Expand All @@ -70,8 +71,11 @@ defmodule Mix.Tasks.Stokowski do

@doc false
def resolve_runner!(root, runner) when runner in ["codex", "claude"] do
resolve_runner!(root, runner, mise_executable!())
end

defp resolve_runner!(root, runner, mise) do
expected_version = configured_runner_version!(Path.join(root, "mise.toml"), runner)
mise = executable!("mise")

case System.cmd(mise, ["which", runner], cd: root) do
{output, 0} ->
Expand Down Expand Up @@ -132,7 +136,20 @@ defmodule Mix.Tasks.Stokowski do
end
end

defp resolve_configured_runners!(workflow, root) do
defp mise_executable! do
local_mise =
case System.get_env("HOME") do
home when is_binary(home) and home != "" ->
System.find_executable(Path.join([home, ".local", "bin", "mise"]))

_missing_home ->
nil
end

local_mise || executable!("mise")
end

defp resolve_configured_runners!(workflow, root, mise) do
runner_names =
case Workflow.read(workflow) do
{:ok, parsed_workflow} -> Workflow.runner_values(parsed_workflow) |> Enum.uniq()
Expand All @@ -146,7 +163,7 @@ defmodule Mix.Tasks.Stokowski do
Mix.raise("workflow.yaml uses unsupported runner #{inspect(runner)}")
end

{runner, resolve_runner!(root, runner)}
{runner, resolve_runner!(root, runner, mise)}
end)
end

Expand Down
43 changes: 39 additions & 4 deletions apps/repo_management/test/mix/tasks/stokowski_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,25 @@ defmodule Mix.Tasks.StokowskiTest do
refute File.exists?(Path.join(vendor, "uv.lock"))
end

@tag :tmp_dir
test "prefers user-local mise and prepends it to the runner path", %{tmp_dir: tmp_dir} do
{root, vendor, _invocation} = fake_checkout(tmp_dir, 0)

capture_io(fn -> StokowskiTask.run([], root) end)

path =
vendor
|> Path.join("path.log")
|> File.read!()
|> String.trim()
|> String.split(":")

assert Enum.take(path, 2) == [
Path.join([tmp_dir, "home", ".local", "bin"]),
Path.join(tmp_dir, "runner-bin")
]
end

@tag :tmp_dir
test "reports a nonzero exit and still removes the generated lockfile", %{tmp_dir: tmp_dir} do
{root, vendor, _invocation} = fake_checkout(tmp_dir, 23)
Expand Down Expand Up @@ -151,7 +170,7 @@ defmodule Mix.Tasks.StokowskiTest do
@tag :tmp_dir
test "accepts prerelease Claude pins", %{tmp_dir: tmp_dir} do
{root, _vendor, _invocation} = fake_checkout(tmp_dir, 0)
claude = Path.join(tmp_dir, "bin/claude")
claude = Path.join(tmp_dir, "runner-bin/claude")

File.write!(claude, "#!/bin/sh\nprintf '%s\\n' '2.1.270-rc.1 (Claude Code)'\n")
File.chmod!(claude, 0o755)
Expand Down Expand Up @@ -188,10 +207,15 @@ defmodule Mix.Tasks.StokowskiTest do
root = Path.join(tmp_dir, "repo")
vendor = Path.join([root, "vendor", "stokowski"])
bin = Path.join(tmp_dir, "bin")
home = Path.join(tmp_dir, "home")
mise_bin = Path.join([home, ".local", "bin"])
runner_bin = Path.join(tmp_dir, "runner-bin")
invocation = Path.join(vendor, "invocation.log")

File.mkdir_p!(vendor)
File.mkdir_p!(bin)
File.mkdir_p!(mise_bin)
File.mkdir_p!(runner_bin)
File.write!(Path.join(root, "workflow.yaml"), "tracker: {kind: linear}\n")

File.write!(
Expand All @@ -209,21 +233,22 @@ defmodule Mix.Tasks.StokowskiTest do
pwd
printf '%s\\n' "$@"
} > "$3/invocation.log"
printf '%s\\n' "$PATH" > "$3/path.log"
: > "$3/uv.lock"
printf 'fake uv stdout\\n'
printf 'fake uv stderr\\n' >&2
exit #{exit_status}
"""
)

codex = Path.join(bin, "codex")
codex = Path.join(runner_bin, "codex")
write_executable!(codex, "#!/bin/sh\nprintf '%s\\n' '#{codex_version}'\n")

claude = Path.join(bin, "claude")
claude = Path.join(runner_bin, "claude")
write_executable!(claude, "#!/bin/sh\nprintf '%s\\n' '2.1.270 (Claude Code)'\n")

write_executable!(
Path.join(bin, "mise"),
Path.join(mise_bin, "mise"),
"""
#!/bin/sh
case "$1 $2" in
Expand All @@ -234,15 +259,25 @@ defmodule Mix.Tasks.StokowskiTest do
"""
)

write_executable!(Path.join(bin, "mise"), "#!/bin/sh\nexit 86\n")

original_path = System.get_env("PATH")
original_home = System.get_env("HOME")
System.put_env("PATH", Enum.join([bin, original_path], ":"))
System.put_env("HOME", home)

on_exit(fn ->
if original_path do
System.put_env("PATH", original_path)
else
System.delete_env("PATH")
end

if original_home do
System.put_env("HOME", original_home)
else
System.delete_env("HOME")
end
end)

{root, vendor, invocation}
Expand Down
Loading