From c45c54887e6d220c3b607ca0dc25ca18fa568acb Mon Sep 17 00:00:00 2001 From: Marcin Bilski Date: Thu, 1 Jan 2026 12:49:25 +0100 Subject: [PATCH 1/2] more tests --- .gitignore | 3 + libs/kit-generator/deps.edn | 39 ++++++--- libs/kit-generator/src/kit/api.clj | 7 +- .../src/kit/generator/modules/generator.clj | 58 +++++++------ .../test/kit_generator/core_test.clj | 64 +++++++++++++- .../test/kit_generator/generator_test.clj | 65 +++++++++----- libs/kit-generator/test/kit_generator/io.clj | 86 ++++++++++++++----- .../resources/modules/kit/html/config.edn | 30 ++++--- .../resources/modules/kit/meta/assets/app.css | 3 + .../modules/kit/meta/assets/styles.css | 6 ++ .../resources/modules/kit/meta/config.edn | 7 ++ .../test/resources/modules/kit/modules.edn | 15 ++-- 12 files changed, 272 insertions(+), 111 deletions(-) create mode 100644 libs/kit-generator/test/resources/modules/kit/meta/assets/app.css create mode 100644 libs/kit-generator/test/resources/modules/kit/meta/assets/styles.css create mode 100644 libs/kit-generator/test/resources/modules/kit/meta/config.edn diff --git a/.gitignore b/.gitignore index 92a04ffb..c24b1fca 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ pom.xml.asc .lsp .clj-kondo/.cache/ .aider* +/libs/kit-generator/test/resources/generated/ +/libs/kit-generator/test/resources/modules/install-log.edn +/.clj-kondo/ diff --git a/libs/kit-generator/deps.edn b/libs/kit-generator/deps.edn index a191e30e..4bbde7ea 100644 --- a/libs/kit-generator/deps.edn +++ b/libs/kit-generator/deps.edn @@ -1,13 +1,26 @@ -{:paths ["src"] - :deps {org.clojure/clojure {:mvn/version "1.11.1"} - hato/hato {:mvn/version "1.0.0"} - selmer/selmer {:mvn/version "1.12.59"} - clj-jgit/clj-jgit {:mvn/version "1.0.2"} - org.clojure/tools.logging {:mvn/version "1.2.4"} - borkdude/rewrite-edn {:mvn/version "0.4.6"} - enlive/enlive {:mvn/version "1.1.6"} - rewrite-clj/rewrite-clj {:mvn/version "1.1.46"} - mvxcvi/cljstyle {:mvn/version "0.15.0"} - cljfmt/cljfmt {:mvn/version "0.9.2"} - clj-fuzzy/clj-fuzzy {:mvn/version "0.4.1"} - clojure-deep-merge/clojure-deep-merge {:mvn/version "0.1.2"}}} +{:paths ["src"] + :deps {org.clojure/clojure {:mvn/version "1.11.1"} + hato/hato {:mvn/version "1.0.0"} + selmer/selmer {:mvn/version "1.12.59"} + clj-jgit/clj-jgit {:mvn/version "1.0.2"} + org.clojure/tools.logging {:mvn/version "1.2.4"} + borkdude/rewrite-edn {:mvn/version "0.4.6"} + enlive/enlive {:mvn/version "1.1.6"} + rewrite-clj/rewrite-clj {:mvn/version "1.1.46"} + mvxcvi/cljstyle {:mvn/version "0.15.0"} + cljfmt/cljfmt {:mvn/version "0.9.2"} + clj-fuzzy/clj-fuzzy {:mvn/version "0.4.1"} + clojure-deep-merge/clojure-deep-merge {:mvn/version "0.1.2"}} + :aliases {:cider + {:extra-deps {nrepl/nrepl {:mvn/version "1.5.1"} + cider/cider-nrepl {:mvn/version "0.58.0"}} + :main-opts ["-m" "nrepl.cmdline" "--middleware" "[cider.nrepl/cider-middleware]" "-i"]} + + :test + {:extra-paths ["test"] + :extra-deps {io.github.cognitect-labs/test-runner + {:git/tag "v0.5.1" :git/sha "dfb30dd"} + babashka/fs {:mvn/version "0.5.27"} + babashka/process {:mvn/version "0.6.23"}} + :main-opts ["-m" "cognitect.test-runner"] + :exec-fn cognitect.test-runner.api/test}}} diff --git a/libs/kit-generator/src/kit/api.clj b/libs/kit-generator/src/kit/api.clj index fe57bd85..6e11a42d 100644 --- a/libs/kit-generator/src/kit/api.clj +++ b/libs/kit-generator/src/kit/api.clj @@ -34,10 +34,10 @@ (defn install-module ([module-key] (install-module module-key {:feature-flag :default})) - ([module-key {:keys [feature-flag] :as opts}] - (let [{:keys [modules] :as ctx} (modules/load-modules (read-ctx))] + ([module-key {:keys [feature-flag kit-edn-path] :or {kit-edn-path "kit.edn"} :as opts}] + (let [{:keys [modules] :as ctx} (modules/load-modules (read-ctx kit-edn-path))] (if (modules/module-exists? ctx module-key) - (let [module-config (generator/read-module-config ctx modules module-key) + (let [{:keys [module-config]} (generator/read-module-config ctx modules module-key) deps (deps/resolve-dependencies module-config feature-flag)] (println module-key "requires following modules:" deps) (doseq [module-key deps] @@ -83,4 +83,3 @@ (defn snippet [id & args] (snippets/gen-snippet (snippets-db (read-ctx)) id args)) - diff --git a/libs/kit-generator/src/kit/generator/modules/generator.clj b/libs/kit-generator/src/kit/generator/modules/generator.clj index 65d31bbf..d0954ce6 100644 --- a/libs/kit-generator/src/kit/generator/modules/generator.clj +++ b/libs/kit-generator/src/kit/generator/modules/generator.clj @@ -1,13 +1,13 @@ (ns kit.generator.modules.generator (:require - [kit.generator.io :as io] - [kit.generator.modules :as modules] - [kit.generator.modules.injections :as ij] - [kit.generator.renderer :as renderer] - [clojure.java.io :as jio] - [clojure.pprint :refer [pprint]] - [deep.merge :as deep-merge] - [rewrite-clj.zip :as z]) + [kit.generator.io :as io] + [kit.generator.modules :as modules] + [kit.generator.modules.injections :as ij] + [kit.generator.renderer :as renderer] + [clojure.java.io :as jio] + [clojure.pprint :refer [pprint]] + [deep.merge :as deep-merge] + [rewrite-clj.zip :as z]) (:import java.io.File java.nio.file.Files)) @@ -27,7 +27,9 @@ (slurp asset-path) (Files/readAllBytes (.toPath (jio/file asset-path)))) (catch Exception e - (println "failed to read asset:" asset-path (.getMessage e))))) + (throw (ex-info (str "failed to read asset " asset-path) + {:asset-path asset-path} + e))))) (defn write-string [template-string target-path] (spit target-path template-string)) @@ -56,10 +58,10 @@ (and (sequential? asset) (contains? #{2 3} (count asset))) (let [[asset-path target-path force?] asset] (write-asset - (->> (read-asset (concat-path module-path asset-path)) - (renderer/render-asset ctx)) - (renderer/render-template ctx target-path) - force?)) + (->> (read-asset (concat-path module-path asset-path)) + (renderer/render-asset ctx)) + (renderer/render-template ctx target-path) + force?)) :else (println "unrecognized asset type:" asset)))) @@ -69,7 +71,7 @@ (defmethod handle-action :default [_ [id]] (println "undefined action:" id)) -(defn read-config [ctx module-path] +(defn- render-module-config [ctx module-path] (some->> (str module-path File/separator "config.edn") (slurp) (renderer/render-template ctx))) @@ -89,8 +91,10 @@ (defn read-module-config [ctx modules module-key] (let [module-path (get-in modules [:modules module-key :path]) ctx (assoc ctx :module-path module-path) - config-str (read-config ctx module-path)] - (io/str->edn config-str))) + config-str (render-module-config ctx module-path)] + {:config-str config-str + :module-config (io/str->edn config-str) + :module-path module-path})) (defn get-throw-on-not-found [m k] @@ -104,8 +108,8 @@ (do (println "applying features to config:" feature-requires) (apply deep-merge/concat-merge - (conj (mapv #(get-throw-on-not-found edn-config %) feature-requires) - config))) + (conj (mapv #(get-throw-on-not-found edn-config %) feature-requires) + config))) config)) (defn generate [{:keys [modules] :as ctx} module-key {:keys [feature-flag] @@ -115,14 +119,12 @@ (if (= :success (module-log module-key)) (println "module" module-key "is already installed!") (try - (let [module-path (get-in modules [:modules module-key :path]) - ctx (assoc ctx :module-path module-path) - config-str (read-config ctx module-path) - edn-config (io/str->edn config-str) - zip-config (z/of-string config-str) - config (get edn-config feature-flag)] + (let [{:keys [module-path module-config config-str]} (read-module-config ctx modules module-key) + ctx (assoc ctx :module-path module-path) + config (get module-config feature-flag) + zip-config (z/of-string config-str)] (cond - (nil? edn-config) + (nil? module-config) (do (println "module" module-key "not found, available modules:") (pprint (modules/list-modules ctx))) @@ -130,11 +132,11 @@ (nil? config) (do (println "feature" feature-flag "not found for module" module-key ", available features:") - (pprint (keys edn-config))) + (pprint (keys module-config))) :else - (let [{:keys [actions success-message require-restart?]} (apply-features edn-config config) - ctx (assoc ctx :zip-config zip-config)] + (let [{:keys [actions success-message require-restart?]} (apply-features module-config config) + ctx (assoc ctx :zip-config zip-config)] (doseq [action actions] (handle-action ctx action)) (write-modules-log modules-root (assoc module-log module-key :success)) diff --git a/libs/kit-generator/test/kit_generator/core_test.clj b/libs/kit-generator/test/kit_generator/core_test.clj index 6285ab41..af991b59 100644 --- a/libs/kit-generator/test/kit_generator/core_test.clj +++ b/libs/kit-generator/test/kit_generator/core_test.clj @@ -1,4 +1,64 @@ (ns kit-generator.core-test (:require - [kit-generator.generator-test] - [kit-generator.injections])) + [clojure.string :as str] + [clojure.test :refer [deftest is testing]] + [kit-generator.generator-test] + [kit-generator.injections] + [kit-generator.io :as io] + [kit.api :as kit])) + +(def source-folder "test/resources") +;; It must be this path because module asset paths are relative to the current +;; working directory and modules under test/resources/modules/ write to +;; test/resources/generated/** +(def project-root "test/resources/generated") + +(defn module-installed? [module-key] + (when-let [install-log (io/read-edn-safe (str project-root "/modules/install-log.edn"))] + (= :success (get install-log module-key)))) + +(defn prepare-project + "Sets up a test project in `project-root` and returns the path to the kit.edn file. + The project has already synced modules and kit.edn but is otherwise empty." + [] + (let [project-modules (str project-root "/modules/") + ctx {:ns-name "myapp" + :sanitized "myapp" + :name "myapp" + :project-root project-root + :modules {:root project-modules + :repositories {:root (str project-root "/modules") + :url "https://github.com/foo/bar/never/used" + :tag "master" + :name "kit"}}} + kit-edn-path (str project-root "/kit.edn")] + (io/delete-folder project-root) + (io/clone-folder (str source-folder "/modules/") + project-modules + {:filter #(not (str/ends-with? % "install-log.edn"))}) + (io/write-edn ctx kit-edn-path) + kit-edn-path)) + +(deftest test-install-module + (testing "installing a module" + (let [kit-edn-path (prepare-project)] + (is (not (module-installed? :meta))) + (is (= :done (kit/install-module :meta {:kit-edn-path kit-edn-path}))) + (is (module-installed? :meta)) + (is (empty? (io/folder-mismatches project-root + {"resources/public/css/styles.css" [] + "resources/public/css/app.css" [] + "kit.edn" []} + {:filter #(not (str/starts-with? % "modules/"))})))))) + +(deftest test-install-module-with-feature-flag + (testing "installing a module with a feature flag" + (let [kit-edn-path (prepare-project)] + (is (not (module-installed? :meta))) + (is (= :done (kit/install-module :meta {:feature-flag :extras + :kit-edn-path kit-edn-path}))) + (is (module-installed? :meta)) + (is (empty? (io/folder-mismatches project-root + {"resources/public/css/styles.css" [] + "kit.edn" []} + {:filter #(not (str/starts-with? % "modules/"))})))))) diff --git a/libs/kit-generator/test/kit_generator/generator_test.clj b/libs/kit-generator/test/kit_generator/generator_test.clj index e7000e55..1b398585 100644 --- a/libs/kit-generator/test/kit_generator/generator_test.clj +++ b/libs/kit-generator/test/kit_generator/generator_test.clj @@ -1,8 +1,8 @@ (ns kit-generator.generator-test (:require - [clojure.java.io :as io] + [clojure.java.io :as jio] [clojure.test :refer [use-fixtures deftest testing is]] - [kit-generator.io :refer [delete-folder folder-mismatches write-file read-edn-safe]] + [kit-generator.io :refer [delete-folder folder-mismatches clone-file read-edn-safe]] [kit.generator.modules :as m] [kit.generator.modules.generator :as g])) @@ -14,16 +14,30 @@ (when-let [install-log (read-edn-safe (str source-folder "/modules/install-log.edn"))] (= :success (get install-log module-key)))) -(use-fixtures :once +(def seeded-files + "Files that are directly copied. They will always be present in the target folder, + even if no injections are made or no assets are copied over." + [["sample-system.edn" "resources/system.edn"] + ["core.clj" "src/myapp/core.clj"]]) + +(defn target-folder-mismatches + "Compare target folder against expected files. By default it ignores seeded files + but you can override it in expected-files map." + [expected-files] + (let [ignored-files (->> seeded-files + (map second) + (map (fn [path] [path []])) + (into {}))] + (folder-mismatches target-folder (merge ignored-files expected-files)))) + +(use-fixtures :each (fn [f] - (let [files ["/sample-system.edn" "/resources/system.edn" - "/core.clj" "/src/myapp/core.clj"] - install-log (io/file "test/resources/modules/install-log.edn")] + (let [install-log (jio/file "test/resources/modules/install-log.edn")] (when (.exists install-log) (.delete install-log)) (delete-folder target-folder) - (doseq [[source target] (partition 2 files)] - (write-file (str source-folder source) (str target-folder target))) + (doseq [[source target] seeded-files] + (clone-file (str source-folder "/" source) (str target-folder "/" target))) (f)))) (deftest test-edn-injection @@ -34,20 +48,27 @@ (is (module-installed? :html)) (let [expected-files {"resources/system.edn" [#"^\{:system/env" #":templating/selmer \{}}$"] + "src/myapp/core.clj" [#"^\(ns myapp.core"] "resources/public/home.html" [#"^$"] "resources/public/img/luminus.png" [] - "src/myapp/core.clj" [#"^\(ns myapp.core"] "src/clj/myapp/web/routes/pages.clj" [#"^\(ns resources\.modules"]}] - (is (empty? (folder-mismatches target-folder expected-files)))))) - -(comment - (slurp (str target-folder "/src/clj/myapp/web/routes/pages.clj")) - - (let [files ["/sample-system.edn" "/resources/system.edn" - "/core.clj" "/src/myapp/core.clj"] - install-log (io/file "test/resources/modules/install-log.edn")] - (when (.exists install-log) - (.delete install-log)) - (delete-folder target-folder) - (doseq [[source target] (partition 2 files)] - (write-file (str source-folder source) (str target-folder target))))) + (is (empty? (target-folder-mismatches expected-files)))))) + +(deftest test-edn-injection-with-feature-flag + (testing "testing injection with a fetaure flag" + (is (not (module-installed? :html))) + (let [ctx (m/load-modules ctx)] + (g/generate ctx :html {:feature-flag :empty})) + (is (module-installed? :html)) + (let [expected-files {}] + (is (empty? (target-folder-mismatches expected-files)))))) + +(deftest test-edn-injection-with-feature-requires + (testing "testing injection with a fetaure flag" + (is (not (module-installed? :meta))) + (let [ctx (m/load-modules ctx)] + (g/generate ctx :meta {})) + (is (module-installed? :meta)) + (let [expected-files {"resources/public/css/styles.css" [#".body"] + "resources/public/css/app.css" [#".app"]}] + (is (empty? (target-folder-mismatches expected-files)))))) diff --git a/libs/kit-generator/test/kit_generator/io.clj b/libs/kit-generator/test/kit_generator/io.clj index 1a8f78d6..54f4d519 100644 --- a/libs/kit-generator/test/kit_generator/io.clj +++ b/libs/kit-generator/test/kit_generator/io.clj @@ -1,7 +1,8 @@ (ns kit-generator.io (:require [clojure.edn :as edn] - [clojure.java.io :as io] + [clojure.java.io :as jio] + [kit.generator.io :as io] [clojure.set :as set] [clojure.test :as t])) @@ -11,13 +12,13 @@ (when (.isDirectory f) (doseq [f2 (.listFiles f)] (func f2))) - (io/delete-file f)))] - (func (io/file file-name)))) + (jio/delete-file f)))] + (func (jio/file file-name)))) (defn ls-R "Walks dir recursively and returns a map of relative file paths to their contents." [dir] - (let [root (io/file dir) + (let [root (jio/file dir) root-path (.toPath root)] (->> (file-seq root) (filter #(.isFile %)) @@ -26,6 +27,31 @@ (assoc acc rel-path (slurp f)))) {})))) +(defn relative-path + "Returns path as relative to base-path" + [path base-path] + (.toString (.relativize (.toURI (jio/file base-path)) + (.toURI (jio/file path))))) + +(defn clone-file + "Copy file from `src` to `target`, creating parent directories as needed." + [src tgt] + (jio/make-parents tgt) + (let [source-file (jio/file src) + target-file (jio/file tgt)] + (clojure.java.io/copy source-file target-file))) + +(defn clone-folder + "Erase `target` then copy all files from `src` to `target` recursively." + [src target & {:keys [filter] :or {filter (constantly true)}}] + (delete-folder target) + (let [files (file-seq (clojure.java.io/file src))] + (doseq [f files] + (let [target-file (clojure.java.io/file target + (relative-path f src))] + (when (and (.isFile f) (filter (.getPath f))) + (clone-file f target-file)))))) + (defn- file-mismatches "Checks content against expectation. Returns set of errors or nil if matches." [content expectation] @@ -52,25 +78,29 @@ Map of path -> set of errors, or empty map if all match." {:test (fn [] (let [dir "test/resources/snippets"] - (t/are [expectations mismatches] (= mismatches (folder-mismatches dir expectations)) - (ls-R dir) {} + (t/are [expectations opts mismatches] (= mismatches (folder-mismatches dir expectations opts)) + (ls-R dir) {} {} (-> (ls-R dir) (dissoc "kit/routing.md") (assoc "foo/bar.txt" - "X")) {"kit/routing.md" #{"unexpected file"} - "foo/bar.txt" #{"file missing"}} + "X")) {} {"kit/routing.md" #{"unexpected file"} + "foo/bar.txt" #{"file missing"}} (-> (ls-R dir) (assoc "kit/routing.md" - #{#"reitit"})) {} + #{#"reitit"})) {} {} (-> (ls-R dir) (assoc "kit/routing.md" - [#"NOMATCH"])) {"kit/routing.md" #{"regex not found: NOMATCH"}})))} - [dir expectations] - (let [actual (ls-R dir) + [#"NOMATCH"])) {} {"kit/routing.md" #{"regex not found: NOMATCH"}} + (-> (ls-R dir) + (assoc "foo.txt" + [#"NOMATCH"])) {:filter + #(not= "foo.txt" %)} {})))} + [dir expectations & {filter-fn :filter :or {filter-fn (constantly true)}}] + (let [actual (ls-R dir) expected-paths (set (keys expectations)) - actual-paths (set (keys actual)) - missing (set/difference expected-paths actual-paths) - extra (set/difference actual-paths expected-paths) + actual-paths (set (keys actual)) + missing (set/difference expected-paths actual-paths) + extra (set/difference actual-paths expected-paths) content-errors (reduce (fn [acc [path expectation]] (if-let [errors (some-> (get actual path) (file-mismatches expectation))] @@ -78,17 +108,28 @@ acc)) {} expectations)] - (cond-> content-errors - (seq missing) (merge (zipmap missing (repeat #{"file missing"}))) - (seq extra) (merge (zipmap extra (repeat #{"unexpected file"})))))) + (->> (cond-> content-errors + (seq missing) (merge (zipmap missing (repeat #{"file missing"}))) + (seq extra) (merge (zipmap extra (repeat #{"unexpected file"})))) + (filter (comp filter-fn first)) + (into {})))) -(defn write-file [source target] - (io/make-parents target) - (->> (slurp source) +(defn write-file + "Writes contents to target file, creating parent directories as needed." + [contents target] + (jio/make-parents target) + (->> contents (spit target))) +(defn write-edn + "Writes EDN contents to target file, creating parent directories as needed." + [data target] + (-> data + (io/edn->str) + (write-file target))) + (defn read-safe [path] - (when (.exists (io/file path)) + (when (.exists (jio/file path)) (slurp path))) (defn read-edn-safe [path] @@ -96,4 +137,5 @@ (edn/read-string content))) (comment + (t/run-all-tests) (t/run-tests 'kit-generator.io)) diff --git a/libs/kit-generator/test/resources/modules/kit/html/config.edn b/libs/kit-generator/test/resources/modules/kit/html/config.edn index 2b0d9e8a..92ee206f 100644 --- a/libs/kit-generator/test/resources/modules/kit/html/config.edn +++ b/libs/kit-generator/test/resources/modules/kit/html/config.edn @@ -4,17 +4,19 @@ :actions {:assets [["assets/home.html" "test/resources/generated/resources/public/home.html"] ["assets/img/luminus.png" "test/resources/generated/resources/public/img/luminus.png"] ["assets/pages.clj" "test/resources/generated/src/clj/<>/web/routes/pages.clj"]] - :injections - [{:type :edn - :path "test/resources/generated/resources/system.edn" - :target [] - :action :merge - :value {:reitit.routes/pages - {:base-path "/" - :env #ig/ref :system/env} - :templating/selmer - {}}} - {:type :clj - :path "test/resources/generated/src/<>/core.clj" - :action :append-requires - :value ["[<>.web.routes.pages]"]}]}}} + :injections [{:type :edn + :path "test/resources/generated/resources/system.edn" + :target [] + :action :merge + :value {:reitit.routes/pages + {:base-path "/" + :env #ig/ref :system/env} + :templating/selmer + {}}} + {:type :clj + :path "test/resources/generated/src/<>/core.clj" + :action :append-requires + :value ["[<>.web.routes.pages]"]}]}} + :empty {:success-message "Empty module installed successfully!" + :actions {:assets [] + :injections []}}} diff --git a/libs/kit-generator/test/resources/modules/kit/meta/assets/app.css b/libs/kit-generator/test/resources/modules/kit/meta/assets/app.css new file mode 100644 index 00000000..ac745f02 --- /dev/null +++ b/libs/kit-generator/test/resources/modules/kit/meta/assets/app.css @@ -0,0 +1,3 @@ +.app { + margin: 10px; +} diff --git a/libs/kit-generator/test/resources/modules/kit/meta/assets/styles.css b/libs/kit-generator/test/resources/modules/kit/meta/assets/styles.css new file mode 100644 index 00000000..460a85b8 --- /dev/null +++ b/libs/kit-generator/test/resources/modules/kit/meta/assets/styles.css @@ -0,0 +1,6 @@ +.body { + font-family: Arial, sans-serif; + margin: 0; + padding: 0; + background-color: #f4f4f4; +} diff --git a/libs/kit-generator/test/resources/modules/kit/meta/config.edn b/libs/kit-generator/test/resources/modules/kit/meta/config.edn new file mode 100644 index 00000000..7d998584 --- /dev/null +++ b/libs/kit-generator/test/resources/modules/kit/meta/config.edn @@ -0,0 +1,7 @@ +{:default + {:actions {:assets [["assets/app.css" "test/resources/generated/resources/public/css/app.css"]]} + :feature-requires [:extras]} + + :extras + {:actions {:assets [["assets/styles.css" "test/resources/generated/resources/public/css/styles.css"]]} + :feature-requires [:extras]}} diff --git a/libs/kit-generator/test/resources/modules/kit/modules.edn b/libs/kit-generator/test/resources/modules/kit/modules.edn index 825383bd..08e0c786 100644 --- a/libs/kit-generator/test/resources/modules/kit/modules.edn +++ b/libs/kit-generator/test/resources/modules/kit/modules.edn @@ -1,8 +1,11 @@ {:module-root "kit" :modules - {:html - {:path "html" - :doc "adds support for HTML templating using Selmer"} - :kit/cljs - {:path "cljs" - :doc "adds support for cljs using shadow-cljs"}}} + {:html + {:path "html" + :doc "adds support for HTML templating using Selmer"} + :kit/cljs + {:path "cljs" + :doc "adds support for cljs using shadow-cljs"} + :meta + {:path "meta" + :doc "meta-module adding support for html and cljs"}}} From cd16b84c04d306ed6d84f8223d5f183b45859012 Mon Sep 17 00:00:00 2001 From: Marcin Bilski Date: Fri, 2 Jan 2026 10:44:40 +0100 Subject: [PATCH 2/2] options for dependencies --- libs/kit-generator/src/kit/api.clj | 136 +++++++++++++----- .../src/kit/generator/modules.clj | 21 +-- .../src/kit/generator/modules/generator.clj | 17 ++- .../test/kit_generator/core_test.clj | 64 ++++++--- .../test/kit_generator/generator_test.clj | 6 +- .../resources/modules/kit/cljs/config.edn | 10 +- .../resources/modules/kit/db/assets/db.clj | 1 + .../modules/kit/db/assets/migrations/001.clj | 3 + .../modules/kit/db/assets/postgres.clj | 1 + .../test/resources/modules/kit/db/config.edn | 9 ++ .../resources/modules/kit/meta/config.edn | 8 +- .../modules/kit/migratus/assets/migratus.clj | 1 + .../resources/modules/kit/migratus/config.edn | 2 + .../test/resources/modules/kit/modules.edn | 8 +- 14 files changed, 194 insertions(+), 93 deletions(-) create mode 100644 libs/kit-generator/test/resources/modules/kit/db/assets/db.clj create mode 100644 libs/kit-generator/test/resources/modules/kit/db/assets/migrations/001.clj create mode 100644 libs/kit-generator/test/resources/modules/kit/db/assets/postgres.clj create mode 100644 libs/kit-generator/test/resources/modules/kit/db/config.edn create mode 100644 libs/kit-generator/test/resources/modules/kit/migratus/assets/migratus.clj create mode 100644 libs/kit-generator/test/resources/modules/kit/migratus/config.edn diff --git a/libs/kit-generator/src/kit/api.clj b/libs/kit-generator/src/kit/api.clj index 6e11a42d..a11289a6 100644 --- a/libs/kit-generator/src/kit/api.clj +++ b/libs/kit-generator/src/kit/api.clj @@ -1,53 +1,108 @@ (ns kit.api (:require - [clojure.string :as string] - [kit.generator.modules.generator :as generator] - [kit.generator.modules.dependencies :as deps] + [clojure.string :as str] + [kit.generator.io :as io] [kit.generator.modules :as modules] + [kit.generator.modules.dependencies :as deps] + [kit.generator.modules.generator :as generator] [kit.generator.snippets :as snippets] - [kit.generator.io :as io])) + [clojure.test :as t])) ;; TODO: Add docstrings -(defn read-ctx - ([] (read-ctx nil)) - ([path] - (-> (or path "kit.edn") - (slurp) - (io/str->edn)))) +(defn- read-ctx + [path] + (assert (not (str/blank? path))) + (-> path + (slurp) + (io/str->edn))) + +(defn- log-install-dependency [module-key feature-flag deps] + (print "Installing module" module-key) + + (when-let [extras (not-empty (cond-> [] + (not= :default feature-flag) (conj (str " - feature flag:" feature-flag)) + (seq deps) (conj (str " - requires:" (str/join ",")))))] + (print (str "(" (str/join "; " extras) ")")))) + +(defn- log-missing-module [module-key] + (println "ERROR: no module found with name:" module-key)) + +(defn- install-dependency + "Installs a module and its dependencies recursively. Asumes ctx has loaded :modules. + Note that `opts` have a different schema than the one passed to `install-module`, + the latter being preserved for backwards compatibility. Here `opts` is a map of + module-key to module-specific options. + + For example, let's say `:html` is the main module. It would still be on the same level + as `:auth`, its dependency: -(defn sync-modules [] - (modules/sync-modules! (read-ctx)) + ```clojure + {:html {:feature-flag :default} + :auth {:feature-flag :oauth}} + ``` + + See flat-module-options for more details." + [{:keys [modules] :as ctx} module-key opts] + (if (modules/module-exists? ctx module-key) + (let [{:keys [module-config]} (generator/read-module-config ctx modules module-key) + {:keys [feature-flag] :or {feature-flag :default} :as module-opts} (get opts module-key {}) + deps (deps/resolve-dependencies module-config feature-flag)] + (log-install-dependency module-key feature-flag deps) + (doseq [module-key deps] + (install-dependency ctx module-key opts)) + (generator/generate ctx module-key module-opts)) + (log-missing-module module-key)) :done) -(defn list-modules [] - (let [ctx (modules/load-modules (read-ctx))] - (modules/list-modules ctx)) +(defn- flat-module-options + "Converts options map passed to install-module into a flat map + of module-key to module-specific options." + {:test (fn [] + (t/are [opts module-key output] (flat-module-options opts module-key) + {} :meta {} + {:feature-flag :extras} :meta {:meta {:feature-flag :extras}} + {:feature-flag :extras + :kit/cljs {:feature-flag :advanced}} :meta {:meta {:feature-flag :extras} + :kit/cljs {:feature-flag :advanced}}))} + [opts module-key] + (let [supported-module-options [:feature-flag]] + (as-> opts $ + {module-key (select-keys $ supported-module-options)} + (merge opts $) + (apply dissoc $ supported-module-options)))) + +(defn sync-modules + "Downloads modules for the current project." + [] + (modules/sync-modules! (read-ctx "kit-edn")) :done) -(declare install-module) -(defn install-dependency [module-key] - (if (vector? module-key) - (apply install-module module-key) - (install-module module-key))) +(defn list-modules + "List modules available for the current project." + [] + (let [ctx (modules/load-modules (read-ctx "kit.edn"))] + (modules/list-modules ctx)) + :done) (defn install-module + "Installs a kit module into the current project or the project specified by a + path to kit.edn file. + + > NOTE: When adding new options, update flat-module-options." ([module-key] (install-module module-key {:feature-flag :default})) - ([module-key {:keys [feature-flag kit-edn-path] :or {kit-edn-path "kit.edn"} :as opts}] - (let [{:keys [modules] :as ctx} (modules/load-modules (read-ctx kit-edn-path))] - (if (modules/module-exists? ctx module-key) - (let [{:keys [module-config]} (generator/read-module-config ctx modules module-key) - deps (deps/resolve-dependencies module-config feature-flag)] - (println module-key "requires following modules:" deps) - (doseq [module-key deps] - (install-dependency module-key)) - (generator/generate ctx module-key opts)) - (println "no module found with name:" module-key)) - :done))) - -(defn list-installed-modules [] - (doseq [[id status] (-> (read-ctx) + ([module-key opts] + (install-module module-key "kit.edn" opts)) + ([module-key kit-edn-path opts] + (let [ctx (modules/load-modules (read-ctx kit-edn-path))] + (install-dependency ctx module-key (flat-module-options opts module-key))))) + +(defn list-installed-modules + "Lists installed modules and modules that failed to install, for the current + project." + [] + (doseq [[id status] (-> (read-ctx "kit.edn") :modules :root (generator/read-modules-log))] @@ -64,22 +119,25 @@ @db)))) (defn sync-snippets [] - (let [ctx (read-ctx)] + (let [ctx (read-ctx "kit.edn")] (snippets/sync-snippets! ctx) (snippets-db ctx true) :done)) (defn find-snippets [query] - (snippets/print-snippets (snippets-db (read-ctx)) query) + (snippets/print-snippets (snippets-db (read-ctx "kit.edn")) query) :done) (defn find-snippet-ids [query] - (println (string/join ", " (map :id (snippets/match-snippets (snippets-db (read-ctx)) query)))) + (println (str/join ", " (map :id (snippets/match-snippets (snippets-db (read-ctx "kit.edn")) query)))) :done) (defn list-snippets [] - (println (string/join "\n" (keys (snippets-db (read-ctx))))) + (println (str/join "\n" (keys (snippets-db (read-ctx "kit.edn"))))) :done) (defn snippet [id & args] - (snippets/gen-snippet (snippets-db (read-ctx)) id args)) + (snippets/gen-snippet (snippets-db (read-ctx "kit.edn")) id args)) + +(comment + (t/run-tests 'kit.api)) diff --git a/libs/kit-generator/src/kit/generator/modules.clj b/libs/kit-generator/src/kit/generator/modules.clj index 4c46d6ae..ab6f60c5 100644 --- a/libs/kit-generator/src/kit/generator/modules.clj +++ b/libs/kit-generator/src/kit/generator/modules.clj @@ -1,9 +1,10 @@ (ns kit.generator.modules (:require - [clojure.java.io :as jio] - [kit.generator.git :as git] - [kit.generator.io :as io]) - (:import java.io.File)) + [clojure.java.io :as jio] + [deep.merge :as deep-merge] + [kit.generator.git :as git]) + (:import + java.io.File)) (defn sync-modules! "Clones or pulls modules from git repositories. @@ -18,18 +19,18 @@ [{:keys [modules]}] (doseq [{:keys [name url] :as repository} (-> modules :repositories)] (git/sync-repository! - (:root modules) - repository))) + (:root modules) + repository))) (defn set-module-path [module-config base-path] (update module-config :path #(str base-path File/separator %))) (defn set-module-paths [root {:keys [module-root modules]}] (reduce - (fn [modules [id config]] - (assoc modules id (set-module-path config (str root File/separator module-root)))) - {} - modules)) + (fn [modules [id config]] + (assoc modules id (set-module-path config (str root File/separator module-root)))) + {} + modules)) (defn load-modules [{:keys [modules] :as ctx}] (let [root (:root modules)] diff --git a/libs/kit-generator/src/kit/generator/modules/generator.clj b/libs/kit-generator/src/kit/generator/modules/generator.clj index d0954ce6..87459ec7 100644 --- a/libs/kit-generator/src/kit/generator/modules/generator.clj +++ b/libs/kit-generator/src/kit/generator/modules/generator.clj @@ -40,7 +40,7 @@ (defn write-asset [asset path force?] (jio/make-parents path) (if (and (.exists (jio/file path)) (not force?)) - (println "asset already exists:" path) + (println "WARNING: Asset already exists:" path) ((if (string? asset) write-string write-binary) asset path))) (defmulti handle-action (fn [_ [id]] id)) @@ -63,13 +63,13 @@ (renderer/render-template ctx target-path) force?)) :else - (println "unrecognized asset type:" asset)))) + (println "ERROR: Unrecognized asset type:" asset)))) (defmethod handle-action :injections [ctx [_ injections]] (ij/inject-data ctx injections)) (defmethod handle-action :default [_ [id]] - (println "undefined action:" id)) + (println "ERROR: Undefined action:" id)) (defn- render-module-config [ctx module-path] (some->> (str module-path File/separator "config.edn") @@ -106,7 +106,6 @@ [edn-config {:keys [feature-requires] :as config}] (if (some? feature-requires) (do - (println "applying features to config:" feature-requires) (apply deep-merge/concat-merge (conj (mapv #(get-throw-on-not-found edn-config %) feature-requires) config))) @@ -117,7 +116,7 @@ (let [modules-root (:root modules) module-log (read-modules-log modules-root)] (if (= :success (module-log module-key)) - (println "module" module-key "is already installed!") + (println "WARNING: Module" module-key "is already installed!") (try (let [{:keys [module-path module-config config-str]} (read-module-config ctx modules module-key) ctx (assoc ctx :module-path module-path) @@ -126,12 +125,12 @@ (cond (nil? module-config) (do - (println "module" module-key "not found, available modules:") + (println "ERROR: Module" module-key "not found, available modules:") (pprint (modules/list-modules ctx))) (nil? config) (do - (println "feature" feature-flag "not found for module" module-key ", available features:") + (println "ERROR: Feature" feature-flag "not found for module" module-key ", available features:") (pprint (keys module-config))) :else @@ -141,11 +140,11 @@ (handle-action ctx action)) (write-modules-log modules-root (assoc module-log module-key :success)) (println (or success-message - (str module-key " installed successfully!"))) + (str "Module " module-key " installed successfully!"))) (when require-restart? (println "restart required!"))))) (catch Exception e - (println "failed to install module" module-key) + (println "ERROR: Failed to install module" module-key) (write-modules-log modules-root (assoc module-log module-key :error)) (.printStackTrace e)))))) diff --git a/libs/kit-generator/test/kit_generator/core_test.clj b/libs/kit-generator/test/kit_generator/core_test.clj index af991b59..94cc16c4 100644 --- a/libs/kit-generator/test/kit_generator/core_test.clj +++ b/libs/kit-generator/test/kit_generator/core_test.clj @@ -1,7 +1,7 @@ (ns kit-generator.core-test (:require [clojure.string :as str] - [clojure.test :refer [deftest is testing]] + [clojure.test :refer [deftest is testing are]] [kit-generator.generator-test] [kit-generator.injections] [kit-generator.io :as io] @@ -39,26 +39,44 @@ (io/write-edn ctx kit-edn-path) kit-edn-path)) -(deftest test-install-module - (testing "installing a module" - (let [kit-edn-path (prepare-project)] - (is (not (module-installed? :meta))) - (is (= :done (kit/install-module :meta {:kit-edn-path kit-edn-path}))) - (is (module-installed? :meta)) - (is (empty? (io/folder-mismatches project-root - {"resources/public/css/styles.css" [] - "resources/public/css/app.css" [] - "kit.edn" []} - {:filter #(not (str/starts-with? % "modules/"))})))))) +(defn test-install-module* + [module-key opts expected-files] + (let [kit-edn-path (prepare-project)] + (is (not (module-installed? module-key))) + (is (= :done (kit/install-module module-key kit-edn-path opts))) + (is (module-installed? module-key)) + (is (empty? (io/folder-mismatches project-root + expected-files + {:filter #(not (str/starts-with? % "modules/"))}))))) -(deftest test-install-module-with-feature-flag - (testing "installing a module with a feature flag" - (let [kit-edn-path (prepare-project)] - (is (not (module-installed? :meta))) - (is (= :done (kit/install-module :meta {:feature-flag :extras - :kit-edn-path kit-edn-path}))) - (is (module-installed? :meta)) - (is (empty? (io/folder-mismatches project-root - {"resources/public/css/styles.css" [] - "kit.edn" []} - {:filter #(not (str/starts-with? % "modules/"))})))))) +(deftest test-install-meta-module + (are [module-key opts expected-files] (test-install-module* module-key opts expected-files) + :meta {} {"resources/public/css/app.css" [] + "kit.edn" []} + :meta {:feature-flag :extras} {"resources/public/css/styles.css" [] + "src/clj/myapp/db.clj" [] + "kit.edn" []} + :meta {:feature-flag :full} {"resources/public/css/app.css" [] + "resources/public/css/styles.css" [] + "src/clj/myapp/db.clj" [] + "kit.edn" []} + :meta {:feature-flag :extras + :db {:feature-flag :postgres}} {"resources/public/css/styles.css" [] + "src/clj/myapp/db.clj" [] + "src/clj/myapp/db/postgres.clj" [] + "kit.edn" []} + :meta {:feature-flag :extras + :db {:feature-flag :migrations}} {"resources/public/css/styles.css" [] + "src/clj/myapp/db.clj" [] + "src/clj/myapp/db/postgres.clj" [] + "src/clj/myapp/db/migratus.clj" [] + "src/clj/myapp/db/migrations/001.clj" [] + "kit.edn" []} + +;; + )) + +;; TODO: Should feature-requires be transient? If so, add tests for that. + +(comment + (clojure.test/run-tests 'kit-generator.core-test)) diff --git a/libs/kit-generator/test/kit_generator/generator_test.clj b/libs/kit-generator/test/kit_generator/generator_test.clj index 1b398585..0f1eea94 100644 --- a/libs/kit-generator/test/kit_generator/generator_test.clj +++ b/libs/kit-generator/test/kit_generator/generator_test.clj @@ -55,7 +55,7 @@ (is (empty? (target-folder-mismatches expected-files)))))) (deftest test-edn-injection-with-feature-flag - (testing "testing injection with a fetaure flag" + (testing "testing injection with a feature flag" (is (not (module-installed? :html))) (let [ctx (m/load-modules ctx)] (g/generate ctx :html {:feature-flag :empty})) @@ -64,10 +64,10 @@ (is (empty? (target-folder-mismatches expected-files)))))) (deftest test-edn-injection-with-feature-requires - (testing "testing injection with a fetaure flag" + (testing "testing injection with a feature flag + feature-requires" (is (not (module-installed? :meta))) (let [ctx (m/load-modules ctx)] - (g/generate ctx :meta {})) + (g/generate ctx :meta {:feature-flag :full})) (is (module-installed? :meta)) (let [expected-files {"resources/public/css/styles.css" [#".body"] "resources/public/css/app.css" [#".app"]}] diff --git a/libs/kit-generator/test/resources/modules/kit/cljs/config.edn b/libs/kit-generator/test/resources/modules/kit/cljs/config.edn index 5e4a0b6f..6c6b39a8 100644 --- a/libs/kit-generator/test/resources/modules/kit/cljs/config.edn +++ b/libs/kit-generator/test/resources/modules/kit/cljs/config.edn @@ -15,11 +15,11 @@ :action :append :target [:body] :value [:script {:src "/js/app.js"}]} - {:type :edn - :path "deps.edn" - :target [:paths] - :action :append - :value "target/classes/cljsbuild"} + {:type :edn + :path "deps.edn" + :target [:paths] + :action :append + :value "target/classes/cljsbuild"} {:type :clj :path "build.clj" :action :append-requires diff --git a/libs/kit-generator/test/resources/modules/kit/db/assets/db.clj b/libs/kit-generator/test/resources/modules/kit/db/assets/db.clj new file mode 100644 index 00000000..3407a310 --- /dev/null +++ b/libs/kit-generator/test/resources/modules/kit/db/assets/db.clj @@ -0,0 +1 @@ +(ns resources.modules.kit.db.assets.db) diff --git a/libs/kit-generator/test/resources/modules/kit/db/assets/migrations/001.clj b/libs/kit-generator/test/resources/modules/kit/db/assets/migrations/001.clj new file mode 100644 index 00000000..c560a2b1 --- /dev/null +++ b/libs/kit-generator/test/resources/modules/kit/db/assets/migrations/001.clj @@ -0,0 +1,3 @@ +(ns resources.modules.kit.db.assets.migrations.001) + +;; First migration diff --git a/libs/kit-generator/test/resources/modules/kit/db/assets/postgres.clj b/libs/kit-generator/test/resources/modules/kit/db/assets/postgres.clj new file mode 100644 index 00000000..6c18595c --- /dev/null +++ b/libs/kit-generator/test/resources/modules/kit/db/assets/postgres.clj @@ -0,0 +1 @@ +(ns resources.modules.kit.db.assets.postgres) diff --git a/libs/kit-generator/test/resources/modules/kit/db/config.edn b/libs/kit-generator/test/resources/modules/kit/db/config.edn new file mode 100644 index 00000000..be387f90 --- /dev/null +++ b/libs/kit-generator/test/resources/modules/kit/db/config.edn @@ -0,0 +1,9 @@ +{:default {:actions + {:assets [["assets/db.clj" "test/resources/generated/src/clj/<>/db.clj"]]}} + :postgres {:actions + {:assets [["assets/postgres.clj" "test/resources/generated/src/clj/<>/db/postgres.clj"]]} + :feature-requires [:default]} + :migrations {:actions + {:assets [["assets/migrations/001.clj" "test/resources/generated/src/clj/<>/db/migrations/001.clj"]]} + :requires [:migratus] + :feature-requires [:postgres :default]}} diff --git a/libs/kit-generator/test/resources/modules/kit/meta/config.edn b/libs/kit-generator/test/resources/modules/kit/meta/config.edn index 7d998584..9c8be667 100644 --- a/libs/kit-generator/test/resources/modules/kit/meta/config.edn +++ b/libs/kit-generator/test/resources/modules/kit/meta/config.edn @@ -1,7 +1,9 @@ {:default - {:actions {:assets [["assets/app.css" "test/resources/generated/resources/public/css/app.css"]]} - :feature-requires [:extras]} + {:actions {:assets [["assets/app.css" "test/resources/generated/resources/public/css/app.css"]]}} :extras {:actions {:assets [["assets/styles.css" "test/resources/generated/resources/public/css/styles.css"]]} - :feature-requires [:extras]}} + :requires [:db]} + + :full + {:feature-requires [:default :extras]}} diff --git a/libs/kit-generator/test/resources/modules/kit/migratus/assets/migratus.clj b/libs/kit-generator/test/resources/modules/kit/migratus/assets/migratus.clj new file mode 100644 index 00000000..c85076c8 --- /dev/null +++ b/libs/kit-generator/test/resources/modules/kit/migratus/assets/migratus.clj @@ -0,0 +1 @@ +(ns resources.modules.kit.migratus.assets.migratus) diff --git a/libs/kit-generator/test/resources/modules/kit/migratus/config.edn b/libs/kit-generator/test/resources/modules/kit/migratus/config.edn new file mode 100644 index 00000000..013691fd --- /dev/null +++ b/libs/kit-generator/test/resources/modules/kit/migratus/config.edn @@ -0,0 +1,2 @@ +{:default + {:actions {:assets [["assets/migratus.clj" "test/resources/generated/src/clj/<>/db/migratus.clj"]]}}} diff --git a/libs/kit-generator/test/resources/modules/kit/modules.edn b/libs/kit-generator/test/resources/modules/kit/modules.edn index 08e0c786..e449c721 100644 --- a/libs/kit-generator/test/resources/modules/kit/modules.edn +++ b/libs/kit-generator/test/resources/modules/kit/modules.edn @@ -8,4 +8,10 @@ :doc "adds support for cljs using shadow-cljs"} :meta {:path "meta" - :doc "meta-module adding support for html and cljs"}}} + :doc "meta-module adding support for html and cljs"} + :db + {:path "db" + :doc "adds support for database access"} + :migratus + {:path "migratus" + :doc "adds support for migrations"}}}