diff --git a/CHANGELOG.md b/CHANGELOG.md index 78a0c71..755287c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,9 @@ - Add `--no-prefix` or `{:launchpad/options {:prefix false}}` to hide the start-of-line per-process prefix in the output +- Introduce `--execute` (or `{:launchpad/options {:execute false}}`) and + functionality that can launch the first configured `:exec-fn` (and + relative `:exec-args`). ## Fixed diff --git a/README.md b/README.md index 63eba40..2c1a786 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,34 @@ Most of the time you want to add extra steps either right before, or right after launchpad/start-process] launchpad/after-steps)}) ``` + +## Launch `:exec-fn` in aliases + +Launchpad can also call the first `:exec-fn` function (and relative `:exec-args`) it finds in its aggregated aliases. + +This behavior is enabled by adding `--execute` to the command line. + +> [!TIP] +> The `Aliases:` line in the summary shows the ordered list of processed aliases. Only the first `:exec-fn` is used. + +For example, you can configure an `:mcp` alias (from bhauman/clojure-mcp) within `deps.local.edn` like this: + +```clojure +:aliases {:mcp {:extra-deps {org.slf4j/slf4j-nop {:mvn/version "2.0.16"} + com.bhauman/clojure-mcp {:local/root "/path/to/clojure-mcp"}} + :exec-fn clojure-mcp.main/start-mcp-server + :exec-args {:port 7888}}} +... +``` + +and launchpad will be able to call `clojure-mcp.main/start-mcp-server` this way: + +``` +$ launchpad --execute mcp +``` + +Note that this is a completely new code path and neither start nrepl nor hooks up the hot reloading facilities. + ## Praise from Users "This looks like a much easier way to get a new project up and running in a consistent and predictable manner where there is less likelihood of steps being missed or being different enough to cause confusion or additional cognitive load when switching projects. As you point out, also great for getting a team all working in a consistent configuration." -- Tim Cross, @theophilusx1 diff --git a/src/lambdaisland/launchpad.clj b/src/lambdaisland/launchpad.clj index 65fc119..dea43a2 100644 --- a/src/lambdaisland/launchpad.clj +++ b/src/lambdaisland/launchpad.clj @@ -53,7 +53,8 @@ "--[no-]debug-repl" {:doc "Include gfredericks/debug-repl dependency and middleware"} "--[no-]go" {:doc "Call (user/go) on boot"} "--[no-]namespace-maps" {:doc "Disable *print-namespace-maps* through nREPL middleware"} - "--[no-]prefix" {:doc "Disable per-process prefix"}]) + "--[no-]prefix" {:doc "Disable per-process prefix"} + "--[no-]execute" {:doc "Parse and execute the first :exec-fn found in aliases"}]) (def library-versions (:deps (edn/read-string (slurp (io/resource "launchpad/deps.edn"))))) @@ -349,7 +350,7 @@ (catch Exception e (println "(user/go) failed" e)))))) -(defn run-nrepl-server [{:keys [nrepl-port nrepl-bind middleware] :as ctx}] +(defn nrepl-server-eval-forms [{:keys [nrepl-port nrepl-bind middleware] :as ctx}] (-> ctx (update :requires conj 'nrepl.cmdline) (update :eval-forms (fnil conj []) @@ -357,6 +358,20 @@ "--bind" ~(str nrepl-bind) "--middleware" ~(pr-str middleware))))) +(defn exec-fn-eval-forms [{:keys [aliases deps-edn] :as ctx}] + (let [{:keys [alias exec-fn exec-args]} + (->> aliases + ;; we use only the first alias where :exec-fn is defined + (some #(when (get-in deps-edn [:aliases % :exec-fn]) + (-> (get-in deps-edn [:aliases %]) + (assoc :alias %)))))] + (assert (and exec-fn (symbol? exec-fn)) + "Launchpad could not find any valid :exec-fn symbol, aborting...") + (info (format "Executing %s from the %s alias" exec-fn alias)) + (-> ctx + (update :requires conj (symbol (namespace exec-fn))) + (update :eval-forms (fnil conj []) `(~exec-fn ~exec-args))))) + (defn register-watch-handlers [ctx handlers] (update ctx :watch-handlers @@ -544,7 +559,7 @@ (System/exit exit))) ctx)))))) -(defn start-clojure-process [{:keys [aliases nrepl-port] :as ctx}] +(defn start-clojure-process [ctx] (let [args (clojure-cli-args ctx)] (apply debug (map shellquote args)) ((run-process {:cmd args @@ -566,7 +581,7 @@ inject-aliases-as-property include-watcher print-summary - run-nrepl-server]) + nrepl-server-eval-forms]) (def after-steps [wait-for-nrepl ;; stuff that happens after the server is up @@ -576,6 +591,14 @@ [start-clojure-process] after-steps)) +(def execute-steps [handle-cli-args + ;; extra java flags + disable-stack-trace-elision + inject-aliases-as-property + print-summary + exec-fn-eval-forms + start-clojure-process]) + (def ^:deprecated start-process start-clojure-process) (defn find-project-root [] @@ -609,17 +632,20 @@ end-steps pre-steps post-steps] :as ctx}] - (let [ctx (process-steps + (let [default-steps + (if-not (:execute ctx) + (concat + start-steps + before-steps + pre-steps + [start-clojure-process] + post-steps + after-steps + end-steps) + execute-steps) + ctx (process-steps (update ctx :aliases concat (map keyword (::cli/argv ctx))) - (or steps - (concat - start-steps - before-steps - pre-steps - [start-clojure-process] - post-steps - after-steps - end-steps))) + (or steps default-steps)) processes (:processes ctx)] (System/exit (apply min (for [p processes] (.waitFor p))))))