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
11 changes: 10 additions & 1 deletion internal/install/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ type InitOptions struct {
// execCmdFn is passed through to MCPInstall when invoking MCP registration.
// Nil → real exec.Command. Injected in tests to capture the registration call.
execCmdFn func(name string, arg ...string) *exec.Cmd
// executableFn is passed through to MCPInstall for binary-path resolution.
// Nil → os.Executable. Injected in tests so CI runners (which have no
// durable guild binary installed) can resolve to a temp-file path and
// avoid the "binary not found in any durable location" error.
executableFn func() (string, error)
}

// InitResult carries what Init accomplished so callers can inspect or log.
Expand Down Expand Up @@ -270,13 +275,17 @@ func Init(ctx context.Context, repoRoot string, opts InitOptions) (*InitResult,
// No-client path keeps the manual-setup hint.
if len(detected) > 0 {
fmt.Fprintln(opts.Out)
execFn := opts.executableFn
if execFn == nil {
execFn = os.Executable
}
mcpOpts := MCPInstallOptions{
Run: true,
Yes: opts.Yes,
Out: opts.Out,
In: opts.In,
clients: detected,
executableFn: os.Executable,
executableFn: execFn,
execCmdFn: opts.execCmdFn,
}
if _, err := MCPInstall(ctx, mcpOpts); err != nil {
Expand Down
43 changes: 30 additions & 13 deletions internal/install/init_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,21 @@ func recordingExec(calls *[][]string) func(name string, arg ...string) *exec.Cmd
}
}

// fakeExecutable creates a real temp file and returns an executableFn that
// resolves to its path. Needed so the MCP registration step's resolveAbsBinPath
// treats it as a durable install location (not a go-build cache miss). CI
// runners have no system-installed guild, so this plug is essential there.
func fakeExecutable(t *testing.T) func() (string, error) {
t.Helper()
f, err := os.CreateTemp(t.TempDir(), "guild-*")
if err != nil {
t.Fatalf("create fake executable: %v", err)
}
_ = f.Close()
path := f.Name()
return func() (string, error) { return path, nil }
}

// makeRepo creates a temp directory whose basename is name, simulating a repo root.
func makeRepo(t *testing.T, name string) string {
t.Helper()
Expand Down Expand Up @@ -424,13 +439,14 @@ func TestInit_MCPRegistration_YesFlagInvokesExec(t *testing.T) {
var calls [][]string

_, err := Init(ctx, dir, InitOptions{
Yes: true,
Out: &out,
In: &bytes.Buffer{},
LoreDBPath: loreDB,
QuestDBPath: questDB,
clients: []Client{fakeClient("FakeClient")},
execCmdFn: recordingExec(&calls),
Yes: true,
Out: &out,
In: &bytes.Buffer{},
LoreDBPath: loreDB,
QuestDBPath: questDB,
clients: []Client{fakeClient("FakeClient")},
execCmdFn: recordingExec(&calls),
executableFn: fakeExecutable(t),
})
if err != nil {
t.Fatalf("Init: %v", err)
Expand All @@ -455,12 +471,13 @@ func TestInit_MCPRegistration_InteractiveYes(t *testing.T) {
// "Run: ... [y/N]" from MCPInstall. Both answered "y".
in := bytes.NewBufferString("y\ny\n")
_, err := Init(ctx, dir, InitOptions{
Out: &out,
In: in,
LoreDBPath: loreDB,
QuestDBPath: questDB,
clients: []Client{fakeClient("FakeClient")},
execCmdFn: recordingExec(&calls),
Out: &out,
In: in,
LoreDBPath: loreDB,
QuestDBPath: questDB,
clients: []Client{fakeClient("FakeClient")},
execCmdFn: recordingExec(&calls),
executableFn: fakeExecutable(t),
})
if err != nil {
t.Fatalf("Init: %v", err)
Expand Down
Loading