Skip to content

Commit edc1e9d

Browse files
authored
Merge pull request #43 from davegarvey:md-fix
Md-fix
2 parents 221fdc7 + 270080a commit edc1e9d

File tree

4 files changed

+342
-5
lines changed

4 files changed

+342
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
### Fixed
1616

1717
- correct changelog formatting for markdownlint compliance
18+
1819
## [4.7.0] - 2025-12-16
1920

2021
### Added

CONTRIBUTING.md

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
# Contributing to Grubble
2+
3+
Thank you for your interest in contributing to Grubble! This document provides guidelines and information for contributors.
4+
5+
## Development Setup
6+
7+
### Prerequisites
8+
9+
- **Rust**: Install the latest stable version from [rustup.rs](https://rustup.rs/)
10+
- **Node.js** (optional): For markdown linting tests, install from [nodejs.org](https://nodejs.org/)
11+
12+
### Getting Started
13+
14+
1. Clone the repository:
15+
16+
```bash
17+
git clone https://github.com/davegarvey/grubble.git
18+
cd grubble
19+
```
20+
21+
2. Run tests:
22+
23+
```bash
24+
cargo test
25+
```
26+
27+
3. Run linting:
28+
29+
```bash
30+
cargo clippy
31+
```
32+
33+
## Testing
34+
35+
### Standard Test Suite
36+
37+
Run all tests with:
38+
39+
```bash
40+
cargo test
41+
```
42+
43+
### Markdown Linting Tests
44+
45+
Grubble includes optional tests that validate generated changelog files against markdown linting rules. These tests require `markdownlint-cli` and are controlled by the `RUN_MARKDOWN_LINT` environment variable.
46+
47+
#### Why Optional?
48+
49+
- **Dependencies**: Requires Node.js and `markdownlint-cli`
50+
- **Performance**: External process calls are slower
51+
- **CI Focus**: Primarily valuable in CI where standards are enforced
52+
53+
#### Running Markdown Linting Tests
54+
55+
**Option 1: Environment Variable (Recommended)**
56+
57+
```bash
58+
# Set for current session
59+
export RUN_MARKDOWN_LINT=1
60+
cargo test
61+
62+
# Or inline
63+
RUN_MARKDOWN_LINT=1 cargo test
64+
```
65+
66+
**Option 2: .env File**
67+
68+
```bash
69+
# Create .env file
70+
echo 'RUN_MARKDOWN_LINT=1' > .env
71+
72+
# Run tests with environment loaded
73+
export $(cat .env) && cargo test
74+
```
75+
76+
**Option 3: Convenience Script**
77+
78+
```bash
79+
# Use the provided script
80+
./test-with-markdown.sh
81+
./test-with-markdown.sh test_markdown_linter_if_available -- --nocapture
82+
```
83+
84+
#### Test Behavior
85+
86+
- **CI Environment**: Tests run automatically when `CI=true`
87+
- **Local Development**: Tests skip gracefully unless `RUN_MARKDOWN_LINT=1` is set
88+
- **Missing Dependencies**: In CI, tests fail if `markdownlint-cli` is unavailable
89+
90+
### Test Coverage
91+
92+
The project maintains comprehensive test coverage including:
93+
94+
- Unit tests for all core functionality
95+
- Integration tests for changelog generation
96+
- Markdown compliance tests
97+
- Cross-platform compatibility tests
98+
99+
## Code Quality
100+
101+
### Linting
102+
103+
Run clippy for code quality checks:
104+
105+
```bash
106+
cargo clippy --all-targets --all-features -- -D warnings
107+
```
108+
109+
### Formatting
110+
111+
Format code according to Rust standards:
112+
113+
```bash
114+
cargo fmt --all
115+
```
116+
117+
### Pre-commit Hooks
118+
119+
The project uses Husky for pre-commit hooks. Install dependencies and set up hooks:
120+
121+
```bash
122+
npm install
123+
```
124+
125+
## Commit Guidelines
126+
127+
Grubble follows [Conventional Commits](https://conventionalcommits.org/) specification:
128+
129+
```
130+
<type>[optional scope]: <description>
131+
132+
[optional body]
133+
134+
[optional footer(s)]
135+
```
136+
137+
### Commit Types
138+
139+
- `feat:` - New features (minor version bump)
140+
- `fix:` - Bug fixes (patch version bump)
141+
- `docs:` - Documentation changes
142+
- `test:` - Testing related changes
143+
- `refactor:` - Code refactoring
144+
- `style:` - Code style changes
145+
- `chore:` - Maintenance tasks
146+
- `ci:` - CI/CD changes
147+
- `build:` - Build system changes
148+
- `perf:` - Performance improvements
149+
150+
### Breaking Changes
151+
152+
Mark breaking changes with `!` after the type/scope or include `BREAKING CHANGE:` in the footer.
153+
154+
### Examples
155+
156+
```bash
157+
feat: add support for custom commit types
158+
fix: resolve changelog spacing issue
159+
test: improve markdown linter test behavior
160+
docs: update installation instructions
161+
refactor!: simplify version parsing logic
162+
```
163+
164+
## Pull Request Process
165+
166+
1. **Fork** the repository
167+
2. **Create** a feature branch from `main`
168+
3. **Make** your changes following the guidelines above
169+
4. **Test** thoroughly (including markdown linting if applicable)
170+
5. **Commit** with conventional commit messages
171+
6. **Push** to your fork
172+
7. **Create** a Pull Request with a clear description
173+
174+
### PR Requirements
175+
176+
- All tests pass
177+
- Code is formatted (`cargo fmt`)
178+
- No clippy warnings (`cargo clippy`)
179+
- Conventional commit messages
180+
- Clear description of changes
181+
182+
## Release Process
183+
184+
Grubble uses automated releases based on conventional commits:
185+
186+
- `feat:` commits → Minor version bump
187+
- `fix:` commits → Patch version bump
188+
- Breaking changes → Major version bump
189+
190+
Releases are automatically created via GitHub Actions when changes are merged to `main`.
191+
192+
## Project Structure
193+
194+
```
195+
src/
196+
├── main.rs # CLI entry point
197+
├── analyser.rs # Commit analysis logic
198+
├── versioner.rs # Version bump calculations
199+
├── changelog.rs # CHANGELOG.md generation
200+
├── config.rs # Configuration handling
201+
├── git.rs # Git operations
202+
├── strategy/ # Version strategy implementations
203+
│ ├── git.rs
204+
│ └── node.rs
205+
└── error.rs # Error types
206+
```
207+
208+
## Configuration Files
209+
210+
- `.versionrc.json` - Version bump configuration
211+
- `.markdownlint.json` - Markdown linting rules
212+
- `.github/workflows/` - CI/CD pipelines
213+
214+
## Getting Help
215+
216+
- **Issues**: [GitHub Issues](https://github.com/davegarvey/grubble/issues)
217+
- **Discussions**: [GitHub Discussions](https://github.com/davegarvey/grubble/discussions)
218+
- **Documentation**: See README.md for detailed usage instructions
219+
220+
## License
221+
222+
By contributing to Grubble, you agree that your contributions will be licensed under the same MIT License that covers the project.

src/changelog.rs

Lines changed: 115 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,6 @@ fn generate_changelog_entry_at_path(
133133
}
134134
entry.push_str(&format!("- {}\n", change.description));
135135
}
136-
// Don't add extra blank line after final list - the header already provides spacing
137136

138137
// Read existing changelog or create header
139138
let mut content = if changelog_path.exists() {
@@ -144,6 +143,8 @@ fn generate_changelog_entry_at_path(
144143

145144
// Find where to insert the new entry (after the header, before existing entries)
146145
let insertion_point = if let Some(pos) = content.find("\n## [") {
146+
// Add blank line after the new entry if there are existing entries
147+
entry.push('\n');
147148
pos + 1
148149
} else {
149150
content.len()
@@ -219,6 +220,63 @@ mod tests {
219220
assert!(content.contains("- add new feature"));
220221
assert!(content.contains("### Fixed"));
221222
assert!(content.contains("- resolve bug"));
223+
224+
// Verify no double blank lines (triple newlines) in the file
225+
assert!(
226+
!content.contains("\n\n\n"),
227+
"New changelog should not have double blank lines"
228+
);
229+
230+
// The file should end with a single newline, not multiple blank lines
231+
assert!(
232+
!content.ends_with("\n\n\n"),
233+
"New changelog should not end with double blank lines"
234+
);
235+
assert!(
236+
!content.ends_with("\n\n"),
237+
"New changelog should not end with a blank line (should end with single newline after last list item)"
238+
);
239+
}
240+
241+
#[test]
242+
fn test_new_changelog_file_has_no_double_blank_lines_at_end() {
243+
let temp_dir = TempDir::new().unwrap();
244+
let changelog_path = temp_dir.path().join("CHANGELOG.md");
245+
246+
let version = Version::parse("1.0.0").unwrap();
247+
let commits = vec!["feat: first feature".to_string()];
248+
249+
generate_changelog_entry_at_path(&version, &commits, &changelog_path).unwrap();
250+
251+
let content = fs::read_to_string(&changelog_path).unwrap();
252+
253+
// For a new file with just one entry, the structure should be:
254+
// - Header (ending with \n\n)
255+
// - Version header: "## [1.0.0] - date\n\n"
256+
// - Category header: "### Added\n\n"
257+
// - List item: "- first feature\n"
258+
// - End of file (no extra newlines)
259+
260+
// Verify the ending structure
261+
assert!(
262+
content.ends_with("- first feature\n"),
263+
"New changelog should end with list item + single newline, but got: {:?}",
264+
&content[content.len().saturating_sub(30)..]
265+
);
266+
267+
// Verify no triple newlines anywhere (which would be double blank lines)
268+
assert!(
269+
!content.contains("\n\n\n"),
270+
"New changelog should not contain double blank lines"
271+
);
272+
273+
// Count the total newlines at the end - should be exactly 1
274+
let trailing_newlines = content.chars().rev().take_while(|&c| c == '\n').count();
275+
assert_eq!(
276+
trailing_newlines, 1,
277+
"New changelog should end with exactly 1 newline, found {}",
278+
trailing_newlines
279+
);
222280
}
223281

224282
#[test]
@@ -446,10 +504,64 @@ mod tests {
446504
}
447505

448506
#[test]
449-
#[ignore] // Run with: cargo test -- --ignored --nocapture (automatically run in CI)
507+
fn test_generate_changelog_entry_proper_spacing_between_releases() {
508+
let temp_dir = TempDir::new().unwrap();
509+
let changelog_path = temp_dir.path().join("CHANGELOG.md");
510+
511+
// Create first release
512+
let version1 = Version::parse("1.0.0").unwrap();
513+
let commits1 = vec!["feat: initial feature".to_string()];
514+
generate_changelog_entry_at_path(&version1, &commits1, &changelog_path).unwrap();
515+
516+
// Create second release
517+
let version2 = Version::parse("1.1.0").unwrap();
518+
let commits2 = vec!["fix: bug fix".to_string()];
519+
generate_changelog_entry_at_path(&version2, &commits2, &changelog_path).unwrap();
520+
521+
// Create third release
522+
let version3 = Version::parse("1.2.0").unwrap();
523+
let commits3 = vec!["feat: another feature".to_string()];
524+
generate_changelog_entry_at_path(&version3, &commits3, &changelog_path).unwrap();
525+
526+
let content = fs::read_to_string(&changelog_path).unwrap();
527+
528+
// Verify proper spacing: should have exactly one blank line between release entries
529+
// Pattern should be: "- item\n\n## [version]" (list item, blank line, next header)
530+
assert!(
531+
content.contains("- another feature\n\n## [1.1.0]"),
532+
"Missing blank line between [1.2.0] and [1.1.0]"
533+
);
534+
assert!(
535+
content.contains("- bug fix\n\n## [1.0.0]"),
536+
"Missing blank line between [1.1.0] and [1.0.0]"
537+
);
538+
539+
// Should NOT have double blank lines (triple newlines)
540+
assert!(
541+
!content.contains("\n\n\n"),
542+
"Found double blank line (triple newlines) in changelog"
543+
);
544+
545+
// Verify all versions are present and in correct order
546+
let v3_pos = content.find("## [1.2.0]").unwrap();
547+
let v2_pos = content.find("## [1.1.0]").unwrap();
548+
let v1_pos = content.find("## [1.0.0]").unwrap();
549+
assert!(
550+
v3_pos < v2_pos && v2_pos < v1_pos,
551+
"Versions not in descending order"
552+
);
553+
}
554+
555+
#[test]
450556
fn test_markdown_linter_if_available() {
451557
use std::process::Command;
452558

559+
// Only run this test in CI or when explicitly requested
560+
if std::env::var("CI").is_err() && std::env::var("RUN_MARKDOWN_LINT").is_err() {
561+
println!("⚠ Skipping markdown linter test (run with RUN_MARKDOWN_LINT=1 to enable)");
562+
return;
563+
}
564+
453565
let temp_dir = TempDir::new().unwrap();
454566
let changelog_path = temp_dir.path().join("CHANGELOG.md");
455567

@@ -463,7 +575,6 @@ mod tests {
463575
generate_changelog_entry_at_path(&version, &commits, &changelog_path).unwrap();
464576

465577
// Try to run markdownlint-cli if available
466-
// Install with: npm install -g markdownlint-cli
467578
let result = Command::new("npx")
468579
.args(["markdownlint-cli", changelog_path.to_str().unwrap()])
469580
.output();
@@ -482,8 +593,7 @@ mod tests {
482593
}
483594
}
484595
Err(e) => {
485-
println!("⚠ markdownlint not found (optional): {}", e);
486-
println!(" To enable this test, install: npm install -g markdownlint-cli");
596+
panic!("markdownlint-cli not available but required in CI: {}", e);
487597
}
488598
}
489599
}

0 commit comments

Comments
 (0)