diff --git a/examples/clojure-luminus/.gitignore b/examples/clojure-luminus/.gitignore
new file mode 100644
index 000000000..73055a619
--- /dev/null
+++ b/examples/clojure-luminus/.gitignore
@@ -0,0 +1,21 @@
+/target
+/lib
+/classes
+/checkouts
+pom.xml
+dev-config.edn
+test-config.edn
+*.jar
+*.class
+/.lein-*
+profiles.clj
+/.env
+.nrepl-port
+/.clj-kondo/.cache/
+/.lsp/.cache/
+/.calva/output-window/
+
+/node_modules
+/log
+
+
diff --git a/examples/clojure-luminus/Capstanfile b/examples/clojure-luminus/Capstanfile
new file mode 100644
index 000000000..3b4920766
--- /dev/null
+++ b/examples/clojure-luminus/Capstanfile
@@ -0,0 +1,28 @@
+
+#
+# Name of the base image. Capstan will download this automatically from
+# Cloudius S3 repository.
+#
+#base: cloudius/osv
+base: cloudius/osv-openjdk8
+
+#
+# The command line passed to OSv to start up the application.
+#
+cmdline: /java.so -jar /clojure-luminus/app.jar
+
+#
+# The command to use to build the application.
+# You can use any build tool/command (make/rake/lein/boot) - this runs locally on your machine
+#
+# For Leiningen, you can use:
+#build: lein uberjar
+# For Boot, you can use:
+#build: boot build
+
+#
+# List of files that are included in the generated image.
+#
+files:
+ /clojure-luminus/app.jar: ./target/uberjar/clojure-luminus.jar
+
diff --git a/examples/clojure-luminus/README.md b/examples/clojure-luminus/README.md
new file mode 100644
index 000000000..1469f8f63
--- /dev/null
+++ b/examples/clojure-luminus/README.md
@@ -0,0 +1,21 @@
+# clojure-luminus
+
+generated using Luminus version "4.50"
+
+FIXME
+
+## Prerequisites
+
+You will need [Leiningen][1] 2.0 or above installed.
+
+[1]: https://github.com/technomancy/leiningen
+
+## Running
+
+To start a web server for the application, run:
+
+ lein run
+
+## License
+
+Copyright © 2024 FIXME
diff --git a/examples/clojure-luminus/env/dev/clj/clojure_luminus/dev_middleware.clj b/examples/clojure-luminus/env/dev/clj/clojure_luminus/dev_middleware.clj
new file mode 100644
index 000000000..a75100c98
--- /dev/null
+++ b/examples/clojure-luminus/env/dev/clj/clojure_luminus/dev_middleware.clj
@@ -0,0 +1,13 @@
+(ns clojure-luminus.dev-middleware
+ (:require
+ [clojure-luminus.config :refer [env]]
+ [ring.middleware.reload :refer [wrap-reload]]
+ [selmer.middleware :refer [wrap-error-page]]
+ [prone.middleware :refer [wrap-exceptions]]))
+
+(defn wrap-dev [handler]
+ (-> handler
+ wrap-reload
+ wrap-error-page
+ ;; disable prone middleware, it can not handle async
+ (cond-> (not (env :async?)) (wrap-exceptions {:app-namespaces ['clojure-luminus]}))))
diff --git a/examples/clojure-luminus/env/dev/clj/clojure_luminus/env.clj b/examples/clojure-luminus/env/dev/clj/clojure_luminus/env.clj
new file mode 100644
index 000000000..88e866d6f
--- /dev/null
+++ b/examples/clojure-luminus/env/dev/clj/clojure_luminus/env.clj
@@ -0,0 +1,15 @@
+(ns clojure-luminus.env
+ (:require
+ [selmer.parser :as parser]
+ [clojure.tools.logging :as log]
+ [clojure-luminus.dev-middleware :refer [wrap-dev]]))
+
+(def defaults
+ {:init
+ (fn []
+ (parser/cache-off!)
+ (log/info "\n-=[clojure-luminus started successfully using the development profile]=-"))
+ :stop
+ (fn []
+ (log/info "\n-=[clojure-luminus has shut down successfully]=-"))
+ :middleware wrap-dev})
diff --git a/examples/clojure-luminus/env/dev/clj/user.clj b/examples/clojure-luminus/env/dev/clj/user.clj
new file mode 100644
index 000000000..21aa85aa9
--- /dev/null
+++ b/examples/clojure-luminus/env/dev/clj/user.clj
@@ -0,0 +1,32 @@
+(ns user
+ "Userspace functions you can run by default in your local REPL."
+ (:require
+ [clojure-luminus.config :refer [env]]
+ [clojure.pprint]
+ [clojure.spec.alpha :as s]
+ [expound.alpha :as expound]
+ [mount.core :as mount]
+ [clojure-luminus.core :refer [start-app]]))
+
+(alter-var-root #'s/*explain-out* (constantly expound/printer))
+
+(add-tap (bound-fn* clojure.pprint/pprint))
+
+(defn start
+ "Starts application.
+ You'll usually want to run this on startup."
+ []
+ (mount/start-without #'clojure-luminus.core/repl-server))
+
+(defn stop
+ "Stops application."
+ []
+ (mount/stop-except #'clojure-luminus.core/repl-server))
+
+(defn restart
+ "Restarts application."
+ []
+ (stop)
+ (start))
+
+
diff --git a/examples/clojure-luminus/env/dev/resources/config.edn b/examples/clojure-luminus/env/dev/resources/config.edn
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/examples/clojure-luminus/env/dev/resources/config.edn
@@ -0,0 +1 @@
+{}
diff --git a/examples/clojure-luminus/env/dev/resources/logback.xml b/examples/clojure-luminus/env/dev/resources/logback.xml
new file mode 100644
index 000000000..57b2928d1
--- /dev/null
+++ b/examples/clojure-luminus/env/dev/resources/logback.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+ UTF-8
+ %date{ISO8601} [%thread] %-5level %logger{36} - %msg %n
+
+
+
+ log/clojure-luminus.log
+
+ log/clojure-luminus.%d{yyyy-MM-dd}.%i.log
+
+ 100MB
+
+
+ 30
+
+
+ UTF-8
+ %date{ISO8601} [%thread] %-5level %logger{36} - %msg %n
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/clojure-luminus/env/prod/clj/clojure_luminus/env.clj b/examples/clojure-luminus/env/prod/clj/clojure_luminus/env.clj
new file mode 100644
index 000000000..6a87c41e4
--- /dev/null
+++ b/examples/clojure-luminus/env/prod/clj/clojure_luminus/env.clj
@@ -0,0 +1,11 @@
+(ns clojure-luminus.env
+ (:require [clojure.tools.logging :as log]))
+
+(def defaults
+ {:init
+ (fn []
+ (log/info "\n-=[clojure-luminus started successfully]=-"))
+ :stop
+ (fn []
+ (log/info "\n-=[clojure-luminus has shut down successfully]=-"))
+ :middleware identity})
diff --git a/examples/clojure-luminus/env/prod/resources/config.edn b/examples/clojure-luminus/env/prod/resources/config.edn
new file mode 100644
index 000000000..e24ec219e
--- /dev/null
+++ b/examples/clojure-luminus/env/prod/resources/config.edn
@@ -0,0 +1,2 @@
+{:prod true
+ :port 3000}
diff --git a/examples/clojure-luminus/env/prod/resources/logback.xml b/examples/clojure-luminus/env/prod/resources/logback.xml
new file mode 100644
index 000000000..46505d92b
--- /dev/null
+++ b/examples/clojure-luminus/env/prod/resources/logback.xml
@@ -0,0 +1,34 @@
+
+
+
+
+ log/clojure-luminus.log
+
+ log/clojure-luminus.%d{yyyy-MM-dd}.%i.log
+
+ 100MB
+
+
+ 30
+
+
+ UTF-8
+ %date{ISO8601} [%thread] %-5level %logger{36} - %msg %n
+
+
+
+
+ %-5relative %-5level %logger{35} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/clojure-luminus/project.clj b/examples/clojure-luminus/project.clj
new file mode 100644
index 000000000..402f04e83
--- /dev/null
+++ b/examples/clojure-luminus/project.clj
@@ -0,0 +1,70 @@
+(defproject clojure-luminus "0.1.0-SNAPSHOT"
+
+ :description "FIXME: write description"
+ :url "http://example.com/FIXME"
+
+ :dependencies [[clojure.java-time "1.1.0"]
+ [cprop "0.1.19"]
+ [expound "0.9.0"]
+ [funcool/struct "1.4.0"]
+ [json-html "0.4.7"]
+ [luminus-immutant "0.2.5"]
+ [luminus-transit "0.1.5"]
+ [markdown-clj "1.11.3"]
+ [metosin/muuntaja "0.6.8"]
+ [metosin/reitit "0.5.18"]
+ [metosin/ring-http-response "0.9.3"]
+ [mount "0.1.16"]
+ [nrepl "1.0.0"]
+ [org.clojure/clojure "1.11.1"]
+ [org.clojure/tools.cli "1.0.214"]
+ [org.clojure/tools.logging "1.2.4"]
+ [org.webjars.npm/bulma "0.9.4"]
+ [org.webjars.npm/material-icons "1.10.8"]
+ [org.webjars/webjars-locator "0.45"]
+ [org.webjars/webjars-locator-jboss-vfs "0.1.0"]
+ [ring-webjars "0.2.0"]
+ [ring/ring-core "1.9.6"]
+ [ring/ring-defaults "0.3.4"]
+ [selmer "1.12.55"]]
+
+ :min-lein-version "2.0.0"
+
+ :source-paths ["src/clj"]
+ :test-paths ["test/clj"]
+ :resource-paths ["resources"]
+ :target-path "target/%s/"
+ :main ^:skip-aot clojure-luminus.core
+
+ :plugins [[lein-immutant "2.1.0"]]
+
+ :profiles
+ {:uberjar {:omit-source true
+ :aot :all
+ :uberjar-name "clojure-luminus.jar"
+ :source-paths ["env/prod/clj" ]
+ :resource-paths ["env/prod/resources"]}
+
+ :dev [:project/dev :profiles/dev]
+ :test [:project/dev :project/test :profiles/test]
+
+ :project/dev {:jvm-opts ["-Dconf=dev-config.edn" ]
+ :dependencies [[org.clojure/tools.namespace "1.3.0"]
+ [pjstadig/humane-test-output "0.11.0"]
+ [prone "2021-04-23"]
+ [ring/ring-devel "1.9.6"]
+ [ring/ring-mock "0.4.0"]]
+ :plugins [[com.jakemccrary/lein-test-refresh "0.24.1"]
+ [jonase/eastwood "1.2.4"]
+ [cider/cider-nrepl "0.26.0"]]
+
+ :source-paths ["env/dev/clj" ]
+ :resource-paths ["env/dev/resources"]
+ :repl-options {:init-ns user
+ :timeout 120000}
+ :injections [(require 'pjstadig.humane-test-output)
+ (pjstadig.humane-test-output/activate!)]}
+ :project/test {:jvm-opts ["-Dconf=test-config.edn" ]
+ :resource-paths ["env/test/resources"] }
+ :profiles/dev {}
+ :profiles/test {}})
diff --git a/examples/clojure-luminus/resources/docs/docs.md b/examples/clojure-luminus/resources/docs/docs.md
new file mode 100644
index 000000000..285104faa
--- /dev/null
+++ b/examples/clojure-luminus/resources/docs/docs.md
@@ -0,0 +1,96 @@
+
Congratulations, your Luminus site is ready!
+
+This page will help guide you through the first steps of building your site.
+
+Why are you seeing this page?
+
+The `home-routes` handler in the `clojure-luminus.routes.home` namespace
+defines the route that invokes the `home-page` function whenever an HTTP
+request is made to the `/` URI using the `GET` method.
+
+```
+(defn home-routes []
+ [""
+ {:middleware [middleware/wrap-csrf
+ middleware/wrap-formats]}
+ ["/" {:get home-page}]
+ ["/about" {:get about-page}]])
+```
+
+The `home-page` function will in turn call the `clojure-luminus.layout/render` function
+to render the HTML content:
+
+```
+(defn home-page [request]
+ (layout/render
+ request
+ "home.html" {:docs (-> "docs/docs.md" io/resource slurp)}))
+```
+
+The `render` function will render the `home.html` template found in the `resources/html`
+folder using a parameter map containing the `:docs` key. This key points to the
+contents of the `resources/docs/docs.md` file containing these instructions.
+
+The HTML templates are written using [Selmer](https://github.com/yogthos/Selmer) templating engine.
+
+```
+
+ {{docs|markdown}}
+
+```
+
+learn more about HTML templating »
+
+
+
+Organizing the routes
+
+The routes are aggregated and wrapped with middleware in the `clojure-luminus.handler` namespace:
+
+```
+(mount/defstate app-routes
+ :start
+ (ring/ring-handler
+ (ring/router
+ [(home-routes)])
+ (ring/routes
+ (ring/create-resource-handler
+ {:path "/"})
+ (wrap-content-type
+ (wrap-webjars (constantly nil)))
+ (ring/create-default-handler
+ {:not-found
+ (constantly (error-page {:status 404, :title "404 - Page not found"}))
+ :method-not-allowed
+ (constantly (error-page {:status 405, :title "405 - Not allowed"}))
+ :not-acceptable
+ (constantly (error-page {:status 406, :title "406 - Not acceptable"}))}))))
+```
+
+The `app` definition groups all the routes in the application into a single handler.
+A default route group is added to handle the `404` case.
+
+learn more about routing »
+
+The `home-routes` are wrapped with two middleware functions. The first enables CSRF protection.
+The second takes care of serializing and deserializing various encoding formats, such as JSON.
+
+Managing your middleware
+
+Request middleware functions are located under the `clojure-luminus.middleware` namespace.
+
+This namespace is reserved for any custom middleware for the application. Some default middleware is
+already defined here. The middleware is assembled in the `wrap-base` function.
+
+Middleware used for development is placed in the `clojure-luminus.dev-middleware` namespace found in
+the `env/dev/clj/` source path.
+
+learn more about middleware »
+
+
+
+
+Need some help?
+
+Visit the [official documentation](https://luminusweb.com/docs/guestbook) for examples
+on how to accomplish common tasks with Luminus. The `#luminus` channel on the [Clojurians Slack](http://clojurians.net/) and [Google Group](https://groups.google.com/forum/#!forum/luminusweb) are both great places to seek help and discuss projects with other users.
diff --git a/examples/clojure-luminus/resources/html/about.html b/examples/clojure-luminus/resources/html/about.html
new file mode 100644
index 000000000..aff3b2aea
--- /dev/null
+++ b/examples/clojure-luminus/resources/html/about.html
@@ -0,0 +1,4 @@
+{% extends "base.html" %}
+{% block content %}
+
+{% endblock %}
diff --git a/examples/clojure-luminus/resources/html/base.html b/examples/clojure-luminus/resources/html/base.html
new file mode 100644
index 000000000..dd562a123
--- /dev/null
+++ b/examples/clojure-luminus/resources/html/base.html
@@ -0,0 +1,58 @@
+
+
+
+
+
+ Welcome to clojure-luminus
+
+
+ {% style "/assets/bulma/css/bulma.min.css" %}
+ {% style "/assets/material-icons/css/material-icons.min.css" %}
+
+ {% style "/css/screen.css" %}
+
+
+
+
+
+
+
+
+
+ {% block content %}
+ {% endblock %}
+
+
+
+
+
+
+
+ {% block page-scripts %}
+ {% endblock %}
+
+
diff --git a/examples/clojure-luminus/resources/html/error.html b/examples/clojure-luminus/resources/html/error.html
new file mode 100644
index 000000000..fd31cc5da
--- /dev/null
+++ b/examples/clojure-luminus/resources/html/error.html
@@ -0,0 +1,49 @@
+
+
+
+ Something Bad Happened
+
+
+ {% style "/assets/bulma/css/bulma.min.css" %}
+
+
+
+
+
+
Error: {{status}}
+
+ {% if title %}
+ {{title}}
+ {% endif %}
+ {% if message %}
+ {{message}}
+ {% endif %}
+
+
+
+
diff --git a/examples/clojure-luminus/resources/html/home.html b/examples/clojure-luminus/resources/html/home.html
new file mode 100644
index 000000000..c09946dd2
--- /dev/null
+++ b/examples/clojure-luminus/resources/html/home.html
@@ -0,0 +1,6 @@
+{% extends "base.html" %}
+{% block content %}
+
+ {{docs|markdown}}
+
+{% endblock %}
diff --git a/examples/clojure-luminus/resources/public/css/screen.css b/examples/clojure-luminus/resources/public/css/screen.css
new file mode 100644
index 000000000..78ac8b8fb
--- /dev/null
+++ b/examples/clojure-luminus/resources/public/css/screen.css
@@ -0,0 +1,33 @@
+html,
+body {
+ font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
+}
+@font-face {
+ font-family: 'Material Icons';
+ font-style: normal;
+ font-weight: 400;
+ src: local('Material Icons'),
+ local('MaterialIcons-Regular'),
+ url(/assets/material-icons/iconfont/material-icons.woff2) format('woff2'),
+ url(/assets/material-icons/iconfont/material-icons.woff) format('woff');
+}
+.material-icons {
+ font-family: 'Material Icons';
+ font-weight: normal;
+ font-style: normal;
+ font-size: 24px; /* Preferred icon size */
+ display: inline-block;
+ line-height: 1;
+ text-transform: none;
+ letter-spacing: normal;
+ word-wrap: normal;
+ white-space: nowrap;
+ direction: ltr;
+ /* Support for all WebKit browsers. */
+ -webkit-font-smoothing: antialiased;
+ /* Support for Safari and Chrome. */
+ text-rendering: optimizeLegibility;
+ /* Support for Firefox. */
+ -moz-osx-font-smoothing: grayscale;
+}
+
diff --git a/examples/clojure-luminus/resources/public/favicon.ico b/examples/clojure-luminus/resources/public/favicon.ico
new file mode 100644
index 000000000..0e50cb2fb
Binary files /dev/null and b/examples/clojure-luminus/resources/public/favicon.ico differ
diff --git a/examples/clojure-luminus/resources/public/img/warning_clojure.png b/examples/clojure-luminus/resources/public/img/warning_clojure.png
new file mode 100644
index 000000000..78d59e9d4
Binary files /dev/null and b/examples/clojure-luminus/resources/public/img/warning_clojure.png differ
diff --git a/examples/clojure-luminus/src/clj/clojure_luminus/config.clj b/examples/clojure-luminus/src/clj/clojure_luminus/config.clj
new file mode 100644
index 000000000..b1ba67f67
--- /dev/null
+++ b/examples/clojure-luminus/src/clj/clojure_luminus/config.clj
@@ -0,0 +1,13 @@
+(ns clojure-luminus.config
+ (:require
+ [cprop.core :refer [load-config]]
+ [cprop.source :as source]
+ [mount.core :refer [args defstate]]))
+
+(defstate env
+ :start
+ (load-config
+ :merge
+ [(args)
+ (source/from-system-props)
+ (source/from-env)]))
diff --git a/examples/clojure-luminus/src/clj/clojure_luminus/core.clj b/examples/clojure-luminus/src/clj/clojure_luminus/core.clj
new file mode 100644
index 000000000..fac4df75a
--- /dev/null
+++ b/examples/clojure-luminus/src/clj/clojure_luminus/core.clj
@@ -0,0 +1,59 @@
+(ns clojure-luminus.core
+ (:require
+ [clojure-luminus.handler :as handler]
+ [clojure-luminus.nrepl :as nrepl]
+ [luminus.http-server :as http]
+ [clojure-luminus.config :refer [env]]
+ [clojure.tools.cli :refer [parse-opts]]
+ [clojure.tools.logging :as log]
+ [mount.core :as mount])
+ (:gen-class))
+
+;; log uncaught exceptions in threads
+(Thread/setDefaultUncaughtExceptionHandler
+ (reify Thread$UncaughtExceptionHandler
+ (uncaughtException [_ thread ex]
+ (log/error {:what :uncaught-exception
+ :exception ex
+ :where (str "Uncaught exception on" (.getName thread))}))))
+
+(def cli-options
+ [["-p" "--port PORT" "Port number"
+ :parse-fn #(Integer/parseInt %)]])
+
+(mount/defstate ^{:on-reload :noop} http-server
+ :start
+ (http/start
+ (-> env
+ (update :io-threads #(or % (* 2 (.availableProcessors (Runtime/getRuntime)))))
+ (assoc :handler (handler/app))
+ (update :port #(or (-> env :options :port) %))
+ (select-keys [:io-threads :handler :host :port :async?])))
+ :stop
+ (http/stop http-server))
+
+(mount/defstate ^{:on-reload :noop} repl-server
+ :start
+ (when (env :nrepl-port)
+ (nrepl/start {:bind (env :nrepl-bind)
+ :port (env :nrepl-port)}))
+ :stop
+ (when repl-server
+ (nrepl/stop repl-server)))
+
+
+(defn stop-app []
+ (doseq [component (:stopped (mount/stop))]
+ (log/info component "stopped"))
+ (shutdown-agents))
+
+(defn start-app [args]
+ (doseq [component (-> args
+ (parse-opts cli-options)
+ mount/start-with-args
+ :started)]
+ (log/info component "started"))
+ (.addShutdownHook (Runtime/getRuntime) (Thread. stop-app)))
+
+(defn -main [& args]
+ (start-app args))
diff --git a/examples/clojure-luminus/src/clj/clojure_luminus/handler.clj b/examples/clojure-luminus/src/clj/clojure_luminus/handler.clj
new file mode 100644
index 000000000..894a0f6cd
--- /dev/null
+++ b/examples/clojure-luminus/src/clj/clojure_luminus/handler.clj
@@ -0,0 +1,40 @@
+(ns clojure-luminus.handler
+ (:require
+ [clojure-luminus.middleware :as middleware]
+ [clojure-luminus.layout :refer [error-page]]
+ [clojure-luminus.routes.home :refer [home-routes]]
+ [reitit.ring :as ring]
+ [ring.middleware.content-type :refer [wrap-content-type]]
+ [ring.middleware.webjars :refer [wrap-webjars]]
+ [clojure-luminus.env :refer [defaults]]
+ [mount.core :as mount]))
+
+(mount/defstate init-app
+ :start ((or (:init defaults) (fn [])))
+ :stop ((or (:stop defaults) (fn []))))
+
+(defn- async-aware-default-handler
+ ([_] nil)
+ ([_ respond _] (respond nil)))
+
+
+(mount/defstate app-routes
+ :start
+ (ring/ring-handler
+ (ring/router
+ [(home-routes)])
+ (ring/routes
+ (ring/create-resource-handler
+ {:path "/"})
+ (wrap-content-type
+ (wrap-webjars async-aware-default-handler))
+ (ring/create-default-handler
+ {:not-found
+ (constantly (error-page {:status 404, :title "404 - Page not found"}))
+ :method-not-allowed
+ (constantly (error-page {:status 405, :title "405 - Not allowed"}))
+ :not-acceptable
+ (constantly (error-page {:status 406, :title "406 - Not acceptable"}))}))))
+
+(defn app []
+ (middleware/wrap-base #'app-routes))
diff --git a/examples/clojure-luminus/src/clj/clojure_luminus/layout.clj b/examples/clojure-luminus/src/clj/clojure_luminus/layout.clj
new file mode 100644
index 000000000..5c2f02c32
--- /dev/null
+++ b/examples/clojure-luminus/src/clj/clojure_luminus/layout.clj
@@ -0,0 +1,39 @@
+(ns clojure-luminus.layout
+ (:require
+ [clojure.java.io]
+ [selmer.parser :as parser]
+ [selmer.filters :as filters]
+ [markdown.core :refer [md-to-html-string]]
+ [ring.util.http-response :refer [content-type ok]]
+ [ring.util.anti-forgery :refer [anti-forgery-field]]
+ [ring.middleware.anti-forgery :refer [*anti-forgery-token*]]
+ [ring.util.response]))
+
+(parser/set-resource-path! (clojure.java.io/resource "html"))
+(parser/add-tag! :csrf-field (fn [_ _] (anti-forgery-field)))
+(filters/add-filter! :markdown (fn [content] [:safe (md-to-html-string content)]))
+
+(defn render
+ "renders the HTML template located relative to resources/html"
+ [request template & [params]]
+ (content-type
+ (ok
+ (parser/render-file
+ template
+ (assoc params
+ :page template
+ :csrf-token *anti-forgery-token*)))
+ "text/html; charset=utf-8"))
+
+(defn error-page
+ "error-details should be a map containing the following keys:
+ :status - error status
+ :title - error title (optional)
+ :message - detailed error message (optional)
+
+ returns a response map with the error page as the body
+ and the status specified by the status key"
+ [error-details]
+ {:status (:status error-details)
+ :headers {"Content-Type" "text/html; charset=utf-8"}
+ :body (parser/render-file "error.html" error-details)})
diff --git a/examples/clojure-luminus/src/clj/clojure_luminus/middleware.clj b/examples/clojure-luminus/src/clj/clojure_luminus/middleware.clj
new file mode 100644
index 000000000..490309b5c
--- /dev/null
+++ b/examples/clojure-luminus/src/clj/clojure_luminus/middleware.clj
@@ -0,0 +1,59 @@
+(ns clojure-luminus.middleware
+ (:require
+ [clojure-luminus.env :refer [defaults]]
+ [clojure.tools.logging :as log]
+ [clojure-luminus.layout :refer [error-page]]
+ [ring.middleware.anti-forgery :refer [wrap-anti-forgery]]
+ [clojure-luminus.middleware.formats :as formats]
+ [muuntaja.middleware :refer [wrap-format wrap-params]]
+ [clojure-luminus.config :refer [env]]
+ [ring.middleware.flash :refer [wrap-flash]]
+ [immutant.web.middleware :refer [wrap-session]]
+ [ring.middleware.defaults :refer [site-defaults wrap-defaults]])
+ )
+
+(defn wrap-internal-error [handler]
+ (let [error-result (fn [^Throwable t]
+ (log/error t (.getMessage t))
+ (error-page {:status 500
+ :title "Something very bad has happened!"
+ :message "We've dispatched a team of highly trained gnomes to take care of the problem."}))]
+ (fn wrap-internal-error-fn
+ ([req respond _]
+ (handler req respond #(respond (error-result %))))
+ ([req]
+ (try
+ (handler req)
+ (catch Throwable t
+ (error-result t)))))))
+
+(defn wrap-csrf [handler]
+ (wrap-anti-forgery
+ handler
+ {:error-response
+ (error-page
+ {:status 403
+ :title "Invalid anti-forgery token"})}))
+
+
+(defn wrap-formats [handler]
+ (let [wrapped (-> handler wrap-params (wrap-format formats/instance))]
+ (fn
+ ([request]
+ ;; disable wrap-formats for websockets
+ ;; since they're not compatible with this middleware
+ ((if (:websocket? request) handler wrapped) request))
+ ([request respond raise]
+ ((if (:websocket? request) handler wrapped) request respond raise)))))
+
+(defn wrap-base [handler]
+ (-> ((:middleware defaults) handler)
+ wrap-flash
+ (wrap-session {:cookie-attrs {:http-only true}})
+ (wrap-defaults
+ (-> site-defaults
+ (assoc-in [:security :anti-forgery] false)
+ (dissoc :session)))
+ wrap-internal-error))
+
+
diff --git a/examples/clojure-luminus/src/clj/clojure_luminus/middleware/formats.clj b/examples/clojure-luminus/src/clj/clojure_luminus/middleware/formats.clj
new file mode 100644
index 000000000..d4801d8f0
--- /dev/null
+++ b/examples/clojure-luminus/src/clj/clojure_luminus/middleware/formats.clj
@@ -0,0 +1,14 @@
+(ns clojure-luminus.middleware.formats
+ (:require
+ [luminus-transit.time :as time]
+ [muuntaja.core :as m]))
+
+(def instance
+ (m/create
+ (-> m/default-options
+ (update-in
+ [:formats "application/transit+json" :decoder-opts]
+ (partial merge time/time-deserialization-handlers))
+ (update-in
+ [:formats "application/transit+json" :encoder-opts]
+ (partial merge time/time-serialization-handlers)))))
diff --git a/examples/clojure-luminus/src/clj/clojure_luminus/nrepl.clj b/examples/clojure-luminus/src/clj/clojure_luminus/nrepl.clj
new file mode 100644
index 000000000..fa9d9aecc
--- /dev/null
+++ b/examples/clojure-luminus/src/clj/clojure_luminus/nrepl.clj
@@ -0,0 +1,27 @@
+(ns clojure-luminus.nrepl
+ (:require
+ [nrepl.server :as nrepl]
+ [clojure.tools.logging :as log]))
+
+(defn start
+ "Start a network repl for debugging on specified port followed by
+ an optional parameters map. The :bind, :transport-fn, :handler,
+ :ack-port and :greeting-fn will be forwarded to
+ nrepl.server/start-server as they are."
+ [{:keys [port bind transport-fn handler ack-port greeting-fn]}]
+ (try
+ (log/info "starting nREPL server on port" port)
+ (nrepl/start-server :port port
+ :bind bind
+ :transport-fn transport-fn
+ :handler handler
+ :ack-port ack-port
+ :greeting-fn greeting-fn)
+
+ (catch Throwable t
+ (log/error t "failed to start nREPL")
+ (throw t))))
+
+(defn stop [server]
+ (nrepl/stop-server server)
+ (log/info "nREPL server stopped"))
diff --git a/examples/clojure-luminus/src/clj/clojure_luminus/routes/home.clj b/examples/clojure-luminus/src/clj/clojure_luminus/routes/home.clj
new file mode 100644
index 000000000..97b475061
--- /dev/null
+++ b/examples/clojure-luminus/src/clj/clojure_luminus/routes/home.clj
@@ -0,0 +1,24 @@
+(ns clojure-luminus.routes.home
+ (:require
+ [clojure-luminus.layout :as layout]
+ [clojure.java.io :as io]
+ [clojure-luminus.middleware :as middleware]
+ [ring.util.response]
+ [ring.util.http-response :as response]))
+
+
+
+(defn home-page [request]
+ (layout/render request "home.html" {:docs (-> "docs/docs.md" io/resource slurp)}))
+
+(defn about-page [request]
+ (layout/render request "about.html"))
+
+(defn home-routes []
+ [ ""
+ {:middleware [middleware/wrap-csrf
+ middleware/wrap-formats
+ ]}
+ ["/" {:get home-page}]
+ ["/about" {:get about-page}]])
+
diff --git a/src/providers/clojure.rs b/src/providers/clojure.rs
index a107fe746..405ac2359 100644
--- a/src/providers/clojure.rs
+++ b/src/providers/clojure.rs
@@ -47,7 +47,9 @@ impl Provider for ClojureProvider {
let mut build = Phase::build(Some(format!("{build_cmd}; {move_file_cmd}")));
build.depends_on_phase("setup");
- let start = StartPhase::new("JAR_FILE=$(find ./target -name \"*standalone.jar\"); bash -c \"java $JAVA_OPTS -jar $JAR_FILE\"");
+ let start = StartPhase::new(
+ r#"JAR_FILE=$(find /app/target -name "*-standalone.jar" -o -name "*.jar" ! -name "*-SNAPSHOT.jar" | head -n 1) && bash -c "java $JAVA_OPTS -jar $JAR_FILE""#,
+ );
let plan = BuildPlan::new(&vec![setup, build], Some(start));
Ok(Some(plan))
diff --git a/tests/snapshots/generate_plan_tests__clojure.snap b/tests/snapshots/generate_plan_tests__clojure.snap
index 2b0a48ce9..4c28c92de 100644
--- a/tests/snapshots/generate_plan_tests__clojure.snap
+++ b/tests/snapshots/generate_plan_tests__clojure.snap
@@ -1,6 +1,7 @@
---
source: tests/generate_plan_tests.rs
expression: plan
+snapshot_kind: text
---
{
"providers": [],
@@ -30,6 +31,6 @@ expression: plan
}
},
"start": {
- "cmd": "JAR_FILE=$(find ./target -name \"*standalone.jar\"); bash -c \"java $JAVA_OPTS -jar $JAR_FILE\""
+ "cmd": "JAR_FILE=$(find /app/target -name \"*-standalone.jar\" -o -name \"*.jar\" ! -name \"*-SNAPSHOT.jar\" | head -n 1) && bash -c \"java $JAVA_OPTS -jar $JAR_FILE\""
}
}
diff --git a/tests/snapshots/generate_plan_tests__clojure_jdk11.snap b/tests/snapshots/generate_plan_tests__clojure_jdk11.snap
index 6e0cb3356..49a0ed9d3 100644
--- a/tests/snapshots/generate_plan_tests__clojure_jdk11.snap
+++ b/tests/snapshots/generate_plan_tests__clojure_jdk11.snap
@@ -1,6 +1,7 @@
---
source: tests/generate_plan_tests.rs
expression: plan
+snapshot_kind: text
---
{
"providers": [],
@@ -30,6 +31,6 @@ expression: plan
}
},
"start": {
- "cmd": "JAR_FILE=$(find ./target -name \"*standalone.jar\"); bash -c \"java $JAVA_OPTS -jar $JAR_FILE\""
+ "cmd": "JAR_FILE=$(find /app/target -name \"*-standalone.jar\" -o -name \"*.jar\" ! -name \"*-SNAPSHOT.jar\" | head -n 1) && bash -c \"java $JAVA_OPTS -jar $JAR_FILE\""
}
}
diff --git a/tests/snapshots/generate_plan_tests__clojure_jdk_latest.snap b/tests/snapshots/generate_plan_tests__clojure_jdk_latest.snap
index fb066b256..b78e42088 100644
--- a/tests/snapshots/generate_plan_tests__clojure_jdk_latest.snap
+++ b/tests/snapshots/generate_plan_tests__clojure_jdk_latest.snap
@@ -1,6 +1,7 @@
---
source: tests/generate_plan_tests.rs
expression: plan
+snapshot_kind: text
---
{
"providers": [],
@@ -30,6 +31,6 @@ expression: plan
}
},
"start": {
- "cmd": "JAR_FILE=$(find ./target -name \"*standalone.jar\"); bash -c \"java $JAVA_OPTS -jar $JAR_FILE\""
+ "cmd": "JAR_FILE=$(find /app/target -name \"*-standalone.jar\" -o -name \"*.jar\" ! -name \"*-SNAPSHOT.jar\" | head -n 1) && bash -c \"java $JAVA_OPTS -jar $JAR_FILE\""
}
}
diff --git a/tests/snapshots/generate_plan_tests__clojure_luminus.snap b/tests/snapshots/generate_plan_tests__clojure_luminus.snap
new file mode 100644
index 000000000..4c28c92de
--- /dev/null
+++ b/tests/snapshots/generate_plan_tests__clojure_luminus.snap
@@ -0,0 +1,36 @@
+---
+source: tests/generate_plan_tests.rs
+expression: plan
+snapshot_kind: text
+---
+{
+ "providers": [],
+ "buildImage": "[build_image]",
+ "variables": {
+ "NIXPACKS_METADATA": "clojure"
+ },
+ "phases": {
+ "build": {
+ "name": "build",
+ "dependsOn": [
+ "install",
+ "setup"
+ ],
+ "cmds": [
+ "lein uberjar; if [ -f /app/target/default+uberjar/*standalone.jar ]; then mv /app/target/default+uberjar/*standalone.jar /app/target/*standalone.jar; fi"
+ ]
+ },
+ "setup": {
+ "name": "setup",
+ "nixPkgs": [
+ "leiningen",
+ "jdk8"
+ ],
+ "nixOverlays": [],
+ "nixpkgsArchive": "[archive]"
+ }
+ },
+ "start": {
+ "cmd": "JAR_FILE=$(find /app/target -name \"*-standalone.jar\" -o -name \"*.jar\" ! -name \"*-SNAPSHOT.jar\" | head -n 1) && bash -c \"java $JAVA_OPTS -jar $JAR_FILE\""
+ }
+}
diff --git a/tests/snapshots/generate_plan_tests__clojure_ring_app.snap b/tests/snapshots/generate_plan_tests__clojure_ring_app.snap
index 327154681..128058fbe 100644
--- a/tests/snapshots/generate_plan_tests__clojure_ring_app.snap
+++ b/tests/snapshots/generate_plan_tests__clojure_ring_app.snap
@@ -1,6 +1,7 @@
---
source: tests/generate_plan_tests.rs
expression: plan
+snapshot_kind: text
---
{
"providers": [],
@@ -30,6 +31,6 @@ expression: plan
}
},
"start": {
- "cmd": "JAR_FILE=$(find ./target -name \"*standalone.jar\"); bash -c \"java $JAVA_OPTS -jar $JAR_FILE\""
+ "cmd": "JAR_FILE=$(find /app/target -name \"*-standalone.jar\" -o -name \"*.jar\" ! -name \"*-SNAPSHOT.jar\" | head -n 1) && bash -c \"java $JAVA_OPTS -jar $JAR_FILE\""
}
}
diff --git a/tests/snapshots/generate_plan_tests__clojure_tools_build.snap b/tests/snapshots/generate_plan_tests__clojure_tools_build.snap
index d92b81d03..dfc0a92cf 100644
--- a/tests/snapshots/generate_plan_tests__clojure_tools_build.snap
+++ b/tests/snapshots/generate_plan_tests__clojure_tools_build.snap
@@ -1,6 +1,7 @@
---
source: tests/generate_plan_tests.rs
expression: plan
+snapshot_kind: text
---
{
"providers": [],
@@ -30,6 +31,6 @@ expression: plan
}
},
"start": {
- "cmd": "JAR_FILE=$(find ./target -name \"*standalone.jar\"); bash -c \"java $JAVA_OPTS -jar $JAR_FILE\""
+ "cmd": "JAR_FILE=$(find /app/target -name \"*-standalone.jar\" -o -name \"*.jar\" ! -name \"*-SNAPSHOT.jar\" | head -n 1) && bash -c \"java $JAVA_OPTS -jar $JAR_FILE\""
}
}