Skip to content

Introduce --execute option and functionality #15

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

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
54 changes: 40 additions & 14 deletions src/lambdaisland/launchpad.clj
Original file line number Diff line number Diff line change
Expand Up @@ -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")))))
Expand Down Expand Up @@ -349,14 +350,28 @@
(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 [])
`(nrepl.cmdline/-main "--port" ~(str nrepl-port)
"--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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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 []
Expand Down Expand Up @@ -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))))))
Expand Down