From bde26bc111ff1bdf111b7ee2e7561ccc4e665292 Mon Sep 17 00:00:00 2001 From: Robert Magnusson Date: Wed, 4 Feb 2026 14:40:23 +0100 Subject: [PATCH 1/3] chore: bump version to v0.1.1 Co-authored-by: Cursor --- CHANGELOG.md | 3 +++ RELEASING.md | 15 +++++++++------ pubspec.yaml | 2 +- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cd96b1..582a785 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.1.1] - 2026-02-04 + ### Added - Release automation with tag-based publishing to pub.dev +- Numbered steps in RELEASING.md Quick Reference for easier scanning - Version bump tooling (`dart tool/version_bump.dart`) - Pre-release validation (`dart tool/pre_release_check.dart`) - Release creation script (`dart tool/create_release.dart`) - creates tag, pushes, and creates GitHub release with changelog diff --git a/RELEASING.md b/RELEASING.md index fdf98af..e15902c 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -7,25 +7,28 @@ This document describes the process for releasing new versions of dartypod to pu ## Quick Reference ```bash -# Create release branch +# 1. Create release branch git checkout main && git pull origin main git checkout -b release/v0.2.0 -# Pre-release validation +# 2. Pre-release validation dart tool/pre_release_check.dart -# Version bump (choose one) +# 3. Version bump (choose one) dart tool/version_bump.dart patch # 0.1.0 -> 0.1.1 dart tool/version_bump.dart minor # 0.1.0 -> 0.2.0 dart tool/version_bump.dart major # 0.1.0 -> 1.0.0 -# Commit +# 4. Commit git add . && git commit -m "chore: bump version to v0.2.0" -# Post-bump validation +# 5. Post-bump validation (dry-run publish check) dart tool/pre_release_check.dart --post -# Push and create PR, then after merge: +# 6. Push and create PR +git push origin release/v0.2.0 + +# 7. After PR merge, create release git checkout main && git pull origin main dart tool/create_release.dart ``` diff --git a/pubspec.yaml b/pubspec.yaml index 3c2aee9..50eb8cd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: dartypod description: A minimal Service Locator with compile-time safe provider references, enabling clean dependency injection patterns. -version: 0.1.0 +version: 0.1.1 repository: https://github.com/robert-northmind/dartypod environment: From eb8223406589a0de7394c735b76d27cbe96bb399 Mon Sep 17 00:00:00 2001 From: Robert Magnusson Date: Wed, 4 Feb 2026 15:07:39 +0100 Subject: [PATCH 2/3] feat: add smart changelog validation script Replaces inline bash in CI with Dart script that auto-detects release branches and validates version-specific changelog entries. Co-authored-by: Cursor --- .github/workflows/ci.yml | 27 ++---- CHANGELOG.md | 1 + tool/check_changelog.dart | 170 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 177 insertions(+), 21 deletions(-) create mode 100644 tool/check_changelog.dart diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 61cbf8f..127b620 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,24 +79,9 @@ jobs: with: persist-credentials: false - - name: Check CHANGELOG has unreleased content - run: | - # Check if CHANGELOG.md exists - if [ ! -f CHANGELOG.md ]; then - echo "❌ CHANGELOG.md not found" - exit 1 - fi - - # Extract content between ## [Unreleased] and next ## heading - unreleased_content=$(awk '/^## \[Unreleased\]/{flag=1; next} /^## \[/{flag=0} flag' CHANGELOG.md | grep -v '^[[:space:]]*$' | head -20) - - if [ -z "$unreleased_content" ]; then - echo "❌ No content found under ## [Unreleased] section" - echo "" - echo "Please add your changes to the CHANGELOG.md under the ## [Unreleased] section." - echo "If this PR doesn't require a changelog entry, add the 'skip-changelog' label." - exit 1 - else - echo "✅ Found unreleased content in CHANGELOG.md:" - echo "$unreleased_content" - fi + - uses: dart-lang/setup-dart@e51d8e571e22473a2ddebf0ef8a2123f0ab2c02c # v1.7.1 + with: + sdk: stable + + - name: Check CHANGELOG + run: dart tool/check_changelog.dart --branch "${{ github.head_ref }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index 582a785..77a16d8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Pre-release validation (`dart tool/pre_release_check.dart`) - Release creation script (`dart tool/create_release.dart`) - creates tag, pushes, and creates GitHub release with changelog - CI changelog check with `skip-changelog` label escape hatch +- Smart changelog validation (`dart tool/check_changelog.dart`) - auto-detects release branches - `CONTRIBUTING.md` with development workflow and PR guidelines - `RELEASING.md` documentation for maintainers - Contributing section in README diff --git a/tool/check_changelog.dart b/tool/check_changelog.dart new file mode 100644 index 0000000..d4348ab --- /dev/null +++ b/tool/check_changelog.dart @@ -0,0 +1,170 @@ +import 'dart:io'; + +/// Check CHANGELOG.md for proper content based on branch type. +/// +/// For release branches (release/*): +/// - Verifies there's a versioned section matching pubspec.yaml version +/// +/// For other branches: +/// - Verifies there's content under [Unreleased] section +Future main(List args) async { + // Parse arguments + String? branchName; + + for (var i = 0; i < args.length; i++) { + if (args[i] == '--branch' && i + 1 < args.length) { + branchName = args[i + 1]; + i++; + } + } + + if (branchName == null) { + stderr.writeln('Usage: dart tool/check_changelog.dart --branch '); + stderr.writeln(''); + stderr.writeln('Examples:'); + stderr.writeln( + ' dart tool/check_changelog.dart --branch feature/add-something'); + stderr.writeln(' dart tool/check_changelog.dart --branch release/v0.1.1'); + exit(1); + } + + // Check if CHANGELOG.md exists + final changelogFile = File('CHANGELOG.md'); + if (!changelogFile.existsSync()) { + _fail('CHANGELOG.md not found'); + } + + final changelog = await changelogFile.readAsString(); + + // Determine check type based on branch name + if (branchName.startsWith('release/')) { + await _checkReleaseBranch(changelog); + } else { + _checkUnreleasedContent(changelog); + } +} + +/// Check that CHANGELOG has a versioned section matching pubspec.yaml version +Future _checkReleaseBranch(String changelog) async { + stdout.writeln('📦 Detected release branch'); + stdout.writeln(''); + + // Get version from pubspec.yaml + final pubspecFile = File('pubspec.yaml'); + if (!pubspecFile.existsSync()) { + _fail('pubspec.yaml not found'); + } + + final pubspec = await pubspecFile.readAsString(); + final versionMatch = RegExp(r'version: (\d+\.\d+\.\d+)').firstMatch(pubspec); + + if (versionMatch == null) { + _fail('Could not find version in pubspec.yaml'); + } + + final version = versionMatch.group(1)!; + stdout.writeln('Expected version: $version'); + stdout.writeln(''); + + // Check if CHANGELOG has a section for this version + // Pattern: ## [0.1.1] - YYYY-MM-DD or ## [0.1.1] + final versionPattern = RegExp(r'## \[' + RegExp.escape(version) + r'\]'); + + if (versionPattern.hasMatch(changelog)) { + // Extract and display the content for this version + final content = _extractVersionContent(changelog, version); + stdout.writeln('✅ Found changelog entry for version $version:'); + stdout.writeln(''); + for (final line in content.take(20)) { + stdout.writeln(' $line'); + } + if (content.length > 20) { + stdout.writeln(' ... (truncated)'); + } + } else { + _fail( + 'No changelog entry found for version $version\n' + '\n' + 'Expected to find: ## [$version] - YYYY-MM-DD\n' + '\n' + 'Make sure you ran: dart tool/version_bump.dart ', + ); + } +} + +/// Check that CHANGELOG has content under [Unreleased] section +void _checkUnreleasedContent(String changelog) { + stdout.writeln('🔍 Checking for unreleased content'); + stdout.writeln(''); + + final content = _extractUnreleasedContent(changelog); + + if (content.isEmpty) { + _fail( + 'No content found under ## [Unreleased] section\n' + '\n' + 'Please add your changes to the CHANGELOG.md under the ## [Unreleased] section.\n' + "If this PR doesn't require a changelog entry, add the 'skip-changelog' label.", + ); + } else { + stdout.writeln('✅ Found unreleased content in CHANGELOG.md:'); + stdout.writeln(''); + for (final line in content.take(20)) { + stdout.writeln(' $line'); + } + if (content.length > 20) { + stdout.writeln(' ... (truncated)'); + } + } +} + +/// Extract content between ## [version] and the next ## heading +List _extractVersionContent(String changelog, String version) { + final lines = changelog.split('\n'); + final result = []; + var inSection = false; + final versionPattern = RegExp(r'## \[' + RegExp.escape(version) + r'\]'); + + for (final line in lines) { + if (versionPattern.hasMatch(line)) { + inSection = true; + continue; + } + if (inSection && line.startsWith('## [')) { + break; + } + if (inSection && line.trim().isNotEmpty) { + result.add(line); + } + } + + return result; +} + +/// Extract content between ## [Unreleased] and the next ## heading +List _extractUnreleasedContent(String changelog) { + final lines = changelog.split('\n'); + final result = []; + var inSection = false; + + for (final line in lines) { + if (line.startsWith('## [Unreleased]')) { + inSection = true; + continue; + } + if (inSection && line.startsWith('## [')) { + break; + } + if (inSection && line.trim().isNotEmpty) { + result.add(line); + } + } + + return result; +} + +/// Print error message and exit with failure +Never _fail(String message) { + stderr.writeln('❌ $message'); + exit(1); +} From 863ce4ea45e13e6d3ac4a201d3d8d1543e5fec22 Mon Sep 17 00:00:00 2001 From: Robert Magnusson Date: Wed, 4 Feb 2026 15:09:17 +0100 Subject: [PATCH 3/3] fix: prevent template injection in changelog check Use environment variable instead of direct template expansion to avoid potential shell injection via crafted branch names. Co-authored-by: Cursor --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 127b620..cf94b1f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,4 +84,6 @@ jobs: sdk: stable - name: Check CHANGELOG - run: dart tool/check_changelog.dart --branch "${{ github.head_ref }}" + env: + BRANCH_NAME: ${{ github.head_ref }} + run: dart tool/check_changelog.dart --branch "$BRANCH_NAME"