Skip to content
Open
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
61 changes: 56 additions & 5 deletions pkg/commands/npm_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ func (m *NpmManager) IsLinked(name string, path string) (bool, error) {
globalPath := filepath.Join(m.NpmRoot, name)
fileInfo, err := os.Lstat(globalPath)
if err != nil {
if err == os.ErrNotExist {
return false, nil
}
// swallowing error. For some reason we're getting 'no such file or directory' here despite checking for os.ErrNotExist
return false, nil
}

Expand All @@ -56,7 +52,13 @@ func (m *NpmManager) IsLinked(name string, path string) (bool, error) {
if err != nil {
return false, err
}
if linkedPath == path {
// The symlink target may be relative. Resolve it against the
// directory containing the symlink to get an absolute path.
if !filepath.IsAbs(linkedPath) {
linkedPath = filepath.Join(filepath.Dir(globalPath), linkedPath)
}
linkedPath = filepath.Clean(linkedPath)
if linkedPath == filepath.Clean(path) {
return true, nil
}
}
Expand Down Expand Up @@ -174,6 +176,55 @@ func (m *NpmManager) GetDeps(currentPkg *Package, previousDeps []*Dependency) ([
return deps, nil
}

// GetLinkedPackagePaths scans node_modules for symlinked entries and returns
// a set of their resolved absolute target paths. This catches links that
// exist in node_modules but are not declared in package.json (e.g. created
// via `npm link <name>`).
func (m *NpmManager) GetLinkedPackagePaths(currentPkg *Package) map[string]bool {
result := map[string]bool{}
nodeModulesPath := filepath.Join(currentPkg.Path, "node_modules")

scanDir := func(dir string) {
entries, err := ioutil.ReadDir(dir)
if err != nil {
return
}
for _, entry := range entries {
if entry.Mode()&os.ModeSymlink == 0 {
continue
}
fullPath := filepath.Join(dir, entry.Name())
resolved, err := filepath.EvalSymlinks(fullPath)
if err != nil {
continue
}
result[resolved] = true
}
}

// Scan top-level entries in node_modules
entries, err := ioutil.ReadDir(nodeModulesPath)
if err != nil {
return result
}
for _, entry := range entries {
name := entry.Name()
if strings.HasPrefix(name, "@") {
// Scoped package: scan the scope directory for symlinked packages
scanDir(filepath.Join(nodeModulesPath, name))
} else if entry.Mode()&os.ModeSymlink != 0 {
fullPath := filepath.Join(nodeModulesPath, name)
resolved, err := filepath.EvalSymlinks(fullPath)
if err != nil {
continue
}
result[resolved] = true
}
}

return result
}

func (m *NpmManager) GetTarballs(currentPkg *Package) ([]*Tarball, error) {
// would be nice if I had a guarantee on the directory I was checking but this should do
paths, err := filepath.Glob("*.tgz")
Expand Down
7 changes: 5 additions & 2 deletions pkg/gui/dependencies_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ func (gui *Gui) handleDepSelect(g *gocui.Gui, v *gocui.View) error {
return nil
}

// linkPathMap returns the set of link paths of the current package's dependencies
// linkPathMap returns the set of resolved symlink target paths from the current
// package's node_modules. This includes both deps declared in package.json and
// links created via `npm link <name>` that aren't in package.json.
func (gui *Gui) linkPathMap() map[string]bool {
linkPathMap := map[string]bool{}
linkPathMap := gui.NpmManager.GetLinkedPackagePaths(gui.currentPackage())
// Also include deps that we already know are linked (from package.json parsing)
for _, dep := range gui.State.Deps {
if dep.Linked() {
linkPathMap[dep.LinkPath] = true
Expand Down
2 changes: 1 addition & 1 deletion pkg/gui/keybindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ func GetKeyDisplay(key interface{}) string {
keyInt = int(key)
}

return string(keyInt)
return string(rune(keyInt))
}

func (gui *Gui) getKey(name string) interface{} {
Expand Down
19 changes: 14 additions & 5 deletions pkg/gui/packages_panel.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,18 +131,25 @@ func (gui *Gui) handleLinkPackage() error {
return nil
}

var cmdStr string
if selectedPkg == gui.currentPackage() {
return gui.surfaceError(errors.New("Cannot link a package to itself"))
}

var cmdStr string
if gui.linkPathMap()[selectedPkg.Path] {
cmdStr = fmt.Sprintf("npm unlink --no-save %s", selectedPkg.Config.Name)
} else {
if !selectedPkg.LinkedGlobally {
cmdStr = fmt.Sprintf("npm link %s", selectedPkg.Path)
} else {
if selectedPkg.LinkedGlobally {
// Already globally linked: just create a local symlink by name.
// This only writes to the local node_modules, no global write needed.
cmdStr = fmt.Sprintf("npm link %s", selectedPkg.Config.Name)
} else {
return gui.surfaceError(
fmt.Errorf(
"%s is not globally linked. Select it and press 'L' to globally link it first",
selectedPkg.Config.Name,
),
)
}
}

Expand All @@ -156,7 +163,9 @@ func (gui *Gui) handleGlobalLinkPackage(pkg *commands.Package) error {

var cmdStr string
if pkg.LinkedGlobally {
cmdStr = "npm unlink"
// In npm v7+, 'npm unlink' is an alias for 'npm uninstall' (local).
// To remove a global link, use 'npm rm -g <name>'.
cmdStr = fmt.Sprintf("npm rm -g %s", pkg.Config.Name)
} else {
cmdStr = "npm link"
}
Expand Down
4 changes: 3 additions & 1 deletion pkg/gui/pty.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build !windows
// +build !windows

package gui
Expand Down Expand Up @@ -176,7 +177,8 @@ func (gui *Gui) newPtyTask(viewName string, commandView *commands.CommandView, c

ptmx, err := pty.Start(commandView.Cmd)
if err != nil {
// swallowing for now (actually continue to swallow this)
gui.Log.Errorf("pty.Start error for command '%s': %v", cmdStr, err)
fmt.Fprint(view, utils.ColoredString(fmt.Sprintf("Error starting command: %v", err), color.FgRed))
return
}

Expand Down
30 changes: 22 additions & 8 deletions vendor/github.com/creack/pty/run.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.