diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 68b8310..5af4e33 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -126,6 +126,7 @@ jobs: - build - crate - docker + - schema - web steps: - uses: actions/download-artifact@v4 @@ -135,9 +136,25 @@ jobs: merge-multiple: true - uses: ncipollo/release-action@v1 with: - artifacts: commitlint/commitlint-*.tar.gz + artifacts: commitlint/commitlint-*.tar.gz,schema.json generateReleaseNotes: true + schema: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: cargo run --package schema -- --path schema.json + - uses: actions/upload-artifact@v4 + with: + name: schema.json + path: schema/schema.json + if-no-files-found: error + web: runs-on: ubuntu-latest environment: diff --git a/Cargo.lock b/Cargo.lock index 0fd57b9..3179607 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,9 +116,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f" dependencies = [ "clap_builder", "clap_derive", @@ -126,9 +126,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.5.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec" dependencies = [ "anstream", "anstyle", @@ -155,13 +155,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] -name = "colorchoice" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" - -[[package]] -name = "commitlint-rs" +name = "cli" version = "0.2.0" dependencies = [ "clap", @@ -174,6 +168,12 @@ dependencies = [ "tokio", ] +[[package]] +name = "colorchoice" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" + [[package]] name = "dyn-clone" version = "1.0.17" @@ -476,6 +476,17 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "schema" +version = "0.2.0" +dependencies = [ + "clap", + "cli", + "schemars", + "serde", + "serde_json", +] + [[package]] name = "schemars" version = "0.8.21" @@ -508,18 +519,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" +checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.214" +version = "1.0.215" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" +checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" dependencies = [ "proc-macro2", "quote", @@ -539,9 +550,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.132" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ "itoa", "memchr", diff --git a/Cargo.toml b/Cargo.toml index 6d58bc5..0dbfeda 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["cli"] +members = ["cli", "schema"] resolver = "2" [workspace.package] diff --git a/cli/Cargo.toml b/cli/Cargo.toml index effea1d..17ebbe2 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "commitlint-rs" +name = "cli" description = "CLI tool to lint commits by Conventional Commits" documentation.workspace = true authors.workspace = true @@ -13,20 +13,23 @@ edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[[bin]] +name = "commitlint" +path = "src/main.rs" + [dependencies] clap = { version = "4.5.4", features = ["derive", "env", "string"] } futures = "0.3.30" regex = "1.10.5" -schemars = "0.8.21" +schemars = { version = "0.8.21", optional = true } serde = { version = "1.0.201", features = ["derive"] } serde_json = "1.0.121" serde_yaml = "0.9.34" tokio = { version = "1.37.0", features = ["full"] } - -[[bin]] -name = "commitlint" -path = "src/main.rs" +[features] +schemars = ["dep:schemars"] +default = [] [package.metadata.binstall] pkg-url = "{ repo }/releases/download/v{ version }/commitlint-v{ version }-{ target }{ archive-suffix }" diff --git a/cli/src/config.rs b/cli/src/config.rs index e5b46ac..2d7be77 100644 --- a/cli/src/config.rs +++ b/cli/src/config.rs @@ -19,6 +19,7 @@ const DEFAULT_CONFIG_FILE: [&str; 4] = [ /// Config represents the configuration of commitlint. #[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Config { /// Rules represents the rules of commitlint. pub rules: Rules, diff --git a/cli/src/git.rs b/cli/src/git.rs index eca382b..99d82bc 100644 --- a/cli/src/git.rs +++ b/cli/src/git.rs @@ -117,7 +117,7 @@ pub fn parse_commit_message( /// /// Note that exclamation mark is not respected as the existing commitlint /// does not have any rules for it. -/// See: https://commitlint.js.org/#/reference-rules +/// See: https://commitlint.js.org/reference/rules.html pub fn parse_subject(subject: &str) -> (Option, Option, Option) { let re = regex::Regex::new( r"^(?P\w+)(?:\((?P[^\)]+)\))?(?:!)?\:\s?(?P.*)$", @@ -188,8 +188,14 @@ Link: Hello"; let mut f = HashMap::new(); f.insert("Link".to_string(), "Hello".to_string()); assert_eq!(subject, "feat(cli): add dummy option"); - assert_eq!(body, Some("Hello, there! -I'm from Japan!".to_string())); + assert_eq!( + body, + Some( + "Hello, there! +I'm from Japan!" + .to_string() + ) + ); assert!(footer.is_some()); assert_eq!(f.get("Link"), Some(&"Hello".to_string())); } diff --git a/cli/src/lib.rs b/cli/src/lib.rs new file mode 100644 index 0000000..c4d8f69 --- /dev/null +++ b/cli/src/lib.rs @@ -0,0 +1,5 @@ +pub mod config; +pub mod git; +pub mod message; +pub mod result; +pub mod rule; diff --git a/cli/src/message.rs b/cli/src/message.rs index f65dbfb..ab1fd17 100644 --- a/cli/src/message.rs +++ b/cli/src/message.rs @@ -61,6 +61,5 @@ impl Message { /// validate the raw commit message. pub async fn validate(msg: &Message, config: &Config) -> Result { let violations = config.rules.validate(msg); - Ok(LintResult { violations }) } diff --git a/cli/src/rule.rs b/cli/src/rule.rs index 18a975d..12a2727 100644 --- a/cli/src/rule.rs +++ b/cli/src/rule.rs @@ -28,8 +28,9 @@ pub mod type_format; pub mod type_max_length; /// Rules represents the rules of commitlint. -/// See: https://commitlint.js.org/#/reference-rules +/// See: https://commitlint.js.org/reference/rules.html #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Rules { #[serde(rename = "body-empty")] #[serde(skip_serializing_if = "Option::is_none")] @@ -227,6 +228,7 @@ pub trait Rule: Default { /// Level represents the level of a rule. #[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub enum Level { #[serde(rename = "error")] Error, diff --git a/cli/src/rule/body_empty.rs b/cli/src/rule/body_empty.rs index 6231930..ecfb444 100644 --- a/cli/src/rule/body_empty.rs +++ b/cli/src/rule/body_empty.rs @@ -5,6 +5,7 @@ use super::Level; /// BodyEmpty represents the body-empty rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct BodyEmpty { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/body_max_length.rs b/cli/src/rule/body_max_length.rs index b7a3afc..ea8b869 100644 --- a/cli/src/rule/body_max_length.rs +++ b/cli/src/rule/body_max_length.rs @@ -5,6 +5,7 @@ use super::Level; /// BodyMaxLength represents the body-max-length rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct BodyMaxLength { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/description_empty.rs b/cli/src/rule/description_empty.rs index d853f5a..4c7e7a2 100644 --- a/cli/src/rule/description_empty.rs +++ b/cli/src/rule/description_empty.rs @@ -5,6 +5,7 @@ use super::Level; /// DescriptionEmpty represents the subject-empty rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct DescriptionEmpty { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/description_format.rs b/cli/src/rule/description_format.rs index 0b5c3db..dc54ff4 100644 --- a/cli/src/rule/description_format.rs +++ b/cli/src/rule/description_format.rs @@ -5,6 +5,7 @@ use super::Level; /// DescriptionFormat represents the description-format rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct DescriptionFormat { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/description_max_length.rs b/cli/src/rule/description_max_length.rs index de0f01a..babed77 100644 --- a/cli/src/rule/description_max_length.rs +++ b/cli/src/rule/description_max_length.rs @@ -5,6 +5,7 @@ use super::Level; /// DescriptionMaxLength represents the description-max-length rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct DescriptionMaxLength { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/footers_empty.rs b/cli/src/rule/footers_empty.rs index 9f5abe9..f4970c3 100644 --- a/cli/src/rule/footers_empty.rs +++ b/cli/src/rule/footers_empty.rs @@ -5,6 +5,7 @@ use super::Level; /// FootersEmpty represents the footer-empty rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct FootersEmpty { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/scope.rs b/cli/src/rule/scope.rs index d3947bd..57bd7b5 100644 --- a/cli/src/rule/scope.rs +++ b/cli/src/rule/scope.rs @@ -5,6 +5,7 @@ use super::Level; /// Scope represents the subject-empty rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Scope { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/scope_empty.rs b/cli/src/rule/scope_empty.rs index 96a5f00..e2d0219 100644 --- a/cli/src/rule/scope_empty.rs +++ b/cli/src/rule/scope_empty.rs @@ -5,6 +5,7 @@ use super::Level; /// ScopeEmpty represents the subject-empty rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ScopeEmpty { /// Level represents the level of the rule. /// @@ -79,10 +80,7 @@ mod tests { let violation = rule.validate(&message); assert!(violation.is_some()); assert_eq!(violation.clone().unwrap().level, Level::Error); - assert_eq!( - violation.unwrap().message, - "scope is empty".to_string() - ); + assert_eq!(violation.unwrap().message, "scope is empty".to_string()); } #[test] @@ -101,9 +99,6 @@ mod tests { let violation = rule.validate(&message); assert!(violation.is_some()); assert_eq!(violation.clone().unwrap().level, Level::Error); - assert_eq!( - violation.unwrap().message, - "scope is empty".to_string() - ); + assert_eq!(violation.unwrap().message, "scope is empty".to_string()); } } diff --git a/cli/src/rule/scope_format.rs b/cli/src/rule/scope_format.rs index ca2abd6..e567834 100644 --- a/cli/src/rule/scope_format.rs +++ b/cli/src/rule/scope_format.rs @@ -5,6 +5,7 @@ use super::Level; /// ScopeFormat represents the scope-format rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ScopeFormat { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/scope_max_length.rs b/cli/src/rule/scope_max_length.rs index e490496..f33da11 100644 --- a/cli/src/rule/scope_max_length.rs +++ b/cli/src/rule/scope_max_length.rs @@ -5,6 +5,7 @@ use super::Level; /// ScopeMaxLength represents the description-max-length rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct ScopeMaxLength { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/subject_empty.rs b/cli/src/rule/subject_empty.rs index 513a818..9b65adb 100644 --- a/cli/src/rule/subject_empty.rs +++ b/cli/src/rule/subject_empty.rs @@ -5,6 +5,7 @@ use super::Level; /// SubjectEmpty represents the subject-empty rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct SubjectEmpty { /// Level represents the level of the rule. /// @@ -85,9 +86,6 @@ Hello world" let violation = rule.validate(&message); assert!(violation.is_some()); assert_eq!(violation.clone().unwrap().level, Level::Error); - assert_eq!( - violation.unwrap().message, - "subject is empty".to_string() - ); + assert_eq!(violation.unwrap().message, "subject is empty".to_string()); } } diff --git a/cli/src/rule/type.rs b/cli/src/rule/type.rs index 26f3e94..39599a0 100644 --- a/cli/src/rule/type.rs +++ b/cli/src/rule/type.rs @@ -5,6 +5,7 @@ use super::Level; /// Type represents the subject-empty rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct Type { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/type_empty.rs b/cli/src/rule/type_empty.rs index 2030467..f9e425b 100644 --- a/cli/src/rule/type_empty.rs +++ b/cli/src/rule/type_empty.rs @@ -5,6 +5,7 @@ use super::Level; /// TypeEmpty represents the type-empty rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct TypeEmpty { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/type_format.rs b/cli/src/rule/type_format.rs index 672549c..364d017 100644 --- a/cli/src/rule/type_format.rs +++ b/cli/src/rule/type_format.rs @@ -5,6 +5,7 @@ use super::Level; /// TypeFormat represents the type-format rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct TypeFormat { /// Level represents the level of the rule. /// diff --git a/cli/src/rule/type_max_length.rs b/cli/src/rule/type_max_length.rs index 5562218..1df8dbb 100644 --- a/cli/src/rule/type_max_length.rs +++ b/cli/src/rule/type_max_length.rs @@ -5,6 +5,7 @@ use super::Level; /// TypeMaxLength represents the description-max-length rule. #[derive(Clone, Debug, Deserialize, Serialize)] +#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] pub struct TypeMaxLength { /// Level represents the level of the rule. /// diff --git a/schema/Cargo.toml b/schema/Cargo.toml new file mode 100644 index 0000000..11b83b9 --- /dev/null +++ b/schema/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "schema" +version.workspace = true +authors.workspace = true +license.workspace = true +documentation.workspace = true +keywords.workspace = true +categories.workspace = true +readme.workspace = true +repository.workspace = true +exclude.workspace = true +edition.workspace = true + +[dependencies] +clap = { version = "4.5.21", features = ["derive"] } +cli = { path = "../cli", features = ["schemars"] } +schemars = { version = "0.8.21" } +serde = { version = "1.0.215", features = ["derive"] } +serde_json = "1.0.133" diff --git a/schema/src/main.rs b/schema/src/main.rs new file mode 100644 index 0000000..989d768 --- /dev/null +++ b/schema/src/main.rs @@ -0,0 +1,20 @@ +use clap::Parser; +use cli::config::Config; +use std::fs; + +/// CLI Arguments +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Path to save the JSON schema + #[arg(long, short)] + path: String, +} + +fn main() { + let args = Args::parse(); + + let config_schema = schemars::schema_for!(Config); + let config_schema_json = serde_json::to_string_pretty(&config_schema).unwrap(); + fs::write(&args.path, config_schema_json).unwrap(); +}