diff --git a/.sealog/changes/1-5-0.edn b/.sealog/changes/1-5-0.edn new file mode 100644 index 0000000..12bc30d --- /dev/null +++ b/.sealog/changes/1-5-0.edn @@ -0,0 +1,11 @@ +{:version {:major 1 + :minor 5 + :patch 0} + :version-type :semver3 + :changes {:added ["`check` command to [verify the current configuration, changelog entries, and project version.](https://github.com/Wall-Brew-Co/lein-sealog#check-sealog-configuration)"] + :changed [] + :deprecated [] + :removed [] + :fixed [] + :security []} + :timestamp "2024-05-03T00:46:45.488497Z"} diff --git a/CHANGELOG.md b/CHANGELOG.md index bef099e..7a050a4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## Table of Contents +* [1.5.0 - 2024-05-03](#150---2024-05-03) * [1.4.0 - 2024-05-01](#140---2024-05-01) * [1.3.0 - 2024-03-13](#130---2024-03-13) * [1.2.1 - 2024-03-10](#121---2024-03-10) @@ -15,6 +16,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) * [1.0.1 - 2022-10-23](#101---2022-10-23) * [1.0.0 - 2022-10-23](#100---2022-10-23) +## 1.5.0 - 2024-05-03 + +* Added + * `check` command to [verify the current configuration, changelog entries, and project version.](https://github.com/Wall-Brew-Co/lein-sealog#check-sealog-configuration) + ## 1.4.0 - 2024-05-01 * Added diff --git a/README.md b/README.md index 908d797..1cbd497 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,7 @@ From the root of your project directory, you may invoke the following commands: * `render` - To compile all changelog entry files into a markdown Changelog * `insert` - To update the most recent changelog entry file with new notes * `version` - To print the current project versions from Leiningen and sealog +* `check` - To validate Sealog's configuration, changelog entry files, the changelog, and the project version * `help` - To view the help text of sealog or any command Most commands will accept several options, which can be configured by the command line arguments passed in or by a configuration file located at `.sealog/config.edn`. @@ -228,6 +229,55 @@ Reading from .sealog/changes/1-3-0.edn 1.3.0 ``` +### Check Sealog Configuration + +Sealog relies on convention to produce sane changelogs. +To validate that Sealog is configured appropriately, you may run a series of introspective checks. + +```sh +$ lein sealog check +Reading from .sealog/config.edn +Sealog configuration is valid. +Reading from .sealog/config.edn +Changelog entry directory contains at least one file. +Reading from .sealog/changes/1-0-1.edn +Reading from .sealog/changes/1-2-0.edn +Reading from .sealog/changes/1-3-0.edn +All changelog entries are valid. +Reading from .sealog/changes/1-0-1.edn +Reading from .sealog/changes/1-2-0.edn +Reading from .sealog/changes/1-3-0.edn +All changelog entries use the same version type. +Reading from .sealog/changes/1-0-1.edn +Reading from .sealog/changes/1-2-0.edn +Reading from .sealog/changes/1-3-0.edn +All changelog entries have distinct versions. +Reading from .sealog/changes/1-0-1.edn +Reading from .sealog/changes/1-2-0.edn +Reading from .sealog/changes/1-3-0.edn +Project version matches latest changelog entry. +Reading from .sealog/changes/1-0-1.edn +Reading from .sealog/changes/1-2-0.edn +Reading from .sealog/changes/1-3-0.edn +Rendered changelog contains all changelog entries. +All checks passed +``` + +Checks: + +* If a configuration file exists, it must be valid. + * If no configuration file exists, sealog will assume its default configuration +* The changelog directory must contain at least one file. +* The changelog directory must contain only valid changelog entries. + * If invalid files are found, the issues will be printed to STDERR with `clojure.spec/explain-str` +* All changelog entry files must use the same version type. + * If mismatched version types are found, all version types will be printed to STDERR +* All changelog entry files must have a distinct version. + * If duplicate versions are found, all versions will be printed to STDERR +* The project.clj version must match the latest changelog entry. + * If mismatched versions are found, both will be printed to STDERR +* The rendered changelog must contain all changelog entries. + ### Render Changelog Finally, Sealog can aggregate and render your change entries into a clean, Markdown format. diff --git a/pom.xml b/pom.xml index 7081938..50f1714 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ com.wallbrew lein-sealog jar - 1.4.0 + 1.5.0 lein-sealog A Leiningen plugin for managing your changelog. https://github.com/Wall-Brew-Co/common-beer-format @@ -20,7 +20,7 @@ https://github.com/Wall-Brew-Co/lein-sealog scm:git:git://github.com/Wall-Brew-Co/lein-sealog.git scm:git:ssh://git@github.com/Wall-Brew-Co/lein-sealog.git - 35a7a6065f42d677e414db4f75a379a1422e82f2 + 0d6214fa1dbfa0929d716b29eff63aee1082fb2a src diff --git a/project.clj b/project.clj index bb78c4b..bc5ca2e 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject com.wallbrew/lein-sealog "1.4.0" +(defproject com.wallbrew/lein-sealog "1.5.0" :description "A Leiningen plugin for managing your changelog." :url "https://github.com/Wall-Brew-Co/common-beer-format" :license {:name "MIT" diff --git a/src/leiningen/sealog.clj b/src/leiningen/sealog.clj index 740940b..78cb9ec 100644 --- a/src/leiningen/sealog.clj +++ b/src/leiningen/sealog.clj @@ -11,7 +11,7 @@ (defn unknown-command "Formats an error message for an unknown command." [command] - (str "Unknown command: " command "\nAvailable commands: init, bump, render, insert, version, and help")) + (str "Unknown command: " command "\nAvailable commands: init, bump, render, insert, version, check, and help")) (defn top-level-help @@ -27,6 +27,7 @@ (main/info " render - Render the changelog to the target file.") (main/info " insert - Insert a note of a specified change type into the most current change file.") (main/info " version - Display information about the current version.") + (main/info " check - Check the current configuration, changelog etries, and the current project version.") (main/info " help - Display this help message.") (main/info "") (main/info "Run `lein sealog help ` for more information on a specific command.")) @@ -96,6 +97,24 @@ (main/info " If no value is provided, the process will load all sources to compare.")) +(defn check-help + "Display help text for the check command" + [] + (main/info "Usage: lein sealog check") + (main/info "") + (main/info "Check the current configuration, changelog etries, and the current project version.") + (main/info "Will exit abnormally if any issues are found.") + (main/info "") + (main/info "Checks:") + (main/info " - If a configuration file exists, it must be valid.") + (main/info " - The changelog directory must contain at least one file.") + (main/info " - The changelog directory must contain only valid changelog entries.") + (main/info " - All changelog entry files must use the same version type.") + (main/info " - All changelog entry files must have a distinct version.") + (main/info " - The project version must match the latest changelog entry.") + (main/info " - The rendered changelog must contain all changelog entries.")) + + (defn help "Display help text for a specific command." [options] @@ -107,6 +126,7 @@ "render" (render-help) "insert" (insert-help) "version" (version-help) + "check" (check-help) "help" (main/info "Run `lein sealog help ` for more information on a specific command.") (main/info (unknown-command command))))) @@ -125,5 +145,6 @@ "render" (sealog/render-changelog options) "insert" (sealog/insert-entry options) "version" (sealog/display-version project options) + "check" (sealog/check project options) "help" (help options) (main/warn "Unknown command: %s" command)))) diff --git a/src/leiningen/sealog/api.clj b/src/leiningen/sealog/api.clj index 45a9945..719fa16 100644 --- a/src/leiningen/sealog/api.clj +++ b/src/leiningen/sealog/api.clj @@ -78,3 +78,25 @@ (do (main/info (str "project.clj: " leiningen-version)) (main/info (str "sealog: " sealog-version))))) (System/exit 1)))) + + +(defn check + "Check the current configuration, changelog entries, and the current project version." + [project _opts] + (if (impl/valid-configuration?) + (let [configuration (impl/load-config!) + some-changelog-entries? (impl/changelog-entry-directory-is-not-empty? configuration) + all-changelog-entries-valid? (impl/changelog-directory-only-contains-valid-files? configuration) + same-version-type? (impl/all-changelog-entries-use-same-version-type? configuration) + distinct-versions? (impl/all-changelog-entries-have-distinct-versions? configuration)] + (if (and some-changelog-entries? + all-changelog-entries-valid? + same-version-type? + distinct-versions?) + (let [versions-match? (impl/project-version-matches-latest-changelog-entry? project configuration) + changelog-rendered? (impl/rendered-changelog-contains-all-changelog-entries? configuration)] + (if (and versions-match? changelog-rendered?) + (main/info "All checks passed") + (System/exit 1))) + (System/exit 1))) + (System/exit 1))) diff --git a/src/leiningen/sealog/impl.clj b/src/leiningen/sealog/impl.clj index 9deb472..1e362b9 100644 --- a/src/leiningen/sealog/impl.clj +++ b/src/leiningen/sealog/impl.clj @@ -203,3 +203,108 @@ [_opts] (io/make-parents (io/file config/config-file)) (write-file! config/config-file config/default-config)) + + +(defn valid-configuration? + "Returns true if the configuration is valid." + [] + (let [configuration-contents (if (file-exists? config/config-file) + (edn/read-string (read-file! config/config-file)) + config/default-config) + contents (st/coerce ::config/config configuration-contents st/string-transformer) + valid? (spec/valid? ::config/config contents)] + (if valid? + (do (main/info "Sealog configuration is valid.") + true) + (do (main/warn (format "Invalid configuration file contents: %s" (spec/explain-str ::config/config contents))) + false)))) + + +(defn changelog-entry-directory-is-not-empty? + "Returns true if the changelog entry directory is not empty." + [{:keys [changelog-entry-directory] :as _configuration}] + (let [has-files? (boolean (seq (list-all-files changelog-entry-directory)))] + (if has-files? + (do (main/info "Changelog entry directory contains at least one file.") + true) + (do (main/warn "Changelog entry directory is empty.") + false)))) + + +(defn changelog-directory-only-contains-valid-files? + "Returns true if the changelog entry directory only contains valid files." + [{:keys [changelog-entry-directory] :as _configuration}] + (let [files (list-all-files changelog-entry-directory) + valid? (fn [filepath] + (let [file-content (edn/read-string (read-file! filepath)) + contents (st/coerce ::changelog/entry file-content st/string-transformer)] + (if (spec/valid? ::changelog/entry contents) + true + (do (main/warn (format "Invalid changelog file contents at path `%s`: %s" + filepath + (spec/explain-str ::changelog/entry contents))) + false)))) + all-valid? (every? valid? files)] + (if all-valid? + (do (main/info "All changelog entries are valid.") + true) + false))) + + +(defn all-changelog-entries-use-same-version-type? + "Returns true if all changelog entries use the same version type." + [{:keys [changelog-entry-directory] :as _configuration}] + (let [files (list-all-files changelog-entry-directory) + reducer (fn [acc filepath] + (let [content (edn/read-string (read-file! filepath))] + (conj acc (:version-type content)))) + version-types (vec (distinct (reduce reducer [] files)))] + (if (= 1 (count version-types)) + (do (main/info "All changelog entries use the same version type.") + true) + (do (main/warn (format "Changelog entries use multiple version types: %s" version-types)) + false)))) + + +(defn all-changelog-entries-have-distinct-versions? + "Returns true if all changelog entries have distinct versions." + [{:keys [changelog-entry-directory] :as _configuration}] + (let [files (list-all-files changelog-entry-directory) + reducer (fn [acc filepath] + (let [content (edn/read-string (read-file! filepath))] + (conj acc (:version content)))) + versions (vec (distinct (reduce reducer [] files)))] + (if (= (count files) (count versions)) + (do (main/info "All changelog entries have distinct versions.") + true) + (do (main/warn (format "Changelog entries have non-distinct versions: %s" versions)) + false)))) + + +(defn project-version-matches-latest-changelog-entry? + "Returns true if the project version matches the latest changelog entry." + [project configuration] + (let [changelog (load-changelog-entry-directory! configuration) + latest-changelog-entry (changelog/max-version changelog) + sealog-version (changelog/render-version latest-changelog-entry) + leiningen-version (:version project)] + (if (= leiningen-version sealog-version) + (do (main/info "Project version matches latest changelog entry.") + true) + (do (main/warn (format "Project version `%s` does not match latest changelog entry `%s`" + leiningen-version + sealog-version)) + false)))) + + +(defn rendered-changelog-contains-all-changelog-entries? + "Returns true if the rendered changelog contains all changelog entries." + [{:keys [changelog-filename] :as configuration}] + (let [changelog (load-changelog-entry-directory! configuration) + rendered-changelog (render-changelog changelog) + rendered-changelog-contents (slurp changelog-filename)] + (if (= (count rendered-changelog) (count rendered-changelog-contents)) + (do (main/info "Rendered changelog contains all changelog entries.") + true) + (do (main/warn "Rendered changelog does not contain all changelog entries. Please run `lein sealog render`.") + false))))