I have a couple lines in my emacs configuration to help me discover and run commands at the project root. I usually use it for building and running a program.
Derived from project.el’s project-compile
(defvar jf/project-compile-commands nil
"alist of command names to command strings, to be executed by `compile`")
(defun jf/project-compile ()
"Simple alist interface to compile a project, at the project-root."
(interactive)
(unless jf/project-compile-commands
(error "jf/project-compile-commands is nil"))
(let* ((completion-extra-properties
'(:annotation-function (lambda (completion)
(format "\t%s" (cdr (assoc completion minibuffer-completion-table))))))
(key (completing-read "Select compile-command" jf/project-compile-commands))
(cmd (cdr (assoc key jf/project-compile-commands))))
;; stole from `project-compile`
(let ((default-directory (project-root (project-current t)))
(compilation-buffer-name-function
(or project-compilation-buffer-name-function
compilation-buffer-name-function)))
(compile cmd))))
At the root, I set the local project’s commands with a file:
;;; Directory Local Variables -*- no-byte-compile: t -*-
;;; For more information see (info "(emacs) Directory Variables")
((nil .
((jf/project-compile-commands .
(("build serve" . "npx quartz build --serve")
("version" . "npx quartz --version"))))))