From b575fe8ebc195b39300342e4e31d98c3003be63a Mon Sep 17 00:00:00 2001 From: bagtoad <47394200+BagToad@users.noreply.github.com> Date: Sun, 17 Nov 2024 16:44:08 -0700 Subject: [PATCH] Warn when installing local ext with no executable --- pkg/cmd/extension/manager.go | 17 +++++++++- pkg/cmd/extension/manager_test.go | 56 +++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/pkg/cmd/extension/manager.go b/pkg/cmd/extension/manager.go index 2431c6b83..5b8406490 100644 --- a/pkg/cmd/extension/manager.go +++ b/pkg/cmd/extension/manager.go @@ -28,6 +28,7 @@ import ( // ErrInitialCommitFailed indicates the initial commit when making a new extension failed. var ErrInitialCommitFailed = errors.New("initial commit failed") +var ErrExtensionExecutableNotFound = errors.New("an extension has been installed but there is no executable") const darwinAmd64 = "darwin-amd64" @@ -194,10 +195,24 @@ func (m *Manager) populateLatestVersions(exts []*Extension) { func (m *Manager) InstallLocal(dir string) error { name := filepath.Base(dir) targetLink := filepath.Join(m.installDir(), name) + cs := m.io.ColorScheme() + if err := os.MkdirAll(filepath.Dir(targetLink), 0755); err != nil { return err } - return makeSymlink(dir, targetLink) + if err := makeSymlink(dir, targetLink); err != nil { + return err + } + + // Check if an executable of the same name exists in the target directory. + // An error here doesn't indicate a failed extension installation, but + // it does indicate that the user will not be able to run the extension until + // the executable file is built or created manually somehow. + if _, err := os.Stat(filepath.Join(dir, name)); err != nil { + errMsg := fmt.Errorf("%v %w: expected executable file named \"%s\" in %s, perhaps you need to build it?", cs.WarningIcon(), ErrExtensionExecutableNotFound, name, dir) + return errMsg + } + return nil } type binManifest struct { diff --git a/pkg/cmd/extension/manager_test.go b/pkg/cmd/extension/manager_test.go index e25bc1496..2ccb619f8 100644 --- a/pkg/cmd/extension/manager_test.go +++ b/pkg/cmd/extension/manager_test.go @@ -719,6 +719,62 @@ func TestManager_UpgradeExtension_GitExtension_Pinned(t *testing.T) { gcOne.AssertExpectations(t) } +func TestManager_Install_local(t *testing.T) { + extManagerDir := t.TempDir() + ios, _, stdout, stderr := iostreams.Test() + m := newTestManager(extManagerDir, nil, nil, ios) + fakeExtensionName := "local-ext" + + // Create a temporary directory to simulate the local extension repo + extensionLocalPath := filepath.Join(extManagerDir, fakeExtensionName) + require.NoError(t, os.MkdirAll(extensionLocalPath, 0755)) + + // Create a fake executable in the local extension directory + fakeExtensionExecutablePath := filepath.Join(extensionLocalPath, fakeExtensionName) + require.NoError(t, stubExtension(fakeExtensionExecutablePath)) + + err := m.InstallLocal(extensionLocalPath) + require.NoError(t, err) + + // This is the path to a file: + // on windows this is a file whose contents is a string describing the path to the local extension dir. + // on other platforms this file is a real symlink to the local extension dir. + extensionLinkFile := filepath.Join(extManagerDir, "extensions", fakeExtensionName) + + if runtime.GOOS == "windows" { + // We don't create symlinks on Windows, so check if we made a file + // with the correct contents + b, err := os.ReadFile(extensionLinkFile) + require.NoError(t, err) + assert.Equal(t, extensionLocalPath, string(b)) + } else { + // Verify the created symlink points to the correct directory + linkTarget, err := os.Readlink(extensionLinkFile) + require.NoError(t, err) + assert.Equal(t, extensionLocalPath, linkTarget) + } + assert.Equal(t, "", stdout.String()) + assert.Equal(t, "", stderr.String()) +} + +func TestManager_Install_local_no_executable_found(t *testing.T) { + tempDir := t.TempDir() + ios, _, stdout, _ := iostreams.Test() + m := newTestManager(tempDir, nil, nil, ios) + fakeExtensionName := "local-ext" + + // Create a temporary directory to simulate the local extension repo + localDir := filepath.Join(tempDir, fakeExtensionName) + require.NoError(t, os.MkdirAll(localDir, 0755)) + + // Intentionally not creating an executable in the local extension repo + // to simulate an attempt to install a local extension without an executable + + err := m.InstallLocal(localDir) + require.ErrorIs(t, err, ErrExtensionExecutableNotFound) + assert.Equal(t, "", stdout.String()) +} + func TestManager_Install_git(t *testing.T) { tempDir := t.TempDir()