fix(run): swap ruby/bundle launcher when toggling Bundler (#1533)

A Ruby script configuration pre-fills Launcher with the interpreter path. Ticking Run with Bundler kept it and added `exec ruby`, so the run became `/…/bin/ruby exec ruby …` and failed with LoadError. A launcher that is another Ruby tool of the same interpreter (ruby, bundle, rake, rspec) now means its sibling in that directory; a launcher of the user's own (binstub, spring) is kept. The rake and RSpec types share the same helper.

Co-authored-by: kjeldahl <[email protected]>
Цей коміт міститься в:
Jacob Kjeldahlтаkjeldahl authored and GitHub committed 2026-11-05 20:43:14 +08:00
1 джерело dadec04d62
коміт 9a48426740
2 files changed
+36 -3

No files matched your search

+18 -3
Переглянути файл
@@ -10,12 +10,27 @@ import { commandName } from "./commandName";
const on = (v: string | undefined) => v === "true";
const RUBY_TOOLS = new Set(["ruby", "bundle", "rake", "rspec"]);
/**
* The launcher for `want` (`bundle` or the tool itself) from the Launcher field. A field holding
* the other Ruby tool of the same interpreter (`/v/bin/ruby` when Bundler is ticked) means its
* sibling in that directory; anything else (a binstub, `spring`) is the user's own and stays.
*/
function launcher(command: string | undefined, want: string): string {
const given = command?.trim();
if (!given) return want;
const current = commandName(given);
if (current === want || !RUBY_TOOLS.has(current)) return given;
const dir = /^.*[/\\]/.exec(given)?.[0] ?? "";
return dir + want + (/\.exe$/i.test(given) ? ".exe" : "");
}
/** `bundle exec <tool>` or `<tool>`, launched through `params.command` when one is set. */
function tool(p: Readonly<Record<string, string>>, name: string): { command: string; args: string[] } {
const command = p.command?.trim();
return on(p.bundler)
? { command: command || "bundle", args: ["exec", name] }
: { command: command || name, args: [] };
? { command: launcher(p.command, "bundle"), args: ["exec", name] }
: { command: launcher(p.command, name), args: [] };
}
/** Splits `bundle exec <tool> rest…` / `<tool> rest…` into the launcher, the Bundler flag and `rest`. */
+18
Переглянути файл
@@ -225,6 +225,24 @@ describe("configuration types", () => {
});
});
it("ticking Bundler swaps the interpreter launcher for its bundle sibling, and back", () => {
const abs = "/v/ruby/bin/ruby";
const run = cfg("r", { kind: "ruby", command: abs, args: ["-Ilib", "test/a_test.rb"] });
expect(withParams(run, { ...formParams(run)!, bundler: "true" })).toMatchObject({
command: "/v/ruby/bin/bundle",
args: ["exec", "ruby", "-Ilib", "test/a_test.rb"],
});
const bundled = cfg("r", { kind: "ruby", command: "/v/ruby/bin/bundle", args: ["exec", "ruby", "a.rb"] });
expect(withParams(bundled, { ...formParams(bundled)!, bundler: "" })).toMatchObject({
command: "/v/ruby/bin/ruby",
args: ["a.rb"],
});
// A launcher of the user's own (binstub, spring) is kept.
expect(withParams(run, { ...formParams(run)!, bundler: "true", command: "bin/spring" })).toMatchObject({
command: "bin/spring",
});
});
it("ruby types keep option values with their options and the argument order as written", () => {
const ruby = (args: string[]) => formParams(cfg("r", { kind: "ruby", command: "ruby", args }));
expect(ruby(["-I", "lib", "-w", "bin/app", "--port", "3000"])).toEqual({