Skip to content

Feature/20 monorepo support #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 7, 2023
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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,13 @@ To automatically run it when opening a new buffer:
(eval-after-load 'js-mode
'(add-hook 'js-mode-hook #'add-node-modules-path))
```

## Monorepo Support
In a monorepo scenario it might make sense to add multiple directories.
To achieve this, additional commands can be specified:

```
(use-package add-node-modules-path
:custom
(add-node-modules-path-command '("pnpm bin" "pnpm bin -w")))
```
90 changes: 68 additions & 22 deletions add-node-modules-path.el
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
;; '(add-hook 'js2-mode-hook #'add-node-modules-path))

;;; Code:

(require 'seq)
(require 's)

(defgroup add-node-modules-path nil
Expand All @@ -38,39 +38,85 @@
:group 'environment)

;;;###autoload
(defcustom add-node-modules-path-command "npm bin"
"Command to find the bin path."
:type 'string)
(defcustom add-node-modules-path-command '("npm bin")
"Command(s) to find the bin path. To add multiple bin paths, simply add
multiple commands to the list, e.g. \\='(\"pnpm bin\" \"pnpm bin -w\")"
:type '(repeat string)
:set (lambda (symbol value)
"Converts a non-list value to a single-element list of the same value.
This is necessary to be backward compatible, since previous versions of this
custom var were of type string."
(set-default symbol (if (listp value) value (list value)))))

;;;###autoload
(defcustom add-node-modules-path-debug nil
"Enable verbose output when non nil."
:type 'boolean
:group 'add-node-modules-path)

(defun add-node-modules-path/trim-list-and-elements (list)
"Trims all string values in LIST and empty / non-string values are removed."
(if (listp list)
(seq-filter 's-present? (mapcar 's-trim (seq-filter 'stringp list)))))

(defun add-node-modules-path/exec-command (command)
"Executes the given COMMAND and returns a plist containing the command,
its shell execution result and a boolean indicating, whether the execution
result denotes a valid directory"
(if (and (stringp command) (s-present? command))
(let ((result (s-chomp (shell-command-to-string command))))
(list 'command command 'result result 'directory-p (file-directory-p result)))))

(defun add-node-modules-path/exec-command-list (command-list)
"Executes all commands in COMMAND-LIST and returns a list of plists
containing the various command execution results. Elements in COMMAND-LIST which
are not strings are ignoredand will not appear in the result."
(if (listp command-list)
(seq-filter 'consp (mapcar 'add-node-modules-path/exec-command command-list))))

(defun add-node-modules-path/get-valid-directories (command-executions)
"Filters the provided COMMAND-EXECUTIONS for entries, whose execution result
denotes an existing directory"
(if (listp command-executions)
(let ((filtered (seq-filter '(lambda (elt) (plist-get elt 'directory-p)) command-executions)))
(mapcar #'(lambda (elt) (plist-get elt 'result)) filtered))))

(defun add-node-modules-path/get-invalid-executions (command-executions)
"Filters the provided COMMAND-EXECUTIONS for entries, whose execution result
denotes an invalid or non-existing directory"
(if (listp command-executions)
(seq-filter #'(lambda (elt) (and (plist-member elt 'directory-p) (not (plist-get elt 'directory-p)))) command-executions)))

(defun add-node-modules-path/warn-about-failed-executions (command-executions)
"Displays warnings about all failed COMMAND-EXECUTIONS."
(let ((failed (add-node-modules-path/get-invalid-executions command-executions)))
(dolist (elt failed)
(let ((cmd (plist-get elt 'command))
(path (plist-get elt 'result)))
(display-warning 'add-node-modules-path (format-message "Failed to run `%s':\n %s" cmd path))))))

(defun add-node-modules-path/add-to-list-multiple (list to-add)
"Adds multiple items to LIST."
(dolist (item to-add)
(add-to-list list item)))

;;;###autoload
(defun add-node-modules-path ()
"Run `npm bin` command and add the path to the `exec-path`.
If `npm` command fails, it does nothing."
(interactive)

(let* ((res (s-chomp (shell-command-to-string add-node-modules-path-command)))
(exists (file-exists-p res))
)
(cond
(exists
(make-local-variable 'exec-path)
(add-to-list 'exec-path res)
(when add-node-modules-path-debug
(message "Added to `exec-path`: %s" res))
)
(t
(when add-node-modules-path-debug
(message "Failed to run `%s':\n %s" add-node-modules-path-command res))
))
)
)

(let* ((commands (add-node-modules-path/trim-list-and-elements add-node-modules-path-command))
(executions (add-node-modules-path/exec-command-list commands))
(dirs (add-node-modules-path/get-valid-directories executions)))
(if (length> dirs 0)
(progn
(make-local-variable 'exec-path)
(add-node-modules-path/add-to-list-multiple 'exec-path (reverse dirs))
(if add-node-modules-path-debug
(message "Added to `exec-path`: %s" (s-join ", " dirs)))))
(if add-node-modules-path-debug
(add-node-modules-path/warn-about-failed-executions executions))))

(provide 'add-node-modules-path)

;;; add-node-modules-path.el ends here
87 changes: 87 additions & 0 deletions test/add-node-modules-path-test.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
;;
;;
;;

(ert-deftest add-node-modules-path/trim-list-and-elements-test ()
(should (equal (add-node-modules-path/trim-list-and-elements '("pnpm bin" "pnpm bin -w" "ls -al "))
'("pnpm bin" "pnpm bin -w" "ls -al")))
(should (equal (add-node-modules-path/trim-list-and-elements '(" pnpm bin -w")) '("pnpm bin -w")))
(should (equal (add-node-modules-path/trim-list-and-elements '(nil "" " ls -al " "" nil "" nil)) '("ls -al")))
(should (eq (add-node-modules-path/trim-list-and-elements "") nil))
(should (eq (add-node-modules-path/trim-list-and-elements " ") nil))
(should (eq (add-node-modules-path/trim-list-and-elements 1701) nil))
(should (eq (add-node-modules-path/trim-list-and-elements "a string") nil)))

(ert-deftest add-node-modules-path/exec-command-test ()
(should (eq (add-node-modules-path/exec-command nil) nil))
(should (eq (add-node-modules-path/exec-command "") nil))
(should (eq (add-node-modules-path/exec-command 3) nil))
(should (eq (add-node-modules-path/exec-command 'a-symbol) nil))
(if (equal system-type 'gnu/linux)
(let ((res (add-node-modules-path/exec-command "echo \"/usr/bin\"")))
(should (equal (plist-get res 'command) "echo \"/usr/bin\""))
(should (equal (plist-get res 'result) "/usr/bin"))
(should (equal (plist-get res 'directory-p) t))))
(if (equal system-type 'gnu/linux)
(let ((res (add-node-modules-path/exec-command "echo \"ls -al\"")))
(should (equal (plist-get res 'command) "echo \"ls -al\""))
(should (equal (plist-get res 'result) "ls -al"))
(should (equal (plist-get res 'directory-p) nil)))))

(ert-deftest add-node-modules-path/exec-command-list-test ()
(let ((should-produce-nil '(nil () 42 "a string" (1 2 3))))
(dolist (elt should-produce-nil)
(should (eq (add-node-modules-path/exec-command-list elt) nil)))))

(ert-deftest add-node-modules-path/get-valid-directories-test ()
(let ((should-produce-nil '(nil () 1 "str" (1 2 3) (('directory-p nil) ('directory-p nil))))
(test-data '(
(((directory-p t result "/usr/bin")) ("/usr/bin"))
(((directory-p t result "/home") (directory-p t result "/usr")) ("/home" "/usr"))
(((directory-p nil result "/not-a-valid-dir") (directory-p t result "/usr")) ("/usr"))
)))
(dolist (elt should-produce-nil)
(should (eq (add-node-modules-path/get-valid-directories elt) nil)))
(dolist (elt test-data)
(should (equal (add-node-modules-path/get-valid-directories (car elt)) (cadr elt))))))

(ert-deftest add-node-modules-path/get-invalid-executions-test ()
(let ((should-produce-nil '(nil () 1 "str" (('directory-p t) ('directory-p t))))
(test-data '(
(((directory-p nil result "/usr/bin")) ((directory-p nil result "/usr/bin")))
(((directory-p t result "/home") (directory-p nil result "/usr")) ((directory-p nil result "/usr")))
(((directory-p nil result "/xxx") (directory-p t result "/usr")) ((directory-p nil result "/xxx")))
)))
(dolist (elt should-produce-nil)
(should (eq (add-node-modules-path/get-invalid-executions elt) nil)))
(dolist (elt test-data)
(should (equal (add-node-modules-path/get-invalid-executions (car elt)) (cadr elt))))))

(defun add-node-modules-path/exec-add-node-modules-path-test (command additions)
;; remove any local binding of EXEC-PATH, if present
(kill-local-variable 'exec-path)
;; prepare environment
(make-local-variable 'add-node-modules-path-debug)
(setq add-node-modules-path-debug nil)
(make-local-variable 'add-node-modules-path-command)
(setq add-node-modules-path-command command)
;; run interactive command, which will create local binding of EXEC-PATH and add to it
(add-node-modules-path)
;; checks
(should (eq (local-variable-p 'exec-path) t))
(let ((i 0))
(dolist (elt additions)
(should (equal (nth i exec-path) elt))
(setq i (1+ i))))
;; env cleanup
(kill-local-variable 'add-node-modules-path-debug)
(kill-local-variable 'add-node-modules-path-command))

(ert-deftest add-node-modules-path-single-command-test ()
(add-node-modules-path/exec-add-node-modules-path-test '("echo \"/usr\"") '("/usr")))

(ert-deftest add-node-modules-path-multiple-commands-test ()
(add-node-modules-path/exec-add-node-modules-path-test '("echo \"/etc\"" "echo \"/var\"") '("/etc" "/var")))

(ert-deftest add-node-modules-path-multiple-commands-with-failures-test ()
(add-node-modules-path/exec-add-node-modules-path-test '("ls -al" "echo \"/var\"" "date" "echo \"/etc\"" "clear") '("/var" "/etc")))