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))))