From d130af2abe303bc921cabdcda826c9f0d7a4d038 Mon Sep 17 00:00:00 2001 From: skulidropek <66840575+skulidropek@users.noreply.github.com> Date: Sat, 23 May 2026 10:55:20 +0000 Subject: [PATCH 1/4] feat: full template from link-foundation + adapted Rust module for noVNC/browser connection per #347 Co-Authored-By: Claude Sonnet 4.6 --- .github/workflows/release.yml | 675 +++++ .gitignore | 57 + .gitkeep | 1 + .pre-commit-config.yaml | 34 + CHANGELOG.md | 2504 +++++++++++++++++ CONTRIBUTING.md | 302 ++ Cargo.lock | 368 +++ Cargo.toml | 38 +- LICENSE | 24 + README.md | 327 +++ .../20251227_224645_changeset_support.md | 14 + ...229_143823_fix_ci_workflow_dependencies.md | 10 + ...231_115800_fix_readme_script_references.md | 4 + changelog.d/20260107_apply_best_practices.md | 21 + .../20260108_171124_fix_changelog_check.md | 17 + ...435_prevent_manual_version_modification.md | 13 + ...20260108_apply_lino_objects_codec_fixes.md | 14 + .../20260111_multi_language_support.md | 19 + ...9_best_practices_from_browser_commander.md | 15 + .../20260311_translate_scripts_to_rust.md | 11 + changelog.d/20260413-ci-cd-best-practices.md | 24 + .../20260413_fix_cargo_token_fallback.md | 10 + changelog.d/20260413_fix_crates_io_check.md | 16 + changelog.d/20260413_fix_lookahead_regex.md | 14 + changelog.d/20260414_fix_per_commit_diff.md | 7 + .../20260415_fix_workspace_release_scripts.md | 2 + changelog.d/20260501_decouple_docs_deploy.md | 6 + changelog.d/20260503_111500_ci_timeouts.md | 6 + ...0503_111700_file_size_warning_threshold.md | 2 + ...09_031015_human_readable_release_titles.md | 2 + ...09_205000_docker_hub_release_publishing.md | 9 + ...512_172908_github_pages_artifact_deploy.md | 6 + ...0053_document_pages_source_prerequisite.md | 6 + ...4_track_browser_commander_preview_regen.md | 6 + ...20260515_223000_cargo_lock_release_sync.md | 6 + changelog.d/README.md | 135 + docs/case-studies/issue-11/README.md | 151 + .../issue-11/analysis-crates-io.md | 227 ++ .../issue-11/analysis-set-output.md | 132 + .../issue-11/analysis-workflow-dispatch.md | 195 ++ docs/case-studies/issue-11/online-research.md | 139 + docs/case-studies/issue-17/README.md | 140 + docs/case-studies/issue-19/README.md | 239 ++ .../ci-logs/ci-run-20885464993.log.gz | Bin 0 -> 20042 bytes .../pr-114-data/issue-113-details.txt | 15 + .../issue-19/pr-114-data/pr-commits.json | 1 + .../pr-114-data/pr-conversation-comments.json | 1 + .../issue-19/pr-114-data/pr-details.json | 1 + .../issue-19/pr-114-data/pr-diff.patch | 879 ++++++ .../pr-114-data/pr-review-comments.json | 1 + .../issue-19/pr-114-data/pr-reviews.json | 1 + .../pr-114-data/solution-draft-log-1.txt.gz | Bin 0 -> 89348 bytes .../pr-114-data/solution-draft-log-2.txt.gz | Bin 0 -> 69878 bytes docs/case-studies/issue-21/README.md | 240 ++ .../issue-21/browser-commander-issue-27.md | 116 + .../issue-21/browser-commander-issue-29.md | 136 + .../issue-21/browser-commander-issue-31.md | 129 + .../issue-21/browser-commander-issue-33.md | 156 + .../issue-21/browser-commander-rust.yml | 416 +++ docs/case-studies/issue-25/README.md | 94 + docs/case-studies/issue-29/README.md | 126 + docs/case-studies/issue-32/README.md | 97 + docs/case-studies/issue-34/README.md | 124 + docs/case-studies/issue-38/README.md | 142 + ...downstream-meta-after-run-24985948212.json | 1 + ...wnstream-meta-after-run-24985948212.log.gz | Bin 0 -> 106114 bytes ...ownstream-meta-before-run-24983875003.json | 1 + ...nstream-meta-before-run-24983875003.log.gz | Bin 0 -> 116384 bytes .../downstream-meta-ontology-issue-3.json | 1 + .../downstream-meta-ontology-pr-4.json | 1 + .../issue-38/raw-data/issue-38-comments.json | 1 + .../issue-38/raw-data/issue-38.json | 1 + .../raw-data/js-template-issue-search.json | 1 + .../raw-data/main-run-24465255225.json | 1 + .../raw-data/main-run-24465255225.log.gz | Bin 0 -> 89367 bytes .../issue-38/raw-data/main-runs.json | 1 + .../raw-data/pr-39-conversation-comments.json | 1 + .../raw-data/pr-39-review-comments.json | 1 + .../issue-38/raw-data/pr-39-reviews.json | 1 + .../case-studies/issue-38/raw-data/pr-39.json | 1 + .../issue-38/raw-data/pr-branch-runs.json | 1 + .../issue-38/raw-data/pr-run-25212295127.json | 1 + .../raw-data/pr-run-25212295127.log.gz | Bin 0 -> 72323 bytes .../raw-data/rust-template-issue-search.json | 1 + .../template-data/js-template-ci-tree.txt | 22 + .../template-data/js-template-links.yml | 81 + .../template-data/js-template-release.yml | 537 ++++ .../template-data/rust-template-ci-tree.txt | 16 + .../rust-template-release-after.yml | 491 ++++ .../rust-template-release-before.yml | 488 ++++ docs/case-studies/issue-52/README.md | 80 + .../issue-52/raw-data/issue-52-comments.json | 1 + .../issue-52/raw-data/issue-52.json | 1 + .../issue-52/raw-data/js-issue-62.json | 1 + .../raw-data/vk-bot-desktop-issue-51.json | 1 + .../raw-data/vk-bot-desktop-pr-52.json | 1 + docs/ci-cd/troubleshooting.md | 258 ++ examples/basic_usage.rs | 7 + experiments/test-changelog-parsing.rs | 100 + experiments/test-crates-io-check.rs | 104 + experiments/test-detect-code-changes.sh | 114 + .../test-version-check-dependencies.sh | 22 + experiments/test-version-check.sh | 37 + scripts/bump-version.rs | 165 ++ scripts/check-changelog-fragment.rs | 164 ++ scripts/check-file-size.rs | 293 ++ scripts/check-release-needed.rs | 395 +++ scripts/check-version-modification.rs | 163 ++ scripts/collect-changelog.rs | 234 ++ scripts/create-changelog-fragment.rs | 119 + scripts/create-github-release.rs | 413 +++ scripts/detect-code-changes.rs | 186 ++ scripts/get-bump-type.rs | 174 ++ scripts/get-version.rs | 72 + scripts/git-config.rs | 62 + scripts/publish-crate.rs | 179 ++ scripts/rust-paths.rs | 273 ++ scripts/version-and-commit.rs | 736 +++++ scripts/wait-for-crate.rs | 178 ++ src/lib.rs | 90 +- src/main.rs | 18 + src/sum.rs | 4 + tests/integration/mod.rs | 1 + tests/integration/sum.rs | 36 + tests/unit/ci-cd/changelog_parsing.rs | 94 + tests/unit/ci-cd/mod.rs | 12 + tests/unit/ci-cd/workflow_release.rs | 238 ++ .../ci-cd/workspace_manifest_resolution.rs | 146 + tests/unit/mod.rs | 4 + tests/unit/sum.rs | 26 + 130 files changed, 15371 insertions(+), 105 deletions(-) create mode 100644 .github/workflows/release.yml create mode 100644 .gitignore create mode 100644 .gitkeep create mode 100644 .pre-commit-config.yaml create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING.md create mode 100644 Cargo.lock create mode 100644 LICENSE create mode 100644 README.md create mode 100644 changelog.d/20251227_224645_changeset_support.md create mode 100644 changelog.d/20251229_143823_fix_ci_workflow_dependencies.md create mode 100644 changelog.d/20251231_115800_fix_readme_script_references.md create mode 100644 changelog.d/20260107_apply_best_practices.md create mode 100644 changelog.d/20260108_171124_fix_changelog_check.md create mode 100644 changelog.d/20260108_171435_prevent_manual_version_modification.md create mode 100644 changelog.d/20260108_apply_lino_objects_codec_fixes.md create mode 100644 changelog.d/20260111_multi_language_support.md create mode 100644 changelog.d/20260119_best_practices_from_browser_commander.md create mode 100644 changelog.d/20260311_translate_scripts_to_rust.md create mode 100644 changelog.d/20260413-ci-cd-best-practices.md create mode 100644 changelog.d/20260413_fix_cargo_token_fallback.md create mode 100644 changelog.d/20260413_fix_crates_io_check.md create mode 100644 changelog.d/20260413_fix_lookahead_regex.md create mode 100644 changelog.d/20260414_fix_per_commit_diff.md create mode 100644 changelog.d/20260415_fix_workspace_release_scripts.md create mode 100644 changelog.d/20260501_decouple_docs_deploy.md create mode 100644 changelog.d/20260503_111500_ci_timeouts.md create mode 100644 changelog.d/20260503_111700_file_size_warning_threshold.md create mode 100644 changelog.d/20260509_031015_human_readable_release_titles.md create mode 100644 changelog.d/20260509_205000_docker_hub_release_publishing.md create mode 100644 changelog.d/20260512_172908_github_pages_artifact_deploy.md create mode 100644 changelog.d/20260512_230053_document_pages_source_prerequisite.md create mode 100644 changelog.d/20260515_074404_track_browser_commander_preview_regen.md create mode 100644 changelog.d/20260515_223000_cargo_lock_release_sync.md create mode 100644 changelog.d/README.md create mode 100644 docs/case-studies/issue-11/README.md create mode 100644 docs/case-studies/issue-11/analysis-crates-io.md create mode 100644 docs/case-studies/issue-11/analysis-set-output.md create mode 100644 docs/case-studies/issue-11/analysis-workflow-dispatch.md create mode 100644 docs/case-studies/issue-11/online-research.md create mode 100644 docs/case-studies/issue-17/README.md create mode 100644 docs/case-studies/issue-19/README.md create mode 100644 docs/case-studies/issue-19/ci-logs/ci-run-20885464993.log.gz create mode 100644 docs/case-studies/issue-19/pr-114-data/issue-113-details.txt create mode 100644 docs/case-studies/issue-19/pr-114-data/pr-commits.json create mode 100644 docs/case-studies/issue-19/pr-114-data/pr-conversation-comments.json create mode 100644 docs/case-studies/issue-19/pr-114-data/pr-details.json create mode 100644 docs/case-studies/issue-19/pr-114-data/pr-diff.patch create mode 100644 docs/case-studies/issue-19/pr-114-data/pr-review-comments.json create mode 100644 docs/case-studies/issue-19/pr-114-data/pr-reviews.json create mode 100644 docs/case-studies/issue-19/pr-114-data/solution-draft-log-1.txt.gz create mode 100644 docs/case-studies/issue-19/pr-114-data/solution-draft-log-2.txt.gz create mode 100644 docs/case-studies/issue-21/README.md create mode 100644 docs/case-studies/issue-21/browser-commander-issue-27.md create mode 100644 docs/case-studies/issue-21/browser-commander-issue-29.md create mode 100644 docs/case-studies/issue-21/browser-commander-issue-31.md create mode 100644 docs/case-studies/issue-21/browser-commander-issue-33.md create mode 100644 docs/case-studies/issue-21/browser-commander-rust.yml create mode 100644 docs/case-studies/issue-25/README.md create mode 100644 docs/case-studies/issue-29/README.md create mode 100644 docs/case-studies/issue-32/README.md create mode 100644 docs/case-studies/issue-34/README.md create mode 100644 docs/case-studies/issue-38/README.md create mode 100644 docs/case-studies/issue-38/raw-data/downstream-meta-after-run-24985948212.json create mode 100644 docs/case-studies/issue-38/raw-data/downstream-meta-after-run-24985948212.log.gz create mode 100644 docs/case-studies/issue-38/raw-data/downstream-meta-before-run-24983875003.json create mode 100644 docs/case-studies/issue-38/raw-data/downstream-meta-before-run-24983875003.log.gz create mode 100644 docs/case-studies/issue-38/raw-data/downstream-meta-ontology-issue-3.json create mode 100644 docs/case-studies/issue-38/raw-data/downstream-meta-ontology-pr-4.json create mode 100644 docs/case-studies/issue-38/raw-data/issue-38-comments.json create mode 100644 docs/case-studies/issue-38/raw-data/issue-38.json create mode 100644 docs/case-studies/issue-38/raw-data/js-template-issue-search.json create mode 100644 docs/case-studies/issue-38/raw-data/main-run-24465255225.json create mode 100644 docs/case-studies/issue-38/raw-data/main-run-24465255225.log.gz create mode 100644 docs/case-studies/issue-38/raw-data/main-runs.json create mode 100644 docs/case-studies/issue-38/raw-data/pr-39-conversation-comments.json create mode 100644 docs/case-studies/issue-38/raw-data/pr-39-review-comments.json create mode 100644 docs/case-studies/issue-38/raw-data/pr-39-reviews.json create mode 100644 docs/case-studies/issue-38/raw-data/pr-39.json create mode 100644 docs/case-studies/issue-38/raw-data/pr-branch-runs.json create mode 100644 docs/case-studies/issue-38/raw-data/pr-run-25212295127.json create mode 100644 docs/case-studies/issue-38/raw-data/pr-run-25212295127.log.gz create mode 100644 docs/case-studies/issue-38/raw-data/rust-template-issue-search.json create mode 100644 docs/case-studies/issue-38/template-data/js-template-ci-tree.txt create mode 100644 docs/case-studies/issue-38/template-data/js-template-links.yml create mode 100644 docs/case-studies/issue-38/template-data/js-template-release.yml create mode 100644 docs/case-studies/issue-38/template-data/rust-template-ci-tree.txt create mode 100644 docs/case-studies/issue-38/template-data/rust-template-release-after.yml create mode 100644 docs/case-studies/issue-38/template-data/rust-template-release-before.yml create mode 100644 docs/case-studies/issue-52/README.md create mode 100644 docs/case-studies/issue-52/raw-data/issue-52-comments.json create mode 100644 docs/case-studies/issue-52/raw-data/issue-52.json create mode 100644 docs/case-studies/issue-52/raw-data/js-issue-62.json create mode 100644 docs/case-studies/issue-52/raw-data/vk-bot-desktop-issue-51.json create mode 100644 docs/case-studies/issue-52/raw-data/vk-bot-desktop-pr-52.json create mode 100644 docs/ci-cd/troubleshooting.md create mode 100644 examples/basic_usage.rs create mode 100644 experiments/test-changelog-parsing.rs create mode 100644 experiments/test-crates-io-check.rs create mode 100644 experiments/test-detect-code-changes.sh create mode 100755 experiments/test-version-check-dependencies.sh create mode 100755 experiments/test-version-check.sh create mode 100644 scripts/bump-version.rs create mode 100644 scripts/check-changelog-fragment.rs create mode 100644 scripts/check-file-size.rs create mode 100644 scripts/check-release-needed.rs create mode 100644 scripts/check-version-modification.rs create mode 100644 scripts/collect-changelog.rs create mode 100644 scripts/create-changelog-fragment.rs create mode 100644 scripts/create-github-release.rs create mode 100644 scripts/detect-code-changes.rs create mode 100644 scripts/get-bump-type.rs create mode 100644 scripts/get-version.rs create mode 100644 scripts/git-config.rs create mode 100644 scripts/publish-crate.rs create mode 100644 scripts/rust-paths.rs create mode 100644 scripts/version-and-commit.rs create mode 100644 scripts/wait-for-crate.rs create mode 100644 src/main.rs create mode 100644 src/sum.rs create mode 100644 tests/integration/mod.rs create mode 100644 tests/integration/sum.rs create mode 100644 tests/unit/ci-cd/changelog_parsing.rs create mode 100644 tests/unit/ci-cd/mod.rs create mode 100644 tests/unit/ci-cd/workflow_release.rs create mode 100644 tests/unit/ci-cd/workspace_manifest_resolution.rs create mode 100644 tests/unit/mod.rs create mode 100644 tests/unit/sum.rs diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..b43695f --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,675 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: + inputs: + release_mode: + description: 'Manual release mode' + required: true + type: choice + default: 'instant' + options: + - instant + - changelog-pr + bump_type: + description: 'Version bump type' + required: true + type: choice + options: + - patch + - minor + - major + description: + description: 'Release description (optional)' + required: false + type: string + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -Dwarnings + # Support both CARGO_REGISTRY_TOKEN (cargo's native env var) and CARGO_TOKEN (for backwards compatibility) + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + # Optional: set repository variable DOCKERHUB_IMAGE to namespace/image to publish Docker Hub releases. + DOCKERHUB_IMAGE: ${{ vars.DOCKERHUB_IMAGE }} + +jobs: + # === DETECT CHANGES - determines which jobs should run === + detect-changes: + name: Detect Changes + runs-on: ubuntu-latest + timeout-minutes: 5 + if: github.event_name != 'workflow_dispatch' + outputs: + rs-changed: ${{ steps.changes.outputs.rs-changed }} + toml-changed: ${{ steps.changes.outputs.toml-changed }} + docs-changed: ${{ steps.changes.outputs.docs-changed }} + workflow-changed: ${{ steps.changes.outputs.workflow-changed }} + any-code-changed: ${{ steps.changes.outputs.any-code-changed }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Detect changes + id: changes + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + run: rust-script scripts/detect-code-changes.rs + + # === CHANGELOG CHECK - only runs on PRs with code changes === + # Docs-only PRs (./docs folder, markdown files) don't require changelog fragments + changelog: + name: Changelog Fragment Check + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: [detect-changes] + if: github.event_name == 'pull_request' && needs.detect-changes.outputs.any-code-changed == 'true' + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Check for changelog fragments + env: + GITHUB_BASE_REF: ${{ github.base_ref }} + run: rust-script scripts/check-changelog-fragment.rs + + # === VERSION CHECK - prevents manual version modification in PRs === + # This ensures versions are only modified by the automated release pipeline + version-check: + name: Version Modification Check + runs-on: ubuntu-latest + timeout-minutes: 5 + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Check for manual version changes + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_BASE_REF: ${{ github.base_ref }} + run: rust-script scripts/check-version-modification.rs + + # === LINT AND FORMAT CHECK === + # Lint runs independently of changelog check - it's a fast check that should always run + # See: https://github.com/link-assistant/hive-mind/pull/1024 for why this dependency was removed + lint: + name: Lint and Format Check + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: [detect-changes] + # Note: always() is required because detect-changes is skipped on workflow_dispatch, + # and without always(), this job would also be skipped even though its condition includes workflow_dispatch. + # See: https://github.com/actions/runner/issues/491 + if: | + always() && !cancelled() && ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' || + needs.detect-changes.outputs.toml-changed == 'true' || + needs.detect-changes.outputs.docs-changed == 'true' || + needs.detect-changes.outputs.workflow-changed == 'true' + ) + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Install rust-script + run: cargo install rust-script + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Run Clippy + run: cargo clippy --all-targets --all-features + + - name: Check file size limit + run: rust-script scripts/check-file-size.rs + + # === TEST === + # Test runs independently of changelog check + test: + name: Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + timeout-minutes: 10 + needs: [detect-changes, changelog] + # Run if: push event, OR changelog succeeded, OR changelog was skipped (docs-only PR) + if: always() && !cancelled() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changelog.result == 'success' || needs.changelog.result == 'skipped') + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Run tests + run: cargo test --all-features --verbose + + - name: Run doc tests + run: cargo test --doc --verbose + + # === CODE COVERAGE === + # Generate and upload code coverage using cargo-llvm-cov + coverage: + name: Code Coverage + runs-on: ubuntu-latest + timeout-minutes: 15 + needs: [detect-changes] + if: | + always() && !cancelled() && ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' || + needs.detect-changes.outputs.toml-changed == 'true' + ) + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: llvm-tools-preview + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-coverage- + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Generate code coverage + run: cargo llvm-cov --all-features --lcov --output-path lcov.info + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: lcov.info + fail_ci_if_error: false + + # === BUILD === + # Build package - only runs if lint and test pass + build: + name: Build Package + runs-on: ubuntu-latest + timeout-minutes: 10 + needs: [lint, test] + if: always() && !cancelled() && needs.lint.result == 'success' && needs.test.result == 'success' + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-build- + + - name: Build release + run: cargo build --release --verbose + + - name: Check package + run: cargo package --list --allow-dirty + + # === AUTO RELEASE === + # Automatic release on push to main using changelog fragments + # This job automatically bumps version based on fragments in changelog.d/ + auto-release: + name: Auto Release + needs: [lint, test, build] + # Note: always() ensures consistent behavior with other jobs that depend on jobs using always(). + if: | + always() && !cancelled() && + github.event_name == 'push' && + github.ref == 'refs/heads/main' && + needs.build.result == 'success' + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + DOCKERHUB_USERNAME: ${{ vars.DOCKERHUB_USERNAME || secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Configure git + run: rust-script scripts/git-config.rs + + - name: Determine bump type from changelog fragments + id: bump_type + run: rust-script scripts/get-bump-type.rs + + - name: Check if version already released or no fragments + id: check + env: + HAS_FRAGMENTS: ${{ steps.bump_type.outputs.has_fragments }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: rust-script scripts/check-release-needed.rs + + - name: Collect changelog and bump version + id: version + if: steps.check.outputs.should_release == 'true' && steps.check.outputs.skip_bump != 'true' + run: | + rust-script scripts/version-and-commit.rs \ + --bump-type "${{ steps.bump_type.outputs.bump_type }}" + + - name: Get current version + id: current_version + if: steps.check.outputs.should_release == 'true' + run: rust-script scripts/get-version.rs + + - name: Build release + if: steps.check.outputs.should_release == 'true' + run: cargo build --release + + - name: Publish to Crates.io + if: steps.check.outputs.should_release == 'true' && steps.check.outputs.crate_published != 'true' + id: publish-crate + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + run: rust-script scripts/publish-crate.rs + + - name: Wait for Crate availability on Crates.io + if: steps.check.outputs.should_release == 'true' + run: rust-script scripts/wait-for-crate.rs --release-version "${{ steps.current_version.outputs.version }}" + + - name: Configure Docker Hub publishing + if: steps.check.outputs.should_release == 'true' + id: dockerhub + run: | + disable_dockerhub() { + echo "enabled=false" >> "$GITHUB_OUTPUT" + echo "$1" + } + + if [ -z "$DOCKERHUB_IMAGE" ]; then + disable_dockerhub "Docker Hub publishing disabled: DOCKERHUB_IMAGE repository variable is not set" + exit 0 + fi + + if [ ! -f Dockerfile ]; then + disable_dockerhub "Docker Hub publishing disabled: Dockerfile was not found at repository root" + exit 0 + fi + + if [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_TOKEN" ]; then + echo "::error::Docker Hub publishing requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN" + echo "Set DOCKERHUB_USERNAME as a repository variable or secret, and DOCKERHUB_TOKEN as a secret." + exit 1 + fi + + echo "enabled=true" >> "$GITHUB_OUTPUT" + echo "docker_hub_url=https://hub.docker.com/r/${DOCKERHUB_IMAGE}" >> "$GITHUB_OUTPUT" + + - name: Log in to Docker Hub + if: steps.dockerhub.outputs.enabled == 'true' + uses: docker/login-action@v4 + with: + username: ${{ env.DOCKERHUB_USERNAME }} + password: ${{ env.DOCKERHUB_TOKEN }} + + - name: Set up Docker Buildx + if: steps.dockerhub.outputs.enabled == 'true' + uses: docker/setup-buildx-action@v4 + + - name: Extract Docker metadata + if: steps.dockerhub.outputs.enabled == 'true' + id: docker-meta + uses: docker/metadata-action@v6 + with: + images: ${{ env.DOCKERHUB_IMAGE }} + tags: | + type=raw,value=latest + type=raw,value=${{ steps.current_version.outputs.version }} + labels: | + org.opencontainers.image.version=${{ steps.current_version.outputs.version }} + + - name: Publish Docker image to Docker Hub + if: steps.dockerhub.outputs.enabled == 'true' + uses: docker/build-push-action@v7 + with: + context: . + push: true + tags: ${{ steps.docker-meta.outputs.tags }} + labels: ${{ steps.docker-meta.outputs.labels }} + + - name: Create GitHub Release + if: steps.check.outputs.should_release == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCKER_HUB_URL: ${{ steps.dockerhub.outputs.docker_hub_url }} + run: | + # Use new_version from version-and-commit when available (tag-checked), else fall back to Cargo.toml version + RELEASE_VERSION="${{ steps.version.outputs.new_version }}" + if [ -z "$RELEASE_VERSION" ]; then + RELEASE_VERSION="${{ steps.current_version.outputs.version }}" + fi + + release_args=( + --release-version "$RELEASE_VERSION" + --repository "${{ github.repository }}" + ) + if [ -n "$DOCKER_HUB_URL" ]; then + release_args+=(--docker-hub-url "$DOCKER_HUB_URL") + fi + rust-script scripts/create-github-release.rs "${release_args[@]}" + + # === MANUAL INSTANT RELEASE === + # Manual release via workflow_dispatch - only after CI passes + manual-release: + name: Instant Release + needs: [lint, test, build] + # Note: always() is required to evaluate the condition when dependencies use always(). + # The build job ensures lint and test passed before this job runs. + if: | + always() && !cancelled() && + github.event_name == 'workflow_dispatch' && + github.event.inputs.release_mode == 'instant' && + needs.build.result == 'success' + runs-on: ubuntu-latest + timeout-minutes: 30 + env: + DOCKERHUB_USERNAME: ${{ vars.DOCKERHUB_USERNAME || secrets.DOCKERHUB_USERNAME }} + DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }} + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Configure git + run: rust-script scripts/git-config.rs + + - name: Collect changelog fragments + run: rust-script scripts/collect-changelog.rs + + - name: Version and commit + id: version + env: + BUMP_TYPE: ${{ github.event.inputs.bump_type }} + DESCRIPTION: ${{ github.event.inputs.description }} + run: rust-script scripts/version-and-commit.rs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" + + - name: Build release + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + run: cargo build --release + + - name: Publish to Crates.io + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + id: publish-crate + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + run: rust-script scripts/publish-crate.rs + + - name: Wait for Crate availability on Crates.io + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + run: rust-script scripts/wait-for-crate.rs --release-version "${{ steps.version.outputs.new_version }}" + + - name: Configure Docker Hub publishing + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + id: dockerhub + run: | + disable_dockerhub() { + echo "enabled=false" >> "$GITHUB_OUTPUT" + echo "$1" + } + + if [ -z "$DOCKERHUB_IMAGE" ]; then + disable_dockerhub "Docker Hub publishing disabled: DOCKERHUB_IMAGE repository variable is not set" + exit 0 + fi + + if [ ! -f Dockerfile ]; then + disable_dockerhub "Docker Hub publishing disabled: Dockerfile was not found at repository root" + exit 0 + fi + + if [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_TOKEN" ]; then + echo "::error::Docker Hub publishing requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN" + echo "Set DOCKERHUB_USERNAME as a repository variable or secret, and DOCKERHUB_TOKEN as a secret." + exit 1 + fi + + echo "enabled=true" >> "$GITHUB_OUTPUT" + echo "docker_hub_url=https://hub.docker.com/r/${DOCKERHUB_IMAGE}" >> "$GITHUB_OUTPUT" + + - name: Log in to Docker Hub + if: steps.dockerhub.outputs.enabled == 'true' + uses: docker/login-action@v4 + with: + username: ${{ env.DOCKERHUB_USERNAME }} + password: ${{ env.DOCKERHUB_TOKEN }} + + - name: Set up Docker Buildx + if: steps.dockerhub.outputs.enabled == 'true' + uses: docker/setup-buildx-action@v4 + + - name: Extract Docker metadata + if: steps.dockerhub.outputs.enabled == 'true' + id: docker-meta + uses: docker/metadata-action@v6 + with: + images: ${{ env.DOCKERHUB_IMAGE }} + tags: | + type=raw,value=latest + type=raw,value=${{ steps.version.outputs.new_version }} + labels: | + org.opencontainers.image.version=${{ steps.version.outputs.new_version }} + + - name: Publish Docker image to Docker Hub + if: steps.dockerhub.outputs.enabled == 'true' + uses: docker/build-push-action@v7 + with: + context: . + push: true + tags: ${{ steps.docker-meta.outputs.tags }} + labels: ${{ steps.docker-meta.outputs.labels }} + + - name: Create GitHub Release + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DOCKER_HUB_URL: ${{ steps.dockerhub.outputs.docker_hub_url }} + run: | + release_args=( + --release-version "${{ steps.version.outputs.new_version }}" + --repository "${{ github.repository }}" + ) + if [ -n "$DOCKER_HUB_URL" ]; then + release_args+=(--docker-hub-url "$DOCKER_HUB_URL") + fi + rust-script scripts/create-github-release.rs "${release_args[@]}" + + # === MANUAL CHANGELOG PR === + changelog-pr: + name: Create Changelog PR + if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'changelog-pr' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Create changelog fragment + env: + BUMP_TYPE: ${{ github.event.inputs.bump_type }} + DESCRIPTION: ${{ github.event.inputs.description }} + run: rust-script scripts/create-changelog-fragment.rs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore: add changelog for manual ${{ github.event.inputs.bump_type }} release' + branch: changelog-manual-release-${{ github.run_id }} + delete-branch: true + title: 'chore: manual ${{ github.event.inputs.bump_type }} release' + body: | + ## Manual Release Request + + This PR was created by a manual workflow trigger to prepare a **${{ github.event.inputs.bump_type }}** release. + + ### Release Details + - **Type:** ${{ github.event.inputs.bump_type }} + - **Description:** ${{ github.event.inputs.description || 'Manual release' }} + - **Triggered by:** @${{ github.actor }} + + ### Next Steps + 1. Review the changelog fragment in this PR + 2. Merge this PR to main + 3. The automated release workflow will publish to crates.io and create a GitHub release + + # === DEPLOY DOCUMENTATION === + # Deploy Rust API documentation to GitHub Pages after a successful package build. + # Keep this independent from package/GitHub release publication so the website + # still updates when the release path fails. Use the official Pages artifact + # deployment path so repositories configured with "GitHub Actions" as their + # Pages source fail this job if Pages cannot deploy. + # + # One-time setup: in the repository's Settings -> Pages, set Source to + # "GitHub Actions". Without this, the first run fails on actions/deploy-pages + # with "Get Pages site failed" / "Failed to create deployment". This cannot be + # configured from a workflow. See README.md "Deploying API documentation". + deploy-docs: + name: Deploy Rust Documentation + needs: [build] + if: | + !cancelled() && + needs.build.result == 'success' && ( + (github.event_name == 'push' && github.ref == 'refs/heads/main') || + (github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'instant') + ) + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - uses: actions/checkout@v6 + with: + ref: main + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build documentation + run: cargo doc --no-deps --all-features + + - name: Configure GitHub Pages + uses: actions/configure-pages@v6 + + - name: Upload GitHub Pages artifact + uses: actions/upload-pages-artifact@v5 + with: + path: target/doc + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2863516 --- /dev/null +++ b/.gitignore @@ -0,0 +1,57 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# See https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +# Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb + +# Generated by cargo mutants +# Contains mutation testing data +**/mutants.out*/ + +# IDE and editor files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS files +.DS_Store +Thumbs.db + +# Python virtual environments (for scripts) +.venv/ +venv/ +__pycache__/ +*.pyc +*.pyo + +# Coverage reports +*.lcov +coverage/ +tarpaulin-report.html + +# Benchmark results +criterion/ + +# Documentation build output +doc/ + +# Local development files +.env +.env.local +*.local + +# Log files +*.log +logs/ +ci-logs/ diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..a81614e --- /dev/null +++ b/.gitkeep @@ -0,0 +1 @@ +# .gitkeep file auto-generated at 2026-05-12T22:59:34.075Z for PR creation at branch issue-50-f6272907aac6 for issue https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/50 \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..ce4da86 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,34 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v5.0.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict + - id: check-toml + - id: debug-statements + + - repo: local + hooks: + - id: cargo-fmt + name: cargo fmt + entry: cargo fmt --all -- + language: system + types: [rust] + pass_filenames: false + + - id: cargo-clippy + name: cargo clippy + entry: cargo clippy --all-targets --all-features -- -D warnings + language: system + types: [rust] + pass_filenames: false + + - id: cargo-test + name: cargo test + entry: cargo test + language: system + types: [rust] + pass_filenames: false diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..d966725 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2504 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + + + + + + + + + + + + + + + + +## [0.15.0] - 2026-05-16 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fix publish steps overriding workflow-level CARGO_TOKEN fallback, breaking CARGO_REGISTRY_TOKEN-only configurations (#32) +- Fix non-fast-forward push failures in multi-workflow repos by adding fetch/rebase and push retry logic (#31) +- Add mono-repo path support to check-changelog-fragment.rs, check-version-modification.rs, and create-changelog-fragment.rs +- Add `!cancelled()` guard to test job condition to respect workflow cancellation + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation + +### Fixed + +- Change detection script now uses per-commit diff instead of full PR diff, so commits touching only non-code files correctly skip CI jobs even when earlier commits in the same PR changed code files + +### Fixed +- Make release scripts resolve the publishable crate manifest when the repository root uses a Cargo workspace manifest. + +### Fixed +- Decoupled GitHub Pages documentation deployment from package release publication and fixed release-script warning failures under `RUSTFLAGS=-Dwarnings`. + +### Changed +- Added explicit GitHub Actions job timeouts and documented Rust test timeout guidance. + +### Fixed +- Added a non-blocking warning threshold to the Rust file-size check so near-limit files are surfaced before concurrent PR merges can exceed the hard limit. + +### Fixed +- Made `create-github-release.rs` build GitHub release titles as `[Language] X.Y.Z` instead of reusing the tag prefix. + +### Added +- Added optional Docker Hub image publishing tied to Rust crate releases, including crates.io visibility waiting, version/latest image tags, and Docker Hub badges in GitHub release notes. + +### Changed +- Release completeness checks now self-heal when crates.io exists but configured Docker Hub or GitHub release artifacts are missing. + +### Fixed +- Switched documentation deployment to the official GitHub Pages artifact workflow so repositories using GitHub Actions as their Pages source do not get false-positive branch-push deploys. + +### Added +- Documented the one-time `Settings → Pages → Source = GitHub Actions` prerequisite for the `deploy-docs` job in `README.md` and as a comment above the `deploy-docs` job in `release.yml`, so downstream template users hit a documented setup step instead of a `Get Pages site failed` error on the first deploy. + +### Added +- Tracking case study at `docs/case-studies/issue-52/` registering the `browser-commander` + Playwright preview-regeneration pattern from [`konard/vk-bot-desktop#52`](https://github.com/konard/vk-bot-desktop/pull/52), with an activation checklist for when an example-app surface lands in this template. Documentation only — no workflow, script, or runtime code changes. Primary upstream tracking issue: [`link-foundation/js-ai-driven-development-pipeline-template#62`](https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/62). + +### Fixed +- Release automation now keeps the workspace package entry in `Cargo.lock` synchronized when `scripts/version-and-commit.rs` bumps `Cargo.toml`, preventing stale lock-file version diffs in later pull requests. + +## [0.14.0] - 2026-05-15 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fix publish steps overriding workflow-level CARGO_TOKEN fallback, breaking CARGO_REGISTRY_TOKEN-only configurations (#32) +- Fix non-fast-forward push failures in multi-workflow repos by adding fetch/rebase and push retry logic (#31) +- Add mono-repo path support to check-changelog-fragment.rs, check-version-modification.rs, and create-changelog-fragment.rs +- Add `!cancelled()` guard to test job condition to respect workflow cancellation + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation + +### Fixed + +- Change detection script now uses per-commit diff instead of full PR diff, so commits touching only non-code files correctly skip CI jobs even when earlier commits in the same PR changed code files + +### Fixed +- Make release scripts resolve the publishable crate manifest when the repository root uses a Cargo workspace manifest. + +### Fixed +- Decoupled GitHub Pages documentation deployment from package release publication and fixed release-script warning failures under `RUSTFLAGS=-Dwarnings`. + +### Changed +- Added explicit GitHub Actions job timeouts and documented Rust test timeout guidance. + +### Fixed +- Added a non-blocking warning threshold to the Rust file-size check so near-limit files are surfaced before concurrent PR merges can exceed the hard limit. + +### Fixed +- Made `create-github-release.rs` build GitHub release titles as `[Language] X.Y.Z` instead of reusing the tag prefix. + +### Added +- Added optional Docker Hub image publishing tied to Rust crate releases, including crates.io visibility waiting, version/latest image tags, and Docker Hub badges in GitHub release notes. + +### Changed +- Release completeness checks now self-heal when crates.io exists but configured Docker Hub or GitHub release artifacts are missing. + +### Fixed +- Switched documentation deployment to the official GitHub Pages artifact workflow so repositories using GitHub Actions as their Pages source do not get false-positive branch-push deploys. + +### Added +- Documented the one-time `Settings → Pages → Source = GitHub Actions` prerequisite for the `deploy-docs` job in `README.md` and as a comment above the `deploy-docs` job in `release.yml`, so downstream template users hit a documented setup step instead of a `Get Pages site failed` error on the first deploy. + +### Added +- Tracking case study at `docs/case-studies/issue-52/` registering the `browser-commander` + Playwright preview-regeneration pattern from [`konard/vk-bot-desktop#52`](https://github.com/konard/vk-bot-desktop/pull/52), with an activation checklist for when an example-app surface lands in this template. Documentation only — no workflow, script, or runtime code changes. Primary upstream tracking issue: [`link-foundation/js-ai-driven-development-pipeline-template#62`](https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/62). + +## [0.13.0] - 2026-05-12 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fix publish steps overriding workflow-level CARGO_TOKEN fallback, breaking CARGO_REGISTRY_TOKEN-only configurations (#32) +- Fix non-fast-forward push failures in multi-workflow repos by adding fetch/rebase and push retry logic (#31) +- Add mono-repo path support to check-changelog-fragment.rs, check-version-modification.rs, and create-changelog-fragment.rs +- Add `!cancelled()` guard to test job condition to respect workflow cancellation + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation + +### Fixed + +- Change detection script now uses per-commit diff instead of full PR diff, so commits touching only non-code files correctly skip CI jobs even when earlier commits in the same PR changed code files + +### Fixed +- Make release scripts resolve the publishable crate manifest when the repository root uses a Cargo workspace manifest. + +### Fixed +- Decoupled GitHub Pages documentation deployment from package release publication and fixed release-script warning failures under `RUSTFLAGS=-Dwarnings`. + +### Changed +- Added explicit GitHub Actions job timeouts and documented Rust test timeout guidance. + +### Fixed +- Added a non-blocking warning threshold to the Rust file-size check so near-limit files are surfaced before concurrent PR merges can exceed the hard limit. + +### Fixed +- Made `create-github-release.rs` build GitHub release titles as `[Language] X.Y.Z` instead of reusing the tag prefix. + +### Added +- Added optional Docker Hub image publishing tied to Rust crate releases, including crates.io visibility waiting, version/latest image tags, and Docker Hub badges in GitHub release notes. + +### Changed +- Release completeness checks now self-heal when crates.io exists but configured Docker Hub or GitHub release artifacts are missing. + +### Fixed +- Switched documentation deployment to the official GitHub Pages artifact workflow so repositories using GitHub Actions as their Pages source do not get false-positive branch-push deploys. + +### Added +- Documented the one-time `Settings → Pages → Source = GitHub Actions` prerequisite for the `deploy-docs` job in `README.md` and as a comment above the `deploy-docs` job in `release.yml`, so downstream template users hit a documented setup step instead of a `Get Pages site failed` error on the first deploy. + +## [0.12.0] - 2026-05-12 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fix publish steps overriding workflow-level CARGO_TOKEN fallback, breaking CARGO_REGISTRY_TOKEN-only configurations (#32) +- Fix non-fast-forward push failures in multi-workflow repos by adding fetch/rebase and push retry logic (#31) +- Add mono-repo path support to check-changelog-fragment.rs, check-version-modification.rs, and create-changelog-fragment.rs +- Add `!cancelled()` guard to test job condition to respect workflow cancellation + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation + +### Fixed + +- Change detection script now uses per-commit diff instead of full PR diff, so commits touching only non-code files correctly skip CI jobs even when earlier commits in the same PR changed code files + +### Fixed +- Make release scripts resolve the publishable crate manifest when the repository root uses a Cargo workspace manifest. + +### Fixed +- Decoupled GitHub Pages documentation deployment from package release publication and fixed release-script warning failures under `RUSTFLAGS=-Dwarnings`. + +### Changed +- Added explicit GitHub Actions job timeouts and documented Rust test timeout guidance. + +### Fixed +- Added a non-blocking warning threshold to the Rust file-size check so near-limit files are surfaced before concurrent PR merges can exceed the hard limit. + +### Fixed +- Made `create-github-release.rs` build GitHub release titles as `[Language] X.Y.Z` instead of reusing the tag prefix. + +### Added +- Added optional Docker Hub image publishing tied to Rust crate releases, including crates.io visibility waiting, version/latest image tags, and Docker Hub badges in GitHub release notes. + +### Changed +- Release completeness checks now self-heal when crates.io exists but configured Docker Hub or GitHub release artifacts are missing. + +### Fixed +- Switched documentation deployment to the official GitHub Pages artifact workflow so repositories using GitHub Actions as their Pages source do not get false-positive branch-push deploys. + +## [0.11.0] - 2026-05-09 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fix publish steps overriding workflow-level CARGO_TOKEN fallback, breaking CARGO_REGISTRY_TOKEN-only configurations (#32) +- Fix non-fast-forward push failures in multi-workflow repos by adding fetch/rebase and push retry logic (#31) +- Add mono-repo path support to check-changelog-fragment.rs, check-version-modification.rs, and create-changelog-fragment.rs +- Add `!cancelled()` guard to test job condition to respect workflow cancellation + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation + +### Fixed + +- Change detection script now uses per-commit diff instead of full PR diff, so commits touching only non-code files correctly skip CI jobs even when earlier commits in the same PR changed code files + +### Fixed +- Make release scripts resolve the publishable crate manifest when the repository root uses a Cargo workspace manifest. + +### Fixed +- Decoupled GitHub Pages documentation deployment from package release publication and fixed release-script warning failures under `RUSTFLAGS=-Dwarnings`. + +### Changed +- Added explicit GitHub Actions job timeouts and documented Rust test timeout guidance. + +### Fixed +- Added a non-blocking warning threshold to the Rust file-size check so near-limit files are surfaced before concurrent PR merges can exceed the hard limit. + +### Fixed +- Made `create-github-release.rs` build GitHub release titles as `[Language] X.Y.Z` instead of reusing the tag prefix. + +### Added +- Added optional Docker Hub image publishing tied to Rust crate releases, including crates.io visibility waiting, version/latest image tags, and Docker Hub badges in GitHub release notes. + +### Changed +- Release completeness checks now self-heal when crates.io exists but configured Docker Hub or GitHub release artifacts are missing. + +## [0.10.0] - 2026-05-09 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fix publish steps overriding workflow-level CARGO_TOKEN fallback, breaking CARGO_REGISTRY_TOKEN-only configurations (#32) +- Fix non-fast-forward push failures in multi-workflow repos by adding fetch/rebase and push retry logic (#31) +- Add mono-repo path support to check-changelog-fragment.rs, check-version-modification.rs, and create-changelog-fragment.rs +- Add `!cancelled()` guard to test job condition to respect workflow cancellation + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation + +### Fixed + +- Change detection script now uses per-commit diff instead of full PR diff, so commits touching only non-code files correctly skip CI jobs even when earlier commits in the same PR changed code files + +### Fixed +- Make release scripts resolve the publishable crate manifest when the repository root uses a Cargo workspace manifest. + +### Fixed +- Decoupled GitHub Pages documentation deployment from package release publication and fixed release-script warning failures under `RUSTFLAGS=-Dwarnings`. + +### Changed +- Added explicit GitHub Actions job timeouts and documented Rust test timeout guidance. + +### Fixed +- Added a non-blocking warning threshold to the Rust file-size check so near-limit files are surfaced before concurrent PR merges can exceed the hard limit. + +### Fixed +- Made `create-github-release.rs` build GitHub release titles as `[Language] X.Y.Z` instead of reusing the tag prefix. + +## [0.9.0] - 2026-05-03 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fix publish steps overriding workflow-level CARGO_TOKEN fallback, breaking CARGO_REGISTRY_TOKEN-only configurations (#32) +- Fix non-fast-forward push failures in multi-workflow repos by adding fetch/rebase and push retry logic (#31) +- Add mono-repo path support to check-changelog-fragment.rs, check-version-modification.rs, and create-changelog-fragment.rs +- Add `!cancelled()` guard to test job condition to respect workflow cancellation + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation + +### Fixed + +- Change detection script now uses per-commit diff instead of full PR diff, so commits touching only non-code files correctly skip CI jobs even when earlier commits in the same PR changed code files + +### Fixed +- Make release scripts resolve the publishable crate manifest when the repository root uses a Cargo workspace manifest. + +### Fixed +- Decoupled GitHub Pages documentation deployment from package release publication and fixed release-script warning failures under `RUSTFLAGS=-Dwarnings`. + +### Changed +- Added explicit GitHub Actions job timeouts and documented Rust test timeout guidance. + +### Fixed +- Added a non-blocking warning threshold to the Rust file-size check so near-limit files are surfaced before concurrent PR merges can exceed the hard limit. + +## [0.8.0] - 2026-05-01 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fix publish steps overriding workflow-level CARGO_TOKEN fallback, breaking CARGO_REGISTRY_TOKEN-only configurations (#32) +- Fix non-fast-forward push failures in multi-workflow repos by adding fetch/rebase and push retry logic (#31) +- Add mono-repo path support to check-changelog-fragment.rs, check-version-modification.rs, and create-changelog-fragment.rs +- Add `!cancelled()` guard to test job condition to respect workflow cancellation + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation + +### Fixed + +- Change detection script now uses per-commit diff instead of full PR diff, so commits touching only non-code files correctly skip CI jobs even when earlier commits in the same PR changed code files + +### Fixed +- Make release scripts resolve the publishable crate manifest when the repository root uses a Cargo workspace manifest. + +### Fixed +- Decoupled GitHub Pages documentation deployment from package release publication and fixed release-script warning failures under `RUSTFLAGS=-Dwarnings`. + +## [0.7.0] - 2026-04-14 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fix publish steps overriding workflow-level CARGO_TOKEN fallback, breaking CARGO_REGISTRY_TOKEN-only configurations (#32) +- Fix non-fast-forward push failures in multi-workflow repos by adding fetch/rebase and push retry logic (#31) +- Add mono-repo path support to check-changelog-fragment.rs, check-version-modification.rs, and create-changelog-fragment.rs +- Add `!cancelled()` guard to test job condition to respect workflow cancellation + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation + +### Fixed + +- Change detection script now uses per-commit diff instead of full PR diff, so commits touching only non-code files correctly skip CI jobs even when earlier commits in the same PR changed code files + +## [0.6.0] - 2026-04-13 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fix publish steps overriding workflow-level CARGO_TOKEN fallback, breaking CARGO_REGISTRY_TOKEN-only configurations (#32) +- Fix non-fast-forward push failures in multi-workflow repos by adding fetch/rebase and push retry logic (#31) +- Add mono-repo path support to check-changelog-fragment.rs, check-version-modification.rs, and create-changelog-fragment.rs +- Add `!cancelled()` guard to test job condition to respect workflow cancellation + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation + +## [0.5.0] - 2026-04-13 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation + +## [0.4.0] - 2026-04-13 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +## [0.3.0] - 2026-04-13 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 + +## [0.2.0] - 2026-03-11 + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection + +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` + +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references + +## [0.1.0] - 2025-01-XX + +### Added + +- Initial project structure +- Basic example functions (add, multiply, delay) +- Comprehensive test suite +- Code quality tools (rustfmt, clippy) +- Pre-commit hooks configuration +- GitHub Actions CI/CD pipeline +- Changelog fragment system (similar to Changesets/Scriv) +- Release automation (GitHub releases) +- Template structure for AI-driven Rust development \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..96300ba --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,302 @@ +# Contributing to rust-ai-driven-development-pipeline-template + +Thank you for your interest in contributing! This document provides guidelines and instructions for contributing to this project. + +## Development Setup + +1. **Fork and clone the repository** + + ```bash + git clone https://github.com/YOUR-USERNAME/rust-ai-driven-development-pipeline-template.git + cd rust-ai-driven-development-pipeline-template + ``` + +2. **Install Rust** + + Install Rust using rustup (if not already installed): + + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + ``` + +3. **Install development tools** + + ```bash + rustup component add rustfmt clippy + cargo install rust-script + ``` + +4. **Install pre-commit hooks** (optional but recommended) + + ```bash + pip install pre-commit + pre-commit install + ``` + +5. **Build the project** + + ```bash + cargo build + ``` + +## Development Workflow + +1. **Create a feature branch** + + ```bash + git checkout -b feature/my-feature + ``` + +2. **Make your changes** + + - Write code following the project's style guidelines + - Add tests for any new functionality + - Update documentation as needed + +3. **Run quality checks** + + ```bash + # Format code + cargo fmt + + # Run Clippy lints + cargo clippy --all-targets --all-features + + # Check file sizes (requires rust-script) + rust-script scripts/check-file-size.rs + + # Run all checks together + cargo fmt --check && cargo clippy --all-targets --all-features && rust-script scripts/check-file-size.rs + ``` + +4. **Run tests** + + ```bash + # Run all tests + cargo test + + # Run tests with verbose output + cargo test --verbose + + # Run doc tests + cargo test --doc + + # Run a specific test + cargo test test_name + ``` + + CI caps each test-matrix job at 10 minutes. Rust's built-in `cargo test` runner does not provide a portable global per-test timeout, so wrap long-running network, IO, or async tests with explicit test-level deadlines. If a repository adopts `cargo nextest`, configure runner deadlines with options such as `--slow-timeout` and `--leak-timeout`. + +5. **Add a changelog fragment** + + For any user-facing changes, create a changelog fragment: + + ```bash + # Create a new file in changelog.d/ + # Format: YYYYMMDD_HHMMSS_description.md + touch changelog.d/$(date +%Y%m%d_%H%M%S)_my_change.md + ``` + + Edit the file to document your changes: + + ```markdown + ### Added + - Description of new feature + + ### Fixed + - Description of bug fix + ``` + + **Why fragments?** This prevents merge conflicts in CHANGELOG.md when multiple PRs are open simultaneously. + +6. **Commit your changes** + + ```bash + git add . + git commit -m "feat: add new feature" + ``` + + Pre-commit hooks will automatically run and check your code. + +7. **Push and create a Pull Request** + + ```bash + git push origin feature/my-feature + ``` + + Then create a Pull Request on GitHub. + +## Code Style Guidelines + +This project uses: + +- **rustfmt** for code formatting +- **Clippy** for linting with pedantic and nursery lints enabled +- **cargo test** for testing + +### Code Standards + +- Follow Rust idioms and best practices +- Use documentation comments (`///`) for all public APIs +- Write tests for all new functionality +- Keep functions focused and reasonably sized +- Keep files under 1000 lines +- Use meaningful variable and function names + +### Documentation Format + +Use Rust documentation comments: + +```rust +/// Brief description of the function. +/// +/// Longer description if needed. +/// +/// # Arguments +/// +/// * `arg1` - Description of arg1 +/// * `arg2` - Description of arg2 +/// +/// # Returns +/// +/// Description of return value +/// +/// # Errors +/// +/// Description of when errors are returned +/// +/// # Examples +/// +/// ``` +/// use my_package::example_function; +/// let result = example_function(1, 2); +/// assert_eq!(result, 3); +/// ``` +pub fn example_function(arg1: i32, arg2: i32) -> i32 { + arg1 + arg2 +} +``` + +## Testing Guidelines + +- Write tests for all new features +- Maintain or improve test coverage +- Use descriptive test names +- Organize tests in modules when appropriate +- Use `#[cfg(test)]` for test-only code + +Example test structure: + +```rust +#[cfg(test)] +mod tests { + use super::*; + + mod my_feature_tests { + use super::*; + + #[test] + fn test_basic_functionality() { + assert_eq!(my_function(), expected_result); + } + + #[test] + fn test_edge_case() { + assert_eq!(my_function(edge_case_input), expected_result); + } + } +} +``` + +## Pull Request Process + +1. Ensure all tests pass locally +2. Update documentation if needed +3. Add a changelog fragment (see step 5 in Development Workflow) +4. Ensure the PR description clearly describes the changes +5. Link any related issues in the PR description +6. Wait for CI checks to pass +7. Address any review feedback + +## Changelog Management + +This project uses a fragment-based changelog system similar to [Scriv](https://scriv.readthedocs.io/) (Python) and [Changesets](https://github.com/changesets/changesets) (JavaScript). + +### Creating a Fragment + +```bash +# Create a new fragment with timestamp +touch changelog.d/$(date +%Y%m%d_%H%M%S)_description.md +``` + +### Fragment Categories + +Use these categories in your fragments: + +- **Added**: New features +- **Changed**: Changes to existing functionality +- **Deprecated**: Features that will be removed in future +- **Removed**: Features that were removed +- **Fixed**: Bug fixes +- **Security**: Security-related changes + +### During Release + +Fragments are automatically collected into CHANGELOG.md during the release process. The release workflow: + +1. Collects all fragments +2. Updates CHANGELOG.md with the new version entry +3. Removes processed fragment files +4. Bumps the version in Cargo.toml +5. Creates a git tag and GitHub release + +## Project Structure + +``` +. +├── .github/workflows/ # GitHub Actions CI/CD +├── changelog.d/ # Changelog fragments +│ ├── README.md # Fragment instructions +│ └── *.md # Individual changelog fragments +├── examples/ # Usage examples +├── scripts/ # Rust scripts (via rust-script) +├── src/ +│ ├── lib.rs # Library entry point +│ └── main.rs # Binary entry point +├── tests/ # Integration tests +├── .gitignore # Git ignore patterns +├── .pre-commit-config.yaml # Pre-commit hooks +├── Cargo.toml # Project configuration +├── CHANGELOG.md # Project changelog +├── CONTRIBUTING.md # This file +├── LICENSE # Unlicense (public domain) +└── README.md # Project README +``` + +## Release Process + +This project uses semantic versioning (MAJOR.MINOR.PATCH): + +- **MAJOR**: Breaking changes +- **MINOR**: New features (backward compatible) +- **PATCH**: Bug fixes (backward compatible) + +Releases are managed through GitHub releases. To trigger a release: + +1. Manually trigger the release workflow with a version bump type +2. Or: Update the version in Cargo.toml and push to main + +## Getting Help + +- Open an issue for bugs or feature requests +- Use discussions for questions and general help +- Check existing issues and PRs before creating new ones + +## Code of Conduct + +- Be respectful and inclusive +- Provide constructive feedback +- Focus on what is best for the community +- Show empathy towards other community members + +Thank you for contributing! diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..a50c4bb --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,368 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "ctor" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec09e802f5081de6157da9a75701d6c713d8dc3ba52571fd4bd25f412644e8a6" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "dtor" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97cbdf2ad6846025e8e25df05171abfb30e3ababa12ee0a0e44b9bbe570633a8" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7454e41ff9012c00d53cf7f475c5e3afa3b91b7c90568495495e8d9bf47a1055" + +[[package]] +name = "example-sum-package-name" +version = "0.15.0" +dependencies = [ + "clap", + "lino-arguments", + "regex", + "walkdir", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "lino-arguments" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be512a5c5eacea6ef5ec015fb0c7e1725c8e4cda1befd31606e203f281069968" +dependencies = [ + "clap", + "ctor", + "dotenvy", + "lino-env", + "serde", + "thiserror", +] + +[[package]] +name = "lino-env" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f453c53827aabe91a3d3856d61d14ae3867ab1a4344db22f9fa5396664c8d0e" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "proc-macro2" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] diff --git a/Cargo.toml b/Cargo.toml index 5e25ed6..620bfad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,33 +1,31 @@ [package] -name = "docker-git-browser-connection" -version = "0.1.0" +name = "example-sum-package-name" +version = "0.15.0" edition = "2021" -description = "Rust module for noVNC + browser connection (single browser for docker-git, MCP and Hermes tools) - per issue #347" -license = "MIT" -keywords = ["docker-git", "browser", "novnc", "cdp", "rust"] -repository = "https://github.com/ProverCoderAI/rust-browser-connection" +description = "A Rust package template for AI-driven development" +readme = "README.md" +license = "Unlicense" +keywords = ["template", "rust", "ai-driven"] +categories = ["development-tools"] +repository = "https://github.com/link-foundation/rust-ai-driven-development-pipeline-template" +documentation = "https://github.com/link-foundation/rust-ai-driven-development-pipeline-template" rust-version = "1.70" [lib] -name = "docker_git_browser_connection" +name = "example_sum_package_name" path = "src/lib.rs" [[bin]] -name = "docker-git-browser-connection" +name = "example-sum-package-name" path = "src/main.rs" [dependencies] -bollard = "0.16" -tokio = { version = "1", features = ["full"] } -serde = { version = "1", features = ["derive"] } -clap = { version = "4.4", features = ["derive"] } -anyhow = "1.0" -futures = "0.3" -log = "0.4" -env_logger = "0.11" +lino-arguments = "0.3" +clap = { version = "4.4", features = ["derive", "env"] } [dev-dependencies] -tempfile = "3.0" +regex = "1" +walkdir = "2" [lints.rust] unsafe_code = "forbid" @@ -37,6 +35,12 @@ all = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } nursery = { level = "warn", priority = -1 } +# Allow some common patterns +module_name_repetitions = "allow" +too_many_lines = "allow" +missing_errors_doc = "allow" +missing_panics_doc = "allow" + [[test]] name = "unit" path = "tests/unit/mod.rs" diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..fdddb29 --- /dev/null +++ b/LICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to diff --git a/README.md b/README.md new file mode 100644 index 0000000..1ade911 --- /dev/null +++ b/README.md @@ -0,0 +1,327 @@ +# rust-ai-driven-development-pipeline-template + +A comprehensive template for AI-driven Rust development with full CI/CD pipeline support. + +[![CI/CD Pipeline](https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/workflows/CI%2FCD%20Pipeline/badge.svg)](https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions?workflow=CI%2FCD+Pipeline) +[![Crates.io](https://img.shields.io/crates/v/example-sum-package-name?label=crates.io&style=flat)](https://crates.io/crates/example-sum-package-name) +[![Docs.rs](https://docs.rs/example-sum-package-name/badge.svg)](https://docs.rs/example-sum-package-name) +[![Rust Version](https://img.shields.io/badge/rust-1.70%2B-blue.svg)](https://www.rust-lang.org/) +[![Codecov](https://codecov.io/gh/link-foundation/rust-ai-driven-development-pipeline-template/branch/main/graph/badge.svg)](https://codecov.io/gh/link-foundation/rust-ai-driven-development-pipeline-template) +[![License: Unlicense](https://img.shields.io/badge/license-Unlicense-blue.svg)](http://unlicense.org/) + +## Features + +- **Rust stable support**: Works with Rust stable version +- **Cross-platform testing**: CI runs on Ubuntu, macOS, and Windows +- **Comprehensive testing**: Unit tests, integration tests, and doc tests +- **Code quality**: rustfmt + Clippy with pedantic lints +- **Pre-commit hooks**: Automated code quality checks before commits +- **CI/CD pipeline**: GitHub Actions with multi-platform support +- **Changelog management**: Fragment-based changelog (like Changesets/Scriv) +- **Code coverage**: Automated coverage reports with cargo-llvm-cov and Codecov +- **Release automation**: Automatic GitHub releases, crates.io publishing, and optional Docker Hub image publishing +- **Template-safe defaults**: CI/CD skips publishing when package name is `example-sum-package-name` + +## Quick Start + +### Using This Template + +1. Click "Use this template" on GitHub to create a new repository +2. Clone your new repository +3. Update `Cargo.toml`: + - Change `name` from `example-sum-package-name` to your package name + - Update `description`, `repository`, and `documentation` URLs + - Update `[lib]` name and `[[bin]]` name +4. Update imports in `src/main.rs`, `tests/`, and `examples/` +5. Build and start developing! + +### Development Setup + +```bash +# Clone the repository +git clone https://github.com/link-foundation/rust-ai-driven-development-pipeline-template.git +cd rust-ai-driven-development-pipeline-template + +# Build the project +cargo build + +# Run tests +cargo test + +# Run the CLI binary +cargo run -- --a 3 --b 7 + +# Run an example +cargo run --example basic_usage +``` + +### Running Tests + +```bash +# Run all tests +cargo test + +# Run tests with verbose output +cargo test --verbose + +# Run doc tests +cargo test --doc + +# Run a specific test +cargo test test_sum_positive_numbers + +# Run tests with output +cargo test -- --nocapture +``` + +CI caps each test-matrix job at 10 minutes. `cargo test` does not provide a portable global per-test timeout, so long-running network, IO, or async tests should use explicit test-level timeouts. Repositories that adopt `cargo nextest` can configure runner deadlines with options such as `--slow-timeout` and `--leak-timeout`. + +### Code Quality Checks + +```bash +# Format code +cargo fmt + +# Check formatting (CI style) +cargo fmt --check + +# Run Clippy lints +cargo clippy --all-targets --all-features + +# Check file size limits (requires rust-script: cargo install rust-script) +rust-script scripts/check-file-size.rs + +# Run all checks +cargo fmt --check && cargo clippy --all-targets --all-features && rust-script scripts/check-file-size.rs +``` + +## Project Structure + +``` +. +├── .github/ +│ └── workflows/ +│ └── release.yml # CI/CD pipeline configuration +├── changelog.d/ # Changelog fragments +│ ├── README.md # Fragment instructions +│ └── *.md # Individual changelog entries +├── examples/ +│ └── basic_usage.rs # Usage examples +├── experiments/ # Experiment and debug scripts +│ ├── test-changelog-parsing.rs # Changelog parsing validation +│ └── test-crates-io-check.rs # Crates.io version check validation +├── scripts/ # Rust scripts (via rust-script) +│ ├── bump-version.rs # Version bumping utility +│ ├── check-changelog-fragment.rs # Changelog fragment validation +│ ├── check-file-size.rs # File size validation script +│ ├── check-release-needed.rs # Release necessity check +│ ├── check-version-modification.rs # Version modification detection +│ ├── collect-changelog.rs # Changelog collection script +│ ├── create-changelog-fragment.rs # Changelog fragment creation +│ ├── create-github-release.rs # GitHub release creation +│ ├── detect-code-changes.rs # Code change detection for CI +│ ├── get-bump-type.rs # Version bump type determination +│ ├── get-version.rs # Version extraction from Cargo.toml +│ ├── git-config.rs # Git configuration for CI +│ ├── publish-crate.rs # Crates.io publishing +│ ├── rust-paths.rs # Rust root path detection +│ ├── version-and-commit.rs # CI/CD version management +│ └── wait-for-crate.rs # Crates.io availability wait before image publishing +├── src/ +│ ├── lib.rs # Library entry point +│ ├── main.rs # CLI binary (uses lino-arguments) +│ └── sum.rs # Sum function module +├── tests/ +│ ├── unit_tests.rs # Unit test entry point +│ ├── unit/ +│ │ ├── mod.rs +│ │ ├── sum.rs # Unit tests for sum function +│ │ └── ci-cd/ +│ │ ├── mod.rs +│ │ └── changelog_parsing.rs # CI/CD changelog parsing tests +│ ├── integration_tests.rs # Integration test entry point +│ └── integration/ +│ ├── mod.rs +│ └── sum.rs # CLI integration tests +├── .gitignore # Git ignore patterns +├── .pre-commit-config.yaml # Pre-commit hooks configuration +├── Cargo.toml # Project configuration +├── CHANGELOG.md # Project changelog +├── CONTRIBUTING.md # Contribution guidelines +├── LICENSE # Unlicense (public domain) +└── README.md # This file +``` + +## Design Choices + +### Example Application + +The template includes a simple CLI sum application using [lino-arguments](https://github.com/link-foundation/lino-arguments) (a drop-in replacement for clap that also supports `.lenv` and `.env` files). This demonstrates: + +- Library module (`src/sum.rs`) with a pure function +- CLI binary (`src/main.rs`) using `lino-arguments` for argument parsing +- Unit tests (`tests/unit/sum.rs`) testing the function directly +- Integration tests (`tests/integration/sum.rs`) testing the full CLI binary + +### Code Quality Tools + +- **rustfmt**: Standard Rust code formatter +- **Clippy**: Rust linter with pedantic and nursery lints enabled +- **Pre-commit hooks**: Automated checks before each commit + +### Testing Strategy + +The template supports multiple levels of testing: + +- **Unit tests**: In `tests/unit/` directory, testing functions directly +- **Integration tests**: In `tests/integration/` directory, testing CLI binary +- **CI/CD tests**: In `tests/unit/ci-cd/` directory, testing CI/CD script logic +- **Doc tests**: In documentation examples using `///` comments +- **Examples**: In `examples/` directory (also serve as documentation) + +Users can easily delete CI/CD tests in `tests/unit/ci-cd/` if not needed. + +### Changelog Management + +This template uses a fragment-based changelog system similar to [Changesets](https://github.com/changesets/changesets) and [Scriv](https://scriv.readthedocs.io/). + +```bash +# Create a changelog fragment +touch changelog.d/$(date +%Y%m%d_%H%M%S)_my_change.md + +# Edit the fragment to document your changes +``` + +### CI/CD Pipeline + +The GitHub Actions workflow provides: + +1. **Change detection**: Only runs relevant jobs based on changed files +2. **Changelog check**: Validates changelog fragments on PRs with code changes +3. **Version check**: Prevents manual version modification in PRs +4. **Linting**: rustfmt and Clippy checks +5. **Test matrix**: 3 OS (Ubuntu, macOS, Windows) with Rust stable +6. **Code coverage**: cargo-llvm-cov with Codecov upload +7. **Building**: Release build and package validation +8. **Auto release**: Automatic releases when changelog fragments are merged to main +9. **Manual release**: Workflow dispatch with version bump type selection +10. **Optional Docker Hub publishing**: Pushes `latest` and version tags after the matching crates.io version is visible +11. **Documentation**: Automatic docs deployment to GitHub Pages after release + +### Template-Safe Defaults + +The default package name `example-sum-package-name` triggers skip logic in CI/CD scripts: +- `publish-crate.rs` skips crates.io publishing +- `create-github-release.rs` skips GitHub release creation +- Docker Hub publishing stays disabled unless `DOCKERHUB_IMAGE` is configured and a root `Dockerfile` exists + +Rename the package in `Cargo.toml` to enable full CI/CD publishing. + +## Configuration + +### Updating Package Name + +After creating a repository from this template: + +1. Update `Cargo.toml`: + - Change `name` field from `example-sum-package-name` + - Update `repository` and `documentation` URLs + - Change `[lib]` name and `[[bin]]` name + +2. Update imports: + - `src/main.rs` + - `tests/unit/sum.rs` + - `tests/integration/sum.rs` + - `examples/basic_usage.rs` + +3. Update badges in this `README.md` + +### Optional Docker Hub Publishing + +Projects that ship a Docker image can publish Docker Hub releases from the same Rust release workflow. Add a root `Dockerfile`, then configure: + +| Name | Type | Example | Purpose | +| ---- | ---- | ------- | ------- | +| `DOCKERHUB_IMAGE` | Repository variable | `my-dockerhub-user/my-image` | Docker Hub repository to publish | +| `DOCKERHUB_USERNAME` | Repository variable or secret | `my-dockerhub-user` | Docker Hub login username | +| `DOCKERHUB_TOKEN` | Repository secret | Docker Hub access token | Docker Hub login token | + +When configured, the release workflow publishes both `latest` and the Cargo package version tag, for example `my-dockerhub-user/my-image:0.10.0`. Docker publishing runs only after crates.io reports the matching version as available, and release checks rerun missing Docker Hub or GitHub release artifacts without bumping the version again. + +Add a visible Docker Hub badge next to the crates.io badge in repositories that enable image publishing: + +```markdown +[![Docker Hub](https://img.shields.io/docker/v/my-dockerhub-user/my-image?label=docker%20hub)](https://hub.docker.com/r/my-dockerhub-user/my-image) +``` + +## Deploying API documentation + +The `deploy-docs` job in `.github/workflows/release.yml` publishes `cargo doc --no-deps --all-features` output to GitHub Pages on every push to `main` and on `workflow_dispatch` with `release_mode == 'instant'`. It uses the official `actions/configure-pages` / `actions/upload-pages-artifact` / `actions/deploy-pages` flow, which requires the repository's Pages source to be set to **GitHub Actions**. + +Before the first run on `main`, open **Settings → Pages** of the new repository and set **Source = GitHub Actions**. This is a one-time manual step and cannot be configured from a workflow. The `deploy-docs` job will then provision the Pages site on its first run. + +If this step is skipped, the first `deploy-docs` run fails on `actions/deploy-pages@v5` with `Error: Get Pages site failed.` / `Error: Failed to create deployment`. Flip the Pages source as described above and re-run the failed job; no workflow changes are required. + +## Scripts Reference + +All scripts in `scripts/` are Rust scripts that use [rust-script](https://github.com/fornwall/rust-script). +Install rust-script with: `cargo install rust-script` + +| Command | Description | +| ------------------------------------- | ------------------------ | +| `cargo test` | Run all tests | +| `cargo fmt` | Format code | +| `cargo clippy` | Run lints | +| `cargo run -- --a 3 --b 7` | Run CLI (sum 3 + 7) | +| `cargo run --example basic_usage` | Run example | +| `rust-script scripts/check-file-size.rs` | Check file size limits | +| `rust-script scripts/bump-version.rs` | Bump version | + +## Example Usage + +```rust +use example_sum_package_name::sum; + +fn main() { + let result = sum(2, 3); + println!("2 + 3 = {result}"); +} +``` + +See `examples/basic_usage.rs` for more examples. + +## Contributing + +Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines. + +### Development Workflow + +1. Fork the repository +2. Create a feature branch: `git checkout -b feature/my-feature` +3. Make your changes and add tests +4. Run quality checks: `cargo fmt && cargo clippy && cargo test` +5. Add a changelog fragment +6. Commit your changes (pre-commit hooks will run automatically) +7. Push and create a Pull Request + +## License + +[Unlicense](LICENSE) - Public Domain + +This is free and unencumbered software released into the public domain. See [LICENSE](LICENSE) for details. + +## Acknowledgments + +Inspired by: +- [js-ai-driven-development-pipeline-template](https://github.com/link-foundation/js-ai-driven-development-pipeline-template) +- [python-ai-driven-development-pipeline-template](https://github.com/link-foundation/python-ai-driven-development-pipeline-template) +- [lino-arguments](https://github.com/link-foundation/lino-arguments) +- [trees-rs](https://github.com/linksplatform/trees-rs) + +## Resources + +- [Rust Book](https://doc.rust-lang.org/book/) +- [Cargo Book](https://doc.rust-lang.org/cargo/) +- [Clippy Documentation](https://rust-lang.github.io/rust-clippy/) +- [rustfmt Documentation](https://rust-lang.github.io/rustfmt/) +- [Pre-commit Documentation](https://pre-commit.com/) diff --git a/changelog.d/20251227_224645_changeset_support.md b/changelog.d/20251227_224645_changeset_support.md new file mode 100644 index 0000000..c25cd3f --- /dev/null +++ b/changelog.d/20251227_224645_changeset_support.md @@ -0,0 +1,14 @@ +--- +bump: minor +--- + +### Added +- Changeset-style fragment format with frontmatter for specifying version bump type +- New `get-bump-type.mjs` script to automatically determine version bump from fragments +- Automatic version bumping on merge to main based on changelog fragments +- Detailed documentation for the changelog fragment system in `changelog.d/README.md` + +### Changed +- Updated `collect-changelog.mjs` to strip frontmatter when collecting fragments +- Updated `version-and-commit.mjs` to handle frontmatter in fragments +- Enhanced release workflow to automatically determine bump type from changesets diff --git a/changelog.d/20251229_143823_fix_ci_workflow_dependencies.md b/changelog.d/20251229_143823_fix_ci_workflow_dependencies.md new file mode 100644 index 0000000..2667f49 --- /dev/null +++ b/changelog.d/20251229_143823_fix_ci_workflow_dependencies.md @@ -0,0 +1,10 @@ +--- +bump: patch +--- + +### Changed +- Add `detect-changes` job with cross-platform `detect-code-changes.mjs` script +- Make lint job independent of changelog check (runs based on file changes only) +- Allow docs-only PRs without changelog fragment requirement +- Handle changelog check 'skipped' state in dependent jobs +- Exclude `changelog.d/`, `docs/`, `experiments/`, `examples/` folders and markdown files from code changes detection diff --git a/changelog.d/20251231_115800_fix_readme_script_references.md b/changelog.d/20251231_115800_fix_readme_script_references.md new file mode 100644 index 0000000..7337c49 --- /dev/null +++ b/changelog.d/20251231_115800_fix_readme_script_references.md @@ -0,0 +1,4 @@ +### Fixed +- Fixed README.md to correctly reference Node.js scripts (`.mjs`) instead of Python scripts (`.py`) +- Updated project structure in README.md to match actual script files in `scripts/` directory +- Fixed example code in README.md that had invalid Rust with two `main` functions diff --git a/changelog.d/20260107_apply_best_practices.md b/changelog.d/20260107_apply_best_practices.md new file mode 100644 index 0000000..a2d8563 --- /dev/null +++ b/changelog.d/20260107_apply_best_practices.md @@ -0,0 +1,21 @@ +--- +bump: minor +--- + +### Added + +- Added crates.io publishing support to CI/CD workflow +- Added `release_mode` input with "instant" and "changelog-pr" options for manual releases +- Added `--tag-prefix` and `--crates-io-url` options to create-github-release.mjs script +- Added comprehensive case study documentation for Issue #11 in docs/case-studies/issue-11/ + +### Changed + +- Changed changelog fragment check from warning to error (exit 1) to enforce changelog requirements +- Updated job conditions with `always() && !cancelled()` to fix workflow_dispatch job skipping issue +- Renamed manual-release job to "Instant Release" for clarity + +### Fixed + +- Fixed deprecated `::set-output` GitHub Actions command in version-and-commit.mjs +- Fixed workflow_dispatch triggering issues where lint/build/release jobs were incorrectly skipped diff --git a/changelog.d/20260108_171124_fix_changelog_check.md b/changelog.d/20260108_171124_fix_changelog_check.md new file mode 100644 index 0000000..56d1687 --- /dev/null +++ b/changelog.d/20260108_171124_fix_changelog_check.md @@ -0,0 +1,17 @@ +--- +bump: patch +--- + +### Fixed + +- Fixed changelog fragment check to validate that a fragment is **added in the PR diff** rather than just checking if any fragments exist in the directory. This prevents the check from incorrectly passing when there are leftover fragments from previous PRs that haven't been released yet. + +### Changed + +- Converted shell scripts in `release.yml` to cross-platform `.mjs` scripts for improved portability and performance: + - `check-changelog-fragment.mjs` - validates changelog fragment is added in PR diff + - `git-config.mjs` - configures git user for CI/CD + - `check-release-needed.mjs` - checks if release is needed + - `publish-crate.mjs` - publishes package to crates.io + - `create-changelog-fragment.mjs` - creates changelog fragments for manual releases + - `get-version.mjs` - gets current version from Cargo.toml diff --git a/changelog.d/20260108_171435_prevent_manual_version_modification.md b/changelog.d/20260108_171435_prevent_manual_version_modification.md new file mode 100644 index 0000000..132467f --- /dev/null +++ b/changelog.d/20260108_171435_prevent_manual_version_modification.md @@ -0,0 +1,13 @@ +--- +bump: minor +--- + +### Added + +- Added `check-version-modification.mjs` script to detect manual version changes in Cargo.toml +- Added `version-check` job to CI/CD workflow that runs on pull requests +- Added skip logic for automated release branches (changelog-manual-release-*, changeset-release/*, release/*, automated-release/*) + +### Changed + +- Version modifications in Cargo.toml are now blocked in pull requests to enforce automated release pipeline diff --git a/changelog.d/20260108_apply_lino_objects_codec_fixes.md b/changelog.d/20260108_apply_lino_objects_codec_fixes.md new file mode 100644 index 0000000..c1ac23b --- /dev/null +++ b/changelog.d/20260108_apply_lino_objects_codec_fixes.md @@ -0,0 +1,14 @@ +--- +bump: patch +--- + +### Added + +- Added support for `CARGO_REGISTRY_TOKEN` as alternative to `CARGO_TOKEN` for crates.io publishing +- Added case study documentation for Issue #17 (yargs reserved word and dual token support) + +### Changed + +- Updated workflow to use fallback logic: `${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}` +- Improved publish-crate.mjs to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables +- Added warning message when neither token is set diff --git a/changelog.d/20260111_multi_language_support.md b/changelog.d/20260111_multi_language_support.md new file mode 100644 index 0000000..6c8f81d --- /dev/null +++ b/changelog.d/20260111_multi_language_support.md @@ -0,0 +1,19 @@ +--- +bump: minor +--- + +### Added +- New `scripts/rust-paths.mjs` utility for automatic Rust package root detection +- Support for both single-language and multi-language repository structures in all CI/CD scripts +- Configuration options via `--rust-root` CLI argument and `RUST_ROOT` environment variable +- Comprehensive case study documentation in `docs/case-studies/issue-19/` + +### Changed +- Updated all release scripts to use the new path detection utility: + - `scripts/bump-version.mjs` + - `scripts/check-release-needed.mjs` + - `scripts/collect-changelog.mjs` + - `scripts/get-bump-type.mjs` + - `scripts/get-version.mjs` + - `scripts/publish-crate.mjs` + - `scripts/version-and-commit.mjs` diff --git a/changelog.d/20260119_best_practices_from_browser_commander.md b/changelog.d/20260119_best_practices_from_browser_commander.md new file mode 100644 index 0000000..b4bca1d --- /dev/null +++ b/changelog.d/20260119_best_practices_from_browser_commander.md @@ -0,0 +1,15 @@ +### Changed + +- **check-release-needed.mjs**: Now checks crates.io API directly instead of git tags to determine if a version is already released. This prevents false positives where git tags exist but the package was never actually published to crates.io. + +### Added + +- **CI/CD Troubleshooting Guide**: New documentation at `docs/ci-cd/troubleshooting.md` covering common issues like skipped jobs, false positive version checks, publishing failures, and secret configuration. + +- **Enhanced Error Handling in publish-crate.mjs**: Added specific detection and helpful error messages for authentication failures, including guidance on secret configuration and workflow setup. + +- **Case Study Documentation**: Added comprehensive case study at `docs/case-studies/issue-21/` analyzing CI/CD failures from browser-commander repository (issues #27, #29, #31, #33) with timeline, root causes, and lessons learned. + +### Fixed + +- **Prevent False Positive Version Checks**: The release workflow now correctly identifies unpublished versions by checking crates.io instead of relying on git tags, which can exist without the package being published. diff --git a/changelog.d/20260311_translate_scripts_to_rust.md b/changelog.d/20260311_translate_scripts_to_rust.md new file mode 100644 index 0000000..4df7c4a --- /dev/null +++ b/changelog.d/20260311_translate_scripts_to_rust.md @@ -0,0 +1,11 @@ +--- +bump: minor +--- + +### Changed + +- Translated all CI/CD scripts from JavaScript (.mjs) to Rust (.rs) using rust-script +- Scripts now use native Rust with rust-script for execution in shell +- Removed Node.js dependency from CI/CD pipeline +- Updated GitHub Actions workflow to use rust-script instead of node +- Updated README and CONTRIBUTING documentation with new script references diff --git a/changelog.d/20260413-ci-cd-best-practices.md b/changelog.d/20260413-ci-cd-best-practices.md new file mode 100644 index 0000000..65a108f --- /dev/null +++ b/changelog.d/20260413-ci-cd-best-practices.md @@ -0,0 +1,24 @@ +--- +bump: minor +--- + +### Added + +- Cache `restore-keys` for partial cache hits across all workflow jobs +- Explicit `token` parameter in checkout for release jobs +- Code coverage job with `cargo-llvm-cov` and Codecov integration +- Codecov badge in README.md +- Pre-release version support (e.g., `0.1.0-beta.1`) in version parsing +- `--release-label` parameter for multi-language release disambiguation +- `ensure_version_exceeds_published()` logic to prevent publishing duplicate versions +- `get_max_published_version()` to query highest non-yanked version from crates.io +- `max_published_version` output from check-release-needed for downstream use +- Version fallback logic in auto-release Create GitHub Release step + +### Changed + +- Updated `actions/checkout` from v4 to v6 +- Updated `actions/cache` from v4 to v5 +- Updated `peter-evans/create-pull-request` from v7 to v8 +- Made `publish-crate.rs` fail (exit 1) when version already exists on crates.io +- Improved `create-github-release.rs` to check combined stdout+stderr and detect "Validation Failed" diff --git a/changelog.d/20260413_fix_cargo_token_fallback.md b/changelog.d/20260413_fix_cargo_token_fallback.md new file mode 100644 index 0000000..87ca39c --- /dev/null +++ b/changelog.d/20260413_fix_cargo_token_fallback.md @@ -0,0 +1,10 @@ +--- +bump: patch +--- + +### Fixed + +- Fix publish steps overriding workflow-level CARGO_TOKEN fallback, breaking CARGO_REGISTRY_TOKEN-only configurations (#32) +- Fix non-fast-forward push failures in multi-workflow repos by adding fetch/rebase and push retry logic (#31) +- Add mono-repo path support to check-changelog-fragment.rs, check-version-modification.rs, and create-changelog-fragment.rs +- Add `!cancelled()` guard to test job condition to respect workflow cancellation diff --git a/changelog.d/20260413_fix_crates_io_check.md b/changelog.d/20260413_fix_crates_io_check.md new file mode 100644 index 0000000..9fd4fef --- /dev/null +++ b/changelog.d/20260413_fix_crates_io_check.md @@ -0,0 +1,16 @@ +--- +bump: patch +--- + +### Fixed + +- Fixed `version-and-commit.rs` to check crates.io instead of git tags for determining if a version is already released +- This prevents the release pipeline from getting stuck when git tags exist without corresponding crates.io publication + +### Added + +- Added `--tag-prefix` support to `version-and-commit.rs` for multi-language repository compatibility +- Added crates.io and docs.rs badges to README.md +- Added automatic crates.io and docs.rs badge injection in GitHub release notes +- Added documentation deployment job to CI/CD pipeline (deploys to GitHub Pages after release) +- Added case study documentation for issue #25 diff --git a/changelog.d/20260413_fix_lookahead_regex.md b/changelog.d/20260413_fix_lookahead_regex.md new file mode 100644 index 0000000..f5011de --- /dev/null +++ b/changelog.d/20260413_fix_lookahead_regex.md @@ -0,0 +1,14 @@ +--- +bump: minor +--- + +### Fixed +- Fixed unsupported look-ahead regex in `create-github-release.rs` that caused a panic when parsing CHANGELOG.md. Replaced with a two-step approach using only features supported by Rust's `regex` crate. + +### Changed +- Restructured example application as a simple CLI sum calculator using `lino-arguments` +- Renamed default package to `example-sum-package-name` with Unlicense license +- Reorganized test structure: `tests/unit/sum.rs`, `tests/integration/sum.rs`, `tests/unit/ci-cd/` +- Converted experiment scripts into proper unit tests in `tests/unit/ci-cd/changelog_parsing.rs` +- Added CI/CD skip logic for template default package name `example-sum-package-name` +- Updated README.md badges and documentation diff --git a/changelog.d/20260414_fix_per_commit_diff.md b/changelog.d/20260414_fix_per_commit_diff.md new file mode 100644 index 0000000..c1a11c1 --- /dev/null +++ b/changelog.d/20260414_fix_per_commit_diff.md @@ -0,0 +1,7 @@ +--- +bump: patch +--- + +### Fixed + +- Change detection script now uses per-commit diff instead of full PR diff, so commits touching only non-code files correctly skip CI jobs even when earlier commits in the same PR changed code files diff --git a/changelog.d/20260415_fix_workspace_release_scripts.md b/changelog.d/20260415_fix_workspace_release_scripts.md new file mode 100644 index 0000000..5db0549 --- /dev/null +++ b/changelog.d/20260415_fix_workspace_release_scripts.md @@ -0,0 +1,2 @@ +### Fixed +- Make release scripts resolve the publishable crate manifest when the repository root uses a Cargo workspace manifest. diff --git a/changelog.d/20260501_decouple_docs_deploy.md b/changelog.d/20260501_decouple_docs_deploy.md new file mode 100644 index 0000000..6b76928 --- /dev/null +++ b/changelog.d/20260501_decouple_docs_deploy.md @@ -0,0 +1,6 @@ +--- +bump: patch +--- + +### Fixed +- Decoupled GitHub Pages documentation deployment from package release publication and fixed release-script warning failures under `RUSTFLAGS=-Dwarnings`. diff --git a/changelog.d/20260503_111500_ci_timeouts.md b/changelog.d/20260503_111500_ci_timeouts.md new file mode 100644 index 0000000..05d0849 --- /dev/null +++ b/changelog.d/20260503_111500_ci_timeouts.md @@ -0,0 +1,6 @@ +--- +bump: patch +--- + +### Changed +- Added explicit GitHub Actions job timeouts and documented Rust test timeout guidance. diff --git a/changelog.d/20260503_111700_file_size_warning_threshold.md b/changelog.d/20260503_111700_file_size_warning_threshold.md new file mode 100644 index 0000000..cc99b19 --- /dev/null +++ b/changelog.d/20260503_111700_file_size_warning_threshold.md @@ -0,0 +1,2 @@ +### Fixed +- Added a non-blocking warning threshold to the Rust file-size check so near-limit files are surfaced before concurrent PR merges can exceed the hard limit. diff --git a/changelog.d/20260509_031015_human_readable_release_titles.md b/changelog.d/20260509_031015_human_readable_release_titles.md new file mode 100644 index 0000000..4615bd6 --- /dev/null +++ b/changelog.d/20260509_031015_human_readable_release_titles.md @@ -0,0 +1,2 @@ +### Fixed +- Made `create-github-release.rs` build GitHub release titles as `[Language] X.Y.Z` instead of reusing the tag prefix. diff --git a/changelog.d/20260509_205000_docker_hub_release_publishing.md b/changelog.d/20260509_205000_docker_hub_release_publishing.md new file mode 100644 index 0000000..193993e --- /dev/null +++ b/changelog.d/20260509_205000_docker_hub_release_publishing.md @@ -0,0 +1,9 @@ +--- +bump: minor +--- + +### Added +- Added optional Docker Hub image publishing tied to Rust crate releases, including crates.io visibility waiting, version/latest image tags, and Docker Hub badges in GitHub release notes. + +### Changed +- Release completeness checks now self-heal when crates.io exists but configured Docker Hub or GitHub release artifacts are missing. diff --git a/changelog.d/20260512_172908_github_pages_artifact_deploy.md b/changelog.d/20260512_172908_github_pages_artifact_deploy.md new file mode 100644 index 0000000..6ba258c --- /dev/null +++ b/changelog.d/20260512_172908_github_pages_artifact_deploy.md @@ -0,0 +1,6 @@ +--- +bump: patch +--- + +### Fixed +- Switched documentation deployment to the official GitHub Pages artifact workflow so repositories using GitHub Actions as their Pages source do not get false-positive branch-push deploys. diff --git a/changelog.d/20260512_230053_document_pages_source_prerequisite.md b/changelog.d/20260512_230053_document_pages_source_prerequisite.md new file mode 100644 index 0000000..626f837 --- /dev/null +++ b/changelog.d/20260512_230053_document_pages_source_prerequisite.md @@ -0,0 +1,6 @@ +--- +bump: patch +--- + +### Added +- Documented the one-time `Settings → Pages → Source = GitHub Actions` prerequisite for the `deploy-docs` job in `README.md` and as a comment above the `deploy-docs` job in `release.yml`, so downstream template users hit a documented setup step instead of a `Get Pages site failed` error on the first deploy. diff --git a/changelog.d/20260515_074404_track_browser_commander_preview_regen.md b/changelog.d/20260515_074404_track_browser_commander_preview_regen.md new file mode 100644 index 0000000..8adb6cb --- /dev/null +++ b/changelog.d/20260515_074404_track_browser_commander_preview_regen.md @@ -0,0 +1,6 @@ +--- +bump: patch +--- + +### Added +- Tracking case study at `docs/case-studies/issue-52/` registering the `browser-commander` + Playwright preview-regeneration pattern from [`konard/vk-bot-desktop#52`](https://github.com/konard/vk-bot-desktop/pull/52), with an activation checklist for when an example-app surface lands in this template. Documentation only — no workflow, script, or runtime code changes. Primary upstream tracking issue: [`link-foundation/js-ai-driven-development-pipeline-template#62`](https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/62). diff --git a/changelog.d/20260515_223000_cargo_lock_release_sync.md b/changelog.d/20260515_223000_cargo_lock_release_sync.md new file mode 100644 index 0000000..5539321 --- /dev/null +++ b/changelog.d/20260515_223000_cargo_lock_release_sync.md @@ -0,0 +1,6 @@ +--- +bump: patch +--- + +### Fixed +- Release automation now keeps the workspace package entry in `Cargo.lock` synchronized when `scripts/version-and-commit.rs` bumps `Cargo.toml`, preventing stale lock-file version diffs in later pull requests. diff --git a/changelog.d/README.md b/changelog.d/README.md new file mode 100644 index 0000000..b3437e3 --- /dev/null +++ b/changelog.d/README.md @@ -0,0 +1,135 @@ +# Changelog Fragments + +This directory contains changelog fragments that will be collected into `CHANGELOG.md` during releases. + +## How to Add a Changelog Fragment + +When making changes that should be documented in the changelog, create a fragment file: + +```bash +# Create a new fragment with timestamp +touch changelog.d/$(date +%Y%m%d_%H%M%S)_description.md + +# Or manually create a file matching the pattern: YYYYMMDD_HHMMSS_description.md +``` + +## Fragment Format + +Each fragment should include a **frontmatter section** specifying the version bump type: + +```markdown +--- +bump: patch +--- + +### Fixed +- Description of bug fix +``` + +### Bump Types + +Use semantic versioning bump types in the frontmatter: + +- **`major`**: Breaking changes (incompatible API changes) +- **`minor`**: New features (backward compatible) +- **`patch`**: Bug fixes (backward compatible) + +### Content Categories + +Use these categories in your fragment content: + +```markdown +--- +bump: minor +--- + +### Added +- Description of new feature + +### Changed +- Description of change to existing functionality + +### Fixed +- Description of bug fix + +### Removed +- Description of removed feature + +### Deprecated +- Description of deprecated feature + +### Security +- Description of security fix +``` + +## Examples + +### Adding a new feature (minor bump) + +```markdown +--- +bump: minor +--- + +### Added +- New async processing mode for batch operations +``` + +### Fixing a bug (patch bump) + +```markdown +--- +bump: patch +--- + +### Fixed +- Fixed memory leak in connection pool handling +``` + +### Breaking change (major bump) + +```markdown +--- +bump: major +--- + +### Changed +- Renamed `process()` to `process_async()` - this is a breaking change + +### Removed +- Removed deprecated `legacy_mode` option +``` + +## Why Fragments? + +Using changelog fragments (similar to [Changesets](https://github.com/changesets/changesets) in JavaScript and [Scriv](https://scriv.readthedocs.io/) in Python): + +1. **No merge conflicts**: Multiple PRs can add fragments without conflicts +2. **Per-PR documentation**: Each PR documents its own changes +3. **Automated version bumping**: Version bump type is specified per-change +4. **Automated collection**: Fragments are automatically collected during release +5. **Consistent format**: Template ensures consistent changelog entries + +## How It Works + +1. **During PR**: Add a fragment file with your changes and bump type +2. **On merge to main**: The release workflow automatically: + - Reads all fragment files and determines the highest bump type + - Bumps the version in `Cargo.toml` accordingly + - Collects fragments into `CHANGELOG.md` + - Creates a git tag and GitHub release + - Removes processed fragment files + +## Multiple PRs and Bump Priority + +When multiple PRs are merged before a release, all pending fragments are processed together. The **highest** bump type wins: + +- If any fragment specifies `major`, the release is a major version bump +- Otherwise, if any specifies `minor`, the release is a minor version bump +- Otherwise, the release is a patch version bump + +This ensures that breaking changes are never missed, even when combined with smaller changes. + +## Default Behavior + +If a fragment doesn't include a bump type in the frontmatter, it defaults to `patch`. diff --git a/docs/case-studies/issue-11/README.md b/docs/case-studies/issue-11/README.md new file mode 100644 index 0000000..b5ab5bb --- /dev/null +++ b/docs/case-studies/issue-11/README.md @@ -0,0 +1,151 @@ +# Case Study: Issue #11 - Apply Best Practices from Other Repositories + +## Summary + +This case study analyzes best practices discovered in several link-foundation repositories and applies them to the `rust-ai-driven-development-pipeline-template`. The goal is to improve the Rust CI/CD pipeline by incorporating lessons learned from real-world issues. + +## Referenced Pull Requests + +| Repository | PR | Title | Key Fix | +|------------|-----|-------|---------| +| [link-foundation/start](https://github.com/link-foundation/start) | [#58](https://github.com/link-foundation/start/pull/58) | fix: Use 'close' event instead of 'exit' for reliable stdout capture | CI/CD changelog check bug fix | +| [link-foundation/lino-env](https://github.com/link-foundation/lino-env) | [#27](https://github.com/link-foundation/lino-env/pull/27) | fix(rust): remove deprecated set-output GitHub Actions command | Removed deprecated `::set-output` | +| [link-foundation/lino-env](https://github.com/link-foundation/lino-env) | [#25](https://github.com/link-foundation/lino-env/pull/25) | fix(rust): fix manual release workflow_dispatch not running | Fixed job skipping issue | +| [link-foundation/lino-env](https://github.com/link-foundation/lino-env) | [#23](https://github.com/link-foundation/lino-env/pull/23) | feat: add crates.io publishing support to Rust CI/CD workflow | Added crates.io publishing | + +## Best Practices Identified + +### 1. Remove Deprecated `set-output` Command (PR #27) + +**Problem**: The `::set-output` command was deprecated by GitHub in October 2022 and will eventually be disabled. + +**Root Cause**: The `setOutput()` function in version-and-commit.mjs was using both: +1. The new `GITHUB_OUTPUT` environment file approach (correct) +2. The deprecated `::set-output` stdout command (causes warnings) + +**Solution**: Remove the deprecated `console.log(`::set-output...`)` line and replace with a plain log for visibility. + +**References**: +- [GitHub Changelog: Deprecating save-state and set-output commands](https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/) +- [GitHub Actions Update on save-state and set-output commands](https://github.blog/changelog/2023-07-24-github-actions-update-on-save-state-and-set-output-commands/) + +### 2. Enforce Changelog Fragment Requirement (PR #27, #58) + +**Problem**: The changelog fragment check only produced a warning (`::warning::` with `exit 0`) when source code changed without a changelog entry, allowing PRs to pass without proper documentation. + +**Root Cause**: Using `exit 0` (success) instead of `exit 1` (failure) in the changelog check. + +**Solution**: Change `::warning::` to `::error::` and `exit 0` to `exit 1` to properly fail CI when changelog fragments are missing. + +### 3. Fix workflow_dispatch Job Skipping (PR #25) + +**Problem**: When triggering the workflow via `workflow_dispatch`, the `detect-changes` job is intentionally skipped. However, jobs with `needs: [detect-changes]` are also skipped due to GitHub Actions' default behavior. + +**Root Cause**: When a job dependency is skipped, the dependent job is also skipped - even if its own `if` condition would evaluate to true. This is documented in [GitHub Actions Runner Issue #491](https://github.com/actions/runner/issues/491). + +**Solution**: Add `always() && !cancelled()` to job conditions to ensure they run properly when dependencies are skipped, plus explicit checks for `needs.job.result == 'success'`. + +**References**: +- [GitHub Actions Runner Issue #491](https://github.com/actions/runner/issues/491) +- [GitHub Actions Runner Issue #2205](https://github.com/actions/runner/issues/2205) +- [GitHub Community Discussion #45058](https://github.com/orgs/community/discussions/45058) + +### 4. Add Release Mode Options (PR #25) + +**Problem**: The Rust workflow only had one release mode (instant), while the JavaScript workflow had both "instant" and "changelog-pr" modes. + +**Solution**: Add `release_mode` workflow input with options: +- `instant` (default): Direct release that goes through lint/test/build verification +- `changelog-pr`: Creates a pull request with a changelog fragment for review + +### 5. Add crates.io Publishing Support (PR #23) + +**Problem**: The Rust workflow only created GitHub releases but didn't publish to crates.io. + +**Solution**: Add crates.io publishing step with: +- `CARGO_TOKEN` secret environment variable for authentication +- "Publish to Crates.io" step in both `auto-release` and `manual-release` jobs +- Graceful handling of "already exists" case to avoid failing when version already published + +**Note on Trusted Publishing**: crates.io now supports [Trusted Publishing](https://crates.io/docs/trusted-publishing) which uses OIDC for secure, tokenless publishing. This is the recommended approach for new setups but requires `rust-lang/crates-io-auth-action@v1`. + +### 6. Update Release Script for Better Flexibility (PR #23) + +**Problem**: The `create-github-release.mjs` script had hardcoded tag prefix and didn't support crates.io links. + +**Solution**: Add options to the script: +- `--tag-prefix`: Support different tag formats (e.g., "v" or "rust-v") +- `--crates-io-url`: Include crates.io link in release notes + +## Timeline of Events + +### October 11, 2022 +GitHub announces deprecation of `set-output` and `save-state` commands. + +### May 31, 2023 +Originally planned disablement date for deprecated commands. + +### July 24, 2023 +GitHub postpones removal due to significant usage still observed. + +### January 2026 +Issues identified in link-foundation repositories: +- Issue #26 (lino-env): CI/CD deprecation warnings +- Issue #24 (lino-env): Manual release not working +- Issue #22 (lino-env): Add crates.io publishing +- Issue #57 (start): macOS stdout capture issue (led to discovering changelog check bug) + +### January 2026 (PRs Merged) +- PR #23: crates.io publishing support +- PR #25: workflow_dispatch fix +- PR #27: set-output deprecation fix +- PR #58: macOS stdout capture fix + changelog check enforcement + +## Files in This Case Study + +- [README.md](./README.md) - This overview document +- [analysis-set-output.md](./analysis-set-output.md) - Detailed analysis of set-output deprecation +- [analysis-workflow-dispatch.md](./analysis-workflow-dispatch.md) - Detailed analysis of job skipping issue +- [analysis-crates-io.md](./analysis-crates-io.md) - Detailed analysis of crates.io publishing +- [online-research.md](./online-research.md) - Online research findings + +## Changes Applied to This Template + +Based on the analysis, the following changes were applied to this repository: + +1. **scripts/version-and-commit.mjs**: Removed deprecated `::set-output` command +2. **.github/workflows/release.yml**: + - Changed changelog check from warning to error (`exit 1`) + - Added `always() && !cancelled()` to job conditions + - Added `release_mode` input with "instant" and "changelog-pr" options + - Added crates.io publishing steps + - Added `CARGO_TOKEN` environment variable +3. **scripts/create-github-release.mjs**: Added `--tag-prefix` and `--crates-io-url` options + +## Key Takeaways + +1. **Test failure paths**: CI checks should be tested to ensure they actually fail when they should +2. **Use consistent approaches**: Apply the same patterns across all workflows (JS and Rust) +3. **Verify CI annotations**: Using `::warning::` instead of `::error::` is a hint that the check might not be enforced +4. **Understand GitHub Actions behavior**: The `needs` dependency behavior with skipped jobs is subtle and well-documented +5. **Use `always()` carefully**: Combine with `!cancelled()` and explicit result checks for safety +6. **Stay current with deprecations**: Regularly check for deprecated GitHub Actions commands + +## References + +### GitHub Documentation +- [Workflow syntax for GitHub Actions](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions) +- [Using jobs in a workflow](https://docs.github.com/actions/using-jobs/using-jobs-in-a-workflow) +- [Using conditions to control job execution](https://docs.github.com/en/actions/using-jobs/using-conditions-to-control-job-execution) + +### GitHub Changelog +- [Deprecating save-state and set-output commands](https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/) +- [Update on save-state and set-output commands](https://github.blog/changelog/2023-07-24-github-actions-update-on-save-state-and-set-output-commands/) + +### GitHub Issues +- [Runner Issue #491: Job-level "if" condition not evaluated correctly](https://github.com/actions/runner/issues/491) +- [Runner Issue #2205: Jobs skipped when NEEDS job ran successfully](https://github.com/actions/runner/issues/2205) + +### crates.io +- [Trusted Publishing Documentation](https://crates.io/docs/trusted-publishing) +- [RFC #3691: Trusted Publishing for crates.io](https://rust-lang.github.io/rfcs/3691-trusted-publishing-cratesio.html) diff --git a/docs/case-studies/issue-11/analysis-crates-io.md b/docs/case-studies/issue-11/analysis-crates-io.md new file mode 100644 index 0000000..024cd60 --- /dev/null +++ b/docs/case-studies/issue-11/analysis-crates-io.md @@ -0,0 +1,227 @@ +# Analysis: crates.io Publishing Support + +## Issue Summary + +- **Source Repository**: [link-foundation/lino-env](https://github.com/link-foundation/lino-env) +- **Issue**: [#22](https://github.com/link-foundation/lino-env/issues/22) +- **PR**: [#23](https://github.com/link-foundation/lino-env/pull/23) +- **Type**: Feature (new functionality) + +## Problem Description + +The Rust CI/CD workflow only created GitHub releases but didn't publish packages to crates.io, making it incomplete compared to the JavaScript workflow which publishes to npm. + +## Solution Overview + +### Option 1: Traditional API Token Approach + +Uses a manually created crates.io API token stored as a GitHub secret. + +**Workflow Addition:** +```yaml +env: + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + +jobs: + release: + steps: + - name: Publish to Crates.io + run: | + PACKAGE_NAME=$(grep '^name = ' Cargo.toml | head -1 | sed 's/name = "\(.*\)"/\1/') + PACKAGE_VERSION=$(grep '^version = ' Cargo.toml | head -1 | sed 's/version = "\(.*\)"/\1/') + echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION" + + set +e # Don't exit on error + cargo publish --token ${{ secrets.CARGO_TOKEN }} --allow-dirty 2>&1 | tee publish_output.txt + PUBLISH_EXIT_CODE=$? + set -e + + if [ $PUBLISH_EXIT_CODE -eq 0 ]; then + echo "Successfully published to crates.io" + elif grep -q "already uploaded" publish_output.txt || grep -q "already exists" publish_output.txt; then + echo "Version already exists on crates.io - this is OK" + else + echo "Failed to publish" + exit 1 + fi +``` + +**Setup:** +1. Go to [crates.io API tokens](https://crates.io/settings/tokens) +2. Create a new token with publish permissions +3. Add it as a repository secret named `CARGO_TOKEN` + +### Option 2: Trusted Publishing (Recommended for 2025+) + +Uses OIDC for secure, tokenless publishing. This is now the recommended approach. + +**Workflow:** +```yaml +jobs: + release: + runs-on: ubuntu-latest + permissions: + id-token: write # Required for OIDC token exchange + steps: + - uses: actions/checkout@v4 + - uses: rust-lang/crates-io-auth-action@v1 + id: auth + - name: Publish to Crates.io + run: cargo publish + env: + CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} +``` + +**Setup:** +1. Go to crates.io package settings +2. Configure trusted publishing for your GitHub repository +3. No manual token needed - OIDC handles authentication + +**Benefits of Trusted Publishing:** +- No long-lived secrets to manage +- Tokens are short-lived (30 minutes) +- More secure against credential leaks +- Easier to set up and maintain + +## Handling Edge Cases + +### Version Already Exists + +When a version is already published, `cargo publish` returns an error. Handle gracefully: + +```bash +if grep -q "already uploaded" publish_output.txt || grep -q "already exists" publish_output.txt; then + echo "Version already exists on crates.io - this is OK" + # Don't fail the workflow +fi +``` + +### Dry Run Testing + +Before releasing, test with dry run: + +```bash +cargo publish --dry-run +``` + +### Workspace Publishing + +For monorepos with multiple crates, consider: +- Publishing crates in dependency order +- Using `cargo publish -p crate-name` for specific packages +- Using tools like `cargo-release` or `cargo-workspaces` + +## Release Script Updates + +The `create-github-release.mjs` script was updated to support crates.io: + +```javascript +const config = makeConfig({ + yargs: ({ yargs, getenv }) => + yargs + .option('tag-prefix', { + type: 'string', + default: getenv('TAG_PREFIX', 'v'), + describe: 'Tag prefix (e.g., "v" or "rust-v")', + }) + .option('crates-io-url', { + type: 'string', + default: getenv('CRATES_IO_URL', ''), + describe: 'Crates.io package URL to include in release notes', + }), +}); + +// Add crates.io link to release notes +if (cratesIoUrl) { + releaseNotes = `${cratesIoUrl}\n\n${releaseNotes}`; +} +``` + +**Usage:** +```bash +node scripts/create-github-release.mjs \ + --release-version "1.0.0" \ + --repository "owner/repo" \ + --tag-prefix "rust-v" \ + --crates-io-url "https://crates.io/crates/package-name" +``` + +## Complete Workflow Example + +```yaml +name: Rust CI/CD + +on: + push: + branches: [main] + workflow_dispatch: + inputs: + bump_type: + description: 'Version bump type' + type: choice + options: [patch, minor, major] + +env: + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - uses: dtolnay/rust-toolchain@stable + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Version and commit + id: version + run: node scripts/version-and-commit.mjs --bump-type "${{ inputs.bump_type }}" + + - name: Build release + if: steps.version.outputs.version_committed == 'true' + run: cargo build --release + + - name: Publish to Crates.io + if: steps.version.outputs.version_committed == 'true' + run: | + set +e + cargo publish --token ${{ secrets.CARGO_TOKEN }} --allow-dirty 2>&1 | tee publish_output.txt + PUBLISH_EXIT_CODE=$? + set -e + + if [ $PUBLISH_EXIT_CODE -eq 0 ]; then + echo "Successfully published" + elif grep -q "already" publish_output.txt; then + echo "Version already exists - OK" + else + exit 1 + fi + + - name: Create GitHub Release + if: steps.version.outputs.version_committed == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + PACKAGE_NAME=$(grep '^name = ' Cargo.toml | head -1 | sed 's/name = "\(.*\)"/\1/') + node scripts/create-github-release.mjs \ + --release-version "${{ steps.version.outputs.new_version }}" \ + --repository "${{ github.repository }}" \ + --crates-io-url "https://crates.io/crates/$PACKAGE_NAME" +``` + +## References + +- [crates.io Trusted Publishing Documentation](https://crates.io/docs/trusted-publishing) +- [RFC #3691: Trusted Publishing for crates.io](https://rust-lang.github.io/rfcs/3691-trusted-publishing-cratesio.html) +- [rust-lang/crates-io-auth-action](https://github.com/rust-lang/crates-io-auth-action) +- [How to Automate Publishing your Crates with GitHub Actions](https://fassbender.dev/blog/001-cargo-publish-action/) +- [katyo/publish-crates GitHub Action](https://github.com/katyo/publish-crates) diff --git a/docs/case-studies/issue-11/analysis-set-output.md b/docs/case-studies/issue-11/analysis-set-output.md new file mode 100644 index 0000000..a9efdcb --- /dev/null +++ b/docs/case-studies/issue-11/analysis-set-output.md @@ -0,0 +1,132 @@ +# Analysis: GitHub Actions `set-output` Deprecation + +## Issue Summary + +- **Source Repository**: [link-foundation/lino-env](https://github.com/link-foundation/lino-env) +- **Issue**: [#26](https://github.com/link-foundation/lino-env/issues/26) +- **PR**: [#27](https://github.com/link-foundation/lino-env/pull/27) +- **Type**: Bug (deprecation warning) +- **Severity**: Low (warnings only, workflow still succeeds) +- **Risk**: Medium (command may be disabled in future GitHub Actions updates) + +## Problem Description + +The CI/CD pipeline generates deprecation warnings during the "Instant Release" job: + +``` +The `set-output` command is deprecated and will be disabled soon. Please upgrade to using Environment Files. +For more information see: https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/ +``` + +## Root Cause Analysis + +### Source Code Location + +File: `scripts/version-and-commit.mjs`, `setOutput` function: + +```javascript +function setOutput(key, value) { + const outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + appendFileSync(outputFile, `${key}=${value}\n`); + } + // Also log for visibility + console.log(`::set-output name=${key}::${value}`); // <-- DEPRECATED +} +``` + +### Technical Explanation + +The `setOutput` function does two things: +1. **Correctly** writes to the `GITHUB_OUTPUT` environment file (new approach) +2. **Also** outputs the deprecated `::set-output` command to stdout (old approach) + +While the new approach works correctly, the legacy stdout command is still being printed, causing GitHub Actions to emit deprecation warnings. + +### Why Warnings Appear Multiple Times + +The `setOutput` function is called in several places: +- When tag already exists: `setOutput('already_released', 'true')` and `setOutput('new_version', newVersion)` +- When no changes to commit: `setOutput('version_committed', 'false')` and `setOutput('new_version', newVersion)` +- After successful push: `setOutput('version_committed', 'true')` and `setOutput('new_version', newVersion)` + +## Official Deprecation Timeline + +| Date | Event | +|------|-------| +| October 11, 2022 | GitHub announces deprecation | +| May 31, 2023 | Originally planned full disablement | +| July 24, 2023 | GitHub postpones removal due to significant usage | +| Present | Warnings shown but commands still functional | + +## Migration Guide + +### Old Approach (Deprecated) + +```bash +# Shell +echo "::set-output name=myOutput::myValue" +``` + +```javascript +// JavaScript +console.log(`::set-output name=${key}::${value}`); +``` + +### New Approach (Recommended) + +```bash +# Shell +echo "myOutput=myValue" >> $GITHUB_OUTPUT +``` + +```javascript +// JavaScript +import { appendFileSync } from 'fs'; + +function setOutput(key, value) { + const outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + appendFileSync(outputFile, `${key}=${value}\n`); + console.log(`Output: ${key}=${value}`); // Optional: plain log for visibility + } +} +``` + +### Handling Multi-line Values + +For multi-line outputs, use delimiters: + +```bash +echo "JSON_RESPONSE<> $GITHUB_OUTPUT +echo "$response_json" >> $GITHUB_OUTPUT +echo "EOF" >> $GITHUB_OUTPUT +``` + +## Fix Applied + +The deprecated `console.log` line was removed and replaced with a plain log for visibility: + +```javascript +function setOutput(key, value) { + const outputFile = process.env.GITHUB_OUTPUT; + if (outputFile) { + appendFileSync(outputFile, `${key}=${value}\n`); + console.log(`Output: ${key}=${value}`); // Plain log, not GitHub command + } +} +``` + +## Verification + +After the fix: +1. Run a workflow that sets outputs +2. Check that no deprecation warnings appear in the logs +3. Verify that subsequent steps can still access the output values + +## References + +- [GitHub Blog: Deprecating save-state and set-output commands](https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/) +- [GitHub Blog: Update on save-state and set-output commands](https://github.blog/changelog/2023-07-24-github-actions-update-on-save-state-and-set-output-commands/) +- [GitHub Docs: Environment Files](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#environment-files) +- [How to Fix the set-output GitHub Actions Deprecation Warning](https://hynek.me/til/set-output-deprecation-github-actions/) diff --git a/docs/case-studies/issue-11/analysis-workflow-dispatch.md b/docs/case-studies/issue-11/analysis-workflow-dispatch.md new file mode 100644 index 0000000..9ebd84b --- /dev/null +++ b/docs/case-studies/issue-11/analysis-workflow-dispatch.md @@ -0,0 +1,195 @@ +# Analysis: GitHub Actions workflow_dispatch Job Skipping Issue + +## Issue Summary + +- **Source Repository**: [link-foundation/lino-env](https://github.com/link-foundation/lino-env) +- **Issue**: [#24](https://github.com/link-foundation/lino-env/issues/24) +- **PR**: [#25](https://github.com/link-foundation/lino-env/pull/25) +- **Type**: Bug (jobs not running) +- **Severity**: High (release functionality broken) + +## Problem Description + +When triggering the workflow via `workflow_dispatch` (manual release), the release job was being skipped instead of executing. The workflow ran successfully for automatic releases but failed for manual triggers. + +## Root Cause Analysis + +### The Core Issue + +The `detect-changes` job has this condition: +```yaml +detect-changes: + if: github.event_name != 'workflow_dispatch' +``` + +This means `detect-changes` is intentionally skipped during manual triggers. + +However, the `lint` job depends on `detect-changes`: +```yaml +lint: + needs: [detect-changes] + if: | + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' || + ... +``` + +### GitHub Actions' Hidden Behavior + +When a job dependency is skipped, the dependent job is also skipped by default - **regardless of its own `if` condition**. This happens because: + +1. There's an implicit `success()` check applied to all jobs by default +2. `success()` returns `false` if any dependency was skipped +3. The job's custom `if` condition is never even evaluated + +This behavior is documented in [GitHub Actions Runner Issue #491](https://github.com/actions/runner/issues/491). + +### Dependency Chain Breakdown + +``` +detect-changes (skipped on workflow_dispatch) + | + v + lint (skipped because dependency was skipped) + | + v + build (skipped because lint.result != 'success') + | + v +manual-release (never runs because build was skipped) +``` + +### Why Some Jobs Worked + +The `test` job had this pattern: +```yaml +test: + needs: [detect-changes, changelog] + if: always() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || ...) +``` + +The `always()` function forces the `if` condition to be evaluated even when dependencies are skipped. + +## Solution + +### Fix Job Conditions + +Add `always() && !cancelled()` to job conditions: + +```yaml +lint: + needs: [detect-changes] + if: | + always() && !cancelled() && ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' || + ... + ) +``` + +### Why `!cancelled()`? + +Using just `always()` has a side effect: the job will run even if the workflow is cancelled. Adding `!cancelled()` ensures the job respects cancellation requests. + +### Check Dependency Results Explicitly + +For jobs that depend on other jobs' success: + +```yaml +build: + needs: [lint, test] + if: | + always() && !cancelled() && + needs.lint.result == 'success' && + needs.test.result == 'success' +``` + +## Best Practice Patterns + +### Pattern 1: Force Evaluation but Require Success + +```yaml +jobs: + job-b: + needs: [job-a] + if: always() && !cancelled() && needs.job-a.result == 'success' +``` + +### Pattern 2: Run When Dependency Succeeded OR Skipped + +```yaml +jobs: + job-b: + needs: [job-a] + if: | + always() && !cancelled() && ( + needs.job-a.result == 'success' || + needs.job-a.result == 'skipped' + ) +``` + +### Pattern 3: Run Unless Failure + +```yaml +jobs: + job-b: + needs: [job-a] + if: "!failure()" +``` + +This is simpler but less explicit about what conditions are acceptable. + +## Full Example + +```yaml +jobs: + detect-changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name != 'workflow_dispatch' + outputs: + changed: ${{ steps.changes.outputs.changed }} + steps: + # ... detect changes + + lint: + name: Lint + runs-on: ubuntu-latest + needs: [detect-changes] + # Note: always() is required because detect-changes is skipped on workflow_dispatch + if: | + always() && !cancelled() && ( + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.changed == 'true' + ) + steps: + # ... lint code + + build: + name: Build + runs-on: ubuntu-latest + needs: [lint] + if: always() && !cancelled() && needs.lint.result == 'success' + steps: + # ... build + + release: + name: Release + needs: [build] + if: | + always() && !cancelled() && + github.event_name == 'workflow_dispatch' && + needs.build.result == 'success' + steps: + # ... release +``` + +## References + +- [GitHub Actions Runner Issue #491](https://github.com/actions/runner/issues/491) - Job-level "if" condition not evaluated correctly +- [GitHub Actions Runner Issue #2205](https://github.com/actions/runner/issues/2205) - Jobs skipped when NEEDS job ran successfully +- [GitHub Community Discussion #45058](https://github.com/orgs/community/discussions/45058) - success() returns false if dependent jobs are skipped +- [GitHub Docs: Using conditions to control job execution](https://docs.github.com/en/actions/using-jobs/using-conditions-to-control-job-execution) +- [CodeStudy: GitHub Actions - Ensure Deploy Job Runs When Previous Jobs Are Skipped](https://www.codestudy.net/blog/github-action-job-fire-when-previous-job-skipped/) diff --git a/docs/case-studies/issue-11/online-research.md b/docs/case-studies/issue-11/online-research.md new file mode 100644 index 0000000..e3e9e55 --- /dev/null +++ b/docs/case-studies/issue-11/online-research.md @@ -0,0 +1,139 @@ +# Online Research: GitHub Actions Best Practices + +This document compiles research findings from various online sources related to the issues addressed in this case study. + +## 1. GitHub Actions `set-output` Deprecation + +### Official Sources + +- **[GitHub Changelog: Deprecating save-state and set-output commands](https://github.blog/changelog/2022-10-11-github-actions-deprecating-save-state-and-set-output-commands/)** (October 2022) + - Announced deprecation of `::set-output` and `::save-state` commands + - Migration path: Use `$GITHUB_OUTPUT` and `$GITHUB_STATE` environment files + +- **[GitHub Changelog: Update on save-state and set-output commands](https://github.blog/changelog/2023-07-24-github-actions-update-on-save-state-and-set-output-commands/)** (July 2023) + - Removal postponed due to "significant usage" + - Commands still functional but showing warnings + +### Community Resources + +- **[How to Fix the set-output GitHub Actions Deprecation Warning](https://hynek.me/til/set-output-deprecation-github-actions/)** + - Practical migration examples + - GNU sed one-liner for bulk migration + +- **[GitHub Community Discussion #35994](https://github.com/orgs/community/discussions/35994)** + - Community discussion on migration challenges + - Workarounds for complex scenarios + +- **[Earthly Blog: Resolving Deprecation Errors](https://earthly.dev/blog/deprecation-error-github-action-command/)** + - Comprehensive guide covering `set-output`, `save-state`, `add-path`, and `set-env` + +## 2. GitHub Actions Job Dependencies and Skipping + +### Official Documentation + +- **[GitHub Docs: Workflow syntax - jobs..needs](https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idneeds)** + - Official documentation on job dependencies + +- **[GitHub Docs: Using conditions to control job execution](https://docs.github.com/en/actions/using-jobs/using-conditions-to-control-job-execution)** + - Explanation of `success()`, `always()`, `failure()`, `cancelled()` functions + +### Key GitHub Issues + +- **[GitHub Actions Runner Issue #491](https://github.com/actions/runner/issues/491)**: "Job-level 'if' condition not evaluated correctly if job in 'needs' property is skipped" + - This is the primary issue documenting the behavior + - Acknowledged by GitHub, in backlog with no timeline + +- **[GitHub Actions Runner Issue #2205](https://github.com/actions/runner/issues/2205)**: "Jobs skipped when NEEDS job ran successfully" + - Related issue about unexpected skipping behavior + +- **[GitHub Community Discussion #45058](https://github.com/orgs/community/discussions/45058)**: "success() returns false if dependent jobs are skipped" + - Community workarounds and best practices + +### Blog Posts and Tutorials + +- **[CodeStudy: GitHub Actions - How to Ensure Your Deploy Job Runs When Previous Jobs Are Skipped](https://www.codestudy.net/blog/github-action-job-fire-when-previous-job-skipped/)** + - Practical examples and solutions + - Comparison of different approaches + +- **[Kerno Blog: Advanced Workflows in GitHub Actions](https://www.kerno.io/blog/advanced-workflows-in-github-actions)** + - Comprehensive guide to complex workflow patterns + +## 3. crates.io Publishing with GitHub Actions + +### Official Documentation + +- **[crates.io Trusted Publishing Documentation](https://crates.io/docs/trusted-publishing)** + - Official setup guide for OIDC-based publishing + - Security benefits explained + +- **[RFC #3691: Trusted Publishing for crates.io](https://rust-lang.github.io/rfcs/3691-trusted-publishing-cratesio.html)** + - Technical specification of trusted publishing + - OIDC flow details + +### GitHub Actions + +- **[rust-lang/crates-io-auth-action](https://github.com/rust-lang/crates-io-auth-action)** + - Official action for OIDC authentication + - Used with trusted publishing + +- **[katyo/publish-crates](https://github.com/marketplace/actions/publish-crates)** + - Popular third-party action + - Supports workspace publishing + +### Tutorials + +- **[Jonas' Blog: How to Automate Publishing your Crates with GitHub Actions](https://fassbender.dev/blog/001-cargo-publish-action/)** + - Step-by-step guide + - Traditional and modern approaches + +- **[Medium: Publishing crates using GitHub Actions](https://pratikpc.medium.com/publishing-crates-using-github-actions-165ee67780e1)** + - Beginner-friendly tutorial + +- **[RapidRecast: Simplify Rust Releases with GitHub Actions](https://rapidrecast.io/blog/simplify-rust-releases-with-github-actions/)** + - End-to-end release automation + +## 4. Best Practices Summary + +### From Official Documentation + +1. **Use Environment Files**: Migrate from `::set-output` to `$GITHUB_OUTPUT` +2. **Understand Job Dependencies**: Jobs in `needs` chain skip if dependency skips +3. **Use Status Functions**: `always()`, `success()`, `failure()`, `cancelled()` +4. **Combine Conditions**: `always() && !cancelled() && needs.job.result == 'success'` + +### From Community Experience + +1. **Test Failure Paths**: Ensure CI checks actually fail when they should +2. **Be Explicit**: Use explicit result checks rather than relying on defaults +3. **Document Quirks**: Add comments explaining non-obvious behavior +4. **Stay Current**: Regularly update actions and check for deprecations + +### From Security Best Practices + +1. **Use Trusted Publishing**: Prefer OIDC over long-lived API tokens +2. **Limit Token Scope**: Use least-privilege tokens when manual tokens required +3. **Use Environments**: Add protection rules for sensitive publishing +4. **Audit Dependencies**: Review third-party actions before use + +## 5. Tools and Resources + +### GitHub Actions Tools + +- [actionlint](https://github.com/rhysd/actionlint) - Static checker for GitHub Actions workflow files +- [act](https://github.com/nektos/act) - Run GitHub Actions locally + +### Rust Publishing Tools + +- [cargo-release](https://github.com/crate-ci/cargo-release) - Automate cargo releases +- [cargo-workspaces](https://github.com/pksunkara/cargo-workspaces) - Manage cargo workspaces +- [semantic-release-cargo](https://crates.io/crates/semantic-release-cargo) - Semantic versioning for Rust + +### Migration Helpers + +```bash +# Find all set-output usages in workflows +grep -r "::set-output" .github/ + +# GNU sed one-liner to migrate set-output +sed -i 's/echo "::set-output name=\([^:]*\)::\(.*\)"/echo "\1=\2" >> $GITHUB_OUTPUT/g' .github/workflows/*.yml +``` diff --git a/docs/case-studies/issue-17/README.md b/docs/case-studies/issue-17/README.md new file mode 100644 index 0000000..15efc07 --- /dev/null +++ b/docs/case-studies/issue-17/README.md @@ -0,0 +1,140 @@ +# Case Study: Issue #17 - Apply Fixes from lino-objects-codec + +## Summary + +This case study documents the fixes applied from the [lino-objects-codec](https://github.com/link-foundation/lino-objects-codec) repository to improve the CI/CD pipeline in `rust-ai-driven-development-pipeline-template`. + +## Referenced Pull Requests + +| Repository | PR | Title | Key Fix | +|------------|-----|-------|---------| +| [link-foundation/lino-objects-codec](https://github.com/link-foundation/lino-objects-codec) | [#23](https://github.com/link-foundation/lino-objects-codec/pull/23) | Fix yargs reserved word conflict for --version option | Yargs `--version` reserved word workaround | +| [link-foundation/lino-objects-codec](https://github.com/link-foundation/lino-objects-codec) | [#24](https://github.com/link-foundation/lino-objects-codec/pull/24) | feat(rust): support both CARGO_REGISTRY_TOKEN and CARGO_TOKEN | Dual token support for crates.io | + +## Best Practices Identified + +### 1. Yargs Reserved Word Conflict (PR #23) + +**Problem**: The `--version` flag is a reserved word in [yargs](https://yargs.js.org/) (v17.2.0+). When using `lino-arguments` (which wraps yargs), defining a custom `--version` option causes yargs to interpret it as its built-in version display command, returning `false` instead of the actual argument value. + +**Root Cause**: Yargs reserves `--version` for displaying the application version. When a user defines a custom `version` option without disabling the built-in handling, the argument value is not properly parsed. + +**Error Manifestation**: +``` +Error: Missing required arguments +Usage: node scripts/create-github-release.mjs --version --repository +``` + +The error is misleading because the arguments ARE being passed, but yargs interprets `--version` specially. + +**Solutions** (choose one): + +1. **Disable yargs built-in `--version`** (recommended for existing codebases): + ```javascript + const config = makeConfig({ + yargs: ({ yargs, getenv }) => + yargs + .version(false) // Disable yargs built-in --version handling + .option('version', { + type: 'string', + default: getenv('VERSION', ''), + describe: 'Version number', + }) + }); + ``` + +2. **Use an alternative option name** (cleaner for new scripts): + ```javascript + const config = makeConfig({ + yargs: ({ yargs, getenv }) => + yargs + .option('release-version', { // Avoid reserved word entirely + type: 'string', + default: getenv('VERSION', ''), + describe: 'Version number', + }) + }); + ``` + +**Status in this repository**: This template uses the alternative option name approach (`--release-version`) in `create-github-release.mjs`, which already avoids the conflict. + +**References**: +- [yargs/yargs#2064 - Cannot have version as both option and command](https://github.com/yargs/yargs/issues/2064) +- [yargs version() documentation](https://yargs.js.org/docs/#api-reference-version) +- [CycloneDX/cdxgen#83 - Warning: "version" is a reserved word](https://github.com/CycloneDX/cdxgen/issues/83) + +### 2. Dual Token Support for crates.io (PR #24) + +**Problem**: Different CI/CD setups may use different secret names for the crates.io API token: +- `CARGO_REGISTRY_TOKEN` - Cargo's native environment variable +- `CARGO_TOKEN` - Alternative name used in some setups + +**Root Cause**: Inconsistency in secret naming conventions across repositories and tooling. + +**Solution**: Support both token names with fallback logic: + +**In workflow files**: +```yaml +env: + # Support both token names with fallback + CARGO_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} +``` + +**In scripts** (publish-crate.mjs): +```javascript +const config = makeConfig({ + yargs: ({ yargs, getenv }) => + yargs.option('token', { + type: 'string', + default: getenv('CARGO_REGISTRY_TOKEN', '') || getenv('CARGO_TOKEN', ''), + describe: 'Crates.io API token', + }), +}); +``` + +**Warning message** when no token is found: +``` +::warning::Neither CARGO_REGISTRY_TOKEN nor CARGO_TOKEN is set, attempting publish without explicit token +``` + +## Changes Applied + +### 1. `.github/workflows/release.yml` + +Updated the global environment variable to support both token names: + +```yaml +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -Dwarnings + # Support both CARGO_REGISTRY_TOKEN (cargo's native env var) and CARGO_TOKEN (for backwards compatibility) + CARGO_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} +``` + +### 2. `scripts/publish-crate.mjs` + +- Updated documentation to mention both token environment variables +- Modified `getenv()` call to check both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` +- Added warning message when neither token is set + +## Key Takeaways + +1. **Be aware of reserved words**: CLI argument parsing libraries often reserve common flags like `--version`, `--help`, `$0`. Consult the library documentation before choosing option names. + +2. **Provide backwards compatibility**: When changing secret/environment variable names, support the old name as a fallback to avoid breaking existing setups. + +3. **Use clear warning messages**: When configuration is missing, provide actionable messages that list ALL possible variable names. + +4. **Test CI scripts locally**: Running CI scripts locally with the same arguments used in workflows helps identify parsing issues before they cause failures. + +## References + +### yargs Documentation +- [yargs API Reference - version()](https://yargs.js.org/docs/#api-reference-version) +- [yargs Reserved Words](https://yargs.js.org/docs/#api-reference-version) + +### GitHub Issues +- [yargs/yargs#2064 - version reserved word conflict](https://github.com/yargs/yargs/issues/2064) + +### crates.io +- [Cargo Environment Variables](https://doc.rust-lang.org/cargo/reference/environment-variables.html) diff --git a/docs/case-studies/issue-19/README.md b/docs/case-studies/issue-19/README.md new file mode 100644 index 0000000..7374ddd --- /dev/null +++ b/docs/case-studies/issue-19/README.md @@ -0,0 +1,239 @@ +# Case Study: Issue #19 - Supporting Both Single-Language and Multi-Language Repositories in CI/CD Scripts + +## Summary + +This case study documents the investigation and resolution of a CI/CD pipeline failure that occurred when scripts designed for a multi-language repository structure (`./js/` subfolder) were used in a single-language repository. The root cause was identified as the `command-stream` library's implementation of `cd` as a virtual command that calls `process.chdir()`, permanently changing the Node.js process working directory. + +## Background + +The `link-foundation` organization maintains multiple repositories with different structures: +- **Single-language repositories**: Have `package.json` (JS) or `Cargo.toml` (Rust) in the root directory +- **Multi-language repositories**: Have language-specific code in subfolders (e.g., `./js/`, `./rust/`) + +Scripts originally developed for the `link-assistant/agent` multi-language repository were causing failures when paths like `./js/package.json` didn't exist in single-language repositories. + +## Original Issue: Issue #113 + +### CI Failure Details + +**CI Run:** [#20885464993](https://github.com/link-assistant/agent/actions/runs/20885464993/job/60008012717) +**Error Message:** +``` +Error: ENOENT: no such file or directory, open './js/package.json' +``` + +### Timeline of Events + +1. **2026-01-10 22:39:00 UTC** - CI run triggered on push to main branch +2. **2026-01-10 22:39:03 UTC** - Unit tests passed on all platforms (Ubuntu, Windows, macOS) +3. **2026-01-10 22:40:29 UTC** - Release job started +4. **2026-01-10 22:40:29 UTC** - Version bump executed successfully via `cd js && npm run changeset:version` +5. **2026-01-10 22:40:29 UTC** - Script attempted to read `./js/package.json` after the `cd` command +6. **2026-01-10 22:40:29 UTC** - **FAILURE**: `ENOENT: no such file or directory, open './js/package.json'` + +### The Bug: command-stream's Virtual `cd` Command + +The root cause was a subtle interaction between the `command-stream` library and Node.js's process working directory: + +1. **`command-stream`'s Virtual `cd` Command**: The library implements `cd` as a **virtual command** that calls `process.chdir()` on the Node.js process itself, rather than just affecting the subprocess. + +2. **Working Directory Persistence**: When the script executed: + ```javascript + await $`cd js && npm run changeset:version`; + ``` + The `cd js` command permanently changed the Node.js process's working directory from the repository root to the `js/` subdirectory. + +3. **Subsequent File Access Failure**: After the command returned, when the script tried to read `./js/package.json`, it was looking for the file relative to the **new** working directory (`js/`), which would resolve to `js/js/package.json` - a path that doesn't exist. + +### Code Flow Illustration + +``` +Repository Root (/) +├── js/ +│ └── package.json <- This is what we want to read +└── scripts/ + └── version-and-commit.mjs + +1. Script starts with cwd = / +2. Script runs: await $`cd js && npm run changeset:version` +3. command-stream's cd command calls: process.chdir('js') +4. cwd is now /js/ +5. Script tries to read: readFileSync('./js/package.json') +6. This resolves to: /js/js/package.json <- DOES NOT EXIST! +7. Error: ENOENT +``` + +### Why This Was Hard to Detect + +- The `cd` command in most shell scripts only affects the subprocess, not the parent process +- Developers familiar with Unix shells would not expect `cd` to affect the Node.js process +- The error message didn't clearly indicate that the working directory had changed +- The `command-stream` library documentation doesn't prominently warn about this behavior + +## Solution Implemented in PR #114 + +### 1. Working Directory Preservation and Restoration + +The fix involves saving the original working directory and restoring it after any command that uses `cd`: + +```javascript +// Store the original working directory +const originalCwd = process.cwd(); + +try { + // ... code that uses cd ... + await $`cd js && npm run changeset:version`; + + // Restore the original working directory + process.chdir(originalCwd); + + // Now file operations work correctly + const packageJson = JSON.parse(readFileSync('./js/package.json', 'utf8')); +} catch (error) { + // Handle error +} +``` + +### 2. Auto-Detection of Package Root + +New utility modules were created to automatically detect the package root: + +**`scripts/js-paths.mjs`** - JavaScript package root detection: +```javascript +export function getJsRoot(options = {}) { + // Check for single-language repo (package.json in root) + if (existsSync('./package.json')) { + return '.'; + } + // Check for multi-language repo (package.json in js/ subfolder) + if (existsSync('./js/package.json')) { + return 'js'; + } + // Error with helpful suggestions + throw new Error('Could not find package.json...'); +} +``` + +**`scripts/rust-paths.mjs`** - Rust package root detection: +```javascript +export function getRustRoot(options = {}) { + // Check for single-language repo (Cargo.toml in root) + if (existsSync('./Cargo.toml')) { + return '.'; + } + // Check for multi-language repo (Cargo.toml in rust/ subfolder) + if (existsSync('./rust/Cargo.toml')) { + return 'rust'; + } + // Error with helpful suggestions + throw new Error('Could not find Cargo.toml...'); +} +``` + +### 3. Configuration Options + +Scripts now support explicit configuration via: +- CLI arguments: `--js-root ` or `--rust-root ` +- Environment variables: `JS_ROOT` or `RUST_ROOT` + +### Usage Examples + +```bash +# Auto-detection (default) +node scripts/version-and-commit.mjs --mode changeset + +# Explicit configuration +node scripts/version-and-commit.mjs --mode changeset --js-root js + +# Via environment variable +JS_ROOT=js node scripts/version-and-commit.mjs --mode changeset +``` + +## Best Practices Identified + +### 1. Working Directory Management + +When using libraries that may modify process state (like `process.chdir()`): +- Always save the original state before potentially modifying operations +- Restore the original state after the operation completes +- Handle restoration in error paths as well + +### 2. Multi-Language Repository Support + +Following industry best practices for monorepo CI/CD: + +1. **Automatic Detection**: Check for package manifests in standard locations: + - Root directory first (single-language repo) + - Language-specific subfolders second (multi-language repo) + +2. **Explicit Configuration**: Allow overrides via CLI arguments and environment variables + +3. **Helpful Error Messages**: When auto-detection fails, provide clear guidance on how to configure manually + +4. **Path Abstraction**: Create utility functions that return appropriate paths based on repository structure: + - `getPackageJsonPath()` returns `./package.json` or `js/package.json` + - `getCargoTomlPath()` returns `./Cargo.toml` or `rust/Cargo.toml` + +### 3. CI/CD Pipeline Organization + +Based on research from [Buildkite](https://buildkite.com/resources/blog/monorepo-ci-best-practices/), [CircleCI](https://circleci.com/blog/monorepo-dev-practices/), and [Graphite](https://graphite.dev/guides/managing-multiple-languages-in-a-monorepo): + +1. **Selective Triggering**: Use path-based filters to only run relevant jobs +2. **Caching**: Cache language-specific artifacts (node_modules, target/) +3. **Standardized Commands**: Offer unified scripts that call into the right language-specific tooling +4. **Modular Organization**: Group projects logically (frontend/, backend/, lib/shared/) + +## Files Modified in PR #114 + +| File | Changes | +|------|---------| +| `scripts/js-paths.mjs` | New utility for JS package root detection | +| `scripts/rust-paths.mjs` | New utility for Rust package root detection | +| `scripts/version-and-commit.mjs` | Added cwd preservation and auto-detection | +| `scripts/instant-version-bump.mjs` | Added cwd preservation and auto-detection | +| `scripts/publish-to-npm.mjs` | Added cwd preservation and auto-detection | +| `scripts/rust-version-and-commit.mjs` | Added auto-detection | +| `scripts/rust-collect-changelog.mjs` | Added auto-detection | +| `scripts/rust-get-bump-type.mjs` | Added auto-detection | +| `docs/case-studies/issue-113/README.md` | Case study documentation | + +## Lessons Learned + +1. **Understand Library Internals**: Third-party libraries may have non-obvious behaviors. The `command-stream` library's virtual `cd` command is a powerful feature for maintaining working directory state, but it can cause issues if not handled properly. + +2. **Test Edge Cases**: The CI environment differs from local development. File path handling can behave differently depending on the working directory context. + +3. **Add Defensive Code**: When using commands that modify process state, always save and restore the original state. + +4. **Document Non-Obvious Behaviors**: The fix includes detailed comments explaining why the `process.chdir()` restoration is necessary. + +5. **Design for Multiple Repository Structures**: Scripts should be designed to work in both single-language and multi-language repository structures from the start. + +## References + +- [GitHub Issue #113 - JavaScript publish does not work](https://github.com/link-assistant/agent/issues/113) +- [GitHub PR #114 - Add configurable package root for release scripts](https://github.com/link-assistant/agent/pull/114) +- [CI Run #20885464993](https://github.com/link-assistant/agent/actions/runs/20885464993) +- [Node.js process.chdir() Method](https://www.geeksforgeeks.org/node-js-process-chdir-method/) +- [Monorepo CI Best Practices - Buildkite](https://buildkite.com/resources/blog/monorepo-ci-best-practices/) +- [Benefits and Challenges of Monorepo - CircleCI](https://circleci.com/blog/monorepo-dev-practices/) +- [Managing Multiple Languages in a Monorepo - Graphite](https://graphite.dev/guides/managing-multiple-languages-in-a-monorepo) +- [Monorepo Tooling in 2025](https://www.wisp.blog/blog/monorepo-tooling-in-2025-a-comprehensive-guide) + +## Appendix: Data Files + +The following data files were collected for this case study: + +| File | Description | +|------|-------------| +| `pr-114-data/pr-details.json` | Full PR metadata including files, comments, reviews | +| `pr-114-data/pr-diff.patch` | Complete diff of changes in PR #114 | +| `pr-114-data/pr-review-comments.json` | Inline code review comments | +| `pr-114-data/pr-conversation-comments.json` | General PR discussion comments | +| `pr-114-data/pr-commits.json` | Commit history of the PR | +| `pr-114-data/issue-113-details.txt` | Original issue description | +| `pr-114-data/solution-draft-log-1.txt.gz` | AI solution draft log (first iteration, compressed) | +| `pr-114-data/solution-draft-log-2.txt.gz` | AI solution draft log (second iteration, compressed) | +| `ci-logs/ci-run-20885464993.log.gz` | Full CI run log showing the failure (compressed) | + +Note: Log files are compressed with gzip to reduce repository size. Use `gunzip` to decompress. diff --git a/docs/case-studies/issue-19/ci-logs/ci-run-20885464993.log.gz b/docs/case-studies/issue-19/ci-logs/ci-run-20885464993.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..6c166e69fa2be24b1a29196e5c9da17454e9db42 GIT binary patch literal 20042 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","messageHeadline":"fix: Restore working directory after cd commands in release scripts","oid":"583f7cce33ffeecc7e0d1ebeba7ef86e4911e674"},{"authoredDate":"2026-01-10T22:52:49Z","authors":[{"email":"drakonard@gmail.com","id":"MDQ6VXNlcjE0MzE5MDQ=","login":"konard","name":"konard"}],"committedDate":"2026-01-10T22:52:49Z","messageBody":"This reverts commit 43e241ab09e1ac47e3abba0b1f3235ee6b33c130.","messageHeadline":"Revert \"Initial commit with task details\"","oid":"cc35a3720bca86104d66849ef31f725a3093e9ab"},{"authoredDate":"2026-01-10T23:04:45Z","authors":[{"email":"drakonard@gmail.com","id":"MDQ6VXNlcjE0MzE5MDQ=","login":"konard","name":"konard"},{"email":"noreply@anthropic.com","id":"MDQ6VXNlcjgxODQ3","login":"claude","name":"Claude Opus 4.5"}],"committedDate":"2026-01-10T23:04:45Z","messageBody":"Add js-paths.mjs and rust-paths.mjs utilities that automatically detect\nthe package root for both single-language and multi-language repositories:\n\n- Check for ./package.json or ./Cargo.toml first (single-language repo)\n- If not found, check ./js/ or ./rust/ subfolder (multi-language repo)\n- Support explicit configuration via --js-root/--rust-root CLI options\n- Support environment variables JS_ROOT and RUST_ROOT\n\nUpdated scripts to use the shared utilities:\n- version-and-commit.mjs\n- instant-version-bump.mjs\n- publish-to-npm.mjs\n- rust-version-and-commit.mjs\n- rust-collect-changelog.mjs\n- rust-get-bump-type.mjs\n\nThis makes the scripts work seamlessly in both single-language repositories\n(where package.json/Cargo.toml is in the root) and multi-language repositories\n(where they are in js/ and rust/ subfolders).\n\n🤖 Generated with [Claude Code](https://claude.com/claude-code)\n\nCo-Authored-By: Claude Opus 4.5 ","messageHeadline":"feat: Add configurable package root for release scripts","oid":"b4b1d66d59b6d563ed1d8395917b575974e692d0"}]} diff --git a/docs/case-studies/issue-19/pr-114-data/pr-conversation-comments.json b/docs/case-studies/issue-19/pr-114-data/pr-conversation-comments.json new file mode 100644 index 0000000..cf01669 --- /dev/null +++ b/docs/case-studies/issue-19/pr-114-data/pr-conversation-comments.json @@ -0,0 +1 @@ +[{"url":"https://api.github.com/repos/link-assistant/agent/issues/comments/3733645876","html_url":"https://github.com/link-assistant/agent/pull/114#issuecomment-3733645876","issue_url":"https://api.github.com/repos/link-assistant/agent/issues/114","id":3733645876,"node_id":"IC_kwDOQYTy3M7eiuo0","user":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2026-01-10T22:53:00Z","updated_at":"2026-01-10T22:53:00Z","body":"## 🤖 Solution Draft Log\nThis log file contains the complete execution trace of the AI solution draft process.\n\n💰 **Cost estimation:**\n- Public pricing estimate: $5.396049 USD\n- Calculated by Anthropic: $3.198203 USD\n- Difference: $-2.197846 (-40.73%)\n📎 **Log file uploaded as GitHub Gist** (561KB)\n🔗 [View complete solution draft log](https://gist.githubusercontent.com/konard/cf310db68d1ace851764202b38f809fa/raw/ad252e754e160d857ab82a4fc691510e8bf77e9f/solution-draft-log-pr-1768085575316.txt)\n---\n*Now working session is ended, feel free to review and add any feedback on the solution draft.*","author_association":"MEMBER","reactions":{"url":"https://api.github.com/repos/link-assistant/agent/issues/comments/3733645876/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null},{"url":"https://api.github.com/repos/link-assistant/agent/issues/comments/3733647962","html_url":"https://github.com/link-assistant/agent/pull/114#issuecomment-3733647962","issue_url":"https://api.github.com/repos/link-assistant/agent/issues/114","id":3733647962,"node_id":"IC_kwDOQYTy3M7eivJa","user":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2026-01-10T22:56:16Z","updated_at":"2026-01-10T22:56:36Z","body":"I think in all JavaScript related scripts we need to have configurable repository root, that will be automatically determined if not specified. We should check for repository root `./package.json` and also `./js/package.json` by default.\r\n\r\nSo if not in root, try to search in js folder, if that also don't exist throw an error, and suggest user to configure the root folder explicitly.\r\n\r\nThat way scripts will support both single language repository and multi language repositories.\r\n\r\nWe can also do similar thing for Rust .mjs scripts.","author_association":"MEMBER","reactions":{"url":"https://api.github.com/repos/link-assistant/agent/issues/comments/3733647962/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null},{"url":"https://api.github.com/repos/link-assistant/agent/issues/comments/3733648511","html_url":"https://github.com/link-assistant/agent/pull/114#issuecomment-3733648511","issue_url":"https://api.github.com/repos/link-assistant/agent/issues/114","id":3733648511,"node_id":"IC_kwDOQYTy3M7eivR_","user":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2026-01-10T22:57:10Z","updated_at":"2026-01-10T22:57:10Z","body":"🤖 **AI Work Session Started**\n\nStarting automated work session at 2026-01-10T22:57:08.495Z\n\nThe PR has been converted to draft mode while work is in progress.\n\n_This comment marks the beginning of an AI work session. Please wait working session to finish, and provide your feedback._","author_association":"MEMBER","reactions":{"url":"https://api.github.com/repos/link-assistant/agent/issues/comments/3733648511/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null},{"url":"https://api.github.com/repos/link-assistant/agent/issues/comments/3733663208","html_url":"https://github.com/link-assistant/agent/pull/114#issuecomment-3733663208","issue_url":"https://api.github.com/repos/link-assistant/agent/issues/114","id":3733663208,"node_id":"IC_kwDOQYTy3M7eiy3o","user":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2026-01-10T23:07:39Z","updated_at":"2026-01-10T23:07:39Z","body":"## 🤖 Solution Draft Log\nThis log file contains the complete execution trace of the AI solution draft process.\n\n💰 **Cost estimation:**\n- Public pricing estimate: $5.270661 USD\n- Calculated by Anthropic: $3.049357 USD\n- Difference: $-2.221304 (-42.14%)\n📎 **Log file uploaded as GitHub Gist** (612KB)\n🔗 [View complete solution draft log](https://gist.githubusercontent.com/konard/08328f143f1dfaee30edf6160e8bcb98/raw/1cec9c53daaa224d5a5d4fe32883410d957ea421/solution-draft-log-pr-1768086455505.txt)\n---\n*Now working session is ended, feel free to review and add any feedback on the solution draft.*","author_association":"MEMBER","reactions":{"url":"https://api.github.com/repos/link-assistant/agent/issues/comments/3733663208/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null}] \ No newline at end of file diff --git a/docs/case-studies/issue-19/pr-114-data/pr-details.json b/docs/case-studies/issue-19/pr-114-data/pr-details.json new file mode 100644 index 0000000..b25c430 --- /dev/null +++ b/docs/case-studies/issue-19/pr-114-data/pr-details.json @@ -0,0 +1 @@ +{"additions":615,"author":{"id":"MDQ6VXNlcjE0MzE5MDQ=","is_bot":false,"login":"konard","name":"Konstantin Diachenko"},"baseRefName":"main","body":"## Summary\n\nThis PR enhances the release scripts to support both single-language and multi-language repository structures by adding automatic package root detection.\n\n**New utility modules:**\n- `scripts/js-paths.mjs` - JavaScript package root detection\n- `scripts/rust-paths.mjs` - Rust package root detection\n\n**Updated scripts:**\n- `scripts/version-and-commit.mjs`\n- `scripts/instant-version-bump.mjs`\n- `scripts/publish-to-npm.mjs`\n- `scripts/rust-version-and-commit.mjs`\n- `scripts/rust-collect-changelog.mjs`\n- `scripts/rust-get-bump-type.mjs`\n\n## How It Works\n\nThe utilities automatically detect the package root:\n\n1. **Single-language repository**: Check for `./package.json` or `./Cargo.toml` in root\n2. **Multi-language repository**: Check for `./js/package.json` or `./rust/Cargo.toml`\n3. If neither exists, throw an error with helpful suggestions\n\n### Configuration Options\n\nScripts support explicit configuration via:\n- CLI arguments: `--js-root ` or `--rust-root `\n- Environment variables: `JS_ROOT` or `RUST_ROOT`\n\n### Example Usage\n\n```bash\n# Auto-detection (default)\nnode scripts/version-and-commit.mjs --mode changeset\n\n# Explicit configuration\nnode scripts/version-and-commit.mjs --mode changeset --js-root js\n\n# Via environment variable\nJS_ROOT=js node scripts/version-and-commit.mjs --mode changeset\n```\n\n## Root Cause (Original Issue)\n\nThe original issue (#113) was that `command-stream` library's `cd` command calls `process.chdir()`, which permanently changes the working directory. This caused file operations to fail after running commands like `cd js && npm run ...`.\n\nThis PR addresses the issue comprehensively by:\n1. ✅ Preserving and restoring the working directory after `cd` commands (previous fix)\n2. ✅ Adding auto-detection of package root to support both repo structures (this enhancement)\n\n## Test Plan\n\n- [x] Verified syntax check passes for all modified scripts\n- [x] Tested `js-paths.mjs` utility - correctly detects `js/` as package root\n- [x] Tested `rust-paths.mjs` utility - correctly detects `rust/` as package root\n- [ ] Verify CI pipeline passes\n- [ ] Manual test in a single-language repository (optional)\n\nFixes #113\n\n---\n🤖 Generated with [Claude Code](https://claude.com/claude-code)","comments":[{"id":"IC_kwDOQYTy3M7eiuo0","author":{"login":"konard"},"authorAssociation":"MEMBER","body":"## 🤖 Solution Draft Log\nThis log file contains the complete execution trace of the AI solution draft process.\n\n💰 **Cost estimation:**\n- Public pricing estimate: $5.396049 USD\n- Calculated by Anthropic: $3.198203 USD\n- Difference: $-2.197846 (-40.73%)\n📎 **Log file uploaded as GitHub Gist** (561KB)\n🔗 [View complete solution draft log](https://gist.githubusercontent.com/konard/cf310db68d1ace851764202b38f809fa/raw/ad252e754e160d857ab82a4fc691510e8bf77e9f/solution-draft-log-pr-1768085575316.txt)\n---\n*Now working session is ended, feel free to review and add any feedback on the solution draft.*","createdAt":"2026-01-10T22:53:00Z","includesCreatedEdit":false,"isMinimized":false,"minimizedReason":"","reactionGroups":[],"url":"https://github.com/link-assistant/agent/pull/114#issuecomment-3733645876","viewerDidAuthor":true},{"id":"IC_kwDOQYTy3M7eivJa","author":{"login":"konard"},"authorAssociation":"MEMBER","body":"I think in all JavaScript related scripts we need to have configurable repository root, that will be automatically determined if not specified. We should check for repository root `./package.json` and also `./js/package.json` by default.\r\n\r\nSo if not in root, try to search in js folder, if that also don't exist throw an error, and suggest user to configure the root folder explicitly.\r\n\r\nThat way scripts will support both single language repository and multi language repositories.\r\n\r\nWe can also do similar thing for Rust .mjs scripts.","createdAt":"2026-01-10T22:56:16Z","includesCreatedEdit":true,"isMinimized":false,"minimizedReason":"","reactionGroups":[],"url":"https://github.com/link-assistant/agent/pull/114#issuecomment-3733647962","viewerDidAuthor":true},{"id":"IC_kwDOQYTy3M7eivR_","author":{"login":"konard"},"authorAssociation":"MEMBER","body":"🤖 **AI Work Session Started**\n\nStarting automated work session at 2026-01-10T22:57:08.495Z\n\nThe PR has been converted to draft mode while work is in progress.\n\n_This comment marks the beginning of an AI work session. Please wait working session to finish, and provide your feedback._","createdAt":"2026-01-10T22:57:10Z","includesCreatedEdit":false,"isMinimized":false,"minimizedReason":"","reactionGroups":[],"url":"https://github.com/link-assistant/agent/pull/114#issuecomment-3733648511","viewerDidAuthor":true},{"id":"IC_kwDOQYTy3M7eiy3o","author":{"login":"konard"},"authorAssociation":"MEMBER","body":"## 🤖 Solution Draft Log\nThis log file contains the complete execution trace of the AI solution draft process.\n\n💰 **Cost estimation:**\n- Public pricing estimate: $5.270661 USD\n- Calculated by Anthropic: $3.049357 USD\n- Difference: $-2.221304 (-42.14%)\n📎 **Log file uploaded as GitHub Gist** (612KB)\n🔗 [View complete solution draft log](https://gist.githubusercontent.com/konard/08328f143f1dfaee30edf6160e8bcb98/raw/1cec9c53daaa224d5a5d4fe32883410d957ea421/solution-draft-log-pr-1768086455505.txt)\n---\n*Now working session is ended, feel free to review and add any feedback on the solution draft.*","createdAt":"2026-01-10T23:07:39Z","includesCreatedEdit":false,"isMinimized":false,"minimizedReason":"","reactionGroups":[],"url":"https://github.com/link-assistant/agent/pull/114#issuecomment-3733663208","viewerDidAuthor":true}],"createdAt":"2026-01-10T22:42:12Z","deletions":30,"files":[{"path":"docs/case-studies/issue-113/README.md","additions":111,"deletions":0},{"path":"scripts/instant-version-bump.mjs","additions":38,"deletions":5},{"path":"scripts/js-paths.mjs","additions":159,"deletions":0},{"path":"scripts/publish-to-npm.mjs","additions":41,"deletions":8},{"path":"scripts/rust-collect-changelog.mjs","additions":25,"deletions":4},{"path":"scripts/rust-get-bump-type.mjs","additions":12,"deletions":1},{"path":"scripts/rust-paths.mjs","additions":169,"deletions":0},{"path":"scripts/rust-version-and-commit.mjs","additions":17,"deletions":4},{"path":"scripts/version-and-commit.mjs","additions":43,"deletions":8}],"headRefName":"issue-113-e95eec3e2b2f","mergedAt":"2026-01-10T23:07:46Z","number":114,"reviews":[],"state":"MERGED","title":"feat: Add configurable package root for release scripts"} diff --git a/docs/case-studies/issue-19/pr-114-data/pr-diff.patch b/docs/case-studies/issue-19/pr-114-data/pr-diff.patch new file mode 100644 index 0000000..33f28bd --- /dev/null +++ b/docs/case-studies/issue-19/pr-114-data/pr-diff.patch @@ -0,0 +1,879 @@ +diff --git a/docs/case-studies/issue-113/README.md b/docs/case-studies/issue-113/README.md +new file mode 100644 +index 0000000..bd453f9 +--- /dev/null ++++ b/docs/case-studies/issue-113/README.md +@@ -0,0 +1,111 @@ ++# Case Study: Issue #113 - JavaScript Publish Does Not Work ++ ++## Summary ++ ++The JavaScript CI/CD pipeline was failing during the release step due to a subtle bug related to how the `command-stream` library handles the `cd` command. ++ ++## Timeline of Events ++ ++1. **CI Run Triggered**: Push to main branch triggered the JS CI/CD Pipeline (run #20885464993) ++2. **Tests Passed**: Lint, format check, and unit tests all passed successfully ++3. **Release Job Started**: The release job started and began processing changesets ++4. **Version Bump Executed**: The `version-and-commit.mjs` script ran `cd js && npm run changeset:version` ++5. **Failure**: After the version bump completed, the script failed with: ++ ``` ++ Error: ENOENT: no such file or directory, open './js/package.json' ++ ``` ++ ++## Root Cause Analysis ++ ++### The Bug ++ ++The root cause was a subtle interaction between the `command-stream` library and Node.js's process working directory: ++ ++1. **command-stream's Virtual `cd` Command**: The `command-stream` library implements `cd` as a **virtual command** that calls `process.chdir()` on the Node.js process itself, rather than just affecting the subprocess. ++ ++2. **Working Directory Persistence**: When the script executed: ++ ```javascript ++ await $`cd js && npm run changeset:version`; ++ ``` ++ The `cd js` command permanently changed the Node.js process's working directory from the repository root to the `js/` subdirectory. ++ ++3. **Subsequent File Access Failure**: After the command returned, when the script tried to read `./js/package.json`, it was looking for the file relative to the **new** working directory (`js/`), which would resolve to `js/js/package.json` - a path that doesn't exist. ++ ++### Code Flow ++ ++``` ++Repository Root (/) ++├── js/ ++│ └── package.json <- This is what we want to read ++└── scripts/ ++ └── version-and-commit.mjs ++ ++1. Script starts with cwd = / ++2. Script runs: await $`cd js && npm run changeset:version` ++3. command-stream's cd command calls: process.chdir('js') ++4. cwd is now /js/ ++5. Script tries to read: readFileSync('./js/package.json') ++6. This resolves to: /js/js/package.json <- DOES NOT EXIST! ++7. Error: ENOENT ++``` ++ ++### Why This Was Hard to Detect ++ ++- The `cd` command in most shell scripts only affects the subprocess, not the parent process ++- Developers familiar with Unix shells would not expect `cd` to affect the Node.js process ++- The error message didn't clearly indicate that the working directory had changed ++- The `command-stream` library documentation doesn't prominently warn about this behavior ++ ++## Solution ++ ++The fix involves saving the original working directory and restoring it after any command that uses `cd`: ++ ++```javascript ++// Store the original working directory ++const originalCwd = process.cwd(); ++ ++try { ++ // ... code that uses cd ... ++ await $`cd js && npm run changeset:version`; ++ ++ // Restore the original working directory ++ process.chdir(originalCwd); ++ ++ // Now file operations work correctly ++ const packageJson = JSON.parse(readFileSync('./js/package.json', 'utf8')); ++} catch (error) { ++ // Handle error ++} ++``` ++ ++### Files Modified ++ ++1. **scripts/version-and-commit.mjs**: Added cwd preservation and restoration after `cd js && npm run changeset:version` ++ ++2. **scripts/instant-version-bump.mjs**: Added cwd preservation and restoration after: ++ - `cd js && npm version ${bumpType} --no-git-tag-version` ++ - `cd js && npm install --package-lock-only --legacy-peer-deps` ++ ++3. **scripts/publish-to-npm.mjs**: Added cwd preservation and restoration after `cd js && npm run changeset:publish`, including proper handling in the retry loop error path ++ ++## Lessons Learned ++ ++1. **Understand Library Internals**: Third-party libraries may have non-obvious behaviors. The `command-stream` library's virtual `cd` command is a powerful feature for maintaining working directory state, but it can cause issues if not handled properly. ++ ++2. **Test Edge Cases**: The CI environment differs from local development. File path handling can behave differently depending on the working directory context. ++ ++3. **Add Defensive Code**: When using commands that modify process state, always save and restore the original state. ++ ++4. **Document Non-Obvious Behaviors**: The fix includes detailed comments explaining why the `process.chdir()` restoration is necessary. ++ ++## CI Logs ++ ++The full CI logs are preserved in: ++- `ci-logs/full-run-20885464993.log` - Complete run log ++- `ci-logs/release-job-60008012717.log` - Detailed release job log ++ ++## References ++ ++- [GitHub Issue #113](https://github.com/link-assistant/agent/issues/113) ++- [CI Run #20885464993](https://github.com/link-assistant/agent/actions/runs/20885464993) ++- [command-stream npm package](https://www.npmjs.com/package/command-stream) +diff --git a/scripts/instant-version-bump.mjs b/scripts/instant-version-bump.mjs +index c1a34dd..7673338 100644 +--- a/scripts/instant-version-bump.mjs ++++ b/scripts/instant-version-bump.mjs +@@ -14,6 +14,13 @@ + + import { readFileSync, writeFileSync } from 'fs'; + ++import { ++ getJsRoot, ++ getPackageJsonPath, ++ needsCd, ++ parseJsRootConfig, ++} from './js-paths.mjs'; ++ + // Load use-m dynamically + const { use } = eval( + await (await fetch('https://unpkg.com/use-m/use.js')).text() +@@ -37,11 +44,24 @@ const config = makeConfig({ + type: 'string', + default: getenv('DESCRIPTION', ''), + describe: 'Description for the version bump', ++ }) ++ .option('js-root', { ++ type: 'string', ++ default: getenv('JS_ROOT', ''), ++ describe: 'JavaScript package root directory (auto-detected if not specified)', + }), + }); + ++// Store the original working directory to restore after cd commands ++// IMPORTANT: command-stream's cd is a virtual command that calls process.chdir() ++const originalCwd = process.cwd(); ++ + try { +- const { bumpType, description } = config; ++ const { bumpType, description, jsRoot: jsRootArg } = config; ++ ++ // Get JavaScript package root (auto-detect or use explicit config) ++ const jsRootConfig = jsRootArg || parseJsRootConfig(); ++ const jsRoot = getJsRoot({ jsRoot: jsRootConfig, verbose: true }); + const finalDescription = description || `Manual ${bumpType} release`; + + if (!bumpType || !['major', 'minor', 'patch'].includes(bumpType)) { +@@ -54,15 +74,22 @@ try { + console.log(`\nBumping version (${bumpType})...`); + + // Get current version +- const packageJson = JSON.parse(readFileSync('js/package.json', 'utf-8')); ++ const packageJsonPath = getPackageJsonPath({ jsRoot }); ++ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); + const oldVersion = packageJson.version; + console.log(`Current version: ${oldVersion}`); + + // Bump version using npm version (doesn't create git tag) +- await $`cd js && npm version ${bumpType} --no-git-tag-version`; ++ // IMPORTANT: cd is a virtual command that calls process.chdir(), so we restore after ++ if (needsCd({ jsRoot })) { ++ await $`cd ${jsRoot} && npm version ${bumpType} --no-git-tag-version`; ++ process.chdir(originalCwd); ++ } else { ++ await $`npm version ${bumpType} --no-git-tag-version`; ++ } + + // Get new version +- const updatedPackageJson = JSON.parse(readFileSync('js/package.json', 'utf-8')); ++ const updatedPackageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); + const newVersion = updatedPackageJson.version; + console.log(`New version: ${newVersion}`); + +@@ -108,7 +135,13 @@ try { + + // Synchronize package-lock.json + console.log('\nSynchronizing package-lock.json...'); +- await $`cd js && npm install --package-lock-only --legacy-peer-deps`; ++ // IMPORTANT: cd is a virtual command that calls process.chdir(), so we restore after ++ if (needsCd({ jsRoot })) { ++ await $`cd ${jsRoot} && npm install --package-lock-only --legacy-peer-deps`; ++ process.chdir(originalCwd); ++ } else { ++ await $`npm install --package-lock-only --legacy-peer-deps`; ++ } + + console.log('\n✅ Instant version bump complete'); + console.log(`Version: ${oldVersion} → ${newVersion}`); +diff --git a/scripts/js-paths.mjs b/scripts/js-paths.mjs +new file mode 100644 +index 0000000..810d56b +--- /dev/null ++++ b/scripts/js-paths.mjs +@@ -0,0 +1,159 @@ ++#!/usr/bin/env node ++ ++/** ++ * JavaScript package path detection utility ++ * ++ * Automatically detects the JavaScript package root for both: ++ * - Single-language repositories (package.json in root) ++ * - Multi-language repositories (package.json in js/ subfolder) ++ * ++ * Usage: ++ * import { getJsRoot, getPackageJsonPath, getChangesetDir } from './js-paths.mjs'; ++ * ++ * const jsRoot = getJsRoot(); // Returns 'js' or '.' ++ * const pkgPath = getPackageJsonPath(); // Returns 'js/package.json' or './package.json' ++ */ ++ ++import { existsSync } from 'fs'; ++import { join } from 'path'; ++ ++// Cache for detected paths (computed once per process) ++let cachedJsRoot = null; ++ ++/** ++ * Detect JavaScript package root directory ++ * Checks in order: ++ * 1. ./package.json (single-language repo) ++ * 2. ./js/package.json (multi-language repo) ++ * ++ * @param {Object} options - Configuration options ++ * @param {string} [options.jsRoot] - Explicitly set JavaScript root (overrides auto-detection) ++ * @param {boolean} [options.verbose=false] - Log detection details ++ * @returns {string} The JavaScript root directory ('.' or 'js') ++ * @throws {Error} If no package.json is found in expected locations ++ */ ++export function getJsRoot(options = {}) { ++ const { jsRoot: explicitRoot, verbose = false } = options; ++ ++ // If explicitly configured, use that ++ if (explicitRoot !== undefined) { ++ if (verbose) { ++ console.log(`Using explicitly configured JavaScript root: ${explicitRoot}`); ++ } ++ return explicitRoot; ++ } ++ ++ // Return cached value if already computed ++ if (cachedJsRoot !== null) { ++ return cachedJsRoot; ++ } ++ ++ // Check for single-language repo (package.json in root) ++ if (existsSync('./package.json')) { ++ if (verbose) { ++ console.log('Detected single-language repository (package.json in root)'); ++ } ++ cachedJsRoot = '.'; ++ return cachedJsRoot; ++ } ++ ++ // Check for multi-language repo (package.json in js/ subfolder) ++ if (existsSync('./js/package.json')) { ++ if (verbose) { ++ console.log('Detected multi-language repository (package.json in js/)'); ++ } ++ cachedJsRoot = 'js'; ++ return cachedJsRoot; ++ } ++ ++ // No package.json found ++ throw new Error( ++ 'Could not find package.json in expected locations.\n' + ++ 'Searched in:\n' + ++ ' - ./package.json (single-language repository)\n' + ++ ' - ./js/package.json (multi-language repository)\n\n' + ++ 'To fix this, either:\n' + ++ ' 1. Run the script from the repository root\n' + ++ ' 2. Explicitly configure the JavaScript root using --js-root option\n' + ++ ' 3. Set the JS_ROOT environment variable' ++ ); ++} ++ ++/** ++ * Get the path to package.json ++ * @param {Object} options - Configuration options (passed to getJsRoot) ++ * @returns {string} Path to package.json ++ */ ++export function getPackageJsonPath(options = {}) { ++ const jsRoot = getJsRoot(options); ++ return jsRoot === '.' ? './package.json' : join(jsRoot, 'package.json'); ++} ++ ++/** ++ * Get the path to package-lock.json ++ * @param {Object} options - Configuration options (passed to getJsRoot) ++ * @returns {string} Path to package-lock.json ++ */ ++export function getPackageLockPath(options = {}) { ++ const jsRoot = getJsRoot(options); ++ return jsRoot === '.' ? './package-lock.json' : join(jsRoot, 'package-lock.json'); ++} ++ ++/** ++ * Get the path to .changeset directory ++ * @param {Object} options - Configuration options (passed to getJsRoot) ++ * @returns {string} Path to .changeset directory ++ */ ++export function getChangesetDir(options = {}) { ++ const jsRoot = getJsRoot(options); ++ return jsRoot === '.' ? './.changeset' : join(jsRoot, '.changeset'); ++} ++ ++/** ++ * Get the cd command prefix for running npm commands ++ * Returns empty string for single-language repos, 'cd js && ' for multi-language repos ++ * @param {Object} options - Configuration options (passed to getJsRoot) ++ * @returns {string} CD prefix for shell commands ++ */ ++export function getCdPrefix(options = {}) { ++ const jsRoot = getJsRoot(options); ++ return jsRoot === '.' ? '' : `cd ${jsRoot} && `; ++} ++ ++/** ++ * Check if we need to change directory before running npm commands ++ * @param {Object} options - Configuration options (passed to getJsRoot) ++ * @returns {boolean} True if cd is needed ++ */ ++export function needsCd(options = {}) { ++ const jsRoot = getJsRoot(options); ++ return jsRoot !== '.'; ++} ++ ++/** ++ * Reset the cached JavaScript root (useful for testing) ++ */ ++export function resetCache() { ++ cachedJsRoot = null; ++} ++ ++/** ++ * Parse JavaScript root from CLI arguments or environment ++ * Supports --js-root argument and JS_ROOT environment variable ++ * @returns {string|undefined} Configured JavaScript root or undefined for auto-detection ++ */ ++export function parseJsRootConfig() { ++ // Check CLI arguments ++ const args = process.argv.slice(2); ++ const jsRootIndex = args.indexOf('--js-root'); ++ if (jsRootIndex >= 0 && args[jsRootIndex + 1]) { ++ return args[jsRootIndex + 1]; ++ } ++ ++ // Check environment variable ++ if (process.env.JS_ROOT) { ++ return process.env.JS_ROOT; ++ } ++ ++ return undefined; ++} +diff --git a/scripts/publish-to-npm.mjs b/scripts/publish-to-npm.mjs +index 450af67..9b41bc4 100644 +--- a/scripts/publish-to-npm.mjs ++++ b/scripts/publish-to-npm.mjs +@@ -15,6 +15,13 @@ + + import { readFileSync, appendFileSync } from 'fs'; + ++import { ++ getJsRoot, ++ getPackageJsonPath, ++ needsCd, ++ parseJsRootConfig, ++} from './js-paths.mjs'; ++ + // Package name from package.json + const PACKAGE_NAME = '@link-assistant/agent'; + +@@ -30,14 +37,24 @@ const { makeConfig } = await use('lino-arguments'); + // Parse CLI arguments using lino-arguments + const config = makeConfig({ + yargs: ({ yargs, getenv }) => +- yargs.option('should-pull', { +- type: 'boolean', +- default: getenv('SHOULD_PULL', false), +- describe: 'Pull latest changes before publishing', +- }), ++ yargs ++ .option('should-pull', { ++ type: 'boolean', ++ default: getenv('SHOULD_PULL', false), ++ describe: 'Pull latest changes before publishing', ++ }) ++ .option('js-root', { ++ type: 'string', ++ default: getenv('JS_ROOT', ''), ++ describe: 'JavaScript package root directory (auto-detected if not specified)', ++ }), + }); + +-const { shouldPull } = config; ++const { shouldPull, jsRoot: jsRootArg } = config; ++ ++// Get JavaScript package root (auto-detect or use explicit config) ++const jsRootConfig = jsRootArg || parseJsRootConfig(); ++const jsRoot = getJsRoot({ jsRoot: jsRootConfig, verbose: true }); + const MAX_RETRIES = 3; + const RETRY_DELAY = 10000; // 10 seconds + +@@ -62,6 +79,10 @@ function setOutput(key, value) { + } + + async function main() { ++ // Store the original working directory to restore after cd commands ++ // IMPORTANT: command-stream's cd is a virtual command that calls process.chdir() ++ const originalCwd = process.cwd(); ++ + try { + if (shouldPull) { + // Pull the latest changes we just pushed +@@ -69,7 +90,8 @@ async function main() { + } + + // Get current version +- const packageJson = JSON.parse(readFileSync('./js/package.json', 'utf8')); ++ const packageJsonPath = getPackageJsonPath({ jsRoot }); ++ const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8')); + const currentVersion = packageJson.version; + console.log(`Current version to publish: ${currentVersion}`); + +@@ -101,7 +123,14 @@ async function main() { + for (let i = 1; i <= MAX_RETRIES; i++) { + console.log(`Publish attempt ${i} of ${MAX_RETRIES}...`); + try { +- await $`npm run changeset:publish`; ++ // Run changeset:publish from the js directory where package.json with this script exists ++ // IMPORTANT: cd is a virtual command that calls process.chdir(), so we restore after ++ if (needsCd({ jsRoot })) { ++ await $`cd ${jsRoot} && npm run changeset:publish`; ++ process.chdir(originalCwd); ++ } else { ++ await $`npm run changeset:publish`; ++ } + setOutput('published', 'true'); + setOutput('published_version', currentVersion); + console.log( +@@ -109,6 +138,10 @@ async function main() { + ); + return; + } catch (_error) { ++ // Restore cwd on error before retry ++ if (needsCd({ jsRoot })) { ++ process.chdir(originalCwd); ++ } + if (i < MAX_RETRIES) { + console.log( + `Publish failed, waiting ${RETRY_DELAY / 1000}s before retry...` +diff --git a/scripts/rust-collect-changelog.mjs b/scripts/rust-collect-changelog.mjs +index dc4f385..dd6712c 100644 +--- a/scripts/rust-collect-changelog.mjs ++++ b/scripts/rust-collect-changelog.mjs +@@ -15,8 +15,29 @@ import { + } from 'fs'; + import { join } from 'path'; + +-const CHANGELOG_DIR = 'rust/changelog.d'; +-const CHANGELOG_FILE = 'rust/CHANGELOG.md'; ++import { ++ getRustRoot, ++ getCargoTomlPath, ++ getChangelogDir, ++ getChangelogPath, ++ parseRustRootConfig, ++} from './rust-paths.mjs'; ++ ++// Simple CLI argument parsing ++const args = process.argv.slice(2); ++const getArg = (name, defaultValue) => { ++ const index = args.indexOf(`--${name}`); ++ return index >= 0 && args[index + 1] ? args[index + 1] : defaultValue; ++}; ++ ++// Get Rust package root (auto-detect or use explicit config) ++const rustRootConfig = getArg('rust-root', '') || parseRustRootConfig(); ++const rustRoot = getRustRoot({ rustRoot: rustRootConfig || undefined, verbose: true }); ++ ++// Get paths based on detected/configured rust root ++const CARGO_TOML = getCargoTomlPath({ rustRoot }); ++const CHANGELOG_DIR = getChangelogDir({ rustRoot }); ++const CHANGELOG_FILE = getChangelogPath({ rustRoot }); + const INSERT_MARKER = ''; + + /** +@@ -24,11 +45,11 @@ const INSERT_MARKER = ''; + * @returns {string} + */ + function getVersionFromCargo() { +- const cargoToml = readFileSync('rust/Cargo.toml', 'utf-8'); ++ const cargoToml = readFileSync(CARGO_TOML, 'utf-8'); + const match = cargoToml.match(/^version\s*=\s*"([^"]+)"/m); + + if (!match) { +- console.error('Error: Could not find version in rust/Cargo.toml'); ++ console.error(`Error: Could not find version in ${CARGO_TOML}`); + process.exit(1); + } + +diff --git a/scripts/rust-get-bump-type.mjs b/scripts/rust-get-bump-type.mjs +index 31b492e..0a608a9 100644 +--- a/scripts/rust-get-bump-type.mjs ++++ b/scripts/rust-get-bump-type.mjs +@@ -20,6 +20,12 @@ + import { readFileSync, readdirSync, existsSync, appendFileSync } from 'fs'; + import { join } from 'path'; + ++import { ++ getRustRoot, ++ getChangelogDir, ++ parseRustRootConfig, ++} from './rust-paths.mjs'; ++ + // Simple CLI argument parsing + const args = process.argv.slice(2); + const getArg = (name, defaultValue) => { +@@ -29,7 +35,12 @@ const getArg = (name, defaultValue) => { + + const defaultBump = getArg('default', process.env.DEFAULT_BUMP || 'patch'); + +-const CHANGELOG_DIR = 'rust/changelog.d'; ++// Get Rust package root (auto-detect or use explicit config) ++const rustRootConfig = getArg('rust-root', '') || parseRustRootConfig(); ++const rustRoot = getRustRoot({ rustRoot: rustRootConfig || undefined, verbose: true }); ++ ++// Get paths based on detected/configured rust root ++const CHANGELOG_DIR = getChangelogDir({ rustRoot }); + + // Bump type priority (higher = more significant) + const BUMP_PRIORITY = { +diff --git a/scripts/rust-paths.mjs b/scripts/rust-paths.mjs +new file mode 100644 +index 0000000..4f4636a +--- /dev/null ++++ b/scripts/rust-paths.mjs +@@ -0,0 +1,169 @@ ++#!/usr/bin/env node ++ ++/** ++ * Rust package path detection utility ++ * ++ * Automatically detects the Rust package root for both: ++ * - Single-language repositories (Cargo.toml in root) ++ * - Multi-language repositories (Cargo.toml in rust/ subfolder) ++ * ++ * Usage: ++ * import { getRustRoot, getCargoTomlPath, getChangelogDir } from './rust-paths.mjs'; ++ * ++ * const rustRoot = getRustRoot(); // Returns 'rust' or '.' ++ * const cargoPath = getCargoTomlPath(); // Returns 'rust/Cargo.toml' or './Cargo.toml' ++ */ ++ ++import { existsSync } from 'fs'; ++import { join } from 'path'; ++ ++// Cache for detected paths (computed once per process) ++let cachedRustRoot = null; ++ ++/** ++ * Detect Rust package root directory ++ * Checks in order: ++ * 1. ./Cargo.toml (single-language repo) ++ * 2. ./rust/Cargo.toml (multi-language repo) ++ * ++ * @param {Object} options - Configuration options ++ * @param {string} [options.rustRoot] - Explicitly set Rust root (overrides auto-detection) ++ * @param {boolean} [options.verbose=false] - Log detection details ++ * @returns {string} The Rust root directory ('.' or 'rust') ++ * @throws {Error} If no Cargo.toml is found in expected locations ++ */ ++export function getRustRoot(options = {}) { ++ const { rustRoot: explicitRoot, verbose = false } = options; ++ ++ // If explicitly configured, use that ++ if (explicitRoot !== undefined) { ++ if (verbose) { ++ console.log(`Using explicitly configured Rust root: ${explicitRoot}`); ++ } ++ return explicitRoot; ++ } ++ ++ // Return cached value if already computed ++ if (cachedRustRoot !== null) { ++ return cachedRustRoot; ++ } ++ ++ // Check for single-language repo (Cargo.toml in root) ++ if (existsSync('./Cargo.toml')) { ++ if (verbose) { ++ console.log('Detected single-language repository (Cargo.toml in root)'); ++ } ++ cachedRustRoot = '.'; ++ return cachedRustRoot; ++ } ++ ++ // Check for multi-language repo (Cargo.toml in rust/ subfolder) ++ if (existsSync('./rust/Cargo.toml')) { ++ if (verbose) { ++ console.log('Detected multi-language repository (Cargo.toml in rust/)'); ++ } ++ cachedRustRoot = 'rust'; ++ return cachedRustRoot; ++ } ++ ++ // No Cargo.toml found ++ throw new Error( ++ 'Could not find Cargo.toml in expected locations.\n' + ++ 'Searched in:\n' + ++ ' - ./Cargo.toml (single-language repository)\n' + ++ ' - ./rust/Cargo.toml (multi-language repository)\n\n' + ++ 'To fix this, either:\n' + ++ ' 1. Run the script from the repository root\n' + ++ ' 2. Explicitly configure the Rust root using --rust-root option\n' + ++ ' 3. Set the RUST_ROOT environment variable' ++ ); ++} ++ ++/** ++ * Get the path to Cargo.toml ++ * @param {Object} options - Configuration options (passed to getRustRoot) ++ * @returns {string} Path to Cargo.toml ++ */ ++export function getCargoTomlPath(options = {}) { ++ const rustRoot = getRustRoot(options); ++ return rustRoot === '.' ? './Cargo.toml' : join(rustRoot, 'Cargo.toml'); ++} ++ ++/** ++ * Get the path to Cargo.lock ++ * @param {Object} options - Configuration options (passed to getRustRoot) ++ * @returns {string} Path to Cargo.lock ++ */ ++export function getCargoLockPath(options = {}) { ++ const rustRoot = getRustRoot(options); ++ return rustRoot === '.' ? './Cargo.lock' : join(rustRoot, 'Cargo.lock'); ++} ++ ++/** ++ * Get the path to changelog.d directory ++ * @param {Object} options - Configuration options (passed to getRustRoot) ++ * @returns {string} Path to changelog.d directory ++ */ ++export function getChangelogDir(options = {}) { ++ const rustRoot = getRustRoot(options); ++ return rustRoot === '.' ? './changelog.d' : join(rustRoot, 'changelog.d'); ++} ++ ++/** ++ * Get the path to CHANGELOG.md ++ * @param {Object} options - Configuration options (passed to getRustRoot) ++ * @returns {string} Path to CHANGELOG.md ++ */ ++export function getChangelogPath(options = {}) { ++ const rustRoot = getRustRoot(options); ++ return rustRoot === '.' ? './CHANGELOG.md' : join(rustRoot, 'CHANGELOG.md'); ++} ++ ++/** ++ * Get the cd command prefix for running cargo commands ++ * Returns empty string for single-language repos, 'cd rust && ' for multi-language repos ++ * @param {Object} options - Configuration options (passed to getRustRoot) ++ * @returns {string} CD prefix for shell commands ++ */ ++export function getCdPrefix(options = {}) { ++ const rustRoot = getRustRoot(options); ++ return rustRoot === '.' ? '' : `cd ${rustRoot} && `; ++} ++ ++/** ++ * Check if we need to change directory before running cargo commands ++ * @param {Object} options - Configuration options (passed to getRustRoot) ++ * @returns {boolean} True if cd is needed ++ */ ++export function needsCd(options = {}) { ++ const rustRoot = getRustRoot(options); ++ return rustRoot !== '.'; ++} ++ ++/** ++ * Reset the cached Rust root (useful for testing) ++ */ ++export function resetCache() { ++ cachedRustRoot = null; ++} ++ ++/** ++ * Parse Rust root from CLI arguments or environment ++ * Supports --rust-root argument and RUST_ROOT environment variable ++ * @returns {string|undefined} Configured Rust root or undefined for auto-detection ++ */ ++export function parseRustRootConfig() { ++ // Check CLI arguments ++ const args = process.argv.slice(2); ++ const rustRootIndex = args.indexOf('--rust-root'); ++ if (rustRootIndex >= 0 && args[rustRootIndex + 1]) { ++ return args[rustRootIndex + 1]; ++ } ++ ++ // Check environment variable ++ if (process.env.RUST_ROOT) { ++ return process.env.RUST_ROOT; ++ } ++ ++ return undefined; ++} +diff --git a/scripts/rust-version-and-commit.mjs b/scripts/rust-version-and-commit.mjs +index f90bbd7..7b9117c 100644 +--- a/scripts/rust-version-and-commit.mjs ++++ b/scripts/rust-version-and-commit.mjs +@@ -18,6 +18,14 @@ import { + import { join } from 'path'; + import { execSync } from 'child_process'; + ++import { ++ getRustRoot, ++ getCargoTomlPath, ++ getChangelogDir, ++ getChangelogPath, ++ parseRustRootConfig, ++} from './rust-paths.mjs'; ++ + // Simple CLI argument parsing + const args = process.argv.slice(2); + const getArg = (name, defaultValue) => { +@@ -28,16 +36,21 @@ const getArg = (name, defaultValue) => { + const bumpType = getArg('bump-type', process.env.BUMP_TYPE || ''); + const description = getArg('description', process.env.DESCRIPTION || ''); + ++// Get Rust package root (auto-detect or use explicit config) ++const rustRootConfig = getArg('rust-root', '') || parseRustRootConfig(); ++const rustRoot = getRustRoot({ rustRoot: rustRootConfig || undefined, verbose: true }); ++ + if (!bumpType || !['major', 'minor', 'patch'].includes(bumpType)) { + console.error( +- 'Usage: node scripts/rust-version-and-commit.mjs --bump-type [--description ]' ++ 'Usage: node scripts/rust-version-and-commit.mjs --bump-type [--description ] [--rust-root ]' + ); + process.exit(1); + } + +-const CARGO_TOML = 'rust/Cargo.toml'; +-const CHANGELOG_DIR = 'rust/changelog.d'; +-const CHANGELOG_FILE = 'rust/CHANGELOG.md'; ++// Get paths based on detected/configured rust root ++const CARGO_TOML = getCargoTomlPath({ rustRoot }); ++const CHANGELOG_DIR = getChangelogDir({ rustRoot }); ++const CHANGELOG_FILE = getChangelogPath({ rustRoot }); + + /** + * Append to GitHub Actions output file +diff --git a/scripts/version-and-commit.mjs b/scripts/version-and-commit.mjs +index 7235407..28d21dc 100644 +--- a/scripts/version-and-commit.mjs ++++ b/scripts/version-and-commit.mjs +@@ -14,6 +14,14 @@ + + import { readFileSync, appendFileSync, readdirSync } from 'fs'; + ++import { ++ getJsRoot, ++ getPackageJsonPath, ++ getChangesetDir, ++ needsCd, ++ parseJsRootConfig, ++} from './js-paths.mjs'; ++ + // Load use-m dynamically + const { use } = eval( + await (await fetch('https://unpkg.com/use-m/use.js')).text() +@@ -42,16 +50,26 @@ const config = makeConfig({ + type: 'string', + default: getenv('DESCRIPTION', ''), + describe: 'Description for instant version bump', ++ }) ++ .option('js-root', { ++ type: 'string', ++ default: getenv('JS_ROOT', ''), ++ describe: 'JavaScript package root directory (auto-detected if not specified)', + }), + }); + +-const { mode, bumpType, description } = config; ++const { mode, bumpType, description, jsRoot: jsRootArg } = config; ++ ++// Get JavaScript package root (auto-detect or use explicit config) ++const jsRootConfig = jsRootArg || parseJsRootConfig(); ++const jsRoot = getJsRoot({ jsRoot: jsRootConfig, verbose: true }); + + // Debug: Log parsed configuration + console.log('Parsed configuration:', { + mode, + bumpType, + description: description || '(none)', ++ jsRoot, + }); + + // Detect if positional arguments were used (common mistake) +@@ -112,7 +130,7 @@ function setOutput(key, value) { + */ + function countChangesets() { + try { +- const changesetDir = 'js/.changeset'; ++ const changesetDir = getChangesetDir({ jsRoot }); + const files = readdirSync(changesetDir); + return files.filter((f) => f.endsWith('.md') && f !== 'README.md').length; + } catch { +@@ -125,16 +143,24 @@ function countChangesets() { + * @param {string} source - 'local' or 'remote' + */ + async function getVersion(source = 'local') { ++ const packageJsonPath = getPackageJsonPath({ jsRoot }); + if (source === 'remote') { +- const result = await $`git show origin/main:js/package.json`.run({ ++ // For remote, we need the path relative to repo root (without ./ prefix) ++ const remotePath = packageJsonPath.replace(/^\.\//, ''); ++ const result = await $`git show origin/main:${remotePath}`.run({ + capture: true, + }); + return JSON.parse(result.stdout).version; + } +- return JSON.parse(readFileSync('./js/package.json', 'utf8')).version; ++ return JSON.parse(readFileSync(packageJsonPath, 'utf8')).version; + } + + async function main() { ++ // Store the original working directory to restore after cd commands ++ // IMPORTANT: command-stream's cd is a virtual command that calls process.chdir() ++ // This means `cd js` actually changes the Node.js process's working directory ++ const originalCwd = process.cwd(); ++ + try { + // Configure git + await $`git config user.name "github-actions[bot]"`; +@@ -186,17 +212,26 @@ async function main() { + + if (mode === 'instant') { + console.log('Running instant version bump...'); +- // Run instant version bump script ++ // Run instant version bump script, passing js-root for consistent path handling + // Rely on command-stream's auto-quoting for proper argument handling + if (description) { +- await $`node scripts/instant-version-bump.mjs --bump-type ${bumpType} --description ${description}`; ++ await $`node scripts/instant-version-bump.mjs --bump-type ${bumpType} --description ${description} --js-root ${jsRoot}`; + } else { +- await $`node scripts/instant-version-bump.mjs --bump-type ${bumpType}`; ++ await $`node scripts/instant-version-bump.mjs --bump-type ${bumpType} --js-root ${jsRoot}`; + } + } else { + console.log('Running changeset version...'); + // Run changeset version to bump versions and update CHANGELOG +- await $`cd js && npm run changeset:version`; ++ // IMPORTANT: cd is a virtual command in command-stream that calls process.chdir() ++ // We need to restore the original directory after this command ++ if (needsCd({ jsRoot })) { ++ await $`cd ${jsRoot} && npm run changeset:version`; ++ // Restore the original working directory ++ process.chdir(originalCwd); ++ } else { ++ // Single-language repo - run in current directory ++ await $`npm run changeset:version`; ++ } + } + + // Get new version after bump diff --git a/docs/case-studies/issue-19/pr-114-data/pr-review-comments.json b/docs/case-studies/issue-19/pr-114-data/pr-review-comments.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/docs/case-studies/issue-19/pr-114-data/pr-review-comments.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/docs/case-studies/issue-19/pr-114-data/pr-reviews.json b/docs/case-studies/issue-19/pr-114-data/pr-reviews.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/docs/case-studies/issue-19/pr-114-data/pr-reviews.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/docs/case-studies/issue-19/pr-114-data/solution-draft-log-1.txt.gz b/docs/case-studies/issue-19/pr-114-data/solution-draft-log-1.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..a7a5d631c9dfb75cd776a457ffbd97593c9c58c2 GIT binary patch literal 89348 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 z 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 z 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 a5LU*msM(PcQ8mUSc)tO%6hVA>#0UUYNN-~R literal 0 HcmV?d00001 diff --git a/docs/case-studies/issue-19/pr-114-data/solution-draft-log-2.txt.gz b/docs/case-studies/issue-19/pr-114-data/solution-draft-log-2.txt.gz new file mode 100644 index 0000000000000000000000000000000000000000..49262ff5ad0490f1c8f67dd2c885d69975b5950d GIT binary patch literal 69878 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/dev/null 2>&1; then + echo "... already released" +fi +``` + +This caused false positives because git tags can exist without the package being published to crates.io. + +**Evidence:** +- `browser-commander` had 10 GitHub releases (v0.1.1 through v0.5.4) +- But **NONE** of them existed on crates.io +- The workflow incorrectly marked versions as "already released" + +**Fix:** Check crates.io API directly: +```bash +CRATES_IO_RESPONSE=$(curl -s "https://crates.io/api/v1/crates/${CRATE_NAME}/${CURRENT_VERSION}") +if echo "$CRATES_IO_RESPONSE" | grep -q '"version"'; then + VERSION_ON_CRATES_IO=true +fi +``` + +#### Issue #31: Missing Cargo Publish Step (2026-01-18 01:16) + +**Root Cause:** The workflow correctly detected that versions weren't published to crates.io, but lacked the actual `cargo publish` step. + +The workflow only: +1. Built the release binary (`cargo build --release`) +2. Created a GitHub release + +**Missing step:** Actual `cargo publish` to publish to crates.io. + +**Fix:** Add `publish-crate.mjs` script and workflow step: +```yaml +- name: Publish to Crates.io + if: steps.check.outputs.should_release == 'true' + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} + run: node scripts/publish-crate.mjs +``` + +#### Issue #33: Secret Name Mismatch (2026-01-18 17:53) + +**Root Cause:** The workflow referenced `secrets.CARGO_REGISTRY_TOKEN` but the organization secret was named `CARGO_TOKEN`. + +**CI Log Evidence:** +``` +CARGO_REGISTRY_TOKEN: +##[warning]Neither CARGO_REGISTRY_TOKEN nor CARGO_TOKEN is set +error: please provide a non-empty token +``` + +**Fix:** Map the organization secret to the expected environment variable: +```yaml +env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} +``` + +## Root Causes Summary + +### 1. GitHub Actions Job Dependency Behavior + +**Problem:** When a job is skipped, all dependent jobs are also skipped unless they use `always()`. + +**Best Practice:** Always use `always() && !cancelled()` in job conditions when depending on jobs that may be skipped: +```yaml +if: always() && !cancelled() && needs.build.result == 'success' +``` + +### 2. Version Check Logic + +**Problem:** Checking git tags is not sufficient to determine if a version is published. + +**Best Practice:** Check the actual package registry (crates.io for Rust, npm for JS, PyPI for Python): +```javascript +// Example: Check crates.io API +const response = await fetch(`https://crates.io/api/v1/crates/${crateName}/${version}`); +const isPublished = response.ok && (await response.json()).version; +``` + +### 3. Missing Publication Steps + +**Problem:** Building and creating GitHub releases doesn't mean the package is published to the registry. + +**Best Practice:** Always include the publication step: +- Rust: `cargo publish` +- JavaScript: `npm publish` +- Python: `twine upload` + +### 4. Environment Variable Naming Conventions + +**Problem:** Different naming conventions between organization secrets and what tools expect. + +**Best Practice:** Document and standardize secret names. Use mapping when necessary: +```yaml +env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} +``` + +## Template vs Browser-Commander Comparison + +### Files Present in Template but Missing in Browser-Commander + +| Template File | Purpose | Browser-Commander Status | +|---------------|---------|-------------------------| +| `scripts/check-release-needed.mjs` | Checks crates.io for version status | Present (in rust/scripts/) | +| `scripts/git-config.mjs` | Configures git for automated commits | Missing (uses inline script) | +| `scripts/check-changelog-fragment.mjs` | Validates changelog fragments | Missing (uses inline script) | +| `scripts/check-version-modification.mjs` | Prevents manual version changes | Missing | +| `scripts/create-changelog-fragment.mjs` | Creates changelog fragment from workflow | Missing | +| `.pre-commit-config.yaml` | Pre-commit hooks | Missing | +| `CONTRIBUTING.md` | Contributing guidelines | Missing | +| `changelog.d/README.md` | Changelog fragment documentation | Present | + +### Workflow Differences + +| Feature | Template | Browser-Commander | +|---------|----------|-------------------| +| Secret handling | `CARGO_REGISTRY_TOKEN \|\| CARGO_TOKEN` | `CARGO_TOKEN` mapped to `CARGO_REGISTRY_TOKEN` | +| Version check | External script with crates.io check | Inline script with crates.io check | +| Git config | External script | Inline commands | +| Changelog check | External script | Inline script | +| Version modification check | Present | Missing | +| Release modes | instant + changelog-pr | Single mode | + +## Recommendations for Template Improvements + +### 1. Robust Secret Handling (Priority: High) + +Add fallback support for multiple secret naming conventions: +```yaml +env: + CARGO_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} +``` + +This is already implemented in the template. + +### 2. Comprehensive Documentation (Priority: High) + +Add a "CI/CD Troubleshooting Guide" in the template that covers: +- Common failure modes (jobs skipped, secret issues, version check failures) +- How to verify crates.io publication status +- How to manually trigger releases +- Secret setup requirements + +### 3. Multi-Language Repository Support (Priority: Medium) + +The `rust-paths.mjs` module in browser-commander provides excellent support for both: +- Single-language repos (Cargo.toml at root) +- Multi-language repos (rust/Cargo.toml) + +This should be incorporated into the template. + +### 4. Enhanced Error Reporting (Priority: Medium) + +The `publish-crate.mjs` script should: +- Fail explicitly when authentication fails +- Provide clear error messages about which secret is expected +- Log the masked token presence for debugging + +### 5. Job Result Verification (Priority: High) + +All release jobs should verify that upstream jobs succeeded: +```yaml +if: | + always() && !cancelled() && + needs.lint.result == 'success' && + needs.test.result == 'success' && + needs.build.result == 'success' +``` + +## Files in This Case Study + +- `README.md` - This analysis document +- `browser-commander-issue-27.md` - Case study from issue #27 (jobs skipped) +- `browser-commander-issue-29.md` - Case study from issue #29 (false positive version check) +- `browser-commander-issue-31.md` - Case study from issue #31 (missing publish step) +- `browser-commander-issue-33.md` - Case study from issue #33 (secret name mismatch) +- `browser-commander-rust.yml` - Browser-commander's Rust CI/CD workflow (reference) + +## References + +- Issue #21: https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/21 +- Browser-Commander PRs: #28, #30, #32, #34 +- GitHub Actions Runner Issue #491: https://github.com/actions/runner/issues/491 +- Cargo Registry Documentation: https://doc.rust-lang.org/cargo/reference/registries.html +- Template Repository: https://github.com/link-foundation/rust-ai-driven-development-pipeline-template + +## Lessons Learned + +1. **Test the complete pipeline end-to-end** - Don't just test individual steps; verify that packages actually appear on the registry. + +2. **Document secret naming conventions** - Clearly document which secrets are needed and their exact names. + +3. **Use defensive coding in workflows** - Always handle the case where dependencies are skipped using `always() && !cancelled()`. + +4. **Check the source of truth** - For package publication, check the actual registry (crates.io), not proxies like git tags. + +5. **Provide clear error messages** - When authentication fails, make it obvious which secret is missing or misconfigured. + +6. **Keep templates synchronized** - Regularly audit derived repositories against the template to catch missing features or fixes. diff --git a/docs/case-studies/issue-21/browser-commander-issue-27.md b/docs/case-studies/issue-21/browser-commander-issue-27.md new file mode 100644 index 0000000..29c1e3a --- /dev/null +++ b/docs/case-studies/issue-21/browser-commander-issue-27.md @@ -0,0 +1,116 @@ +# Case Study: Rust Release Jobs Skipped (Issue #27) + +## Timeline of Events + +### 2026-01-16 17:09:55 UTC - Run ID 21074589083 +- **Event**: `workflow_dispatch` (manual trigger) +- **Result**: Pipeline completed but release jobs were skipped +- **Jobs Status**: + - Detect Changes: SKIPPED (expected - has `if: github.event_name != 'workflow_dispatch'`) + - Test (all platforms): SUCCESS + - Changelog Fragment Check: SKIPPED + - **Lint and Format Check: SKIPPED** (unexpected) + - **Build Package: SKIPPED** (unexpected - depends on lint) + - **Manual Release: SKIPPED** (unexpected - depends on build) + - Auto Release: SKIPPED (expected - only on push to main) + +### 2026-01-10 13:38:44 UTC - Run ID 20879147120 +- **Event**: `push` to main branch +- **Result**: Build succeeded but Auto Release was skipped +- **Jobs Status**: + - Detect Changes: SUCCESS + - Lint and Format Check: SUCCESS + - Test (all platforms): SUCCESS + - Build Package: SUCCESS + - **Auto Release: SKIPPED** (unexpected) + +## Root Cause Analysis + +### Primary Root Cause: Missing `always()` in Job Conditions + +The GitHub Actions workflow has a fundamental issue with job dependency evaluation. When a job is skipped, all jobs that depend on it are also skipped by default unless they use `always()` in their condition. + +**The Problematic Pattern (current):** +```yaml +lint: + needs: [detect-changes] + if: | + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' || + ... +``` + +**The Correct Pattern (from template):** +```yaml +lint: + needs: [detect-changes] + if: | + always() && !cancelled() && ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' || + ... + ) +``` + +### Chain Reaction + +1. On `workflow_dispatch`, `detect-changes` is skipped (by design) +2. Without `always()`, `lint` job is automatically skipped when its dependency is skipped +3. `build` depends on `lint`, so it's also skipped +4. `manual-release` depends on `build`, so it's also skipped + +### Secondary Root Cause: Inconsistent Condition for Auto Release + +The `auto-release` job has the condition: +```yaml +if: github.event_name == 'push' && github.ref == 'refs/heads/main' +``` + +But it lacks `always() && !cancelled()` prefix and `needs.build.result == 'success'` verification, which can cause issues when upstream jobs use `always()`. + +## Solution + +### 1. Add `always() && !cancelled()` to All Dependent Jobs + +Jobs that depend on `detect-changes` need the pattern: +```yaml +if: | + always() && !cancelled() && ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + ... + ) +``` + +This ensures: +- `always()` - Job runs even when dependencies are skipped +- `!cancelled()` - Job doesn't run if workflow was cancelled +- The actual condition determines if job should run + +### 2. Add Result Verification for Release Jobs + +Release jobs should verify upstream jobs succeeded: +```yaml +auto-release: + if: | + always() && !cancelled() && + github.event_name == 'push' && + github.ref == 'refs/heads/main' && + needs.build.result == 'success' +``` + +## Reference + +- [GitHub Actions: Jobs that use `always()` need dependencies to also use it](https://github.com/actions/runner/issues/491) +- [Template Repository Best Practices](https://github.com/link-foundation/rust-ai-driven-development-pipeline-template) + +## Affected Files + +1. `.github/workflows/rust.yml` - Main workflow file needing fixes +2. `rust/scripts/get-bump-type.mjs` - Currently works with `rust/changelog.d`, needs update for monorepo structure + +## Fix Implementation + +See the PR for the complete fix that aligns with the template repository best practices. diff --git a/docs/case-studies/issue-21/browser-commander-issue-29.md b/docs/case-studies/issue-21/browser-commander-issue-29.md new file mode 100644 index 0000000..6fcae8c --- /dev/null +++ b/docs/case-studies/issue-21/browser-commander-issue-29.md @@ -0,0 +1,136 @@ +# Case Study: Issue #29 - Release Failed Due to False Positive Version Check + +## Summary + +The CI/CD release workflow incorrectly determined that version 0.4.0 was "already released" when the package `browser-commander` does NOT exist on crates.io at all. This is a **false positive** that prevented the release process from proceeding. + +## Timeline of Events + +| Timestamp (UTC) | Event | +|-----------------|-------| +| 2025-12-28T02:22:51Z | v0.1.1 - First GitHub Release created | +| 2025-12-28T02:54:13Z | v0.2.0 - GitHub Release created | +| 2025-12-28T03:48:18Z | v0.2.1 - GitHub Release created | +| 2025-12-28T04:14:37Z | v0.3.0 - GitHub Release created | +| 2025-12-28T05:13:22Z | v0.4.0 - GitHub Release created | +| 2026-01-01T04:19:55Z | v0.5.0 - GitHub Release created | +| 2026-01-09T14:04:11Z | v0.5.1 - GitHub Release created | +| 2026-01-10T01:11:29Z | v0.5.2 - GitHub Release created | +| 2026-01-10T13:40:00Z | v0.5.3 - GitHub Release created | +| 2026-01-13T20:37:18Z | v0.5.4 - GitHub Release created | +| 2026-01-17T09:43:31Z | CI Run #21092316062 started | +| 2026-01-17T09:45:29Z | **FALSE POSITIVE**: "No changelog fragments and v0.4.0 already released" | +| 2026-01-17T09:45:31Z | Release skipped (Auto Release job ended) | + +## Root Cause Analysis + +### The Problem + +The workflow's version check logic in `.github/workflows/rust.yml` (lines 273-292) uses the following approach: + +```yaml +- name: Check if version already released or no fragments + id: check + run: | + if [ "${{ steps.bump_type.outputs.has_fragments }}" != "true" ]; then + CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' Cargo.toml) + if git rev-parse "v$CURRENT_VERSION" >/dev/null 2>&1; then + echo "No changelog fragments and v$CURRENT_VERSION already released" + echo "should_release=false" >> $GITHUB_OUTPUT + # ... +``` + +### Why This Is a False Positive + +1. **Git Tag ≠ Published Package**: The workflow only checks if a git tag exists (`git rev-parse "v$CURRENT_VERSION"`), NOT whether the package was successfully published to crates.io. + +2. **GitHub Release ≠ crates.io Release**: While 10 versions have GitHub releases (v0.1.1 through v0.5.4), **NONE** of them exist on crates.io: + ``` + $ curl -s "https://crates.io/api/v1/crates/browser-commander" + {"errors":[{"detail":"crate `browser-commander` does not exist"}]} + ``` + +3. **Missing Publication Step**: The workflow creates GitHub releases but lacks `cargo publish` to actually publish to crates.io. + +### Evidence from CI Logs + +From `ci-run-21092316062.log` at line 4468: +``` +Auto Release UNKNOWN STEP 2026-01-17T09:45:29.1821663Z No changelog fragments and v0.4.0 already released +``` + +This message was triggered because: +- There were no changelog fragments (`has_fragments != "true"`) +- The git tag `v0.4.0` exists (created on 2025-12-28) +- But the version was never published to crates.io + +## Impact + +1. **All 10 releases are GitHub-only**: None of the versions (v0.1.1 through v0.5.4) have been published to crates.io +2. **Future releases will also fail**: Without changelog fragments, the workflow will always skip release because git tags exist +3. **Package unavailable**: Users cannot `cargo install browser-commander` or add it as a dependency + +## Proposed Solutions + +### Solution 1: Add crates.io Check (Recommended) + +Check if the version exists on crates.io before assuming it's "already released": + +```bash +# Check if package exists on crates.io +CRATE_EXISTS=$(curl -s "https://crates.io/api/v1/crates/browser-commander" | grep -c '"errors"' || true) +VERSION_EXISTS=$(curl -s "https://crates.io/api/v1/crates/browser-commander/$CURRENT_VERSION" | grep -c '"errors"' || true) + +if [ "$CRATE_EXISTS" -eq 0 ] && [ "$VERSION_EXISTS" -eq 0 ]; then + echo "Version v$CURRENT_VERSION already published to crates.io" + echo "should_release=false" >> $GITHUB_OUTPUT +else + echo "Version v$CURRENT_VERSION not on crates.io, proceeding with release" + echo "should_release=true" >> $GITHUB_OUTPUT +fi +``` + +### Solution 2: Add cargo publish Step + +Add the missing `cargo publish` step to actually publish to crates.io: + +```yaml +- name: Publish to crates.io + if: steps.check.outputs.should_release == 'true' + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: cargo publish +``` + +### Solution 3: Use katyo/publish-crates Action + +Use a well-tested GitHub Action for Rust publishing: + +```yaml +- name: Publish to crates.io + uses: katyo/publish-crates@v2 + with: + registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }} +``` + +## Recommended Fix + +Implement **all three solutions**: + +1. Fix the version check logic to verify crates.io, not just git tags +2. Add `cargo publish` step to actually publish the package +3. Consider using the battle-tested `katyo/publish-crates` action + +## References + +- Issue: https://github.com/link-foundation/browser-commander/issues/29 +- CI Run: https://github.com/link-foundation/browser-commander/actions/runs/21092316062/job/60665291821 +- crates.io API: https://crates.io/api/v1/crates/browser-commander (shows crate doesn't exist) +- GitHub Releases: https://github.com/link-foundation/browser-commander/releases (shows 10 releases) +- Workflow file: `.github/workflows/rust.yml` (lines 273-292) + +## Files in This Case Study + +- `ci-run-21092316062.txt` - Full CI run logs (renamed from .log to avoid gitignore) +- `ci-run-21092316062-metadata.json` - CI run metadata in JSON format +- `README.md` - This analysis document diff --git a/docs/case-studies/issue-21/browser-commander-issue-31.md b/docs/case-studies/issue-21/browser-commander-issue-31.md new file mode 100644 index 0000000..829c144 --- /dev/null +++ b/docs/case-studies/issue-21/browser-commander-issue-31.md @@ -0,0 +1,129 @@ +# Case Study: Issue #31 - Missing Crates.io Publishing + +## Summary + +The CI/CD pipeline correctly detects that version 0.4.0 is not published to crates.io +but fails to actually publish because there is no `cargo publish` step in the workflow. + +## Timeline/Sequence of Events + +### Commit History Leading to Issue + +1. Issue #27 ("Rust release jobs skipped") - Identified release jobs weren't running +2. Issue #29 ("Release failed, because version check got false positive") - Fixed version + check to use crates.io API instead of git tags +3. Issue #31 - Publishing still not working despite correct detection + +### CI Run Analysis (Run #21103777967) + +**Timestamp**: 2026-01-18T01:16:21Z + +**Key Events**: +1. `Detect Changes` job completed +2. `Lint and Format Check` passed +3. `Test` passed on all platforms (ubuntu, macos, windows) +4. `Build Package` succeeded +5. `Auto Release` job: + - Correctly checked crates.io API + - Found `Published on crates.io: false` + - Set `should_release=true` and `skip_bump=true` + - Built release (`cargo build --release`) + - Tried to create GitHub release (failed: tag already exists) + - **MISSING**: No `cargo publish` step + +## Root Cause Analysis + +### Primary Root Cause + +The `.github/workflows/rust.yml` workflow is missing the `cargo publish` step. +The workflow only: +1. Builds the release binary +2. Creates a GitHub release + +But it never actually publishes to crates.io using `cargo publish`. + +### Comparison with Template Repository + +The template at `link-foundation/rust-ai-driven-development-pipeline-template` +includes a critical step that is missing in browser-commander: + +```yaml +- name: Publish to Crates.io + if: steps.check.outputs.should_release == 'true' + id: publish-crate + env: + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + run: node scripts/publish-crate.mjs +``` + +### Missing Files + +The browser-commander repository is missing: +1. `scripts/publish-crate.mjs` - Script to publish to crates.io +2. `scripts/rust-paths.mjs` - Helper module for multi-language repo support + +## Evidence from CI Logs + +``` +Crate: browser-commander, Version: 0.4.0, Published on crates.io: false +No changelog fragments but v0.4.0 not yet published to crates.io +``` + +The detection logic works correctly. The workflow then: +1. Builds the release binary (`cargo build --release`) +2. Attempts GitHub release creation (HTTP 422 - tag already exists) +3. **Does not run `cargo publish`** + +## Solution + +### Required Changes + +1. **Add `scripts/publish-crate.mjs`**: Copy from template repository +2. **Add `scripts/rust-paths.mjs`**: Copy from template repository (required dependency) +3. **Update `.github/workflows/rust.yml`**: Add the `Publish to Crates.io` step + +### Implementation Details + +The `publish-crate.mjs` script: +- Reads package info from Cargo.toml +- Publishes to crates.io using `cargo publish` +- Handles "already exists" case gracefully +- Supports both single-language and multi-language repos + +### Workflow Addition + +Add after the `Build release` step: + +```yaml +- name: Publish to Crates.io + if: steps.check.outputs.should_release == 'true' + id: publish-crate + env: + CARGO_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} + run: node scripts/publish-crate.mjs +``` + +## Repository Secret Requirements + +The repository needs `CARGO_REGISTRY_TOKEN` (or `CARGO_TOKEN` for backward compatibility) +to be configured in repository secrets for authentication with crates.io. + +## Lessons Learned + +1. **Complete workflow validation**: When setting up CI/CD pipelines, verify all steps + exist (build, test, package verification, AND publish) +2. **Template synchronization**: Regularly sync with template repositories to catch + missing features +3. **End-to-end testing**: The version detection was tested, but the actual publish + step was not verified to exist + +## Related Issues + +- #27: Rust release jobs skipped +- #29: Release failed, because version check got false positive + +## References + +- Template repository: https://github.com/link-foundation/rust-ai-driven-development-pipeline-template +- CI Run logs: https://github.com/link-foundation/browser-commander/actions/runs/21103777967/job/60691866177 +- crates.io package: https://crates.io/crates/browser-commander (not yet published) diff --git a/docs/case-studies/issue-21/browser-commander-issue-33.md b/docs/case-studies/issue-21/browser-commander-issue-33.md new file mode 100644 index 0000000..a3ec042 --- /dev/null +++ b/docs/case-studies/issue-21/browser-commander-issue-33.md @@ -0,0 +1,156 @@ +# Case Study: Issue #33 - Organization Secret CARGO_TOKEN Was Not Applied in Rust Release CI/CD + +## Summary + +After PR #32 added the `cargo publish` step to the workflow, the release still failed to publish to crates.io. The root cause is a **mismatch between the organization secret name (`CARGO_TOKEN`) and the workflow's secret reference (`secrets.CARGO_REGISTRY_TOKEN`)**. + +## Timeline/Sequence of Events + +### Commit History Leading to Issue + +1. **Issue #27** ("Rust release jobs skipped") - Identified release jobs weren't running +2. **Issue #29** ("Release failed, because version check got false positive") - Fixed version check to use crates.io API instead of git tags +3. **Issue #31** ("No actual publishing to crates.io") - Identified missing `cargo publish` step +4. **PR #32** - Added `publish-crate.mjs` script and workflow step to publish to crates.io +5. **Issue #33** (this issue) - Despite the fix in PR #32, publishing still fails due to secret name mismatch + +### CI Run Analysis (Run #21116038007) + +**Timestamp**: 2026-01-18T17:49:49Z +**Trigger**: Push to main (merge of PR #32) +**Head SHA**: b7d1eeab81f5df24ad9f3209950f9d312833659d + +**Key Events**: +1. `Detect Changes` job completed successfully +2. `Lint and Format Check` passed +3. `Test` passed on all platforms (ubuntu, macos, windows) +4. `Build Package` succeeded +5. `Auto Release` job: + - Checked crates.io API - Found `Published on crates.io: false` + - Set `should_release=true` and `skip_bump=true` + - Built release (`cargo build --release`) + - **Attempted `Publish to Crates.io`** - **FAILED** with "please provide a non-empty token" + - Created GitHub Release (HTTP 422 - tag already exists) + +### Critical Log Evidence + +From the CI logs at line 4912: +``` +Auto Release Publish to Crates.io 2026-01-18T17:53:52.6733565Z CARGO_REGISTRY_TOKEN: +``` + +The `CARGO_REGISTRY_TOKEN` environment variable is **empty** (notice there's nothing after the colon). + +From line 4918: +``` +##[warning]Neither CARGO_REGISTRY_TOKEN nor CARGO_TOKEN is set, attempting publish without explicit token +``` + +From lines 4942-4945: +``` +error: failed to publish browser-commander v0.4.0 to registry at https://crates.io + +Caused by: + please provide a non-empty token +``` + +## Root Cause Analysis + +### Primary Root Cause + +**The workflow references `secrets.CARGO_REGISTRY_TOKEN` but the organization secret is named `CARGO_TOKEN`.** + +In `.github/workflows/rust.yml` (lines 325-331 for auto-release, lines 397-403 for manual-release): +```yaml +- name: Publish to Crates.io + if: steps.check.outputs.should_release == 'true' + id: publish-crate + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} # <-- References CARGO_REGISTRY_TOKEN + working-directory: . + run: node rust/scripts/publish-crate.mjs +``` + +The organization has set up a secret named `CARGO_TOKEN`, not `CARGO_REGISTRY_TOKEN`. When the workflow tries to access `secrets.CARGO_REGISTRY_TOKEN`, GitHub returns an empty string because no secret with that exact name exists. + +### Contributing Factor: Misleading Script Behavior + +The `publish-crate.mjs` script has confusing error handling. Despite the cargo command failing with "please provide a non-empty token", the CI job shows "conclusion":"success". This is because: + +1. The script attempts to publish without a token when none is provided +2. The cargo error is printed to stdout/stderr +3. But the error propagation from the `command-stream` library may not be handling this specific error case properly + +This masking of the failure made the overall CI run show as "success" despite the actual publishing failure. + +### Naming Convention Context + +According to [Cargo documentation](https://doc.rust-lang.org/cargo/reference/registries.html): +- **`CARGO_REGISTRY_TOKEN`** is the official environment variable name for crates.io authentication +- **`CARGO_TOKEN`** is a commonly used alternative name in CI/CD templates + +The issue-31 case study recommended using `CARGO_REGISTRY_TOKEN` in the workflow (which is the correct Cargo convention), but the organization secret was created with the name `CARGO_TOKEN`. + +## Solution + +### Option 1: Update Workflow to Use Existing Secret Name (Recommended) + +Change the workflow to reference the existing organization secret `CARGO_TOKEN`: + +```yaml +- name: Publish to Crates.io + if: steps.check.outputs.should_release == 'true' + id: publish-crate + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} # Use CARGO_TOKEN org secret + working-directory: . + run: node rust/scripts/publish-crate.mjs +``` + +This maps the organization secret `CARGO_TOKEN` to the environment variable `CARGO_REGISTRY_TOKEN` that Cargo expects. + +### Option 2: Rename Organization Secret + +Alternatively, rename the organization secret from `CARGO_TOKEN` to `CARGO_REGISTRY_TOKEN` in the GitHub organization settings. + +**Note:** Option 1 is recommended as it doesn't require organization admin access and maintains backward compatibility with other repositories that may use `CARGO_TOKEN`. + +### Secondary Fix: Improve Error Handling in publish-crate.mjs + +The script should fail explicitly when: +1. No token is provided and the publish fails +2. The "non-empty token" error is detected + +## Files Changed + +1. **`.github/workflows/rust.yml`**: Change `${{ secrets.CARGO_REGISTRY_TOKEN }}` to `${{ secrets.CARGO_TOKEN }}` in both `auto-release` and `manual-release` jobs + +## Verification Steps + +After the fix: +1. Merge the PR to main +2. Check the CI run for the `Publish to Crates.io` step +3. Verify the environment shows `CARGO_REGISTRY_TOKEN: ***` (masked, indicating a value is present) +4. Verify no "Neither CARGO_REGISTRY_TOKEN nor CARGO_TOKEN is set" warning appears +5. Verify the package appears on https://crates.io/crates/browser-commander + +## Lessons Learned + +1. **Secret names must match exactly**: GitHub secrets are case-sensitive and must be referenced by their exact names +2. **Naming conventions matter**: When following templates, ensure secret names are consistent across the organization +3. **CI success can be misleading**: A "successful" CI run doesn't guarantee all steps actually succeeded - always check the logs +4. **Defense in depth**: Error handling in scripts should be explicit about authentication failures + +## Related Issues + +- #27: Rust release jobs skipped +- #29: Release failed, because version check got false positive +- #31: I see not actual publishing to crates.io (PR #32 attempted to fix) + +## References + +- GitHub Organization Secrets: https://docs.github.com/actions/security-guides/using-secrets-in-github-actions +- Cargo Registry Configuration: https://doc.rust-lang.org/cargo/reference/registries.html +- CI Run logs: https://github.com/link-foundation/browser-commander/actions/runs/21116038007/job/60721745091 +- PR #32: https://github.com/link-foundation/browser-commander/pull/32 +- crates.io package: https://crates.io/crates/browser-commander (not yet published) diff --git a/docs/case-studies/issue-21/browser-commander-rust.yml b/docs/case-studies/issue-21/browser-commander-rust.yml new file mode 100644 index 0000000..57661e0 --- /dev/null +++ b/docs/case-studies/issue-21/browser-commander-rust.yml @@ -0,0 +1,416 @@ +name: Rust CI/CD Pipeline + +on: + push: + branches: + - main + paths: + - 'rust/**' + - 'scripts/**' + - '.github/workflows/rust.yml' + pull_request: + types: [opened, synchronize, reopened] + paths: + - 'rust/**' + - 'scripts/**' + - '.github/workflows/rust.yml' + workflow_dispatch: + inputs: + bump_type: + description: 'Version bump type' + required: true + type: choice + options: + - patch + - minor + - major + description: + description: 'Release description (optional)' + required: false + type: string + +concurrency: + group: rust-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -Dwarnings + +defaults: + run: + working-directory: rust + +jobs: + # === DETECT CHANGES - determines which jobs should run === + detect-changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name != 'workflow_dispatch' + outputs: + rs-changed: ${{ steps.changes.outputs.rs-changed }} + toml-changed: ${{ steps.changes.outputs.toml-changed }} + mjs-changed: ${{ steps.changes.outputs.mjs-changed }} + docs-changed: ${{ steps.changes.outputs.docs-changed }} + workflow-changed: ${{ steps.changes.outputs.workflow-changed }} + any-code-changed: ${{ steps.changes.outputs.any-code-changed }} + rust-code-changed: ${{ steps.changes.outputs.rust-code-changed }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + + - name: Detect changes + id: changes + working-directory: . + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }} + GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: node scripts/detect-code-changes.mjs + + # === CHANGELOG CHECK - only runs on PRs with code changes === + # Docs-only PRs (./docs folder, markdown files) don't require changelog fragments + changelog: + name: Changelog Fragment Check + runs-on: ubuntu-latest + needs: [detect-changes] + if: github.event_name == 'pull_request' && needs.detect-changes.outputs.rust-code-changed == 'true' + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Check for changelog fragments + run: | + # Get list of fragment files (excluding README and template) + FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" 2>/dev/null | wc -l) + + # Get changed files in PR + CHANGED_FILES=$(git diff --name-only origin/${{ github.base_ref }}...HEAD) + + # Check if any source files changed (excluding docs and config) + SOURCE_CHANGED=$(echo "$CHANGED_FILES" | grep -E "^rust/(src/|tests/|Cargo\.toml)" | wc -l) + + if [ "$SOURCE_CHANGED" -gt 0 ] && [ "$FRAGMENTS" -eq 0 ]; then + echo "::warning::No changelog fragment found. Please add a changelog entry in rust/changelog.d/" + echo "" + echo "To create a changelog fragment:" + echo " Create a new .md file in rust/changelog.d/ with your changes" + echo "" + echo "See rust/changelog.d/README.md for more information." + # Note: This is a warning, not a failure, to allow flexibility + # Change 'exit 0' to 'exit 1' to make it required + exit 0 + fi + + echo "Changelog check passed" + + # === LINT AND FORMAT CHECK === + # Lint runs independently of changelog check - it's a fast check that should always run + # Note: always() is required because detect-changes is skipped on workflow_dispatch, + # and without always(), this job would also be skipped even though its condition includes workflow_dispatch. + # See: https://github.com/actions/runner/issues/491 + lint: + name: Lint and Format Check + runs-on: ubuntu-latest + needs: [detect-changes] + if: | + always() && !cancelled() && ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' || + needs.detect-changes.outputs.toml-changed == 'true' || + needs.detect-changes.outputs.mjs-changed == 'true' || + needs.detect-changes.outputs.docs-changed == 'true' || + needs.detect-changes.outputs.workflow-changed == 'true' + ) + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + rust/target + key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Run Clippy + run: cargo clippy --all-targets --all-features + + - name: Check file size limit + working-directory: . + run: node rust/scripts/check-file-size.mjs + + # === TEST === + # Test runs independently of changelog check + test: + name: Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + needs: [detect-changes, changelog] + # Run if: push event, OR changelog succeeded, OR changelog was skipped (docs-only PR) + # Note: always() is required to evaluate the condition when dependencies are skipped. + if: always() && !cancelled() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changelog.result == 'success' || needs.changelog.result == 'skipped') + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + rust/target + key: ${{ runner.os }}-cargo-${{ hashFiles('rust/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Run tests + run: cargo test --all-features --verbose + + - name: Run doc tests + run: cargo test --doc --verbose + + # === BUILD === + # Build package - only runs if lint and test pass + build: + name: Build Package + runs-on: ubuntu-latest + needs: [lint, test] + # Note: always() ensures this job runs even when lint/test jobs use always(). + if: always() && !cancelled() && needs.lint.result == 'success' && needs.test.result == 'success' + steps: + - uses: actions/checkout@v4 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo registry + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + rust/target + key: ${{ runner.os }}-cargo-build-${{ hashFiles('rust/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-build- + + - name: Build release + run: cargo build --release --verbose + + - name: Check package + run: cargo package --list + + # === AUTO RELEASE === + # Automatic release on push to main using changelog fragments + # This job automatically bumps version based on fragments in changelog.d/ + auto-release: + name: Auto Release + needs: [lint, test, build] + # Note: always() is required to evaluate the condition when dependencies use always(). + # The build job ensures lint and test passed before this job runs. + if: | + always() && !cancelled() && + github.event_name == 'push' && + github.ref == 'refs/heads/main' && + needs.build.result == 'success' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Determine bump type from changelog fragments + id: bump_type + run: node scripts/get-bump-type.mjs + + - name: Check if version already released or no fragments + id: check + run: | + CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' Cargo.toml | head -1) + CRATE_NAME=$(grep -Po '(?<=^name = ")[^"]*' Cargo.toml | head -1) + + # Check if version is published on crates.io (the source of truth for Rust packages) + # Note: We check crates.io, not git tags, because git tags can exist without + # the package being published (e.g., failed publish, GitHub-only releases) + CRATES_IO_RESPONSE=$(curl -s "https://crates.io/api/v1/crates/${CRATE_NAME}/${CURRENT_VERSION}") + VERSION_ON_CRATES_IO=false + if echo "$CRATES_IO_RESPONSE" | grep -q '"version"'; then + VERSION_ON_CRATES_IO=true + fi + + echo "Crate: $CRATE_NAME, Version: $CURRENT_VERSION, Published on crates.io: $VERSION_ON_CRATES_IO" + + # Check if there are changelog fragments + if [ "${{ steps.bump_type.outputs.has_fragments }}" != "true" ]; then + # No fragments - check if current version is published on crates.io + if [ "$VERSION_ON_CRATES_IO" = "true" ]; then + echo "No changelog fragments and v$CURRENT_VERSION already published on crates.io" + echo "should_release=false" >> $GITHUB_OUTPUT + else + echo "No changelog fragments but v$CURRENT_VERSION not yet published to crates.io" + echo "should_release=true" >> $GITHUB_OUTPUT + echo "skip_bump=true" >> $GITHUB_OUTPUT + fi + else + echo "Found changelog fragments, proceeding with release" + echo "should_release=true" >> $GITHUB_OUTPUT + echo "skip_bump=false" >> $GITHUB_OUTPUT + fi + + - name: Collect changelog and bump version + id: version + if: steps.check.outputs.should_release == 'true' && steps.check.outputs.skip_bump != 'true' + run: | + node scripts/version-and-commit.mjs \ + --bump-type "${{ steps.bump_type.outputs.bump_type }}" + + - name: Get current version + id: current_version + if: steps.check.outputs.should_release == 'true' + run: | + CURRENT_VERSION=$(grep -Po '(?<=^version = ")[^"]*' Cargo.toml) + echo "version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + + - name: Build release + if: steps.check.outputs.should_release == 'true' + run: cargo build --release + + - name: Publish to Crates.io + if: steps.check.outputs.should_release == 'true' + id: publish-crate + env: + # Note: Organization secret is named CARGO_TOKEN, we map it to CARGO_REGISTRY_TOKEN + # which is the standard environment variable that Cargo uses for crates.io auth + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} + working-directory: . + run: node rust/scripts/publish-crate.mjs + + - name: Create GitHub Release + if: steps.check.outputs.should_release == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + node scripts/create-github-release.mjs \ + --release-version "${{ steps.current_version.outputs.version }}" \ + --repository "${{ github.repository }}" + + # === MANUAL RELEASE === + # Manual release via workflow_dispatch - only after CI passes + manual-release: + name: Manual Release + needs: [lint, test, build] + # Note: always() is required to evaluate the condition when dependencies use always(). + # The build job ensures lint and test passed before this job runs. + if: | + always() && !cancelled() && + github.event_name == 'workflow_dispatch' && + needs.build.result == 'success' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + + - name: Configure git + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Collect changelog fragments + run: | + # Check if there are any fragments to collect + FRAGMENTS=$(find changelog.d -name "*.md" ! -name "README.md" 2>/dev/null | wc -l) + if [ "$FRAGMENTS" -gt 0 ]; then + echo "Found $FRAGMENTS changelog fragment(s), collecting..." + node scripts/collect-changelog.mjs + else + echo "No changelog fragments found, skipping collection" + fi + + - name: Version and commit + id: version + run: | + node scripts/version-and-commit.mjs \ + --bump-type "${{ github.event.inputs.bump_type }}" \ + --description "${{ github.event.inputs.description }}" + + - name: Build release + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + run: cargo build --release + + - name: Publish to Crates.io + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + id: publish-crate + env: + # Note: Organization secret is named CARGO_TOKEN, we map it to CARGO_REGISTRY_TOKEN + # which is the standard environment variable that Cargo uses for crates.io auth + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} + working-directory: . + run: node rust/scripts/publish-crate.mjs + + - name: Create GitHub Release + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + node scripts/create-github-release.mjs \ + --release-version "${{ steps.version.outputs.new_version }}" \ + --repository "${{ github.repository }}" diff --git a/docs/case-studies/issue-25/README.md b/docs/case-studies/issue-25/README.md new file mode 100644 index 0000000..09a520e --- /dev/null +++ b/docs/case-studies/issue-25/README.md @@ -0,0 +1,94 @@ +# Case Study: Issue #25 - version-and-commit.rs Checks Git Tags Instead of Crates.io + +## Summary + +The `version-and-commit.rs` script used `git rev-parse` to check if a version tag existed, then exited early with `already_released=true` if it did. This created a permanent release pipeline failure loop when GitHub releases created tags without the crate being published to crates.io. + +## Timeline of Events + +| Date | Event | Detail | +|------|-------|--------| +| Prior | browser-commander releases | GitHub releases v0.1.1 through v0.8.0 created tags, but crates.io publishing failed for some | +| Prior | browser-commander#47 | Pipeline stuck at v0.4.0 on crates.io, GitHub had releases up to v0.8.0 | +| 2026-01-17 | Issue #29 (browser-commander) | First discovery of git tag vs crates.io divergence | +| 2026-01-17 | check-release-needed.rs fix | Script was updated to check crates.io API instead of git tags | +| 2026-04-13 | Issue #25 (this template) | Bug reported: version-and-commit.rs still uses git tags | + +## Root Cause Analysis + +### The Bug + +In `version-and-commit.rs` (lines 152-154), the `check_tag_exists()` function used: + +```rust +fn check_tag_exists(version: &str) -> bool { + exec_check("git", &["rev-parse", &format!("v{}", version)]) +} +``` + +This checked for git tags as a proxy for "already released", but git tags are not the correct source of truth for Rust package publication. + +### Why Git Tags Are Unreliable + +1. **GitHub releases create tags** - Creating a GitHub release via the UI or API automatically creates a git tag, even if `cargo publish` was never called +2. **Failed publishes** - If `cargo publish` fails (auth issues, network errors), the tag may already exist from a prior step +3. **Manual tag creation** - Developers or automation can create tags without publishing +4. **Tag prefix mismatches** - Multi-language repos may use different tag prefixes (e.g., `rust_v1.0.0` vs `v1.0.0`) + +### The Failure Loop + +``` +1. version-and-commit.rs bumps version 0.4.0 → 0.4.1 +2. Checks tag v0.4.1 → EXISTS (from a prior GitHub-only release) +3. Exits early WITHOUT updating Cargo.toml +4. cargo publish tries 0.4.0 → "already exists" on crates.io +5. Pipeline is permanently stuck — every run hits the same tag check +``` + +### Why check-release-needed.rs Was Already Correct + +The `check-release-needed.rs` script (added during issue #21 investigation) already used the correct approach — querying the crates.io API: + +```rust +fn check_version_on_crates_io(crate_name: &str, version: &str) -> bool { + let url = format!("https://crates.io/api/v1/crates/{}/{}", crate_name, version); + // HTTP 200 = published, 404 = not published +} +``` + +The inconsistency arose because `version-and-commit.rs` was not updated at the same time. + +## Solution + +### Changes Made + +1. **Replaced `check_tag_exists()` with `check_version_on_crates_io()`** in `version-and-commit.rs` + - Added `ureq`, `serde`, and `serde_json` dependencies + - Added `get_crate_name()` helper to read crate name from Cargo.toml + - Queries `https://crates.io/api/v1/crates/{crate_name}/{version}` API endpoint + - Returns `true` only if crates.io confirms the version exists + +2. **Added `--tag-prefix` support** to `version-and-commit.rs` + - Configurable tag prefix (default `"v"`) for multi-language repos + - Aligns with `create-github-release.rs` which already supports `--tag-prefix` + +3. **Added test script** (`experiments/test-crates-io-check.rs`) + - Validates the crates.io API check against known published and unpublished versions + +### Design Decisions + +- **On error, assume not published** - If the crates.io API is unreachable, the script assumes the version is not yet published. This is safer than incorrectly skipping a release. +- **Consistent with check-release-needed.rs** - Both scripts now use the same approach, reducing confusion and maintenance burden. + +## Lessons Learned + +1. **Use the authoritative source of truth** - For Rust packages, crates.io is the source of truth, not git tags +2. **Keep related scripts consistent** - When fixing a pattern in one script, audit all scripts for the same anti-pattern +3. **Test with real API calls** - The experiment script validates the fix against actual crates.io data +4. **Multi-language repos need tag prefixes** - Hard-coded `v` prefix breaks in monorepos with multiple languages + +## Related Issues + +- [browser-commander#47](https://github.com/link-foundation/browser-commander/issues/47) - Original discovery of the stuck pipeline +- [browser-commander#29](https://github.com/link-foundation/browser-commander/issues/29) - First fix for git tag vs crates.io check +- [Issue #21](../issue-21/) - Previous case study covering the same category of CI/CD issues diff --git a/docs/case-studies/issue-29/README.md b/docs/case-studies/issue-29/README.md new file mode 100644 index 0000000..b097469 --- /dev/null +++ b/docs/case-studies/issue-29/README.md @@ -0,0 +1,126 @@ +# Case Study: Issue #29 - Unsupported Look-Ahead Regex in create-github-release.rs + +## Summary + +The `scripts/create-github-release.rs` script used a regex pattern containing a positive look-ahead assertion `(?=...)`, which is not supported by Rust's `regex` crate. This caused a panic during GitHub release creation, preventing releases from completing even though crates.io publishing succeeded. + +## Timeline of Events + +| Date | Event | Detail | +|------|-------|--------| +| Prior | Template scripts converted to Rust | All CI/CD scripts were translated from JavaScript (.mjs) to Rust (.rs) using rust-script (Issue #25 era) | +| Prior | Regex pattern introduced | The `get_changelog_for_version()` function was written with `(?=\n## \[|$)` look-ahead | +| 2026-04-13 | mem-rs v0.2.0 release attempt | `linksplatform/mem-rs` release published to crates.io but GitHub Release creation panicked | +| 2026-04-13 | linksplatform/mem-rs#34 | Bug reported after investigating the failed release | +| 2026-04-13 | Issue #29 filed | Bug ported back to this template repository for fix | + +## Root Cause Analysis + +### The Bug + +In `scripts/create-github-release.rs` (line 80), the changelog parsing function used: + +```rust +let pattern = format!(r"(?s)## \[{}\].*?\n(.*?)(?=\n## \[|$)", escaped_version); +let re = Regex::new(&pattern).unwrap(); +``` + +The `(?=\n## \[|$)` portion is a **positive look-ahead assertion**, which tells the regex engine: "match only if followed by `\n## [` or end-of-string, but don't consume the match." + +### Why Rust's `regex` Crate Doesn't Support Look-Ahead + +Rust's `regex` crate uses a **finite automaton (FA) engine** that guarantees **linear-time matching** — O(n) in the length of the input, regardless of the pattern. This is a deliberate design choice for safety and performance: + +1. **Look-ahead requires backtracking** — Look-ahead assertions need the engine to "peek ahead" without consuming input, which requires backtracking or a separate pass +2. **Backtracking engines can be exponential** — PCRE-style engines with look-ahead can exhibit catastrophic backtracking (O(2^n)) on adversarial inputs +3. **Rust prioritizes safety** — The `regex` crate trades feature completeness for guaranteed performance bounds + +The `regex` crate documents this explicitly: "look-around, including look-ahead and look-behind, is not supported." + +### The Failure Mode + +``` +thread 'main' panicked at scripts/create-github-release.rs:48:35: +called `Result::unwrap()` on an `Err` value: Syntax( +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +regex parse error: + (?s)## \[0\.2\.0\].*?\n(.*?)(?=\n## \[|$) + ^^^ +error: look-around, including look-ahead and look-behind, is not supported +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +) +``` + +The `.unwrap()` on the `Regex::new()` result converts the compilation error into a panic, crashing the script. + +### How the Bug Was Introduced + +When CI/CD scripts were translated from JavaScript to Rust, the regex pattern was likely carried over from a JavaScript implementation where `RegExp` supports look-ahead natively (JavaScript's regex engine is PCRE-based). The pattern worked correctly in the `.mjs` version but is invalid in Rust. + +## Solution + +### Fix Applied + +Replaced the single look-ahead regex with a **two-step approach**: + +```rust +// Step 1: Find the version header +let header_pattern = format!(r"(?m)^## \[{}\]", escaped_version); +let header_re = Regex::new(&header_pattern).unwrap(); + +if let Some(m) = header_re.find(&content) { + // Step 2: Skip past the header line + let after_header = &content[m.end()..]; + let body_start = after_header.find('\n').map_or(after_header.len(), |i| i + 1); + let body = &after_header[body_start..]; + + // Step 3: Find the next section boundary + let next_section_re = Regex::new(r"(?m)^## \[").unwrap(); + let section_body = if let Some(next) = next_section_re.find(body) { + &body[..next.start()] + } else { + body // Last section — take everything remaining + }; + + let trimmed = section_body.trim(); + if trimmed.is_empty() { + format!("Release v{}", version) + } else { + trimmed.to_string() + } +} +``` + +### Why This Approach + +1. **No look-ahead needed** — Instead of asserting "followed by `## [`", we find the boundary explicitly using a second regex and use string slicing +2. **Handles edge cases** — Works for the last section (no next `## [` header), empty sections, and versions with regex-special characters (dots are escaped) +3. **Uses only `regex` crate features** — All patterns are FA-compatible with guaranteed linear-time matching +4. **Equivalent semantics** — Produces identical output to the original pattern's intent + +### Verification + +A test script (`experiments/test-changelog-parsing.rs`) validates: +- Extracting a version section bounded by another section +- Extracting the last version section (no trailing boundary) +- Non-existent version returns default message +- Version strings with dots are properly regex-escaped +- Empty version sections return the default fallback + +## Impact + +- **Affected repositories** — Any repository using this template's `create-github-release.rs` script +- **Known incident** — `linksplatform/mem-rs` v0.2.0 release: crates.io published successfully, but GitHub Release creation failed +- **Severity** — The release was partially complete: the package was available on crates.io but the GitHub Release with notes and badges was missing + +## Lessons Learned + +1. **Test regex patterns at compile time** — When porting regex patterns between languages, verify compatibility with the target engine. Rust's `regex` crate has documented limitations. +2. **Avoid `.unwrap()` on regex compilation from dynamic patterns** — Consider using `expect()` with a descriptive message or handling the error gracefully to provide actionable diagnostics. +3. **Language migration requires testing** — When translating scripts from JavaScript to Rust, patterns, libraries, and runtime behavior differ. Each translation should include tests that exercise the ported logic. +4. **Two-step parsing is often cleaner** — Using string slicing with simple regex matches is more readable and debuggable than complex single-regex patterns with assertions. + +## Related Issues + +- [linksplatform/mem-rs#34](https://github.com/linksplatform/mem-rs/issues/34) — Original discovery of the panic during mem-rs v0.2.0 release +- [Issue #25](../issue-25/) — Previous case study covering CI/CD script translation to Rust diff --git a/docs/case-studies/issue-32/README.md b/docs/case-studies/issue-32/README.md new file mode 100644 index 0000000..7f14504 --- /dev/null +++ b/docs/case-studies/issue-32/README.md @@ -0,0 +1,97 @@ +# Case Study: Issue #32 — Publish Steps Override Workflow-Level CARGO_TOKEN Fallback + +## Summary + +The CI/CD pipeline's publish steps used a step-level `env` block that overrode the workflow-level `CARGO_TOKEN` fallback chain, breaking repositories that only configure `CARGO_REGISTRY_TOKEN` as a secret. Additionally, the `version-and-commit.rs` script lacked push retry logic, causing failures in multi-workflow repositories with concurrent release jobs. + +## Timeline of Events + +1. **Origin**: The issue was first observed in [link-assistant/web-capture#46](https://github.com/link-assistant/web-capture/issues/46), where `CARGO_REGISTRY_TOKEN` was not set in the publish step, causing the publish to skip silently. + +2. **Discovery**: Investigation revealed that the workflow-level `env` block correctly defined a fallback chain (`${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}`), but the per-step `env` blocks in both `auto-release` and `manual-release` jobs overrode this with only `${{ secrets.CARGO_TOKEN }}`, which would be empty if only `CARGO_REGISTRY_TOKEN` was configured. + +3. **Related issue #31**: In [link-assistant/agent](https://github.com/link-assistant/agent), the Rust auto-release job failed with `non-fast-forward` errors when a JS release job pushed to `main` first. The `version-and-commit.rs` script did a single `git push` with no retry or rebase logic. + +4. **Scope expansion**: Comparison with reference repos (browser-commander, lino-arguments, trees-rs, Numbers) revealed additional gaps in mono-repo path support across several scripts. + +## Root Cause Analysis + +### Problem 1: CARGO_TOKEN Fallback (Issue #32) + +**Root cause**: GitHub Actions step-level `env` blocks override workflow-level `env` for the same variable name. The publish steps set: + +```yaml +env: + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} +``` + +This overrides the workflow-level `CARGO_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }}`, making `CARGO_TOKEN` empty when only `CARGO_REGISTRY_TOKEN` is configured as a repository secret. + +**Mitigating factor**: The `publish-crate.rs` script checks both `CARGO_REGISTRY_TOKEN` and `CARGO_TOKEN` environment variables (in that order), so publishing still worked because `CARGO_REGISTRY_TOKEN` was inherited from the workflow-level env. However, this was fragile and misleading. + +**Fix**: Set both `CARGO_REGISTRY_TOKEN` (with fallback) and `CARGO_TOKEN` at both workflow and step levels: + +```yaml +env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} +``` + +### Problem 2: Non-Fast-Forward Push (Issue #31) + +**Root cause**: The `version-and-commit.rs` script performed a single `git push` without first rebasing on the remote branch. In multi-workflow repositories where multiple release jobs can push to `main` concurrently, the first push succeeds but subsequent pushes fail with `non-fast-forward` errors. + +**Fix**: Added pre-commit `git fetch` + `rebase` and post-commit push retry (3 attempts) with `git pull --rebase` between failures. + +### Problem 3: Inconsistent Mono-Repo Support + +**Root cause**: Several scripts (`check-changelog-fragment.rs`, `check-version-modification.rs`, `create-changelog-fragment.rs`) hardcoded paths like `Cargo.toml`, `changelog.d/`, `src/`, `tests/` without considering the `rust/` subdirectory prefix used in multi-language repositories. + +**Fix**: Added `get_rust_root()` detection (checks `RUST_ROOT` env var, then auto-detects `./Cargo.toml` vs `./rust/Cargo.toml`) to all affected scripts. + +## Requirements from Issue + +| # | Requirement | Status | +|---|---|---| +| 1 | Fix CARGO_TOKEN fallback in publish steps | Done | +| 2 | Support CARGO_REGISTRY_TOKEN-only configurations | Done | +| 3 | Fix non-fast-forward push in multi-workflow repos (#31) | Done | +| 4 | Compare CI/CD with reference repos for best practices | Done | +| 5 | Ensure mono-repo support across all scripts | Done | +| 6 | Add `!cancelled()` guard to test job | Done | +| 7 | Create case study documentation | Done | + +## Affected Repositories + +The same CARGO_TOKEN fallback issue exists in: +- `link-foundation/browser-commander` (`.github/workflows/rust.yml`) +- `link-foundation/lino-arguments` (`.github/workflows/rust.yml`) +- `linksplatform/Numbers` (`.github/workflows/rust.yml`) + +Since these are derived from this template, fixing it here allows downstream repos to adopt the fix. + +## Reference Comparison Results + +A full comparison of CI/CD files was performed against 4 reference repos. Key findings: + +### Our template is ahead of all references in: +- Push retry logic (3 attempts with pull-rebase) +- Pre-commit fetch/rebase for concurrent workflows +- Dual CARGO_REGISTRY_TOKEN + CARGO_TOKEN env setup +- Code coverage with cargo-llvm-cov + Codecov +- Automated badges in GitHub release notes +- Template-aware skips (example-sum-package-name guard) +- Configurable tag prefix and release label +- Updated GitHub Actions versions (v5/v6/v8) +- `--all-features` in cargo doc + +### Adopted from references: +- `!cancelled()` guard in test job condition (from lino-arguments, browser-commander) + +## Files Changed + +- `.github/workflows/release.yml` — CARGO_REGISTRY_TOKEN fallback, `!cancelled()` guard +- `scripts/version-and-commit.rs` — fetch/rebase + push retry logic +- `scripts/check-changelog-fragment.rs` — mono-repo path support +- `scripts/check-version-modification.rs` — mono-repo path support +- `scripts/create-changelog-fragment.rs` — mono-repo path support diff --git a/docs/case-studies/issue-34/README.md b/docs/case-studies/issue-34/README.md new file mode 100644 index 0000000..19033ef --- /dev/null +++ b/docs/case-studies/issue-34/README.md @@ -0,0 +1,124 @@ +# Case Study: Issue #34 — detect-code-changes Uses Full PR Diff Instead of Per-Commit Diff + +## Summary + +The `detect-code-changes.rs` script compared the full PR diff (base SHA to head SHA) instead of evaluating each commit individually. This caused a commit that only modified non-code files (e.g., `.gitkeep`, `README.md`) to trigger all CI jobs if any earlier commit in the same PR touched code files. + +## Timeline of Events + +1. **Origin**: The issue was first observed in [link-assistant/web-capture PR #49](https://github.com/link-assistant/web-capture/pull/49), where commit `0e9b6e8c` only modified `.gitkeep` but triggered all 8 CI jobs because the PR as a whole contained code changes. + +2. **Root cause identified**: GitHub Actions checks out a **synthetic merge commit** for `pull_request` events: + - `HEAD` = synthetic merge commit + - `HEAD^` = base branch (first parent) + - `HEAD^2` = actual PR head commit (second parent) + + Using `git diff HEAD^ HEAD` or `git diff base_sha head_sha` gives the **full PR diff**, not the per-commit diff. This is the same fundamental problem as GitHub Actions' `paths:` filters. + +3. **Fix developed and verified**: The fix was first implemented and CI-verified in [link-assistant/web-capture PR #51](https://github.com/link-assistant/web-capture/pull/51) using a JavaScript implementation (`detect-code-changes.mjs`). + +4. **Cross-repo filing**: The same issue was filed on both template repos: + - Rust template: [link-foundation/rust-ai-driven-development-pipeline-template#34](https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/34) + - JS template: [link-foundation/js-ai-driven-development-pipeline-template#31](https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/31) + +## Root Cause Analysis + +### Problem: Full PR Diff Instead of Per-Commit Diff + +**Root cause**: The `get_changed_files()` function in `detect-code-changes.rs` used `GITHUB_BASE_SHA` and `GITHUB_HEAD_SHA` environment variables to compute `git diff base_sha head_sha`, which returns all files changed across the entire PR — not just the latest commit. + +**Before (broken)**: +```rust +if event_name == "pull_request" { + let base_sha = env::var("GITHUB_BASE_SHA").ok(); + let head_sha = env::var("GITHUB_HEAD_SHA").ok(); + if let (Some(base), Some(head)) = (base_sha, head_sha) { + exec_silent("git", &["fetch", "origin", &base]); + let output = exec("git", &["diff", "--name-only", &base, &head]); + // This returns ALL files changed in the PR, not just the latest commit + } +} +``` + +**Why `HEAD^..HEAD` also doesn't work for PRs**: GitHub Actions creates a synthetic merge commit for `pull_request` events. `HEAD^` is the base branch (first parent), so `git diff HEAD^ HEAD` also gives the full PR diff — the exact same problem. + +**After (fixed)**: +```rust +fn is_merge_commit() -> bool { + let output = exec("git", &["cat-file", "-p", "HEAD"]); + output.lines().filter(|line| line.starts_with("parent ")).count() > 1 +} + +fn get_changed_files() -> Vec { + if is_merge_commit() { + // HEAD^2 = actual PR head, HEAD^2^ = its parent + // This gives the per-commit diff of the latest push + let output = exec("git", &["diff", "--name-only", "HEAD^2^", "HEAD^2"]); + // ... + } + // For push events: HEAD^ to HEAD (regular per-commit diff) +} +``` + +**Key insight**: `HEAD^2` is the actual PR head commit (second parent of the merge commit). `HEAD^2^` is its parent. So `git diff HEAD^2^ HEAD^2` gives exactly the per-commit diff of the latest push to the PR. + +### Workflow Change + +The `GITHUB_BASE_SHA` and `GITHUB_HEAD_SHA` environment variables were removed from the workflow's detect-changes step since they are no longer needed — the script now uses git's commit graph directly. + +## Requirements from Issue + +| # | Requirement | Status | +|---|---|---| +| 1 | Use per-commit diff instead of full PR diff for change detection | Done | +| 2 | Handle GitHub Actions synthetic merge commit correctly | Done | +| 3 | Follow best practices from web-capture PR #51 | Done | +| 4 | Create case study with root cause analysis | Done | +| 5 | Create experiment scripts for testing | Done | + +## Possible Solutions Considered + +| Solution | Pros | Cons | Chosen? | +|---|---|---|---| +| Merge commit detection (`HEAD^2^..HEAD^2`) | Accurate per-commit diff; works without env vars; proven in web-capture CI | Requires understanding of git merge commit structure | Yes | +| Use `GITHUB_BASE_SHA`/`GITHUB_HEAD_SHA` with commit limiting | Uses official GitHub API values | Still gives full PR diff; doesn't solve the core problem | No | +| GitHub Actions `paths:` filters | Built-in, no script needed | Evaluates full PR diff; same fundamental problem | No | + +## CI Verification + +The fix was verified in CI run [#24394764654](https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24394764654). The last commit (removing `.gitkeep`) correctly triggered only the per-commit diff: + +``` +Merge commit detected (pull_request event) +Comparing HEAD^2^ to HEAD^2 (per-commit diff of PR head) +Changed files: + .gitkeep +rs-changed=false +toml-changed=false +any-code-changed=false +``` + +As a result, Lint, Code Coverage, and Changelog Fragment Check were all correctly **skipped** — they would have been triggered under the old full-PR-diff behavior because earlier commits in the PR modified `.rs` and `.yml` files. + +## Additional Context: GitHub Actions Merge Commit Behavior + +GitHub Actions creates two special refs for every pull request: +- `refs/pull/NUMBER/head` — the HEAD of the PR branch +- `refs/pull/NUMBER/merge` — a synthetic merge commit previewing the merge into the target branch + +When using the `pull_request` trigger, `@actions/checkout` checks out `refs/pull/NUMBER/merge` (the synthetic merge commit). This means: +- `HEAD` is the merge commit (has 2 parents) +- `HEAD^` (first parent) is the base branch tip +- `HEAD^2` (second parent) is the actual PR head commit + +This is documented in the [GitHub community discussion on base.sha behavior](https://github.com/orgs/community/discussions/59677) and the [Frontside deep dive into pull_request](https://frontside.com/blog/2020-05-26-github-actions-pull_request/). The [actions/checkout issue #426](https://github.com/actions/checkout/issues/426) also discusses the distinction between checking out the merge commit vs the HEAD commit. + +## References + +- [link-assistant/web-capture#50](https://github.com/link-assistant/web-capture/issues/50) — Original issue +- [link-assistant/web-capture#51](https://github.com/link-assistant/web-capture/pull/51) — Reference implementation (JS) +- [link-foundation/js-ai-driven-development-pipeline-template#31](https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/31) — Same issue on JS template +- [GitHub Actions: Events that trigger workflows](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request) — Documentation on synthetic merge commits +- [GitHub community discussion: base.sha update behavior](https://github.com/orgs/community/discussions/59677) — Explains how `github.event.pull_request.base.sha` works +- [Frontside: GitHub Actions pull_request deep dive](https://frontside.com/blog/2020-05-26-github-actions-pull_request/) — Explains synthetic merge commit structure +- [actions/checkout#426: Merge commit vs HEAD commit](https://github.com/actions/checkout/issues/426) — Discussion on checkout behavior for PRs diff --git a/docs/case-studies/issue-38/README.md b/docs/case-studies/issue-38/README.md new file mode 100644 index 0000000..ee4cb2a --- /dev/null +++ b/docs/case-studies/issue-38/README.md @@ -0,0 +1,142 @@ +# Issue 38 Case Study: Decouple Documentation Deployment From Package Release Publication + +## Summary + +Issue [#38](https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/38) reported that Rust API documentation deployment was coupled to package release publication in `.github/workflows/release.yml`. A failed package or GitHub release caused `deploy-docs` to be skipped, even when the package build had already succeeded and documentation could still be generated. + +The fix changes `deploy-docs` to depend on `build`, gates it on `needs.build.result == 'success'`, and preserves the existing trigger intent: deploy docs on `main` pushes and manual `workflow_dispatch` runs with `release_mode == 'instant'`. The fix also cleans up release-script warning failures that were blocking the observed release path under `RUSTFLAGS=-Dwarnings`. + +## Collected Data + +Raw GitHub and template comparison data is stored in this directory: + +- `raw-data/issue-38.json` and `raw-data/issue-38-comments.json`: issue details and the latest issue comments. +- `raw-data/pr-39.json`, `raw-data/pr-39-conversation-comments.json`, `raw-data/pr-39-review-comments.json`, and `raw-data/pr-39-reviews.json`: prepared PR data. +- `raw-data/main-run-24465255225.json` and `raw-data/main-run-24465255225.log.gz`: Rust template `main` release run that reproduced the issue. +- `raw-data/downstream-meta-before-run-24983875003.json` and `.log.gz`: downstream `meta-ontology` run before the same fix. +- `raw-data/downstream-meta-after-run-24985948212.json` and `.log.gz`: downstream `meta-ontology` run after the same fix. +- `raw-data/pr-run-25212295127.json` and `.log.gz`: initial PR branch CI run. +- `raw-data/js-template-issue-search.json` and `raw-data/rust-template-issue-search.json`: search results for matching template issues. +- `template-data/rust-template-release-before.yml` and `template-data/rust-template-release-after.yml`: before/after workflow snapshots. +- `template-data/js-template-release.yml`, `template-data/js-template-links.yml`, and template tree files: JavaScript template comparison inputs. + +The downloaded CI logs have more than 1500 lines, so the analysis references narrow uncompressed line ranges instead of embedding full logs. + +## Timeline + +- 2026-04-15 16:12:07 UTC: Rust template `main` release run `24465255225` started at commit `353d893ba0a26ecec6fb1ba1716b6a9ad27e1fef`. +- 2026-04-15 16:13:54 to 16:14:09 UTC: `Build Package` succeeded in run `24465255225`. +- 2026-04-15 16:14:11 to 16:15:21 UTC: `Auto Release` failed in run `24465255225`. +- 2026-04-15 16:15:22 UTC: `Deploy Rust Documentation` was skipped in run `24465255225`, even though `Build Package` succeeded. +- 2026-04-27 08:11:01 UTC: downstream `meta-ontology` run `24983875003` reproduced the same pattern: build succeeded, auto release failed, docs deploy skipped. +- 2026-04-27 08:59:30 UTC: downstream `meta-ontology` run `24985948212`, after applying the same workflow dependency fix, showed `Auto Release` failing while `Deploy Rust Documentation` succeeded. +- 2026-05-01 11:11:28 UTC: the Rust template issue was updated with a broader request to compare Rust and JavaScript templates, preserve evidence, and document the analysis. +- 2026-05-01 11:12:08 UTC: PR [#39](https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/pull/39) was created from `issue-38-325a287cfa55`. + +## Requirements + +The issue and follow-up comment required: + +- Decouple documentation deployment from package release publication. +- Keep docs deployment limited to successful builds. +- Preserve the intended release triggers: `main` push and manual instant workflow dispatch. +- Investigate historical CI logs and related downstream work. +- Compare the Rust and JavaScript pipeline templates. +- File or identify related template issues if the same bug exists elsewhere. +- Add a reproducing automated test before the fix. +- Store research and data under `docs/case-studies/issue-38`. + +## Root Cause + +The workflow-level cause was: + +```yaml +deploy-docs: + needs: [auto-release, manual-release] +``` + +Because `deploy-docs` depended on release publication jobs, it was downstream of both release success and release failure. The old `if` condition only allowed docs deployment when either release job succeeded. In the observed `main` run, `auto-release` failed and `manual-release` was skipped, so `deploy-docs` was skipped. + +GitHub Actions documentation confirms the dependency behavior: jobs declared in `needs` wait for those jobs, and failed or skipped dependencies skip downstream jobs unless a job-level condition explicitly changes that behavior. The `needs` context exposes each direct dependency result as `success`, `failure`, `cancelled`, or `skipped`. GitHub also recommends `!cancelled()` for jobs that should continue after non-critical failures without running after cancellation. + +The release-path failure was separate but relevant. In run `24465255225`, the `Auto Release` job failed while running `rust-script scripts/check-release-needed.rs` under `RUSTFLAGS=-Dwarnings`. The downloaded log shows: + +- `raw-data/main-run-24465255225.log.gz`, uncompressed lines 4798-4809: `check-release-needed.rs` was invoked with `HAS_FRAGMENTS=true` and `RUSTFLAGS=-Dwarnings`. +- Lines 4810-4817: `get_arg` in `scripts/check-release-needed.rs` failed as dead code. +- Lines 4819-4854: shared helper functions in `scripts/rust-paths.rs` also failed as dead code when the file was imported as a module. +- Lines 4855-4857: the script compile failed and the job exited with code 1. + +The downstream `meta-ontology` before-fix run `24983875003` showed the same warnings-as-errors pattern in uncompressed lines 5699-5758. + +## Solution + +The workflow fix makes documentation depend only on the successful build artifact boundary: + +```yaml +deploy-docs: + needs: [build] + if: | + !cancelled() && + needs.build.result == 'success' && ( + (github.event_name == 'push' && github.ref == 'refs/heads/main') || + (github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'instant') + ) +``` + +This keeps documentation independent from package publication while still requiring a known-good build. It also avoids using `always()` for the docs job, so cancellation still stops the workflow. + +The script cleanup removes the unused `get_arg` helper from `check-release-needed.rs` and marks `rust-paths.rs` as an importable script utility with `#![allow(dead_code)]`. That resolves the concrete `RUSTFLAGS=-Dwarnings` failure observed in CI without weakening the rest of the repository lint settings. + +## Regression Test + +`tests/unit/ci-cd/workflow_release.rs` reproduces the workflow bug structurally. It extracts the `deploy-docs` job block from `.github/workflows/release.yml` and asserts that: + +- `deploy-docs` depends on `build`. +- the condition checks `needs.build.result == 'success'`. +- the condition still limits push deployments to `refs/heads/main`. +- the job no longer depends on `auto-release` or `manual-release` results. + +Before the workflow change, this test failed on `deploy_docs.contains("needs: [build]")`. After the fix, it passes. + +## Template Comparison + +Rust template: + +- Before fix: `template-data/rust-template-release-before.yml` used `cancel-in-progress: true` and `deploy-docs.needs: [auto-release, manual-release]`. +- After fix: `template-data/rust-template-release-after.yml` uses `cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}` and `deploy-docs.needs: [build]`. +- Search results in `raw-data/rust-template-issue-search.json` found the existing tracked issue: #38. No additional Rust template issue is needed. + +JavaScript template: + +- `template-data/js-template-release.yml` has no documentation deployment job and no GitHub Pages publication job, so the exact docs-release coupling bug does not exist there. +- The JavaScript template already uses `cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}`, which avoids cancelling in-progress PR checks while keeping stale `main` release runs under control. +- The JavaScript template has a `validate-docs` job for documentation-only validation, but that is a validation concern rather than a deployment concern. +- Search results in `raw-data/js-template-issue-search.json` found no matching issue to file or update. + +## Online Research + +Official GitHub documentation used for the workflow reasoning: + +- [Workflow syntax: `jobs..needs`](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idneeds) +- [Contexts reference: `needs` context](https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#needs-context) +- [Expressions: status check functions](https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#status-check-functions) +- [GitHub Pages custom workflows](https://docs.github.com/en/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages) +- [actions/deploy-pages README](https://github.com/actions/deploy-pages) + +GitHub Pages documentation and `actions/deploy-pages` both describe the build-then-deploy shape for Pages deployments. This PR does not migrate from `peaceiris/actions-gh-pages@v4` to `actions/deploy-pages`, because that would be a broader repository settings and permissions change. The narrow fix is to correct this workflow's dependency graph. + +## Verification + +Local checks run on 2026-05-01: + +- `cargo test --test unit ci_cd::workflow_release::documentation_deploy_is_independent_from_release_publication` +- `RUSTFLAGS=-Dwarnings HAS_FRAGMENTS=true rust-script scripts/check-release-needed.rs` +- `cargo fmt --all -- --check` +- `cargo test --all-features --verbose` +- `cargo test --doc --verbose` +- `cargo clippy --all-targets --all-features` +- `rust-script scripts/check-file-size.rs` +- `cargo build --release --verbose` +- `cargo package --list --allow-dirty` + +The downstream after-fix run `24985948212` provides live workflow evidence that the dependency shape works: `Build Package` succeeded, `Auto Release` failed, and `Deploy Rust Documentation` still succeeded. The deploy job built docs at uncompressed lines 5801-5802 and completed `peaceiris/actions-gh-pages@v4` successfully at lines 6074-6082. diff --git a/docs/case-studies/issue-38/raw-data/downstream-meta-after-run-24985948212.json b/docs/case-studies/issue-38/raw-data/downstream-meta-after-run-24985948212.json new file mode 100644 index 0000000..209d9e2 --- /dev/null +++ b/docs/case-studies/issue-38/raw-data/downstream-meta-after-run-24985948212.json @@ -0,0 +1 @@ +{"conclusion":"failure","createdAt":"2026-04-27T08:59:30Z","databaseId":24985948212,"displayTitle":"Merge pull request #4 from link-foundation/issue-3-296ff7e963a4","headSha":"1a5b76cec5d40878c3f1697bb2d0b82697c147f0","jobs":[{"completedAt":"2026-04-27T09:00:30Z","conclusion":"success","databaseId":73159135579,"name":"Detect Changes","startedAt":"2026-04-27T08:59:32Z","status":"completed","steps":[{"completedAt":"2026-04-27T08:59:35Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-27T08:59:33Z","status":"completed"},{"completedAt":"2026-04-27T08:59:36Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-27T08:59:35Z","status":"completed"},{"completedAt":"2026-04-27T08:59:45Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-27T08:59:36Z","status":"completed"},{"completedAt":"2026-04-27T09:00:19Z","conclusion":"success","name":"Install rust-script","number":4,"startedAt":"2026-04-27T08:59:45Z","status":"completed"},{"completedAt":"2026-04-27T09:00:28Z","conclusion":"success","name":"Detect changes","number":5,"startedAt":"2026-04-27T09:00:19Z","status":"completed"},{"completedAt":"2026-04-27T09:00:28Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":10,"startedAt":"2026-04-27T09:00:28Z","status":"completed"},{"completedAt":"2026-04-27T09:00:28Z","conclusion":"success","name":"Complete job","number":11,"startedAt":"2026-04-27T09:00:28Z","status":"completed"}],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159135579"},{"completedAt":"2026-04-27T08:59:30Z","conclusion":"skipped","databaseId":73159135913,"name":"Version Modification Check","startedAt":"2026-04-27T08:59:30Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159135913"},{"completedAt":"2026-04-27T08:59:30Z","conclusion":"skipped","databaseId":73159136285,"name":"Create Changelog PR","startedAt":"2026-04-27T08:59:31Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159136285"},{"completedAt":"2026-04-27T09:01:31Z","conclusion":"success","databaseId":73159301049,"name":"Lint and Format Check","startedAt":"2026-04-27T09:00:33Z","status":"completed","steps":[{"completedAt":"2026-04-27T09:00:36Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-27T09:00:34Z","status":"completed"},{"completedAt":"2026-04-27T09:00:37Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-27T09:00:36Z","status":"completed"},{"completedAt":"2026-04-27T09:00:46Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-27T09:00:37Z","status":"completed"},{"completedAt":"2026-04-27T09:01:20Z","conclusion":"success","name":"Install rust-script","number":4,"startedAt":"2026-04-27T09:00:46Z","status":"completed"},{"completedAt":"2026-04-27T09:01:22Z","conclusion":"success","name":"Cache cargo registry","number":5,"startedAt":"2026-04-27T09:01:20Z","status":"completed"},{"completedAt":"2026-04-27T09:01:22Z","conclusion":"success","name":"Check formatting","number":6,"startedAt":"2026-04-27T09:01:22Z","status":"completed"},{"completedAt":"2026-04-27T09:01:27Z","conclusion":"success","name":"Run Clippy","number":7,"startedAt":"2026-04-27T09:01:22Z","status":"completed"},{"completedAt":"2026-04-27T09:01:28Z","conclusion":"success","name":"Check file size limit","number":8,"startedAt":"2026-04-27T09:01:27Z","status":"completed"},{"completedAt":"2026-04-27T09:01:28Z","conclusion":"success","name":"Post Cache cargo registry","number":15,"startedAt":"2026-04-27T09:01:28Z","status":"completed"},{"completedAt":"2026-04-27T09:01:29Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":16,"startedAt":"2026-04-27T09:01:28Z","status":"completed"},{"completedAt":"2026-04-27T09:01:29Z","conclusion":"success","name":"Complete job","number":17,"startedAt":"2026-04-27T09:01:29Z","status":"completed"}],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159301049"},{"completedAt":"2026-04-27T09:00:48Z","conclusion":"success","databaseId":73159301074,"name":"Ontology Word Coverage","startedAt":"2026-04-27T09:00:32Z","status":"completed","steps":[{"completedAt":"2026-04-27T09:00:34Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-27T09:00:33Z","status":"completed"},{"completedAt":"2026-04-27T09:00:35Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-27T09:00:34Z","status":"completed"},{"completedAt":"2026-04-27T09:00:44Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-27T09:00:35Z","status":"completed"},{"completedAt":"2026-04-27T09:00:45Z","conclusion":"success","name":"Cache cargo registry","number":4,"startedAt":"2026-04-27T09:00:44Z","status":"completed"},{"completedAt":"2026-04-27T09:00:46Z","conclusion":"success","name":"Build CLI","number":5,"startedAt":"2026-04-27T09:00:45Z","status":"completed"},{"completedAt":"2026-04-27T09:00:46Z","conclusion":"success","name":"Check word coverage of README.md","number":6,"startedAt":"2026-04-27T09:00:46Z","status":"completed"},{"completedAt":"2026-04-27T09:00:46Z","conclusion":"success","name":"Post Cache cargo registry","number":11,"startedAt":"2026-04-27T09:00:46Z","status":"completed"},{"completedAt":"2026-04-27T09:00:46Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":12,"startedAt":"2026-04-27T09:00:46Z","status":"completed"},{"completedAt":"2026-04-27T09:00:46Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-04-27T09:00:46Z","status":"completed"}],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159301074"},{"completedAt":"2026-04-27T09:01:02Z","conclusion":"success","databaseId":73159301092,"name":"Code Coverage","startedAt":"2026-04-27T09:00:33Z","status":"completed","steps":[{"completedAt":"2026-04-27T09:00:38Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-27T09:00:34Z","status":"completed"},{"completedAt":"2026-04-27T09:00:39Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-27T09:00:38Z","status":"completed"},{"completedAt":"2026-04-27T09:00:51Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-27T09:00:39Z","status":"completed"},{"completedAt":"2026-04-27T09:00:56Z","conclusion":"success","name":"Cache cargo registry","number":4,"startedAt":"2026-04-27T09:00:51Z","status":"completed"},{"completedAt":"2026-04-27T09:00:56Z","conclusion":"success","name":"Install cargo-llvm-cov","number":5,"startedAt":"2026-04-27T09:00:56Z","status":"completed"},{"completedAt":"2026-04-27T09:00:57Z","conclusion":"success","name":"Generate code coverage","number":6,"startedAt":"2026-04-27T09:00:56Z","status":"completed"},{"completedAt":"2026-04-27T09:00:59Z","conclusion":"success","name":"Upload coverage to Codecov","number":7,"startedAt":"2026-04-27T09:00:57Z","status":"completed"},{"completedAt":"2026-04-27T09:00:59Z","conclusion":"success","name":"Post Cache cargo registry","number":13,"startedAt":"2026-04-27T09:00:59Z","status":"completed"},{"completedAt":"2026-04-27T09:01:00Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":14,"startedAt":"2026-04-27T09:00:59Z","status":"completed"},{"completedAt":"2026-04-27T09:01:00Z","conclusion":"success","name":"Complete job","number":15,"startedAt":"2026-04-27T09:01:00Z","status":"completed"}],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159301092"},{"completedAt":"2026-04-27T09:01:23Z","conclusion":"success","databaseId":73159301378,"name":"Test (windows-latest)","startedAt":"2026-04-27T09:00:33Z","status":"completed","steps":[{"completedAt":"2026-04-27T09:00:35Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-27T09:00:34Z","status":"completed"},{"completedAt":"2026-04-27T09:00:41Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-27T09:00:35Z","status":"completed"},{"completedAt":"2026-04-27T09:01:01Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-27T09:00:41Z","status":"completed"},{"completedAt":"2026-04-27T09:01:07Z","conclusion":"success","name":"Cache cargo registry","number":4,"startedAt":"2026-04-27T09:01:01Z","status":"completed"},{"completedAt":"2026-04-27T09:01:18Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-04-27T09:01:07Z","status":"completed"},{"completedAt":"2026-04-27T09:01:19Z","conclusion":"success","name":"Run doc tests","number":6,"startedAt":"2026-04-27T09:01:18Z","status":"completed"},{"completedAt":"2026-04-27T09:01:20Z","conclusion":"success","name":"Post Cache cargo registry","number":11,"startedAt":"2026-04-27T09:01:19Z","status":"completed"},{"completedAt":"2026-04-27T09:01:22Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":12,"startedAt":"2026-04-27T09:01:20Z","status":"completed"},{"completedAt":"2026-04-27T09:01:22Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-04-27T09:01:22Z","status":"completed"}],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159301378"},{"completedAt":"2026-04-27T09:00:56Z","conclusion":"success","databaseId":73159301499,"name":"Test (ubuntu-latest)","startedAt":"2026-04-27T09:00:33Z","status":"completed","steps":[{"completedAt":"2026-04-27T09:00:37Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-27T09:00:34Z","status":"completed"},{"completedAt":"2026-04-27T09:00:37Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-27T09:00:37Z","status":"completed"},{"completedAt":"2026-04-27T09:00:48Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-27T09:00:37Z","status":"completed"},{"completedAt":"2026-04-27T09:00:51Z","conclusion":"success","name":"Cache cargo registry","number":4,"startedAt":"2026-04-27T09:00:48Z","status":"completed"},{"completedAt":"2026-04-27T09:00:53Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-04-27T09:00:51Z","status":"completed"},{"completedAt":"2026-04-27T09:00:53Z","conclusion":"success","name":"Run doc tests","number":6,"startedAt":"2026-04-27T09:00:53Z","status":"completed"},{"completedAt":"2026-04-27T09:00:53Z","conclusion":"success","name":"Post Cache cargo registry","number":11,"startedAt":"2026-04-27T09:00:53Z","status":"completed"},{"completedAt":"2026-04-27T09:00:53Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":12,"startedAt":"2026-04-27T09:00:53Z","status":"completed"},{"completedAt":"2026-04-27T09:00:53Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-04-27T09:00:53Z","status":"completed"}],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159301499"},{"completedAt":"2026-04-27T09:00:51Z","conclusion":"success","databaseId":73159301604,"name":"Test (macos-latest)","startedAt":"2026-04-27T09:00:33Z","status":"completed","steps":[{"completedAt":"2026-04-27T09:00:36Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-27T09:00:34Z","status":"completed"},{"completedAt":"2026-04-27T09:00:38Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-27T09:00:36Z","status":"completed"},{"completedAt":"2026-04-27T09:00:39Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-27T09:00:38Z","status":"completed"},{"completedAt":"2026-04-27T09:00:42Z","conclusion":"success","name":"Cache cargo registry","number":4,"startedAt":"2026-04-27T09:00:39Z","status":"completed"},{"completedAt":"2026-04-27T09:00:49Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-04-27T09:00:42Z","status":"completed"},{"completedAt":"2026-04-27T09:00:49Z","conclusion":"success","name":"Run doc tests","number":6,"startedAt":"2026-04-27T09:00:49Z","status":"completed"},{"completedAt":"2026-04-27T09:00:49Z","conclusion":"success","name":"Post Cache cargo registry","number":11,"startedAt":"2026-04-27T09:00:49Z","status":"completed"},{"completedAt":"2026-04-27T09:00:50Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":12,"startedAt":"2026-04-27T09:00:49Z","status":"completed"},{"completedAt":"2026-04-27T09:00:50Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-04-27T09:00:50Z","status":"completed"}],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159301604"},{"completedAt":"2026-04-27T09:00:31Z","conclusion":"skipped","databaseId":73159301656,"name":"Changelog Fragment Check","startedAt":"2026-04-27T09:00:31Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159301656"},{"completedAt":"2026-04-27T09:01:57Z","conclusion":"success","databaseId":73159464024,"name":"Build Package","startedAt":"2026-04-27T09:01:33Z","status":"completed","steps":[{"completedAt":"2026-04-27T09:01:36Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-27T09:01:34Z","status":"completed"},{"completedAt":"2026-04-27T09:01:36Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-27T09:01:36Z","status":"completed"},{"completedAt":"2026-04-27T09:01:45Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-27T09:01:36Z","status":"completed"},{"completedAt":"2026-04-27T09:01:47Z","conclusion":"success","name":"Cache cargo registry","number":4,"startedAt":"2026-04-27T09:01:45Z","status":"completed"},{"completedAt":"2026-04-27T09:01:55Z","conclusion":"success","name":"Build release","number":5,"startedAt":"2026-04-27T09:01:47Z","status":"completed"},{"completedAt":"2026-04-27T09:01:55Z","conclusion":"success","name":"Check package","number":6,"startedAt":"2026-04-27T09:01:55Z","status":"completed"},{"completedAt":"2026-04-27T09:01:55Z","conclusion":"success","name":"Post Cache cargo registry","number":11,"startedAt":"2026-04-27T09:01:55Z","status":"completed"},{"completedAt":"2026-04-27T09:01:55Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":12,"startedAt":"2026-04-27T09:01:55Z","status":"completed"},{"completedAt":"2026-04-27T09:01:56Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-04-27T09:01:55Z","status":"completed"}],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159464024"},{"completedAt":"2026-04-27T09:03:35Z","conclusion":"failure","databaseId":73159536666,"name":"Auto Release","startedAt":"2026-04-27T09:02:00Z","status":"completed","steps":[{"completedAt":"2026-04-27T09:02:02Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-27T09:02:01Z","status":"completed"},{"completedAt":"2026-04-27T09:02:03Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-27T09:02:02Z","status":"completed"},{"completedAt":"2026-04-27T09:02:14Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-27T09:02:03Z","status":"completed"},{"completedAt":"2026-04-27T09:02:49Z","conclusion":"success","name":"Install rust-script","number":4,"startedAt":"2026-04-27T09:02:14Z","status":"completed"},{"completedAt":"2026-04-27T09:02:50Z","conclusion":"success","name":"Configure git","number":5,"startedAt":"2026-04-27T09:02:49Z","status":"completed"},{"completedAt":"2026-04-27T09:02:59Z","conclusion":"success","name":"Determine bump type from changelog fragments","number":6,"startedAt":"2026-04-27T09:02:50Z","status":"completed"},{"completedAt":"2026-04-27T09:03:26Z","conclusion":"success","name":"Check if version already released or no fragments","number":7,"startedAt":"2026-04-27T09:02:59Z","status":"completed"},{"completedAt":"2026-04-27T09:03:32Z","conclusion":"success","name":"Collect changelog and bump version","number":8,"startedAt":"2026-04-27T09:03:26Z","status":"completed"},{"completedAt":"2026-04-27T09:03:33Z","conclusion":"failure","name":"Get current version","number":9,"startedAt":"2026-04-27T09:03:32Z","status":"completed"},{"completedAt":"2026-04-27T09:03:33Z","conclusion":"skipped","name":"Build release","number":10,"startedAt":"2026-04-27T09:03:33Z","status":"completed"},{"completedAt":"2026-04-27T09:03:33Z","conclusion":"skipped","name":"Publish to Crates.io","number":11,"startedAt":"2026-04-27T09:03:33Z","status":"completed"},{"completedAt":"2026-04-27T09:03:33Z","conclusion":"skipped","name":"Create GitHub Release","number":12,"startedAt":"2026-04-27T09:03:33Z","status":"completed"},{"completedAt":"2026-04-27T09:03:33Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":24,"startedAt":"2026-04-27T09:03:33Z","status":"completed"},{"completedAt":"2026-04-27T09:03:33Z","conclusion":"success","name":"Complete job","number":25,"startedAt":"2026-04-27T09:03:33Z","status":"completed"}],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159536666"},{"completedAt":"2026-04-27T09:02:22Z","conclusion":"success","databaseId":73159536700,"name":"Deploy Rust Documentation","startedAt":"2026-04-27T09:02:00Z","status":"completed","steps":[{"completedAt":"2026-04-27T09:02:02Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-27T09:02:01Z","status":"completed"},{"completedAt":"2026-04-27T09:02:03Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-27T09:02:02Z","status":"completed"},{"completedAt":"2026-04-27T09:02:12Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-27T09:02:03Z","status":"completed"},{"completedAt":"2026-04-27T09:02:19Z","conclusion":"success","name":"Build documentation","number":4,"startedAt":"2026-04-27T09:02:12Z","status":"completed"},{"completedAt":"2026-04-27T09:02:20Z","conclusion":"success","name":"Deploy to GitHub Pages","number":5,"startedAt":"2026-04-27T09:02:19Z","status":"completed"},{"completedAt":"2026-04-27T09:02:21Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":10,"startedAt":"2026-04-27T09:02:20Z","status":"completed"},{"completedAt":"2026-04-27T09:02:21Z","conclusion":"success","name":"Complete job","number":11,"startedAt":"2026-04-27T09:02:21Z","status":"completed"}],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159536700"},{"completedAt":"2026-04-27T09:01:58Z","conclusion":"skipped","databaseId":73159537103,"name":"Instant Release","startedAt":"2026-04-27T09:01:58Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212/job/73159537103"}],"status":"completed","url":"https://github.com/link-foundation/meta-ontology/actions/runs/24985948212","workflowName":"CI/CD Pipeline"} diff --git a/docs/case-studies/issue-38/raw-data/downstream-meta-after-run-24985948212.log.gz b/docs/case-studies/issue-38/raw-data/downstream-meta-after-run-24985948212.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..9ad8a22a9994fb8514f1e8d8497cd8f8af424590 GIT binary patch literal 106114 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%0Dpe_jAubX;+5}Gb&>i=lKOy?z6dw z;|p(<|Is#jM(F{EJwhcN?I>19>clHnNdoB8)oj-C@fI+_SS4rg9ZU);`FGJIG)*%X zyH^BdoW05XbXYmV`&M=QyXBRFO@Uz-QW7If9P~%qS_e^`Tx#TXXC@M)K~YNUVs+tA zzM6nwBL~u|F0&a}K5uH?POIT_febLnw!MB}8&mDOzVrhNsZ7|Deqiy8=<|+{Fl`uN zSN*_3eT0bD3R&x(?j0;#q`DOKAP~WLv}-G7)6E4NtitsJ!b5yPcyRy4qw)8zo}P@W zhXWUyYWoeN1-16KheQ#E-ZY*m7RxLzr{2M6L=Q)!5#1wBS2>9~FU$YeRx6|`pBo-< zPiD(`jxI9ZWxUL+TeD43410b47G*rf4q*k7th@rr9~WSDzs?U_87IHAuE~EbR=`Zy z>CwcZh&!^p&^UHZ@lp}eYR8wRu`nKgcO1_aS)Lwv!?a9>^TMw#VLH(i%xvew-K-Sa zAEVLZDnx7nvK~;2O`>O<`0X>(JH+uga-V&n_%=JAc~2kMh;&;^HD0_K*FkGpP8Api z#kniBsF5@asb$mV+!m|Jysx>HoNaslcorV|V^dX-hDcRWaR^!X1>%q_6LT#lK>d`z9 zCJID<=7D6)W_Eu3^!clo(C24Qy4%f?1*!ph^625oKVQ81!PR<}OxibI3-|r~Cr=*S zKlx{O8(OkR(pY}?@ca9(p1d4avZ2e&OHQ@8J+BfluU%FN_Q~>m)n)1nMnjafx-K>bKA_y@9qyHjxbZ8oBB`_up+Iqqgt? zj{slalx*gPIxqDUY?B_=wHk(X^Wh~ML<6<+y_>TeT&#-Opp2J;vp?E0FTj2YWLPr~ z&9dngvR<6Wfzs=XCo&s8HZPG5=)TMHvQ4d?Hn3pg@+L_}CssGX0Sw>^_y2hJc8RY) ztUZO=V~3y~!ilkiOtb6&ra5{uwQgv57)HDKk%^^dA5k_;)3{tMsVM2p{?G@bKhbg!3As_Xy3lsfmGF$!`UYLeYHX>Cwu6`vKB%~8JJ8rZe)XsAAf)Ims?XwS zrFzwN05b@J9ugW@C{i|i*LDzDN4;H!B3u-8DHs7DQtMEl6?LPOOjgTfnwPUHOw>ck zf|BXkVf}P-^p$rtUqPGPn=eYAYM_?h>6N#L3raC$jq2&y`mv`icREl~Slgdp{KwBG zKr8QrvM2yPZdh;okqnNx5S_?cJ*~CWP{M&`2J?n&v*mrC^AC$78&b$r%l!o0a3QoXJKT66it~DqR(Vz;Lxoo? zli@5o#T3qmmh^Ca0#`p1bMWV)pkW%UtfGlG7!17m8wau> z)x5fyEGlTlxJZG>Brf9)M9BU2W+KT|GE2vl^s5+`Si*dq%=08&l=dfgsu3lkzL{#8 zpTY2*1Klb~;ut%db{I%34pmXZ87bDJa;yl8A-iLY0bCU0#k>F&xX3Qk4%9-UZlqSu z-=sMbgM&UxFv_rSCxfL(%#CEID=<+L=+z?5i*cM!&}?~AxDqp<6l2)h&GU7!nw8lC z7Rn@@f;c1h$uT+{q{~b9Su;ZObqBA}%!>;kkzJ$}rM&DwhbmM%Gz=NuzYv4K1B}85 z7#M(@!Lux#eC09n)3cwlvm8Ae@Z$y8S*z+ZNcu9xM-ey)zw)Nlae#824OrA(SOHf$ za_Wu@OtgDE-Ekn$ZK2DF5b()q7{wu<#Ia_vX@4RZB`B4tOGAOCM|~nN#}ff?lo4(W z&{OoaHN$#aGt8wspa{F1um+QDsk_z;jqfna%k*qX!F-Rrt9-OnPniT&*yK)^z2cr&(#U+m2BoH%#a3^ z5#P|D+Y{15KvLooD_|#%>AIwgb^?@);#<8T9ca5Zt_MztVf~EX)(_ku)Q#%ir&~ur zDHRUu0vl2!%C)^Ld)v$H^?Kk6F9Mvp69zUb<=uN3Pk|Pj7{BhD(_oUE#?uLprb?u7 z>)jjTx@N62o7#o~U1#gv%OltA8$}VTkNYGWClg$hn9UVVwJ~Pd0C=mPpanomGpHrw z>axyxc}9j@e?NrmEH2jcKzlBi)#bV?X~UEhA4WR8Sd>>_ju63d?c(Q7nG5xSoxaE> z6VN^T>~^5U&P3yF0O-<5I=+bWY?_j_SIlQCBs3b~o#6O9oh^U~_(qwW!#{&5k(Kl1 z6->(&UUN9JgOxO{voi&=b6kg0jVAMCMF}Hm{9)(K=2zpa7-uz&iRe>Xw-_&0 zr?af8M?F30i!+|g;fPqKXX)?bv`FHGs}U3`CWYj!rV3;F#>@G<9K*3f zOr}ftLb+PzWVxrrBFq<_Vbknvwd^!Tih)=S9XSNcW{N8c3}KtH2H1^3nNF9^>_{*h zgLdrT=SxrK9X3FbTsXT943c-ryS@WMgV z?GI;Qc7vg5kL$afXK@!`B#sKe#pN*iVEn?qs{oMD1at$o4|yv$6KNxwPI}f_>VZFRx+4y}Ti7^-TyOu4UbK;oY!L=5hf(wV>N;OKA69bQAXOyN1gy z>|&;q;do(>tzC2D3$Z7|ey;$MXG$L-()4~hEPy0EQW5V=&YuE6zKOYemn6lo2_-W~ zmVqSruyC9*i$WPlZsH$G2qZCh$}RI&oxu#WE*?liP}BJHiv*GwI9aHE zB!NgR)MlX#%w~bR74#n-?d{osQm&aeylVq4f5eu7)S2w^b{NW`1Bf(bVjW%D6xC>^ zMkT!<6)WhuEgd0N*HtKLazs`##g}1fSxo)-E$drEuNi#bQ|tH5)~>egJp+1sncG5) zfA_U(du50yg0;PVS!8`spJjh#MBbJV*M7nK90rHNwPT$AWNdLu#^9*T#UNXZAX6_n zD|R>28;03la0FLtcXCbhI-($4SlFi)h3*Y$E}V_hGkdhY?XjWl7?uI7r z>fSK2Re!?Cs9BRJf6Ug0rl{0`f?d08OjXkwW&_%}jbL_RRM>+JW8o}Hntr&>m@PwE z@-2og$km_LYJ~RAqjf&6BhUj;T-DbQi=Zk2xJ;dR=aCnD%Ow0ZZ9LLu?67O1ui4&sh*yYSH53l8eEWx@5 zNWsg>sb%nd;g@UO2M(vZswG(W041`SugbIT0a*!InspCwfh=m>2hw#gayRE($xjDt z0B_UnS+6JeTi-TXZTNZg?0GL-uwA!b|N8(V+Txva?%;$V@ExAH25e8+PD%KVpD`9l zJ%M)EyK+45SLa)gkB*MMGrx?Icn(&Jvv$P63D358wp3Vw4%8PcXN4rSqz zwc;yd=yUO9rF}H4{+SwCPvxcG5t=_PtkG0*tVD5E8l7_=d`@zG$_RG|M z+JP^Rm!|y?&kM@+)*_|~O3{7@6=W~xm8Sgw8PtAImi9w9j~CQ_+QF2Hn>TmCa(D^T z<|uaT*noPO&GZJG%{)ZNU_EbI>;g!tsl0`L>|R`+o`O96`(>lu>Ar5%PT-e|^X^N0 z`>gh+vB&<#zWy3rR(YR8-&XPr{;l2YoVR{PpMzy{w8+#{wsl(|j_(AUUV;Zr#g|wv zs2?prNk(@5RPEpd$tIRcN0(fGwwT;4R?^0xh)g{mv!_qEdltW-`u2O78a#F!yN(u^ zI`GRa3rI4+j<;DB@L{b>HHyK6g+VKCn~!+XD|efZm_a#4F+gEz($BFhxVRZ8={C55 z6F!a0Csf>N?YIFHYzX*fgOy8%T2bP0I?^r8d&uziV}+tJ;!8+bW7CF|?-X>7XcuPa zmYJUvpD-~lm3ePAoG$*VoY3h9p5y7BFG3MUkrbYEf<7M%de?5x^R@2vqtJ#2p=<-X zFmJPC#@6t8h^H^lL78DT=qjk5D(KP6cB6aVJgKvzqa(JDYJVSB)s0V~w#8-BeBc}Q zkH?r{Xw}abY}oHE@J1~=V&VIcjQv?uy@?jhlMcSW$qKU3tzDip8)x;#qh1~iy}^j^ zl*D5nepEZ#bZTIBy|D}seLFS8Vf8gwbRowwwgh3gDO$OlN3#k%gh4nvMb!p7R^%ou zv=WVb6HHum$bOsWO>txJd=UpvQB_wPtq34=oMJ%kE71qM-cp$_!o_l4!8l5Q6^`>l zI4=D6;J?d&sN(4o zrAgRK+r0`zSS?HGb}u#mzgqq6TN~2ZG{IIx^Y1r^um{ptaKC79q;$7Vm4Q*3bF@t& z8KPgy*en^Mfn4<(DEOa$(!Q7?`+5dNr4x zn=##ESaF_J`fUGdyl-(JNOTnU{l^66S-tE@tE9|!s*P91;P`H_cqK$pU;=(Ye~TgM zV+Z6nLaE@MN~ESYboXU zzf}Q4&HyRnP+MDs^ZHE;QV>^!DcvMQc;9jGgrtxa$9WKV?k+2?@?XYuI&Rg@>aSCm zee!m!=alBBR4=Y^NVEYkyIAOlC1XE<=lg($Oj zy212iIfes3X<5Dph>*R~YNs1G5!a(AW;g(ljEl}Dv&X_{02DBIKi9bt5fF4b@|n+# z3?)zV&wp-YNGZQi?qvY+@N9QSA-a{Z(SBXqvvF8?c!aijb7CFSO05EJ_BNEYjY75Q z)cOhxe0_TPMdmFj!ZN0cE`@-S^`ouz>qYAmR-tW|^T6X}bh+mUzTV{*t<4@kyL@xp zXxVnL#{*Hki@offM_UY)Df5+D8z^Gw6ez{c07^Qufk-Jq&n7Za4jqqcOnLAH?HLbg zS_Ph%hm}{ZJp+(|EYY6vJmLCfMq?XHp~|sm07|Ly15XUHcFJ>vc%DqIxfio%0Eou} zQox=86d@9nitb=-HhA&Nkps^xp0+>CkJ0pkpoGgt*+IWhm~JqV7YY|DqP^2RYsbPRf zDh*k#1%ME^2@}V)cEg?%O380SEf7u%jW5c9Y~ziD*@?Vwrn=`03?E> zckAaTHd}VUP$F@LoHXr*NC#E#Iy zrv;9i(QCElX%|-XMyrKOm~B_V1~cIdMXha}0@0w!)k&gR4sAeGd~Qn0yKXfbIVjAe zr(V_1-*#bLvEzn&N@sx@KQ-!?9h7*RBz*>QGMC)x6d*3FxApv@Bx(j+;J39d#adu9w%vPrjLwk94VlTIza1vGd{rR!A*5;lJ z5*|+*uWI$53$Wy;Q_0fr3mDo-@X} zBYApa%OKV3Fvt9M(^DuvY2rn#g*!h>yM~KEBva|)^zv2n%#I=H@Wf(lfu?AorFD7y z=KSRHw8$DRxzH&r&Y*7R=LB< zcG^6t;SScm2bCjJOc-CQu2Vdn0_`80FFP=u&cAQeJ9c16S#uMYeDU)|vq0-)YL&lj zw2sf)jRIKl^(ceYre3$n+-bmcx`X=p%_%t`ta{6LQB@Rj!+?=bsmJK6i(f4L>e`_B zN#5)Og8P_&ePkak;1pG^FpnoJ(s%rwUgNif@2(BUN*ei1NC4mL3cvrIeWcge5c4L! zePBbv_eDa2`s8`{8;0MBB%(i$-@IaPttj}8IlX7>iaGd3-H-U1z9ny35s>DB z$b(;$YQr6|A7|lo8qF$Bl~;W`FB;|3dX!jhOo?TVMCIGz$lXg;vn%u+Hs<}Z|;u#g>@#2@Q%^TR@94zn7RHh z?59CA>oZm}kgPq8;BCe_!%@UCKjRm5QzzJh4=?ukf8y%|JBYE%sri{Q4SbCk_<_@C zuwaHw8!n{GT{ADJa=deCE|)pPrNbV4nfXzv9MZoXFX#gnY-n;Yx{d??tLhEQzRa+p zRMmU)1UBGv@=b0wV8`d0jGAln$J9nZ;qGq>I*Ib3k!u@C#x>VyejNfVdK=kd}A}SfhQp~|7<)}q2Da|lAg5DILdK_ z_LAw@PKRv-vd=oph}EVu#$D#>LmnJD9zzgz*{NF)g8k}laf~z1w%RqCm6%pRH2jw* z$ANSj&hc@b*A;*Ha&szMu;5dkKVsq4WYW*bDP8L-RUiBs*ImlJYXUw4ia{bif2_66 znrE*tqwsJ}{S#;A?nSc&a012^CT?J%(VbDIQD0e5)>PtqeULi05zb^J3-h_3G=6a# zF7OIorWdcHyUF-wj=cj_()kP}g`uc0D_IFVmkPqhLwd;0f5?v^94_1~Rarl6uGAu?38Yc9 zL8pY=c^~v)XYXidONn*pisM~0zFFK_oB<=OFgG%GW<@OM@50M`uInb|TC8QOi4J9( z_?YZ(xTwVSf?~fIyh#BP>46J=3SL)Mb%5xEA-@jRRj`|tl3>IdT(I1b`L>Kdt^O_F?w)EB+U@~#gP*RWD$O{}_xAm&3k zXD={=E^znL%m^2m`Oup!(^>;@igGONHee;ZG1$Mg(Hq>h%6|v>xn9mmIo}2qSj|a`F-hFgNAM)MY~rZt7TBkT&s~kCK*1 z`a6~@9q57OIjOD+u-ZN~H{mnLQ|u@B&tj#AtdKEpjR^q_0fISgKA} zfB{n+@sRyvZv^iE?Y+Z2qqoz{+7%KeD&zr!2!DdnoX6U6FJ}apt_D1C0z729n9LXK zw+Zab?xGO(ZMj8KD;NS?cIsgdOoQ9uf=zlo^!|XrkI7t^X4d;tw2&(l+?49{%C&HN z+8sDrMzZfnaqTJ)wcr}l{V3P(_qfx??34pn3p+ zg*##0r!}M2@0&6qNxATh^Cz~Y7jf(EiEhk7Og@~0`H$(0SC8dqMSJ8I-z|+#%AlM} zo9AXQz>A>JSFLC?p$Xh>zfOz%td;4ufY#i5a3-d0znk=^>Emz|Rj6bV^hdO)P!aPf zC7!#g&gZvvgOZ;OFd*@Y)NVG?`j5CMM4-HYx~UC0dNA43nVJ@fG+JIs^tm)hz=V<}N%I-0LLoeLIySj<9p8{tknKcsd&4D%^Ais-nx zs*?NW>hS8|Do$;8RRvV|wMr~na_DlwgD*J1Iok51u}7q8;@L7-2z9UyWG4`wrzD1h zqyjM<_wJVcs5xNKn3DK0D*Q<`iYDkF?!)`T@NS;n&g53Xt|vT!*Q1I_&3~laUg1&( zIx$bYe9Q^hN4SBGp@xG5DW8>$PgSSkBF{~RgaM_^rlBwc^r^)xislFZYRimIHJi4X zF@i@F=F_kj6>4!n!9!0ZQbMbIpUPi9QQB_enfJ$u1g3=nejhTQ(8rRQiWRQWgc7_W zd0$jS&(Rey$iqtH@lZq}?DLp5l@Z zTi^iWiT)J*2v&CvonM+HZS~@xqBo=?JPgz;EL9~cW=!-)(h_5u9{L0N2KIsK={tjm zB<10r`Quxtl=HAnT6FY_>kSFJBD@Xqhxe0~o+`fjJgt}h@YX7>IDgPid3>j`vp?py zllPU$Yfyk%%->DI1T&Ay_w;7Bf>Y8)Z=4_znuX@^|v|WPLiyh3N7}dDU2A#*i<)a)n-36 zix2N6W0u7k_t_}CCub^lANrqQ_$b!#ft2u1u3k`$%csrR`yr?qIsuvWqCOo+EXU@o zJLN^N>w9R*AY9&oR4&39Z4bX+jw8m${TJkCuVz_KKvB{#T#=6b(YEqRgLpQTdLzl#3hYmo z3ml3h1z^Ho?ez5gM~bz-JZt0Kj0W+Wmc&G^tC&ZSjV2T<1cN;?(FOA;TBV+heGS)O zNgWtpE53q}krkMfuC65F3{O@jgUT41QQ;1#n9BOmYQ=QJ{NSHD(R{(4gT(hI@8=a0 zm-Ef~6#Kvb`QNQ008dn19>5+Blw%ybnsn{EB!qCES3UTgv;Pz#J1Kas&y`wFUw|Z< zBRrwIn1OCXQqimc1uV?^3wu4Gn34KqG+iztR>QIm_MrdI+_f;bZDZ@d!t1(gI|(HK z#DnnG-KI&}WF~1md9-_5w^@pUY~IL{R+QW(P5RsKIRHt~cH&qBlzOI_5Q{#96(+AN@HZd)9m?zrAT8L4s^MSzbetIAW?>#7Tf=4@d-(r}~GjnXGX9B|LB*){mY`iRez(sno z6Ha$9NfO>#`cG#DkM;4u-!={QL6&$v{G`a|({egmL_awU5czZ1yg0|g16?u9C8wkZ zida%Ye4r$nLD)h`N}@%Ffjj%2^0MmQ~m0%6;J(Y zWm*mQ%w)q;(>on{@Xc&xcBej`8Eea|!SmexqU1P2)D{|6p%2xNsdf1g)D<`2%otZqrs#C>kNnS~frY5Q^g zxEzDt$Ogq2SiL(o^M(IX0=r6uQW4;XVEGOh%sKehCAh&kD8fpfcZU^(Vgl>o+|1DJ zZ#?T&I8Ik+O&2#c*WX0s0(uRsxoHA|92yBm`9} zs)V=)E$u3ME>`_PyMxI@+kDuvJc-N2yvP?eA+w`aaFEIs$v{Z#8){P>Jn)p&T2VAZ z$F4kgT<^`Rl5XB?K(9AiN?b{U62x|C;(CF``E+99Q!ujpDF~MjGct?(1W!OQZ>OVk z>j1TDF5m#c*DLFaCfzYLtJW0W|IFsolc}0)+?zjnUchdM!rB)AYMOsy0gFtdh zf>^Dhr$PBM^yi#@$@nkOl_*iNJ~m`oBhD6UMUu#;nN%#X66BAieYi+sdmu7!;C z$dDMUbDe3EjEGqyd!jqnLShu>2C^tRv!t-R-FnOHS~(V<+8JFr7E*!>OYjH{tauw_ zeS*oM?tBh!w>P&bfwmI5-Jki}|~yUMEyf z3obqOn15r%Y-<@86JVmBeLB9dJ1d&o8CYjnIysJk@dDL)M~>cZ|S< zP>jkn&&2KL+*4HD${t7Ruk3dDeMh z=^mj>3NHO~&^pskl;(b#VXX|cu6%Nlp3j%{Uu6MqjH)nDXVDW=sL?i+PeGl#lxr19{pUEX&z)H7%I1J z-Re95SWJPht7k6ETC>dRRdy}Q)kVFB8NJR=FDbEcf1my7FUSA?%_E_OX6vevPmP3c z63l0KZZOSBN&>I{kSQOZV+@4Are7gE0}I9y(mqK14(}F1dG$y@Dw?h-vca60&8Leg z_;S7KJnn$GTMyei&0c-LtJxeA3G63MQ<->mM*yGOE0+UWd%w47CI(98CKp3aQkCY( zh)S&n12!as6x=?TA+APbe{Mz?REK)2?7Lt0PqU(%6T?YQY49k~aQF_>89s{_UQv}8 z4j0{uewmoxVLC^@Hbn!kI~Y;?brfI-QVd@rWW9wjm}e6^=i>s1qCH|yG(xbWv`o0I zlN|yrO|(sGTJLz~EVg%#QRwBSiH5mPf4=q{cP2y=A~_8piyRmw>A7Gf-bt1y>%&APonwIwY8|P?fkF&a6IU)~2rD?k6&bq>v0~6LvY&>PJ)`9oB zYb<0iRa}ww@skZwwRvmHMWtv)-O*Ffu*JB?yci!kBU?y7>e;eWE3%o07j{u(> zOSL`!y#P(-Qs%M<1#>0mb`ZYXDZRzlLNZV?=g@49+Hz*^SLRv%I@)PH%1*B{G0js= zLqvuH7#22RY)$bAnlXy0o3+zyaq#8C&g3PR*00#CxTHOXNk05S+E{dD44VfjN>C$p z505}{lGQ^nYR_NnKQmlv+qv)4kqWEpJ zy(07Lum41~=1WI#v3i&&`U0;VA1(*caB5shS~GDf>pykGu?t@M4;VR98QUr4Nfy$vsR-Rm*rkx68w+x7#&!>*H?YH zw+E}_>u9@58hiTk#o@~ro{yCZnY7o;xHO)Kny_Szfr{<_wzmIlzVJmgVDGm+>quz9 z+N)_)_~NChzV^-m7jId%cTkTf3CpD!w|%_ik(A z-d687F@}tyk=cx)@6OgNaXlG6GCHf-3I8Ywl`EC%xiy8-30@7BCuTg}k3Q;W4YKm2 z_u$_T?C~o2$oBROU(fR>hF8;QR?H06JCFV(-Rd4x>?qW+Ud{l3q-pqYX$zeak|yDu z0l-y^ZN?b@Y+bdP4&@Aplv2*Z`hkF%kPxL00E{!0P&|jb;9%rn;(|q-V-1*cv79xr zN#kPDnR6t@6WMYzt|a5LU6EAUasn!;lg*FKqCD8%#r6Yu92$XCb|3cpcly!$e}a$T zL{vP9c3|JSzwUq%!9Pt*Xn~8(Wo$~tIYzM`0}z`uyylMA)#o2!xA_8aoNta}om?DR zOsxcII5Y>r$)UB$Zi?VVb~K*c`v_hD0aHb6LhvFqbA9N83SPvOGTn&aMQqK33SRJO z32`lztNpzn`Czfc=De-;pC4SJgWUblo*)UR!}OB;#>4AvH<*~)-~-PUZ%OP6z&<-0 z!Bu)e^sS)5U|zl1kGHm8K7a7+@Y&Pv9zA|=)Uyp#w%)g8&4V9)^kYX5jp5QaL(l*_ zl;QP^kDvh%R|GY@ph0L(cyK|3xVCuA1_TXYE0P91?nOu@0^R4pWPvEWm;BN3^(V{m*xyG7 zny9y1UTD6YkKG+6cKA84=e*Y38<%HbV%w03#r#b~N&OP@nC`Hf?qyWYN-{Jv*+x{!@DmY`qFIc z8o$E;A+dZ3y)mi+*s;}Lyi^5%2)dD*L4;N=r9Z5ykc<#lHX*#K0PG~}%^*UGb~?5- zY;f_sEX}+;ayLrZ@Dvt~vu}JTsW9P1v?rMmsk~@nR$_W3DQdM*9m8AMYL`34A)UGa z{%MTYYOyOZ?7@#KFkNkBe@=Wvfn-wK<~BD&6iDnKEIi#)1QA4SLKGmhjb;oi3M7_{ zW64HD0b(a%&jk@uNckB=0a8lcT;hPgJbZNsllkZcOy}YM!YKW47YTPOuHxVsAweXK z=z$Ic!=P&J3a(~V6#}#9CuUq#CNFL4P`9^ZXGz7gJg$kImS=fa`l%AcR^xN@-R~;0 zX#Li|q0;hDS3ICSEqWx{6&eOwQfXFSszT+C)2SFMnR z`*&YFIN08G!+pCsVW_PU`)V*PP4q6Z=fo3(v7EoA81tQor4{;hslHf3~yd`5?kJk**%(;oavCyFbVYD5?FZAA+g$QYb+^Z@`}YU>gU0 z8yN;>utY!<=adBuYj91<38f5=x&C$q-LNAuo1&wFZa`G<>%RtCyR+9&3cAK+i3*n_?47jG6lFNav zmna^PFd+*KQ5#C)d#hEUF~;;wILdC=k!kl3*BT@tB(#gtKqo9{07Qc;h1!S&L~NUy z6HWpGBqy4G1_`Kbexd(aGk{SLr%WcvWeI2|Rxah!@pN8d(B(a3IdV7k9>f%BWz2kn zO!rOfNG0M5>9^Vdp@g3!YD=qpNcgP?9=Ht`OaMBG3N z4juCnw2x$4ARS(k4)F3Lrx8c7W;E%jxw`avpQl0hgb#7fS55f#`J1`Su6iW||M zly~fTP2tKNyU)@)(P=hp+6=o-J7)^ltf+QI)g=L6?UqLmp1->H3_R!OuO2;pb@=S* zi>I*bE13@xDjr2!t7x#Teh;6h$cx1*yGCDC!&aE`RX#`NUKP_Ng#Xy>eSZ|0}!5Uz!P=QRg0&vh8SvB5xh4|jjgy0)tjb)ltOY%;dG36WV1JDdwY`CzlP$wp575L)T*@&VJLso2b^A7XPx zL&^t)l;m?5n@B0;JhXfOSZZr-g^&+|3aqR4^M>M(Y8skJSRk+$v-z9-&e6UNnne5b zTtrL5Fdbz~a4^RCU^K{b%7++%I#h;dNh&)Jx}cJ99H(;uL94;>!L>13C)0AV|A9#O zjuCAQc#2mDD`V5nuLmCG7;5*mhSM$$;X2>+crKB+dx_TuHs((ifMqkLgJGhw z)TB9K*f-v!85JXKhFmF4M|z;Lp-heDiVj6i)4?DU%nTGWJT)TeY(yYe%3Vy);TO}n ziGjQFB6}Mi7)e}+fV-KvH=mZ})AH^-KPk>k_ioG$I^$MjqR|mgC$U}U@o7UJ0$i zR!oPqmk?67+1;4E1e8*$gWF38n*?Yq31Fb;QVHf4fZf9l8=VvjjE27%k~8U%gldXm zWzS4*Ku!%KEd^1D5SVV&yv8z=J@Vz=orp5o)6tV}%YJD(ni)uy(FEtsjmh)kEeOcJ zGy~jdw;}p%NBa@+u?gCz`yDLHxpntIZ&qD({usF~wXSv1C?S##h>BtO^Oz5Yd7LH; zUk&mRSg4dJ>I{zF;w^9*A4c)LPqw+OyC|&sN?@mp>FEqxEVy0^z~$CkOfHOvT^gam zAiykXi3Q5*L9P_zFe;aM4unR_@wnr4ys};X61=s!fNc}ZZj507d~%M-co$4X%Tt69 z*q?eljz=b2Ea$c#lGkyDC79a2p4D;2HArw=eT6-z0aBF6fi!4nW@59-7fB?} z*WEaME`2%QoC<~^`@0u3zNy~2`Za!dp)b~Nx2qc4B0SIL(euBNe>@+lWL*Ok8DF|; zC&ch4SmN}e6*ZFkjrr0l|e6A@$ z#$=qNboDQPvTW7!eSHZN^9tu%3v&g9@r`|Ado);x{~=@DKd9Kxx})Gbo5;fr;(yvd8@5nUGJN052uNO=AW2g85uZx2p8;n7447-6-0Vv2n z`CxyNO|e8e?rQ-mIC9U!!{LRzK~0BnGXrM^+2S^;G686;CsG>h9CQrn*?r$+fDQ_- z?BEOQ`nH7&@13N2&@#4HMdMFX&G2|@hp21-pi&P8a`C#DZQ5e#XIhY-5tg!H3w+Fm z1WW}<6d!zlyYUPkN=fCzW+Z&NN$%_A504=ZoR|j$_)=Ih*qJ6?xtTvB(G6Sxl%jOl z&f^@LU~)-KJN}@@PA(reh5#BfJcwll?h69o5TBw58CG$~FRPg=ue<3Rg2eQ&=4Fr@ zmDe{ONQMN!N>DQB!S<$Y=u1*kvWLd+O*1R2wyOw@o#R2nsCs^l!50?Smm9o*cGcPVfP6?x!UrSzJZ?M|YK91junoMc zK^5FBci`({L3&}j(yI@>uXxi|tGJE3Q4Co#g6TCJ|Kh$<=PCJ|>5v&_xRys8%B!Mo zZ~A4G{Y@w6n#O}#{#w+XkM92V%X7~*?%Vi7Q>Pc(59W3ALv$54(Y6%TXOs&DiX#Y; zT`PsmvV=4*BB582luZ=pkhn)nP@Kpo7*X6t{WBCS-&|S<0;Ps$#_jZV!yvi2r?_AE z>LNvX88Qq3EmHmvzynI>Z5|5<%NTC6AK-kuDa&ooe>lFr-xWN(*wsjDE(VK_`5jV! z01x*_k$4Jk%*{%O`TW=}_VzVhR-j*e2@R$ZD$lY=KyUC9A4>H+>|W#*95Ag^BdMf2Gg zky|D3a8k6mbpNuf@Nbz*HkbIVm&o*h+deP5%DLy~YnR0(;NJgxxv3jm*}C^N3-Ns(^cK1aX*u^R?Zx+aG)*O7is4@?c!n)aFP=R+{G`9I+<%5EQT`k7?`I&e zshbyGBkz3p3_tBfyoj#j<;FFi?Lb~cds30N65a!9N(LtOrNzn~hU6qy+-6$&7xyE$ z7N0=isJxu9M8rw%Tmqq=NIC2HGiDf=I0pWkRdW@-)_IF75;MA^Av{{^?oFXre`8a-J&{JJ*Bk zcEW!c?}$LR4s*euha>8m(B7i~3Z3lXJ!?l_r#v%oxdz`6iO& zB4Z|tQ{t3KvY)PR!Y4npb2`BM0N&{?=2M#WyGT!E*E^I@!!G7hNTv?6*$->ji?Vf> z^_G9_j#-1|BjX?dIHfl~;q(T*poZ6Qu;~9`ykSN z=|DqIUh$tY!O2r@awaXITIGo!w{fm+t92)LcXD?pclVQWch(4ca3tZwy1PwTw8yhO z{N$A8ozlE}el59L&1C{2h?AahD=CK7UT87G%<%85>{4_%+0b1wMd*4LLsSu|HJ z>s%j-`XSnuNdD8Ntk!L@^1Mj@e(Mwd9CbbTtobDt{^xUJ#+BZ|RtZ9S2D*1TttlR<iHDP*#d}y~Y(pV#RuiMF4UzlRn;DU|l~~Ux%xZkN|JqgYl&#KVAS*dmP-B4OOwdGqVS$AHx((6GpIp ze24&!m43DgCc+dAZ+3QLh-HAH|w3%E51)C5`tMZs~a ztu_~nG(&yHEA22U3oP0PFkMuId5_OSAedTgnTP^4TQTCJj>o{`)%h*%!mr(Vl8f*r zF!<+jO>nDqdjpAy2Zp)6Tqbe)CcFjQQw}_)1*;;TqGX z$equDn`*g1!1HtD@>pEC3Uh+EQ)3UaQ$L%yTH2O z+b0(5O|mS~c#>DBnS9Szh&wEcx~kd*crrhnu7)qPo4WMt9L^6Wyhn7{d2zX^Cu%A8 zxzwnZLOz$H#{S+{>zin`n^5PK+DJyr?ls+-ua{lfHR@ zty7!{E)SRc{K8fZ_~mx(X`&caaY6Z!#V%Z;NUIGPgczKgL+_?Uzi1H(DTPv;A3%1C ziAPouCt4%{YfXH)m;Lo8u)h28cOR68B3Vx4Zq;-jX+QAx{-_2=Pz3kPDu`6X5xnLN zt{eKrykuFW!E1PcgsY?DgG3ad?6Fk~~SvPLp>Z>8x~mHI8{ zW>@8Cw`w#V2MjIykT-rIc<4_%+&R*4q_Bdo$K10)3?Gj0N^qy<1O8$kNGoAD57-n30oHRL zO|%z*@&pTrLQsG<$F{pZg}n%^B;jQAlB$_=$%T-7h)b*UDIpYEJ&xh>IEB_7gwf1d z9>oI802TO?GH3M-Fu0CCnmMZzA_$qD%(55FrY5uO<@%!KQ!{7vvJKOdF=urmC87GM znX`H%ALLKSoYjjJxWyXIoMq-(GE0ZqZzg2ULOf219<1QIlNrjK<78W`a$?oaNyx8!p*CJ#!Yq$_H!h zSJNgumWN_`I2sP;9P=;`4!_rA4k2aofSM2U<4#5;GB}fUn2MrJVFQu zQ|dlP%00~pr$_QQc&zKm>Bl@0*TbY>lhBWOBuR$DfN40GJd~2d!S0l7MIKO_;S6F* zx-$>ht=NAMx--C2gqUH|F$LWj0#PQ132`SPsqIHD)}M6^;O-2l@O*WU}%t0<9jjeG@QQc^v+L&z*)f z4WYP_hYXpYSTTVVQG|q&`W0ATg@t_j{z}9I1t}o!9($V$S|x??$2*_&bSC0RqYf8-PNy?} zvcBhZI`dOLoq6Byo=#`}+)rozbDhDob3v4}Ej_b1(EG|9M*jWa1>mc`7On9AH@30iGwZWA46$~g1^nM|ok zl2|ftD}`~AgT8~qh?@8sRVT#;jOz!3w*mv>`Ov2s8HU?FgmA1Z^r!yJ=bxV)91{Ia z(fVNNvS=|-dKUE;az|rIYB(+efxxY-h|MLQOzDMnhdDC2d(a9d$NX`|Zl)hghn@!RM`VlHR zSSe^5yf8>D@bJL127XV$P4B7vgZ+7azeCTXU;H9EDgn|Hcn&ib|InRh{{Hvq9D3;woPmU^O>!{@ zEckWqI}q4@Lp8@kQy*JV-lMON(jk=8i90{*Wud6EyMlrgi_R4XV}MtccZyY%>2i~~ zuk#3UwYq>@dR-NevBO7TyHkiZ;O|AeY_^TZ%w(j`0DM7-uf+5e+V@|(zFf@VVKCm~ zu5VyFm9~t7NA5)y`Bvhq^RxKuj_JSm8-8()EcO}JC}?ZvnzMfkZim6s zVF&H#)$?pu4PlZ_Q;q@nBf+NYrDk229i)+${Ve_%>S8VTsp4LIc!|}bZl~@{WG-Ww zQ!}1HlqeFF&MagrGpe+3I>nk&YTk8fY%?_BAkqeoqqm14qxi8$feT5nzQ#w?rpcy; zRzy>6KDuTpVHloX`e>SUFrpuOM66I$J(-q0fq6HK5{c%0E4$kT@gsQl5k}B3Nn~(N z^bVaG7{5=Z|5&|GJ(<^gVxMgcm43JJQyEQMy`iD=@x8R^&?J2#V_~VH{C&sbf&A=f z>V(x|XH{KXdRnx1qy#mjIjx&_Uz>00<;cv5&4sq$4*6c@bR;)=K8;^^KDv?CLUs(U z@(WyI#YS${QB|+6;?iG`by)3ctZ+$vQD|JH^jFtfcTe3QUsV3qjm?!M@UryFHL;i1vFXoy2{(PaSe?=vdY;N4sQDBWmaJC znzLkCC1)Kn6K8wPI_tmCeRlTy{wvUiZ(W?N++*8iqeEr>5zyEbtm?ioKU3`PuFy4G|<_nU1-i;Di z!@6k4t3785FSmW(AEW+Ve-1ZTECzCfD3#1q0O#{4HY})Ut%q^Q> zS=yf$=?XH3cT`inN%+k)vb+WSX0u#=>;4LFVFEQV1Z(SmyE`KQ;k9Ql3l}W$zWi&< z7K`@gKdvWHYGZLJ`!!gEO*6rGT7mU_{;*l%$={?{LPI}ay1EV4A#3zh!HS?-b^}?v zHfpL(or2rzt@n7Tcpw8*sdj5{{n55a{lQU>_m-?U1n&A)GOk0_psWR+kBY-ut&~+5 zxc$bpK1A+W4Io`Eu`+jr=hR2ZmUGI5p_rPy+6wE1|37nA+uX)+q(9$pxG!@w6H=BI znAv$}OeeBzN2;a-j~$;j5>Qj|LFK^oqR1EVw}r;xS5XsWe#H;ZP2x5V|N0 z9}I32cV8!-(2Pqg;jvA;!E7=b_b$5#M9-Gc^}l%JdLw9V%W?>oaA5nn5vE7`QkZg3 zP(DOFT27Us#h`CO1+^5&qAtGGJR*SYFBftln;yub)I$aVZi#m=LpvB!UJm=3_xcum zb~P-DUKu8v?og(*M)U>-{G$>genh?D4b>63j$$Yq1rs2z++UDu_D%$S!(cC9+l8ZW zB7&j@Agz@-a>({nTv88%5QW=CW; zorhTgPtkC?pa^DVdW4veaB`9gCm^;wsp_@M##l*Z?(0B0q-HcR;fd9*cv!aBm{~Cy zm)RM(Qqc?yR+uMbSk8*mY*3!Vb|qnPtD~o=HY<7%#=(3JVOAqVRJnE=(_LDh<7K$J zFO3_wa_yH084VK%sEjLcxx9%_K+<^e5aEPq%2gIo?7(%7n!i<^HH3!1_B1zEXAPn3 ze24ADSwqOW(n2@aW(^>1n6+CMI?8xAR)k?cV9~a! z3or1mE#!Bw2k04SoNv6GM!GAIaaEIp?&;*LY_62tBi}Z3LfF%Xlfk8&Oq8Y0tTZ$$ zaw&UojEbRQcLm;ujKCruW@otj0Ewh#bklaa6~O*Z`#w}oh(#F&_FtCZmOF?m%u&(Y zt&z#_zK)*+cL3#B>X>KPk{X+TFN9hxYA_ED%fdIU3HHFgr#ZL6J!73#WALEA$6Q)+ zaMr752xo>ph@*A+$Z%%dFO{DQ(x3Lm#c7AUJ_Wtlq}nwfx-Wza1SeK51p0ubSN(y+ zL$h}Vq3j;!lcBoD>>l!ZvdqIMuMsd(7UdSn%L4o%Edb!mP2IWOLnw;ak{{fz>lKgY-1IHfX6Po>8^u}PpV9Vh& z4#-8-^P8*DBN1}~njn5+tyX+(M>Jw*XVD99t&)-QDN1v(+_7I)HQtH&`GY-BtR`5cbs@TvSE5zLB3ht%yO`UW7QVwEWV(z=FEQu};0UJO|J@cd`Z^gcdFqS5$4X zO&+vG&`D_UUDrIm?!@<3xpC!bQ}~$*8#1t#jrW_u-6#7xfmK1?^`?E?6UAgS@|}Ex z*>Praaia_7(R1&L4$44+9daeP+0eQofRP+ymmsvI(W`h)fc8xeKNd{rAw8OwLqy=BrK!9A+g-d)2^=n7;z&GbUr#4z_Z5W# zQ^64pPd+7Y{N>;92eSCzZKr}{pOX|5AgJ9OR4lGuvQbsfoS)z#_Y`~;)z-a8Cn2V( z*l0VZq=jZM*b=oW5lY`bk4m#nyq(}nWMOxqo%8m9ItPshVUmL7c|A(i+d?dGY_o~cN zhIyY?FpXOQrxBbaUf#u^8XuMkTq1u zUBH`=v@6V+4Bmtk6;Fdk!E|kFu3~tPi1$a9dnyu<16%}WYT3AbBmGw2_7EPD=Sv>d zki2OGd`Yq#`DJdc@$tXOkBdxzgEus@KRkpV4lm2z^w%?pj81?3P?qn1 z9rP|A)PIKm(t9`APqZ-<_ci#N(R$IosmAl4$?-9GCB=(ZsW|C1PKyOy&cZS4>>nN; z?jJ%}2rkh@wtrZ|vL4i3KQ-j-p_`gd-#&mJJ8vI!KJC6`Z}aW}`RudLTGRP}Jp4ob zpsOhUlZy;&ma-4Jx~s6{^yi48wKvXCb==;jtn=F=QXJnjZI4Lx(0*Ed zddTRf)n~`WgNK&-Uz^`|FoO_hxAk@Gk+#Nn)7vmNtEEXj5JO!WQNuazOy(4;qeguGPOG8l=GHt@N!}sKj1Z9iduM4SD$Z?cKHV&aIrIiwW3!9#ysYC z)83#wPR)&3i7AfvuUCPyM&NRcUgb^EsPuZ3quNh@X#!-qwmZ?QKw4UwQm+EV=0T@9 z!bc>6l;w`p>WIZq19O_@Hr8@&b*Uos2yzeIx(~BagYkW>x>f z6f4T$?5=(}%J%m0-`$SuHM93yTZw%2{BKcWSRt_g`bW?pR%k3_6Qw~c_9xzn2CMuX0IGnOv!MCM7(@^pY^T_z~0p$J25+$BQxlVCw33WLb^NHeW$t`0Zfxt* zrgJ9tsr=R9Sn&nYAxB&S#p;!aj3)>+xr`jH%6}jBwp1@FsWoTn<>S{+BUu`*62-eh z8Kpl;ybGM2PZgt8g|bxXU1(LHoO^wpRI4IJis_wbRUoaoOsQ3YqNVwYqOg#Z$;8rJop=6uOW2ctf_C^%TnFm=yjRF#L(N_TxwxHlrSj|xfH(q@6HZ6 zFkn8q!%6=B<;hD}zh|#u1z-LjOwIGh$S<@c4^rnk0a{G34nOJg9xgrDuM~% z<8nm&F|1uK`g$ks`S|^K@?o5f@I5hmHx8RC{*flMJfat(Ix%(MvRD7Zww z*mGQzM?|Kwbu|-2-E^=+iEXvyz4P}KGp?c~x#=V~rw_sqKONf-8#x>z7<~DcqQl5x zDX0NYr$21|l4rSm5Oyw|-56&aZcq7KJpzpENuN>&*4K=d(^b)R{s08ewMl zeh<`ec25zZ&WxvFn)}|A&P+qOsU(-onQ6%CTqJj98qzi${LWLfET=niW*P>ZCWu%Z zE$qm$v^{fI{igi6s%JhA)yD_B@-|FI3r-bXx;u*lx(ZS73VAl!A$N-}6v5xeMrZU^ zDtzo~_}o&7M+jsQSR49%h#18+4+Opfi*L04z6^7&Jas4C>cMuMZiGhG=r+^U6YZCe zUw?JHf9R+B;WEHo_!#b|{-iGPb`BS1Tq64I$HFz3aX_yl^7b&N4!Pt0@9l#OQE_bT zw~Q}--FE=Fx4e6P@E~#=Z3xTL$FH7-kK+`~5vZPcnKYMIpT8H22GY3?_HoMhQC^S* zvy4erusr31u@eP6WyC7nyWs`Sl8J~X+=fPOlS5+~%95si!u4boWo8$K7fI(_B{aN9 zk++|8QX5`mAn_GHf#KB#3$6eDg{MjX!kl4(3wk~eyE*f`3~Q^ktz2S`r=`+u@J&i> zwc*q0G~mhU2A#IzckItsLcIojUF)qRdd=pH-9MJO;Vf9B!v-{5`=0cA4Jd1o7BS<= z=9H(e!`_h2MY1~V4Mo9r>97FUJ^JBbhFlOPRFyaUY$Jo4#L-0C(mA-y!`IP>lbjw8$}MxT!4b_$Qx*Z%{ zRaCPRw^dJ?8LZ0sq?n?lC)MO+QFSJXSk&vY*JokJCldNcOg) z-WdDC+s;7}x$IgNDW)jQi_>It_v^1NPc}|Q?z$6S&H1~yIucUSP_dET z6ziseV>F*Se8RY#xz8Qq*9DgjRcylA#(3@`x2)xn-H~sEVgTNfY2I?u7T9G6^BDEU z?>fKCN7Z?8+MBu36Q7KGAi#$%DD|>;*3E^2fH<3FZijSdlk=+R&Z@H#V(m^&tMLU0 zQoW14O$0erE^XU|3pbD&pKnqaNR-NOc714$HJ(qt{r;QR&t87>)&D+w{?}tyBR`$N z0q&YHmc~LDPK8TNyLSr#oF&BT-K zo(=0M8%EQ1NNmUoo<8fMA+2P}n>nOt@*TQ{z`#6#}K|hw_vSBaB42)d#hy7l`!~o0w40I`a+^FCQ#|G+HLum~+ zvznO*vI09hpHAx85lM{tLh2IVZC9?LTigh8E<@>?Z~pe}0>liHkat)o52R^1Y`&ab zu{u}Vg+~yV2q6uwEZGpzxwG6@s{DxjyHN<;J6uyQzjM1j(>^i(7JnE%CO9F^ro1oV zIJ(koKEBENf)$nxsgD;>q!(!l-Y|Kj!Q2qH{y_;Hifl5@ z+|JIIorLW)jqZ6}FSRqPj4tPw;e-(`tr* z@Q<$^CGAKfDZ_HV-<3rP3TZ$Dvw~BBNkojX98*=mYvUdBhX;>{01xilFY0bx26>~@4p#UKo>YInSK%Ei8=-nSX|pi z0gNumejaY@E;FD*KG228s4>G#6l|FF=}>1yp_J%p+c#EukEJvF1!T)!lI+RddO2d9 zdMuGYIdiRKeNFe?8JUlN2~00F&W-GzxlYN78Wi7{Yw7 zkal@l{}G-A3dxmqb*I-{OeYdS64(WMG|Gl$Z#JL0rY8|s8x21tR~OE=;1qhf;kZG3 zxw+I=8G3uskajUosLgI@aFZNY)KHe1C$-rPS)$Wh9BZ>>-T`F}d(@pacJ|+IY<||X=lL2=*eM8I)Ott)$2W>rnVs z1UsYIR(7+Y@c@boU=$V^drsVC7;feS=Ap>Wdc|~-`(ZK4Be-d^bsEpgv*L70F7i&` zL!f6Pn6G-giA`@@&n`!0)|x^c!A!~&KbSUpnjT{(@SK>Izg8j z4L27_ZRS?hC%#iqZ*+1#8C?#sKm7$l%wl$xM;SOOS5Xt*$TSj^Ne&;$9fwhd~3$-t~z3(Qu^3He3s29x_f6B~EKY+6@t@)(?~2tF=BcgqeR_0CU5<d&hYHZZ*quv%dk*Pff@+oung%de4&V500+MO&BCL3x5rrxzC31T$0AA)0QOKbSl0 zlk28WhVMQsb+~o6&rYk_AyzU32Kvz{8^OkLQ~BW<=0W&pnC_~O#>kr7_45_G$vsgSLky{lht8%gee z=PgiMmD6rwQ1gkI;gzb^XXkcZbyw$FdoNWvU$`JJkgz5R1^{g27g z_7kvL)bS_erXlg}&JApC>r5uuWteA8j@pAwq+3;Rn%CJ3L7_nddxgaFRTMy;#lRWDxE#XPQKKdK&? zDPDLo6N(zT^4)#?`S#>*9bF<<5q;Ai~3)f>KUtA70 z8=a*yLPUEe_6Oj)mhPwB9dqAA?|jpnYYi57XVWWp;Nr{Y!q8m;3l|WAxlvEH4)<)D zW0-USV$uK?6r3?OP7i%^^))!~&y}lu#s9lV^Z0U6fNe>_(k>wDvuPMFs{C18)_E2u zb@eP;;c-%!FYw$j##1Ic&+CiSE<w*SDqNLMpE!HJxdc4=!jcL$%$D{SXBS;mVzsU~TWv^?Z5m-vUm zusN0k9~+!C=W-Wcp}@&kXL~J?nf@Qy;DoqJxDMw@9updQ%G#biG`-B(*O!{AiF)2qCDT)07LF znIuZtsD$6#@N%iiWSyD}!F88nNDblED9a3qnN;aM|6;O9AqL{VMKQFIol>yd(&{9N zr}P$o=TV1X>thaMX&R}_7(&u0PH%HtJJul>u4#olF~-n1&Ww(DBJFLS>#S)IrkkA& zCb^9=YdOypH6p&nwMjvT;MtH0rHHJySP_v?I_0L}xH-%IsjKVbggexSUytkaBGlJQ z8#Jr7;|tgJ_2d*=oZ{2-h7iI@lW@VArHZTQkT>KkQ8A0-*a|~reDa3)<`;E`djdSR zKin1I(cHx@+eh&io@H-`A-QCF|4{rf9L8#q*SGxyN?o1fZ_dH+oie3UrgXqJWHBU3 z=~VM6OA?LUc=vsSnX)vQ##u^r2A^yEtM?73Olgb%POLfv`yGONfypUPJLPG&-LArx z24BMMPVt0pvZ~@sd)xckPcgQ$tIoHc`OGOB2zheKzvxWON*;4#Yzp6|o)US;8?)G` zsT5htX_j!@+Z@6huhsSBkxw4^R1ra}Oet7nEv-E{;gb_SIpLENekx8lVnTlz zC;Y|I-_thq-_bU-j?6Sk6(b3@i7^Kg;fzT_vIK0gf-g4JCm($B!B52ppFC8nhvFkb zl;(R)SAL%_^6JW_-a{d&pMrIB@4u`T#j?)l4v6cYZ*2Bo13!STt8Y9K7m?kXM}bh3 znur~7j@w~wch%_j6@bpuNtuzFb;DC{-dD^uk#=JZj8B=nk`qd6yiDzqy!B|o-675K zcvi$|HL0vkCur@1(P$JD@4NFd5q3>E%qzK0z<=6LIs9t^UlKUK1aB}dYyYpl%ylHl zoy?u{odIj`-9KB1TflrSCinvwI^G8B&0P^}D{z>*U0edID;WH;SfI_E;KbVjm})#) zaZKHnwhhMx#&QUiH$gR7q>YEnV)Q%&MUI&sdbKTo6C zL7sK--Y#&QzP18vRMmHcnnoI%3?E+0MKuR5`P{;smbSu9Xnol!!R}-?hoCH9&ah8O z1~%EnoSt#{p|=77YlW9DyT7jqTk8VI1hS4noDmqUWeob3{x%@4Q?vhD4!K0>|2nsE zwQBwjPNl}o0x@@g8)S{=K^HA)10fK<3Q@euTCkts(VsYO&&#cQ$t75jo85oZdBWH4DXG@R^o~V;6+;%Riye`UX zP?~EnSiPl+IKqP8mMVi>d9sE&8mC3#Hu_uu0sdtlPU14&7LmU#`kQ1U1Zo5_S?4 zvbhWw*?wx^utDk=thX(DO2ui!ZrgzSq{6>{fXKy52A^Pjx%KGBE4$eI)NkeVmJ#vQ zkCL%DGI6rpJbD%5`g-N7uJFWcGRGsPvaf`KM!f4RW_gKm)0RwIZ#wCE3KvDN#&mD< z!0)vk*kl`K&jQ#oL83jjZda>>Q7_iJiUY-ph;Kpnx7TnlCMkf{BnCff(UC@?*`4Xh zQOW3LL$BF#0#C3V&o@DSPe!<%M(6=X)qC8xbGvENS&{VJM5BfpMacn1+ewl0F(o*^ zyswG?J0rU%#?&xEHyPb_cn^``7vEe$=|rF81^h*HyFus6B5}1?)uz$cW^br78&2GJ z^9a=k5GLF%GC0sg#Pzms@Jiat72LY-^f$qugdiv$EL-e4e0?M+(66zg;B?^qD?9K$ z6`lRkkMxHvc{x;Q^;GJNb_&mes}-2bt{YH{7^O+~cmv+oZaZ<9Lk}d!Fxu+zzF>#K zpC@qWfx;;wx(|*@s`jvoci`Q8G|>k?&a*KdlVKk}Zihi<*f?aOh|`;)VdFe$CTS1Y zI8Q?mHaKjYr!38|P_GSHZP@UzagH=?Abtmdjq?;8>c{7Sjq?nIW`n}Ud8!e%8SV`m z2ec3`UbME_9u79nvy-H?0rnuUah?^_zS>#Zb9|fh(^Hc2xen8kb z$4^n&{*m>BbMs7OySW$!&dsyZ?RYW+z~DJPnzcu{{o$ZI8`=Zp%o5`XW=zd(LA+XLIH^$oaZx9eN&qJ#hn#5IA(r`|BM~F2y724ese1Z^bUOHo1-By2m zKgY)Kc6``D@cobz=-n}e>?h!yQy+{k+3!MS5l^^CM3yCLN|S>iasr*usNJOH0`6a%;(+{9u`6ZB(M0dZRf$!g3 z`g&sJmmpk3MEBkc5r;px1@anF+u-M73U0{8i`8<}k)Yy?alB{yA_heauJSr4l4Mo7 zkuTh)5Z=j(Au@PxuY*6hAjOr%g;PNjLo;Hel_|k*4zGIYDIrQkRHX?csn*0~6cRG6 z%@iz9VtEp0M5UHw&jN5iK^1>Mok@^==RpXjqakNS6D=bRQQlvYVh*2H`qxcu=q1ch zbKkftaZ07ZgX1ky0itbm-l@@4L`K0U^A_xVQO0FHyABEJy&2{PECN!-Jr;l|r$ZN;Gk))9#u7Wtg2=eB&&Jsx)VR7GK6=?W{VZ$Fn z$24`EM;&A7&T=ZnFd6&7&I|V`y?OI}(8wI@T$Jw20eD3B&%~@q+(cmX4WpW2$^ou3 zbWzVTz#5NtqpG@k;i+_$hEbxian)gIs@2pd0RgHj7!?o#mIIOxtY^Wp?j-o?7_rZ8 z%Y7F7<~PAX3Xr?Ndyqo-mv-Ly+uwrmS6zq9jZ)~KH%t8e+qbX%)>d$3@!oD%%7Rde zhNIuoUe)#T-3b3%y&Jzfe}^w$t)}lrKo$Nua+3BGlqrqZ4jyaOj~LQ}6ap!Ea^v%c z7Lq#OCDcnxA|jdD){=aY%vPy=l?9MvmAS2gWsyU64!;6Znt+RkpXc$cT1$?pLT3ve zQ4;YxSLmS=|L+^2-;Llsu%%tEPiVY%jzx(L#XMURJT3viw?^2}qlX;e@&}Q_Jyn<1 zR_FiJQzps}&yCm3^G&aVA;u35L_e6 zrE*G*fq2}gP;eWE5S3@42DLLhg&RdhN~WlbRGkIlnW^JwnxnH|R#l*;GS&9w$Y0nDqbs&;RaG=K^nj3Qiy-e1ZG2i#qUQUhf@_gEKbb zoR8?){VWdBkBHV7a&F)0M+UFv@udx3m#_xNXp_AkPu~cX|N8=a_Pdh3Rc(XkmoS*_ zB$Wb^e_qc4K1BqTBgsftnh+Y{rY~;V<91F9X-|EPzq|9(5%7EdpR3ZsmwV}!;t=izGZLKII}Rv@m1&3Sus7|9pw3rbmz^3?wMOphCkRio!iko?K}d-He)sC__dov5w;2gu|&YMoj(HxMhA&CmCS!^3SfIvRXCf^YkVUfL{3KV(|Umw4*h9yM+-7|tQhJ| zEkbYb(se|h4?@sw+P%!HWaTxbFG21?zE@SXvi)f}K&~4-fuO&$Szz4YLj18v?X2oa zO1O);uYFVE-lG&!J8DCaGH4B6uV%9!?0>)l^w$sRmh*?rxi&=A4$P`IU8l%zFni1E z>woz$gf9E?gg0OWR#pGGjNFI&=hY0HLp;rAAm;O#E$hC@6RDw$KhskBNh~4vuZmTf zfTwG0b$6mJx_@2TCAj@y-JE@|FyTnT?of+u5ymZ^5|lK@lAXAZ%$!7op=0^Z)-Ez` zkOb1=;{w}52UxP*jkMH4E0p@>npQ)5tI*?Y9Y?r|xMEd*`yo0DzJBxl^D&kS#R;=Q z5|YMK3qgaikz!a`Br_?5O04E-`b{_KG(fU4ohhoibEUF4`ssX&TIB1~b}SKuLw4 z0NgEJmc?ahtG){)Bd(MY3D=`dM5C z@Kc=ZY;!b`-gjH2$X%m6M_=>xo->lYDTU3e;OzWI@B`IDkLWucW}pm4xWkN_qpxCF zyR)KcX+Pdk!+k6hsf^oPzg5C5MxD+cfM7(qmcuAj#6hiGB(}W9MYuKZcy8I8c3~Od zp;htcXi;RT%}gB9G`68oaTaPz(@wSbgws`&M5lH5u z($7}Ae!M`+x%PWLfrr(wANA386j)Qm{C*sy4z4~G<|=8n`hp^REZd<4r90ceuMoBw z!MN8#-7Icr7U)3kT^q!Uew4Y#+bGUvU*!vk`G2gO$(YAgjRnb|lQ?R3oftkL_mAAq zNiZ$rMRGL?-d=%mZ!V`bKXfm2mdBqvczHfzt6Kkg;y3iw9CGfj@D>(&EIuN@zS!Jy zum|MpCyI8b0$vng4~Y!&#w{06%IjiP1`GS~Mk#VzJqtcw<-pbnUagi0+Euv=mO&FD zJ?kuu&n&$(2?)_KHq?|6cjo0Nb*4dSw2|f?4%ri2h*6^$zx#jouD!W!Bgub|`v<3J zSCTD&m;o>kZI~^`an`PKY?tk9-JPAtARZFdB*+k?Y-R0y_UoPjNsy9AaWIr~S5BoY ziI4sbdS?1D(+@ov-{Ne1XAY?3SY>Dc!QzioC3of< z0T$N*F1~|f=Kw7G@;$s(8XaD$Q3NWzOpoFq%`US3aD?{|2e_Xy=U-FpY2Lx~ zS9raNCq7FVA{Yb~rx!(%EwA8bN)n>axMw^XFnQF?aIIQkE>#CrCDIzk0|=V!6o3v% zGai<_joGY2yc{$X>S(ZQE`)dBx(HSN){22Fuu|8O7o?YjI`P&;1B=8ti^n}&8OgBU z*PU&R(I~%#!e5EFJ4-wqi(}|dh#(Z|&d&n70u26dvg=jtG)_mzuXAIvRPD-J=Phmz z5GsAS9pksnU)*+7FeY*f+x-7R9^;meF1e6=X~u6#W89jsr0R0X(oAok&baM>1q;=I z_MI<+FQ3-96|1=CbDt^kX(y|3YxFuK)Y8hjIlFP|fkjfbUP1OFy|;V}@UNKM=A=SZ z6T9&+Xf^UXOnPaMaOUQum$+dV_1qYI2$v_Yto&Y1B07uwc5bthod-X^e*FC9lmB_} z`pNTWCr%HGo|~Q|b8PWS_$_R%l_{F9p?Pk?_Eut@tgr({nz4fV7z*@7SaD4b$DfBS zBw#AOr!8ArCO7T=K`G(E_A30A4{DaCa|aJyOTV^8I+7AT<4q&|N4(Uvb84x`Qx&SW zVS2zG8I6aNAvjH5eJ#?Z`hFR8$xvM~!*3(td!}E}+9-1uce2>w5wEtpWaeVtCvnu{ zEXk564Sm5y66AzQ-s{oS?}3LUc|t;-`zPZp$H-$;PE|*LyMw1TCXX#Vm>;2hlpn{# z!JRm|h{=5wNaNgd-sr={{1<2T-)$M;xZgSLA74a5+`{^q9rd{VqM9fJL!A+{$CrU1 z`s&sKti$F~^Y z7%Py#N*Jdi6^UEOsFmOxC)MYHrl3L?m~c%W3X36@Dq`H5si}v?N(DxQIWkMhd@B4N z=M0}#YlGaFc66J|t2uAoc@U7U@S1?o|K7n}WO;i9nEZs>>(z$>lueeRFBq%KnOZvO z!+TTeV4b2MTB4=Sh^HfUwL6ZJ_s&7%F%CRqs7Tm2xC2rV-b`s^q;-&HJ*X3><)}CzEn>qo z3CgXUhUaY?vl!B{ocWp)W&zWtpEi+BlgnrZ#a*3M5h% zR^4X(M)Hj=>jn^kTtF1L^X84?{_gBAKz{kX4BqSRfQ6pG_~gV$+~r#&Yff=hvw zMzd0Dpm(!u2_2&O4$PO)bW%CRq{{law>iVHQ>dh@4go)~)sUGw1fCB$OU{SR-oczN z2N*RMFOoadIQN|I{z}`SsuP6;>POH4q#Z?-=~7e7`g)2R^V|Ky`E`~jaqz}>qYy!x?*{`y8^i-PF zt(oMiAA9@%o`2w*npvOs?ve}YjAlOSyl6#Dk}dWMI9t=Ixo2ic)ql6NKP019CD0A? zaTCYg#sqabZ+^wGc0Svl2jctcfOtLU$XieI$_jq~Q?QNvDZ0%SO1#g#~jxY9?Jv(`2E`#5o2_-*KnicyA@I})ar zQ#&-(I!nf~`HqxjXwiHJf*~O`y)~prg&l_=5)ogmr~(c_0){ErvB|{IordNyG}t7^ z7>W_ubPLvkwAg|G)YL$=6j3cVs_y=w3OU9=IFPXV#ZKqA<9z-bSoK=uxTS;LF!Z|z zpbqe@GP5i^Bf*>e(8I*8fdEy`N1Eu7E?&Q3u=5YVc#~a?mxR6bSRsx)^1YEFId|6Qe+x5u0KZ2-Ycg^{zc}j44rHQ(Ek* zHpHxy6BAzK3(EObS&S#b`Ury_vlUZTWk}+2pSkb@;~QxmwPco z`s;U{G`r}O(|*6L!wZPQUpmK1rsKZ8#luw5t~wf3_Ex{bw&v=xe=@#wh(G%kiyf5X zET7`>mcH*)B}!bk!X~_vS%jhP_wKrCSI>@ItMj^DCbp;iq#|7L=1@h1s3eTV)s9mR zF@)r@-P7`GoY;n>Mai^eEWLQ}`f)2nNg9Z4yw@laL|u2^sSu5XR-r9~D3Azu;k_bZ zX8rOBs0MTgg$Xp}D6%O;fuLj!f=Y|8L^En-ZVV7oJHZ-}th2tCq;xIO7JGaqRO@43 zC5Y6z#we75jXGjx| z>;$w(;Hxr|G2RTkqPl1bW`~zq-_bj-QhDlii|sfbC+945UP zWq1~X6H;avG4yMq8kDH;m=oviA@x>9`4@HZR{`B-GbMYVT67 zBB*kb%=Idg;B*Ii6$uMzTdx9D8SF%_B4uvxU`7*^yVR?ACTHdNTzWw$b^Fm3$y&Q8 zm~lDIuuzezTQ?XLed7`x3XIF8o30Ho=DQN0MAR^*F?tN6koRU+UQeDqeEI07N6%hA zczPcc_~$*RW&D`JlyoU}uyNnk!0Y;!uGRoD@^yKU)+fwJ3;X!pu^>ZGW!IStG9)4G zr#C3VoLIi5(^R7!q&H~FoLLlPNN_0=!?qv;0O8vRmjY5UVDg&$iL6utCK>jJ;|c?I z9^!B@H*PdFs!y(|&nxnu_cN}6(LFF;8XWWg@w7yv9)UvFsp6SJCb ziYW1IoTN)H3(iCN_cjl|_Ovhi9B^GyyoQ8Q@j8>hfNBxpja z%qfR>j2)|kk3!d6)USzSXAq~^{1ivMYM$1m2g3a|GP>RtPaeHGefSdG(O0KWo}a#W z`TX^B*xI$&1`*`u&fZOhX!bfDK3$O%lT)=yPiqDiS#_F>aj2)o@N`y+qrDb@hhlUo zFr>(x+qnB@<80)(zc~lLj%N)7$y<-T5%QO}-hqu_@g?Pif!ovM+#c&beNst+t;Wa+ zTOMOWg|1x<-Fkc2(yBO?m4u&u%ZX&%(nJd4|LG@EMAUF1G0)!OEDEv2tTxsWQj?ZE z#t_^7+_j|zpp>);?9?Oz`!PwHaAe)m3bEzzPCQ1W9N4t9kYLDnXlWq;5s`i}6$yI{I;W=KVhH7o+4no~RuAZ^{_(KXgEZXYrszL*_@^ow#aobSA@5k#r`- zAOk1bqkd6dz(9(#mdAoH%&3{<+)5jAymzsn4Pj8aXXbT2H`dHO{pshYub;el`slx& zJo`siRU@~iT>yOPimHu1h(a#--NvXYQ0ceR2S_4hJJ1J6m@(V>0He0)4r19f<MZhA{?5*#Xb?dK zJ-}v*$CoD>$(R;V*HBAQ8S}xCJjQ?>PQzZ_ixU#2TymE3EQ|xeOqtEV_zD-8I-q62 z7IUg97UFzZO<)sSi@yTGfw~J>7=3C^jj4sI8ROHZ|NLoIXA7R5dc>m!BtzfxgSxlO z_5G3ShS27;Wt(bvn#Qc&8dhOGasH?UKQ~^XG}Q1bUyr_8VY5 zDebVhy^ti5W2-J%9~ZTx90Gc5B)~FCDZQ++pde_3RD` z>@eJlGVDUF`1-@)`{_uvJ7`UkY9q9YFEUr#5v@svE#e|-5|Q=5>6&nz7j4Vb(Nu}g zteZMDWooVLV@)vJ*?nRSuykSB+SI9u+Nc!`qyhq_9xknixF?>r~W zrM%3N41`eX@JO&gMgp^I=D%azEzkeU(Xm7P$Yah=-&dU~GclzQ6PzyPgKBQPDt-sq z_Xnpy)j6C)es5y*A$3W^jon>A?>X+r{N`2`F&Mfr*mW+IF?Yk9ArB>hga)=LO!!aC~8qDIwJ zqaA2eO<4riG^!#f{hdV`)&SJ%IbluIM)~J3@CdFgeSbm*|M5oBLEQo5N7z*7xR*d! z7RfNp+yXn+I2WK9JZj`PDm2dNS~L*Ri3?=ML#nZe9~gtp?yqkG8)SpzeC%9M&+P6C zQ3SSrUyJhF6x)=jhY3v?Cwx*zcicEllU(R9jtiI|GuK`N@%2DOb*lSw#QoFJuz#6j zHthvQGqnJvb&2YmKVrwNt=F-Z{DAc0uF(&Mt4T=p}!!A5Qj#x~4X4OTC5lQSrd zGrL8*2}cD7C+EW=sj-X>W;B#oztCs=`)!(yFAtu)gNyUTJZiiu2-_ee zl72io?PnkL0~w16GRqsg2`2?>&s~T=e6ALgMTA_xE`WPgp}0rjxr||?T)!r;MGJzM zonI;p>Tj4KqinsjQ(ypu%`+8NWV{Y&PtzQlrYsS8|XtP4cp{)PuKdv$fh-4Z{pB1wfDePB+=%T(RxV)G}^p<+scT5 ziuDU&b=@iB$oQF<4Aw96atOKttYmXB&g2FWn}V5KUR)}b6q#FI+}~gkRoH7-#R=#& zZ4MNyH&OaUIdtKX)DxLAj{9u=QcyVHqfRjmp+dBv1sd_!Kh=l0|2{30*VIC(bF!Iv z?<=<)PIO&IWMQkPV(Z}=g09_8;$(xB3@GJLPm;OW&na22*5mV`3+fg-N59vWYYVJs zy^=`sGq=cV57!(;tlzf1Vv_f{(9D_-s3KKoRjz?&Xe$n`ML{&^}IXF zCgZqFhXbwqj6G-`u=VWIPZdum!$CZWHCl`H7i?JN7$Q=9Y`gM8vfk9o<4Um)*F@N2 zN$3JyH{%S;1gzIgA9T<*PZVuHQq!c^M$HW&A^COE?uTMLxvUSqn)fHL7C43sZFlW* zbfUU^Dn`)!W*x+?t>&bzN_700>#D3Mw*)Io@%FTFPnV^| zL^bR7DRCBS2H#PpY~334UzLg$#UI_Yh|kI)2$RGeXC-)bS?b1RRUF6TNs-6Nq&m*0 zcn9PbC0_K!llc%Pm!k~*TO>ayCfzg}xmZl=7>AXD6g);F11}u2K@Z4+hQX8WE~saC zEzNqD-sGHG&37Hw~$zk&M$^gqhFj>ImkZfEDnCn@*Bk<}6N=JczmM#jCirUu6ZgrsXC+ zk7(@kyccuA^EtQYH@xpm^$C^HReSUQtv*>+A78bGnbxS|HvW9dLj+NrFcyg@@6nVb z*C~%UhskES5VS`##(Q6thc~Wmt1qWJRtIqBs$*#Y%6GGZtDSWwnFw>5_D|d85-h0Lou=^H<%xrEZq71d+!x8A?J053bYAJj!`0 zC}&X+@BlsA|7Gvlw%bUKdrrP$j$)g>EWo}^U*N25?QYi6Iks14rFG8HY7Nh&hZqaQ zAQ(VeI-C4h9`Z@~g;aG9K#&9oOwUlFZRkOV1Tj_B)irhN>Z+4%KH27zZ9duN$7GxJ zkV#GByC&X)FK;V-uA zJh9#S!s+1lG25<3n;fPF6tmugHhIjGiOaEIm91f%EZ6I!~Vt2E8H*RPCr+^u}hrd{GqCjiU z#`uBF$FP}sH{?de_blAbE&$U6mOig$sHn%&D#>j(oY(=$9kT4ki5Ti}ZDD!nLUcuC z+>}O#3b|Ep@XXMI#o>5k9HTX_$MdSLEqGV&Y-gOV#lQ}n4wLgF$2GWl^*EX3V`qyy zq7=k6*WQ70S-rJ0T%|uh0@YM^U7y6u%lo$P65PNNVvjMngrFLovpdGVmv-^4tKd*d z7~6|^R$l_5^3pc=X3Ngl-0kAv$9-=BM=a0-d>H{TL(o+71SBp0Wkg(eXa7?PvD3o; zcxjV*)%*;0WQma!PSgEmR5Zp0tp*tq6{zlzHrHM)`0;p0Tc?LKI=2f*0*#KQB6ja) z@Z(bGEu%xa7#)CyvwI$vjawo7oj@y`iP|+03x3=A#WU*j)n*5jc?*`DFf%2fEvc_tl=#lznxX@ zXWiWuz$b~`iw}jypcE|F%fLt5=Z-V#3V=u)dY`Aw(G4Dy}ea3 zR1!6dUH_5b$E9eM9u3fwcsb?H6@pL&31S*|S#RubwXW3w)t&SJ^6&bcDaTokZw)ra z0A7r-%P1i6Uex%q%yaPFn)xham#T9ERXWPH=2{*}0)#g%(gOc&O-EPcu^>kx#}*(D zuzIP~kp$Sw`SJ>)ABPqc+H{&G+1sx86_1a^zg|1ey&2~f=s1_M8rwQc<{bkE_$T-+ zI`CD6P$;vQMK25M;@rBLu5kUM5f087PwaFKWWhI<*#wj5Rb66#eb}ul&{N%rGK{;H zfVYV%AnYEmM<0#yDs!1i=SYFNpHHi+aaoVc**d+v9Akt0^%LXyDxH>D(ph*60rO+p zG0pw_4h(NmF3{j3DfI-8hsIk^;u*`L8*Sjp#k+WSA8jlApsJQ*Fs4rS>;k^9TrFlU zvtga&!LFihEy|14q6Z$bn5dS5cTFe6k_Aei{l1yM4MZnkPP#IW$BecmYQ3?ZFi;H1 z#nxBfPwZ^-sn1q*BT;}0d0TNEAh9`@a->{;b&}xb-jGBue;o;ov5(W;gn&y@EkOKd z>r7HF-mU?5$Q_9*pB~(i7wjbufu$X0N4^Eq9a*&k#gl;DvS_%ZXc8b@5h}XjYLiU@ z^*Sr7n``nI z{5Zd6{mxxawW?f8xEG5E;4^SxM1MM>|C>Cb@3~rMS0UMB zDU!NTM;A?2pf`ikO=d_0!3;;3Rd$d2D6WS?4G%3i1&)tXJj#58hXbBA6M8s+k83~V zYyZqT?@@FZRp+rZeX{S4s=EO{bV42AfXLNN>*{erorh}BxeT39=Rh#3NZ%9cJm6;J zX>SwiJk%Q_-Fuo)=OA<1-^^DJkU!kaSC1ejq|XU;9;i!wcb!mYhBTH;-QP32w+VF) z8$ZyYyRQj#9+^Bae6NS8ZVr(k_jgk7JfzNJVfQbm^T0iiMI9Ib)NxpxLlTpt=C^eo z7Uz-iqb93$85Za99GJ1t_iR6h^vFqgoyW#`WQS(+RDF!Y^QaCmvyTaN4w0hwuc`A0 zFNZ}RSWfqWaSn~4M+{o)Hg3=3i4PBr>u21aM`!Nuig&9 z^fa2z;bQEFF?c=9+(Tprt2{oyggS@I9GF1W-^@6V?trNGHV_Z6NkR{d-|KHeokPcW z%&q8gZk)p*4h%o*J~z&zksn~#p2HL$a4Wk`sB=i7I2HggYAP;x-U_AsK(<2f|lvFnI1hlL)PiP+cVE{BXA@VfdKBj^xu{_um( zJfa;Pwt61G=+u%{Y`sx=omZF0qbfnm^k28_A)unV~X$35%e$%&*70fGewVVb{sVaX2+Q!fk=~U zB?D*2d0yO!i66=AI7gj2;2L+HCFrq;1CvvGm{5m^_^x4(AHSK@X+quKWG%|a>%1Yd zgd1s1oNG>m+1cjN#5NIeYzx9nLUVEIym6XDcbY^OVmE}2{Q+^`y&=GV54(1SG%4`b z9=6=lVbME=fS<}O|4~fbsoe7KvE1^uYj7&JJe6DaP;U7*R8|?&nA6byAMSA>d+HV8 zp!uSS;KzLq!5O{BRaObKDIU&eMd3rtiN1i?H~LGJf+e!aY&9Q-!P81fS968#9BvpCF zNUk+81rlRnhayl+x8caN${a&5hJeLQF>(UCKX7i_Ji`#xpV?nLMH7qnm_q^Mk#2uGJy|a2Z-)4P z{buy${0%-lS*34=03LoFI*L0HESPp}Pd>O1v{>3v@_~>%xY2n73qhUV1k^!jn4+<> z#~Yz3XW4X>+vi0D!Q-N|bu_Qa*%JQ*3O57i1wJp6X}u;K1E4@Pj|zmv{YloJHpIU{ zBMKTfLDOs?M4S#1 za3Kc@!hJ&0g2pT!`sD_bBB}RU9DeLQvF|lwK_YtwzNPqwH<4N2d z<~}0!aKgEZk^7UrzO_Fm*z663s)6j;_R$z-RIl>1pIwh+!Bi0uqOw~`NKYz=L-6BT zOg#?fF~N0+XWL3zb|{!fv3O1ymtEtpo$SGuse@`!Ubt~}d%}PP#4<}e^j{mU7Sph0 z2+Fl972Cu{JEEWcX|xV(56t_4v#eb~`RACUMS@rJs9MY?$;@5ywb%gbpFeY@g)e+_ zx0^u!?|=UH7F;^wVi;2;2#EnMCa-79$S-!CS{+4a4ECH4>Dhh2cS3e`wDe{t0|fp2 z5(gN+SODJdI1Xv0cMza9sO~=V*D0iln;gB0nm7C zIA)VCFU#d9iZhC{qE#XDoQYKDmaAN+sU>MDVq$WVWu!<#5DBK_>&QR%1;~-7d_dTM z@>d`=OpgS89Rk?EbJ4$pxZ`epF^c}vBw7kZvjNW;8HilcL84?fP_mGOibPqG|5pH} zSQ_>^divMZ!or();n+W`7RyP735sO8x>uz40F~ge-nz`+9*9yxZi((!bBhU8OtoHt z%v}aR)ZBoSeUq+c%T>tZTBz3%6GI|~BH($$3A{1Cd-byYvy@OKyKcBB41ePXu-hVImhY1Vkhvq-=NkvFP0XOg z_0;F-v`WwYqL?_}qUXH%g8$q3H_caohOcdsU)tei9zbyHE+BZd{B_gI_IP5OUbgl5 zGP&3g{bTm!7TEzbNfvh(Im2$)pWi-z_3WF^e3z5x1-1^TamNF63?9Lu;_?g`Mei5o zGC+zM6B-D7+LT`ZzNyL$hsWJH*3%_A6-h_hxQs!JKgart4p0PT46We|J9T$~BN?c# zFI-FI%Wj}hx&TyJXDh$|9Y9F~R6VcjmF)mUg1LDeUGF~l%K@)3lZt}f`+b$$X&nqi zxmTunqQjb$)B@pj0-{}q&KIlc^lSScE3Av@0867m|9W%S4F0DZlobxqNCl$(5`&s@ zdG&AaI!PsmPl3Ld@Y=5G4m{(;HOu|uY6>bkNy{ma_H=3&%V3e}adZiwtZ`qdx<{35nj!i-nzoWsTOI*taSH+JaOXp_PGb!Vp4?1wqDQqqvBLP?^;{&wtjIE^wmY4HNg%l`pI5EV*)(+JjAH z_iub*cS9jF))t)0+@wqkoscvZ+U6PIg<%P}DpHaxPjzbwg;X)<+9NrKg;l?K7q5v@ zd7N2QfEQa>1HxRGydc~bsp3g$2-mX6vc$F|3LH#Led-Z|*r|mm77MX3`7w%afNBev z!3#%ee5;Y*4#8*-SQAOXU||dXQNYvG<&UB*wvSw?_MoU%v%HxjeWOaIJr1mpK^#k* zYv()KwFknva6syMGQgR98N~C;#VTMcv>>rsg981&7-0%51dgVTcR1;ytZyWtf@zB- z*9g;9{{=&lTsbJU>oGQ13)mp5W_EB1PDwCiyd#T{3%krFgWS$R?4p)|nFFMWZyo3X zLAU}uD}Zw$L}{h{hGNokzNju1whrF{qqSq+vutJI&Lpf%f`H-r+>KRu6WlMQUwxD=l36wxMz1EI(VAON&4=!R_LNA7 z4$Yd9_t<6CzhC$&Jh_Ab?g?J*LDdX82M|}A8g#pZvQQ#oZseK>Knz|qV#PQeWbLY2 zEuxuyf4$|Utv`w0Pf9@ONUm3NoU>HB3wu#x=RXOcLmI``bYy_OwL8Pzpg-Nf2zVNt z&kZO3X_q!Yh7X&q|I9P}Uyjd+9LoS|TyeG;94yE%IcpUqn4t{i@Njp!QxS#ICiX*I|6@fp4!5Noe|43*$9 zawDEuG6VYpf`lB)qjzQEMW#^%AzX;L?7%~8UU*}>8$t({;k&8`HQ^L#il^_MqA$uC zlQ*76u)#H+B4KLaMtMCJz9jj(GP5`3@()gfVUY5qJsp~Gj8ZmgA9l+P#&oJ^%XWjB zXr-%t{Y?Np+#OQvHW|KfB|{&Ga1Cq|s-$i_5kxgWJcO4`c(R%!Qziib zsXIwJ;zCz@&{Fba`Fi{fuGVB|%5N4NZj?A0v~aXYIm zg`83z^AESdAM-j^Ti(PR6fy695vx63CG_4s>2mX`H6_v*wpR;%xMI<^bZbJTI?O@W zt7%_K<94yADpJCP<%3-EVjyfL2L{5ML2hN9Q$mPT>tfO97^#e&e)H05iSk%~U00}Ip!eRuG3J4jeTE7UoNW42M_W9 z8wfX8sOaa>w>|+@d>5_%9lQ8w@T;iPl4I@a{C02cI$trEmTtgc6AE`isF|w(in>8D zl#;zk-i3YF49x0HG#L2E1UBo~eF;FIjp!Ki8?3RxTBz%g-}*sYn=F5GxF2)`2jCgU z`jL(_#-J(pI+Qny5Te{CsoGIOfT3Kyiu4)!ZH8EdW$>wnh5cFaU`XyyJnX5lXQAZZ}wkXh1#xv77ggpS4F| zw2}{|cGQp%4*r$6Rcm%{v0ng+nB&oGK-HOhT2dx_MOZ_~C<>-T5iA{PU&`Vi*1oE& zpHdbFQft;%DGS1bz0`4!r7R96)v@ee%HlvVsXLhug$=H_p|`FhMWe`@`;CZ%m1H>Z zV4d`4yz*YaZn|=N;F6%1-vBR*=-Y3j!C#{vU4#GbyI-Pd2!*dS=NtG@^e=dz@@$nx zc?B*ub|17eZw&stE|KC=y1jCJc`!HpJR& zQbkYBHoQ2)!k*;1mfKPIB>L4)SRg*i**0_R_n@O`AS^6)*}$?>zm;42`00N~e_9tu zpM6UP=DT0O4?n%K$>N7g2pJ|nytnr44|#I+v-;QYm*k@QQOC{Kg5YhG&H6k|zbj{} z@1svY^_?ucqKLI%TPW6>_W$f%TXWk+mi{68f>ktYDG|uNp)cBKrX0tUxXQ7;wllT6 z@dg1wP{No5830tQOgz7R&*=s)q9odAz)BvbDiup2alS@(pIi62tl#^L4i1lwjt-B& zLjsHAGCVx0urb(F;X;mwYiHf__}Q!<3LnV8W2b8z^N z^%p*CCg}aaeZ09L+UTwEPI6B=)NuXLd;pHfvfAc{RR-c;y6$}g1|zv4OYWHcyXH6C zT8`oHhaYj2!}op8dc;vZ@1HIDX5i+h^PFIA$cN@6Kil4ebnyNpNY6erQ71wEh50=H z{D_jz^DocR<1Y+xzc#=3IcJ6uwl!y-9Lfn{JlZoGn^ov*=$ueaH$(SiF%EhQZ>+0} zY&ty+)(*xfsoe3}s^J9wT&6(?Kh1+hzR0lLbMOz+GP~bG8qSrcnanw1QI8SdmLw%c z^=Bq?tV*N$HIq5V%1ynP$sAi$_2lsYEgf|VnoyXxrZgV5?U>x@96OIM(>ibsKh|Ln z*sj6@1azp_GQ9Va_hO}pfY#esNR0%rb?&RP!y_Axi6KWI5#1k$!;@j~{y)KbcH$x| z0xUuAK79a!8$T+t-h^EQGGp7Uv;)LmUI0?8$C$b?ZvKA9FxOvzk*2+mR4B!FMvB>y z=Vda^&U%Ak(qLBeJqo`F{1RLPFX0CdD|Enm6n+siTC)CxU&P%dQJ=yu;*7{1(p?-W z^XmBB|eBbbh7a2iir_385A_X=Sz9BpDF!Rt&7G_m=kYy3wKBm|q=*4YP zaJegfHYCAN-28GH9vtGo!+~v$aPWSc;U7Qyn;$+Hn4H}!w@WFN^5u4TRz#v#ZkJLs zmyOvEw@V4qL-fn-QV}CrFWfE_wRNOAv6xbZ$eS#N;pw&(fU_RJ*DA7XoV5&qMEaWW zEnRmvWv@-IG!=|&p9%i^+R_OxR~Y~C=ixZJ92U#zw1Z2iG-fbtIYH^NoVu$bY>;tg z5B;o#Gp(?Xu)XoE%d3Er)sL7JT$I^liN`wb{>*0Lhhc?nC+k&!muq_*hL$r|hl$li z(;E)^vHfO)2l0B!-u>Iq^37#KP|Pt{;L7toU5l3;XKzbBqFlLVq`Y|e`dd#d#kUTT zJiR=yuN4-byGPNdsdLo4L?1kCc)y}gQx|;Em*~@!Y13z3ucgVYJ=%ll1GblX%MPpyx7x3gzEccPZLp2ec2Npwv7^cWKV;Rk?D^;O~ef+{j#Ttgl&t}1A7|a zq3x;w{jsNsq%FeYCi43i@e5d-C$C|7Ui=Tt!n216)Urs1C97;tiY{Ewq{e3U|7)Xz zz^7=c1z^r-kxc`~K$Xo~Z92NX(=wJKTo!K&aQ?$7UN(j2Mawdo3bT6@#fk|I*%jAq z0I@CG>KQ66`YKtT4ooy4!BGUeT}djl%@;+>f<9`1r*0f;pSdxVtC0UinO)}dWwl0~ zPd1Ka;F_nl#=>#}tSuHwzsam+^%(?1uY$CFF`wNnc*G;TB5eI$VO6&_&4mzzZuV;n zw6r~xHauD{*s2g1qD5QB46VqDXdtzc0>ymm2SI)kX=6P;z={nQfwk`5d_U6!M-vQw zU4K&yXmFGXqK#0aN%Kt}vgo)Se>Fqainf1OML@1MmF#X9Bda`lj{|Z?I%1fee%-#f zPPDbjU@q$E9A^p2ikyhn?^p=oczJDLoZlqR-w zOur^WLs^h6Cy_{mDnt2NGB0geitJrp)h?~gnbCc#b_)#NIG#8+lCxp0jR}uOm-YNp zz#8QtS+U%bZt8J;NU|2LoW@7WR*#V=ftTN{9|V_4vRiKKY`p~6diHJB&S+`K8oqrJ zwnNZMP;%g!Xmcp_^~2YX&km2=bbhgR>sFfF{xq6b8J-{E#1|Px7yX#ps{J;s<}`S7 z#E6Zdu>X5=9AeZFz4lwkSHB+G0Him3xOi~v`2vj6jSBhp;j3@`U*H1N0|<>SgE8#J z6=JhKFf99rL!9J894HI|QxStOrL33BK|2N(36%TysQLg>NuS8x=3%30ud0tT$y_)< zKdKMtDX#lfeH?s(i1(uUfD|YCQ>Z?m9GNa|EfSGR7_)nrb-Kze!K^`W9l(6Ib--qe zeA>QXgM*2)A4Mo_^6C7g4=jhRudyS2vck%SbUvMz6^8UY!XaXA*kal4 zh{uB9d~VC0z2vD=K|hJ`F|Z^ypWF~cMA_ZKwAtlP9>0noy##OVRs7_6 z{Nm;F*Uy1F8~F;7Bb)>Wchny_7!L5Y@-(kwy8+{7&ho5^(-Lo3;(Q*jN;h=l4 zZ%b=kQmtipVGQH?+wtoA~=FNEy zPTIySyV*h?vGQll_U}RA!6>wp+ZuM-an+>iylP79? zTUPRKU^R)h%%7HkKD-Q|k=l|7pRGeS?bZA=Db5GKE~oh-y+~@C4E|Y>0P-(GkhNJd z8!{nDq{2G1%Rj8=i##3H`78r}bHGSmTxM0BpL-vB!2qS$%HHmzsT5uYbkbBf9BuA! zPqAib{N49YUq5;A^znZ`dG^hjEtA}X0>HikjCXZXF&0GPg~j~|ZiF-#?dT)8;bBTm zuTCnG6j4u=HyV0c^;mhs(PY$g4>oN`3HB+@=aF*Lyr>VsjpPjLc-)Vzwy54s2>775 z@PUb>xfVLI7r={GBkUS@f9byt606>N@+v$WDb*{l0!hvK3nKw~l8K&q6=+7PdrnM4 zN|W9rPaS29BJj+YP|+AveC>jMJwQ51 z>}@v_qsb^qsThNoD90)j3D>gyq)KTRUKU)7XV^@F4?TkkZWr^aJ`MT`t^r?|*mhGJ zv&ycV4;>q*{`BeJzFS3W!uzrT9k8~@KyWrWzEyll|#Tfv!}s7 z8|EE;%<<^@%O7Yt=~89)eZynini?xB-e$l$wu+E5?!yt13P*ybhE9lyvS>^qF``m( zqDO=%A*mXtjEuD=W`g}`!EYZKL(`0>$%H6uW$QB{K%wh zPU%+3WfnTs-GP`b&*NiJ;KSF`O*>l@2K&@e(VInq^rWJ_S`=*}NhDt$= z#QcQy&sariK>l=m5^z0;g73bnhE>+mY_VXl>d|T%Ugf`ntokS8Krc92V|~@qsKUz2 zL=tjqKNHYGLHKTRXFYr)z(^WNidECIP$Ux(YQ@H3MoE-P3@)7{PE8N$U$NZuiy(Z| zuKyEO^2O@G^O@}!=W5_4vtU{L+BUsJr~##zTfUydM(vD)YMG`$eX^WRTi$LWO(Ufg zH)z_di$8>_g)uTCgx)O*Yey8=5!qyLYxw!P&kjV3%{k`>$hUW%0fJM-&7GlOS$pr; z(Uig~+`>Jqd5^lxlGH@;4j+`K! zPO{X1QA(Ms=}Bk*_Jd)wraQo2L2g_iOMqm{)A=|Hb8J%)T+#sa!_kK9#i=LS zT|Hj{LR;s|LNKoNJ_r!jX*f&La?ad~P{Ms+tjN9_CMl}=YMO;xZ=*b5u;kG`7#nz( zUd;0}b8pkq^K>B69WdSn^79hgFm z9&$JY<)W1DLa45a;F2MA+Ag))k#}`|?k1mCvFo#tOyk9TdNskmwU-zi0_Lc#++5GLBo?PIWe!VSwKk{^#4t@f@>I!zYwG<)Z^ zZ)-u$Kmyxx8h0aL&jlQR>jN-Lof5rwvF-S+U6hr){qbdQfNf z(DDN>0WW}E;p{s^9!t$Q5qsrGm6hWx#$J#sO{jfP)PAApP*yMFk0XTIJ7?IJ*Iw_3 z7~|xSr11cX2;aN(vuu`Llqert$16Dck=o1Z>20j;v&$siZwwXSLtVlIAfiPZjl$se)C?`w^CFW10vf2ZkFd znbM>>351>nx(bpBSj53Jsp{rXC#eJbgi?~|0fqk)39fwA@(9w-0av?z(IcBynHT51 z%|ZKN3Gj1nFw9GUTMg@{>*o}%*vN2Q31QVFm zSue{h%nLkTP3rX!>Z?Tt+k1rMXL)@#&K4oodN{#hjgvY#!M%@Xx${LGPP5BwddBfr z{Bs7D!E||^!&}yK{3D)boM#2Rd|p>)GMWg}F0ye_z%U=&_@uy=lI7LS z50dHhf$PFG%ExERqDm%cC4#<%Z^DQ3U)EoRwTNd)aTQ~c@#?|NFSvG-5uVVv$uFt3yI;K3wIaK6tDnsk!``w99Q?sNFx5q!TY zs}n4ib$rXm4T+QBGMO&3>gWJpIz2cE)-;dtu}8sbrOw1enslO4g%<>oF!-TuSZq}Z zzV%tCl7yr>ourBAGv{VRP0ygnd&X9)|BP&Z?`~Xg-BMJPqIj z+>%9lb=Ctwh#mgQ!Wt{x=^<+&r7&V5(pef2Eo7v!Y*6B}8#pWfp_dlc(!A6UpSc{BoPx$Lv z!@Va}WidT9RTNu3dwBDKaF(!Qb<98E*LT6r}O+tT(uMJqHUwdpBg($Z6esUE9HH#8&F zy)sQhN}1jhCLLvZt$0_5XoZFxi@LPtMIuTIj)&0hmEubhxvq&`uQ?+atx73h`@EClsC~c$}hUvS0@`J0yZ%Bp$mp%IS z;j?cZKYjkqVAd{D%@L6l?~ETkfA;$2ldryi{p6W19f5f)w${dAI#1vFkg%;G9Ep1F zH6_a4CvHd#u-Us09b1h0>61r~pS^0=WvO}IcI9~a_~F;zJ@%VVO)z-*<>UYQ{)uDI zSDtii_34+-VI&Vg9$hS+M@H!(SlDUCEC7PHKfF}Pw?3O+*(zfHhJ zEmChx8}+?|DG4PiP8N&lRXhSuD_)eCgW?T>qrVkI$5c_ugqzIO=dpF_JfRe(8r}sZ zcobu0b+Fyh>0Kt%xXIXw!OhJlxoZXF2MvbY&E<2@ZYr(sFdzLrOY6#d<0-zk@1!z= z)dp_DQ5w&{(amE_d0zsxPK;o9q;Fk2YIe3pjPbwms096%m2neVot1v5g5q8C4O9*< z2d;ThVRwaw1**87$9NdziKL?2kObvnnuqB)#J3DPG5|J#ydA{td~0mf^S4zx>wxszZ;UMysjz4OFSnC)>3h7%QwW{2f<3|u?$!%$4# zCi;MjEd~nq3Opn{^|IF5YQK4+X>#iuf#xDu#8v`mY58eh&+#PD4@Pr(>vl#MY(qpq zPo-cQPcuAvt59oA=U1Lsn(Rzq2P?xEiYcmiCO5QhD7vI!R-D7X7XuWXJ~YAn*`?`6 z3$!oA=rcr+zlVJ)1Agt=;7_7TtURBF1q#d?9 z(6+6k?xtO4KQD7va=8jYlZMrNy0jdwuNl3hmS+U z)$v2H&0{|TVKHAUr%AawX7seq0Z@y5fZcXOMEC9nI!uaWdR66>yK1V!eC`P(qJ0aq z1q3cs=t}K+B3ZxlV~|!ZR5;E-;nwMeAE2ZdqQ3`lUf>;9xY`+>C<@cc?nF_Ywt=f1 zcqehwiMVEZKXI^kfk7(#w_Fw>BU(q`QyaqvsNT+ZSe42t)wU2pkfh*AKbaz;YjWXUF+*U>Uc<|NC z`oh^>(=Fi8Jhx2k0S1KjiI5v$*fTBrrSp{jKX+H#+_sUVKi_ZIFF3CfO144HI{+oh zu3~RuSKTG9Sl+8!E6IWYC~-}ZEP}M+#NI!)AMPjJU%2ibfCMFy)(mJnbuO39H$2_* z*3;e7)6I8pEnCR?lcRd9cO+M90bxE%2XkDnA;(qXwK}Q3%cXkm>JPAhVZ1Nx|xgSB$=ypt%T?C~kLB(Ciki0-X zXDUFAN~Lb4rdv%%m6#nuQ?%OFxu$}0+rVWzY%O-oGTED@*Ug<DZ&D zJJ7+1F{=%Mxt?J9T_e1S>S%|Mcpp(;6%6d`JN9XlY%JqQGv?jFk=#8icN6t)Yh-q5 z=Nrg+&r~#}?nDW;P_8PD17RS2`lba;>q=zM9mtMV@s6VmD zVLUyF2*&l@#17>w#*{O7&=D@C_AYa^qb_D6Kf_2c!**C>cckyNl?cwnj_q9bmc)qu zKgt#LHo+1=mvS!q(1j)!p8d>Yv2(es6J77@cFh%Hmz!@l!~QIt zd7a(Fctf16;eqZp8hTEAurLAp2O?X}O z!*n|J#4|U>Iigcz9BY6Gy-!&IIo4~)ZRWV9IJ@GP6l(;P7>zm1eqNODcz5O=un2e} z#^lPsOba|>!2=tN#}Ah$fs~jA*Nz`L$&%aXwd1?$RX=R1r+f=LFXlytF>rZNwW92n z9(s~60<*W?!mol%s@n={kwSiCC=6;h8BgSdD!$v3?p}X!HLX$oIvi_uukfnd8q0Yy zfK`J0dty_TuXSvU(GJVuubwj->#W{{tTyCuToiecd*UYOXj{jvM{6ze$O|3x^JYg( zMkAWd%Y%78P$ipPL1nV{HgvH;5B&{r8z#F{fc$_HjB>AmH?3is$t~lT4__=uAN!8kUz<0DA&b67w36v`)+=?Kc>b8BC8> zqt(j@&&cgTjwfn@Ig(db%{?GEqstEnF-bA@%@0MOo2YSJ(Fa4xij!Is^TaWDe1|yK zlUeRLUYv>n;SL2l`*>bl%t+StP~r3fgpc_qK#-)KfC6_C3y9Y+D^p+yK?kXJ*2N?M|E9w&=KT48xr3NMM4v{Ka@8EWm zUK$K=Y{2=>eZyl#c>|wK%6agQNk7P7WX6kW*A)rfqGS)PQbJF%Y%#;UyOS}cI>B%! zFb6RM?*~Z0`!E3ka) zf#_=295)E1HCI@v_N=&?PQXeZj@|@l6wZb@b|7A&I_Yf7GF=SfOcsFJPjwM09^>%! zqcDc$7Y*V*=pv#M^os#ziMvW)A>zeo(oaXky50G!xJ>6c3#8aDpU{+>SFL+PbSoAr@@W~Q0p2@bJ?6m*j7hR>m@7hISP>4xG|!1-cg3mG z4ODjq<0Qdj>>rA&39XFh&z}SpvlV{}yEnN*Cnm-)g!i<4G|8yxcu}3IC18JmHc7G9 z(*Ofh&%0%L`N*O;vtcCHYAYM%qHog!V1oJrqypu=2DwyXVnMv~nFaS;`0d-F~$hBbTqOLtv&PhZE$?gxveo?W!H_-#4>m`89;ym?ZwOkQqzf4yHUhShUZm-aGGEOX(cT7{c2wvFOWT!WcTMKh^*{wB=4?k4W9R)T70s>W=F`UK1kWOwy^v zazYHp*=Ug$PX_^5zF46*m`*?e#jijlf*Fe`tcU5StXW2PgtG_+x(THTZ8vLvT5@}i zcSS)aI^Um8FzXyTges`vphj}L*^f`**`u>Z3K$R+gRln%=vR2gQTFcpG9wvg*u2e5 z*AbPvrerEyXYz^qEkF409s~ zp(-+iYn$oLO9ZxEbmzV!xwW^R`(5;aU@@I8eEiwde&!1Sf7S0} zCJ7mxoV?VS`-w)U8dxJY#;A%EtaEFDKU_X62xi zeOt8X8R-7=qHBNFUCf+=auUN#5;e`r4R*p-Ur>t4s=;;+ln zu<$D;xa_J#x`PRcf$ERwpzAo$8CJky(EvtYsD7LmGRotAzhM2o(k#hYma#$VK}XUp ze>;O7H%j=5kjpd1jzxT|_}@HmsW|$7!3@WNdVC!GrU!AsHP6CAWHMAL%fmF%B9uDi zI#xOZ{@=M1QX2DPoc_9)74X~zb^Ucw&KIReZ)lFU_t#QOIb)#vyD?Y4pBCsEM5o~! zm}OV)fDs+jzUVK;^M%LL5#Fy5uxj!CrbbS8D*>jIaP5aJFxDZZQR{16!CH?^m%F2Jfr{Q!yJP3U^T}X-oz8&C4GyomO0<=(;SgI> z_WGkqzh|2yRIkBKulj-guJ@w)3Gna(B^bg;GiQEIKiRXjX}%y=!aS0&F-QTs{JW41>5pKFx-6 z_xT%}1TS^}XO+_SIKUK9?kJINy)5G-!6M1e3s6}=C7Uc}8BBmvJKD}@2{RZ^@oZL1 zVbTZn`F7(#$q_MTGQn#pH6j`fRi1^suCnfosg&f<=5LER33Ce*nw}HV=UQZG5_;x(+LirKRG?dMUPeB!6EGZ1=AZjqs z1Fck)6|v0oBS*F(1sTmuBS-6o{&&SS zk)$*IYY>db?hh+i_*8zqm}i$5R0=2obH~xrmT{kB{wbRz)EUEYTO4&Y6^?@&i9~K3 zwK|iCC?!}Bl;vfJ39~a$f9JD>^JtnVW;Qn}A}z{dc8unsJ50h9F*L=^zFafYd^t-J zMx}KrFv1fAS|_LmY)R!o!+L^ZL|}ozriCvsueh@l4mN3M=Kxyk0R`HyP2L24mm{Z%9ZUlcQ*bj&uJfg&CgmAfE`8H5TqE3M9{H)=z*_r>?9WfC* ziZwgKp``5h-p1&Dq>+v-Em5@NlL{aa zl5ZQ~OwQT}0b`*nYqKNXD!67?ZgO1hIB{_E@DS}dl3U{^>Oe17P`vfUH^zB6B4O%o zyo=jrf}nc{1-4hHm`Da@ptCeA6i-4GM|p?|MZ&bm6Cq@uCrNboP%&rD6OQWs2Z$7N zB$xk}49#{IzD!q!oBAXo$Rrn(Rx^k?ZfyX*#M zmmtTg@-Wqh^ng1fafD*UGIEXev+csdMQ{&e`R`$!2o%R4YJl!!86sG0V9bvP$zr?- zVtIf^LBBo70ELTgSE~-^sBy)ntm- zx;nTnz<^#@!zRUJkZN9L=@@ntm_pcHdGLCe+R0L_e~&&eWPy5iV8ppu8|jM8@XC8Y zBO|ppKQXz3EI*HjCHg&V|G?Pdm0L3R*McVTR5s1&QuEq`{Enm+8X@=G*+?Y17M5}z z1`4*|`5O{Hjhz}~gGbDWGdqe?rm@X?ggs&na8s^MV>h^r_o^$$=shv*^fs`ASkyew zv4c%MKtQ?zHnZR&o%M0dt2>z<%4!T+)*VzEd!~Jnb&8g-}OdS_%qyX2l@>s`5!%J8it`}+>9cWbKQ#t8Hr?})5-MT`V& zyB{;K-jPB0P^hgjO#i>)>K(gA#ysLl`>=XvvavFEk1BJBs&`~WaYI>pK6N0IBilOB zR9xUa{YO>u?i$V{Gcm{`F2j^F9xAT$Ffq9ab)LnVW8%apslu6pyUfVo^u^he?|*pu zx6`wy-+y-;P^uFT*B{8MM6s>GW;aG)IVC<;K(|A;IX7G@apySXJv7`u{`f<7LX0^FGm)!JKzb!~QBmw;WL z>c4=w(5*bvC&He$pK6U=uifcuiK^vB`sfr2ZvI)( z$FH5~;gzecTSDz_scXnq)_e!hueS%#<$=$f%MP8GrcTXE~C-H2!ia_e4mCK4s=4rv8$8%0>%dQ72y-FnY^k3sj^^Xnn(Tt$vn zTpFxIRpq*Nht&-C^)(Rko-U?z&^Sx8S3#%2c&F=5BS~6$1`$KWR8@2HK){X{gxvQ1 zRXRWU?1?kJ0fGGCL=EVOob1!Jc((ceREX)3BE}$CI{^XK9UKZ z=Pp}kS)z1c!O02i^4a&#TT;*l)!Ze8dcc;gKH=Si!^6XaLooF~wRjyK9F{O_ow8@U zA~-*!uDE}G1TVYiN8S6qbAB#*o#4wazif2hQSjii@~CGR{uL_ZxbYqJqMkWAg7N<< zIH)dSeE;I?*^4v3)uFvT3YC8YoE9sIWZ!5^#cd3Wz4v>U7CHQPNH z`oVx6qVK!{z|dHf&6QAJ`1w=ISx+hZZi$dBUB9h!!^T+jdM|%MCVTJp*ySpb(fj&} zpLcEVwCxcStxd;`Ju*CLPCwp#_b|v#-q%eJgW-eZ{_y@GXZMF+o@7T45@z34@Auea zv68IOW1dShPUM3UXg#6z?})%+kC#*%y^0W|R{B zqscp!BpjU(KJpHpjj-b5{^XrzOq)I3t06dU@NV%Q<(-BPW&3+Ige0Y;-m8}0ao`T2 z7$as@OT!C=(sQ~n=10Yl37w!#f& zuiLLigF$-x(P)go+pO&tRwOjlO}(+OFdUvP?prkh%qUx!#T`E4GH$G}ZNNTrolIli zbnyNAQlSZ7*Camws+Sk9d*j7u zW)W4+@{o3s=5$MSf6@+Y2w`Q(UZh>5VNmv~&qP|uwF8_MNSd2yg+x2(dK{bGN8N!9 zYk3-p*_L!O3(phAIA${5tC11o@m;dFMH$C78{HlkWsDVQ_&ye894ppzN$qV>0x%VQ zu$}ng+0V~lT%Ml6$UOUR=z{M~k&bIn8KC0j%~nf2F^Q(qKp4FP7DJVB0574Ai&0>g zO-c7)?J5g@iOtVOg^Tg4G1&9rh|0!XjNP)ysV>J;bR0EH7>mi4^NJppr>URehKSd9 z?s+%ST^1Z_F#W+gFvaSJ$}RT~u%H##OHs|jtiD<>#j?>H&4!HhcGC2@VGsc)R>1v^zK~ z6a#uue^C8WhC&}Xtt;Xf)FeD2b$kvl15#17O=?Gd+1}m7T_GDRP_(W|tYf+=2ZYO2 zjCUYEj&firYQa1*`&7@$WN+a|6`~PhFUo<1L-)(xl!FSAPN>=`2Ne?4yx6O9V1Xmb zMB=-R6Gjq6X1TvCD5FL3%dni=yK+`f-t#VwbYY{R`fQb`tQOQtMMoDT0Pde^;vq^C z9!X{OyZ4IHvCYGAM`7yDh!W`6bA3)Qm2h$Un2oY6*cyAU3Mb{nqJm73&4aadJRivj zm?l{T{X9NB`^(9}q3y~COM|W6v*}fTQWoe9!cHF-82$5;EzU}TFUP_8pwT{ zg%}mY*WQJ)etSRxLg%tK?Hu{>ZzNmo7*9^0Kk?`P61D>n75==SZ}8@^Uo|+u6@74k zU3`Fi0uOkD=l__y*5$U1Ed3z+2j!_6I~xN=-@CQzsZBX?##PRxoMdZj;{<|8P(n-+ z8~~K8-RzU?L+vZfKl0 zeD&>*U%dSs)ad^_iF#&tIbtF;^e_~Di_pt-;H6zhi-~kVEZl;~PZ@UWDB4QTeFP3Z zuW%JE#}YVz$VR%`@8HykiTxH*r726t5I9&9kv)cG4ZvIsxKcz6NttGcvG5Z(B~~6x zs(Dp6m~it72Z(R6R_maMjibSd*}b{1C(E7eCc=84i4cDW|8!XSp#`WX@QtsXy3_{X` zJ%YTEJWb_}4Ys3M1Iw&;XjBf23t)KP9M+-V$`>b9n$4@`^Rj8f$`d#eaz81;2i}V{ zVI}m9q4bxi#8UlegxRM(;F(ML+qr()L>gy}rQuRX7eRTTdtdirbaXq`mleIkF>;BF^R2aQK4vojt{ zXI0WYOG7-ELHQJAlWc}btntWks$#`>8x3NwmEb)x`L@bNAv z0M4-S5-1&{up(x-Sp!J?QOp_uwkgl!m^FZA$dD)yPtufVrEbT;QQ$;*jWvV7@L1RO zUU*rw_UASe(K=H$+fj#=?srr%ti5CD1kwv;U`JCMxnulnawzRJ`aEBrC;zpcm#fJ| z-rA?ue_7^0`051+*CJoMpd53#;x@KB|Dvr{<>W zg~S`vsJ~)T7|!pnnA~V=t+KmZPpluBee=`XAK!fc_N)JR^X=C!ZJp$y0^d?4WS9er zAdD0B>8_F;MUI^ZgC({5R*qN6qC?bXBnK4W2vN3c=vb24LTWiegxw(-PREwi7T8cW zj-+;ITn)%CLkxzu+jr%3ctu1w6`Cr&m*3&V@57)B)$1qA&h98G)*}(L`_`~g8PvXA z9p^1d2WM6IhzyEvV)vqALy#C5JtFOZ1y^G+DE0yrLn1`juolZO!a*53%fUelyzc`p z$?NNr@MOFW#1=u=yGq{51qH5ZcXq4<1iwe#+px&5HcGG&maLj&v`}W_zxoNCUX+jbjiY1Vn4=+h9gX}$# ziknGYuEMTIEILhhE)TQp6~K)Xs4&0R(-S^U$_hT1^aPnZA@X;Btq{Hh>|3zVj^N|G^F+3;5Xpz?c^?3N0({BWUzc= zsT9h^xwk~#IGM4Tzz9S@gi_8PZyS^_+-3^x5s2Paw)CeZKnROg`Q!ukL&sdp?kNRk zr@$!C0Pci)D6cE5S?tP9?9%Rf_gUKK1@KfkiSxS7uW=3Wv+nm9TRh7z!SqEV{G|Iw z6P^Ia@nNJg;?^SU4y3uri6xOH@;H5xr2XhCLM8XnZ>z-|NtlGt&zn^-i3#Y@^vF=~ z-*$hk*Pt}`XRct=u|Zvx%epwnVF__`Ebx!A?+B=CsPYIRLZgu*j88;NX6JD^!^KRK zP|FN!9oo*s$Ki9371#5kEBS1xgh~f3Qbx6R5=aH(bbSdZP6_v}(mY>f^WvjBbm0WF zpelXHI{jl^)Yp!R2&Ez^CKLInoXtVv#b@i;tf)Ia58iVrndJBG#`V10?O=0XV_{0^ zorX>5nOs!mq$3i%R7wD2cn>ZXy}Zor8+=P5)R@P2N?no4VjT5-R+iy~pGI<}1 z$IZ3fk?`PxHUvzzdlgf3$rIUQ^yq?m*}JoNGh4yl*=50!)@X2nyetSO7Kp^f$JU+O z@Kw?(^&i`HJJX#7VmTw2f)P@BHySP1?5Zw1Sq1;wF-!x=nbbyt?j?tDHN1nwNvT8( zAxO9|emS}083dzKAvE!#7xlErniW2mpY~%$P$eqm-bI_#*Q>UQU6)@wnPAyvQtqms zdCuChoaA`YD6mWTP6#YN>SdSZq{t?PQzMiF8*nJJQt91})p|b1_uBDfk=GyMqM78Y zV&nRSlT$)@Wtct8mObm+PF*lL!&2gvYS;z>=E4(dLcs~4OepVMip9!aUtO|I-)9ZsLRE`y zyF)2Z0w%oIf^V-^#d2-kP8x2-8_E@BUa`9<7L$t_w>mu0#!!@2-qUJhX$!OMH*}9x#!%=JX zh7*y3-(xI-0^i2U8d zrcz2%>D_e8df?$K+x#Q0l@52+jAl$rKZCEEw*1&rei)m1dH*Exwe20BJ8cmd&j9Bx zHHP>No!`pYte0;N=#)xIyjw9Z&n8`{M~B(y056HAsdtgwMSGoJZTwjfwqXZKz!_A) zd(hLHANo+692~Zy+@O_lK}Oful~1<5^J)8Wc0!hvUk8@r65nZmkaw_A}XQ8Xp(wG$sm_i z!J(DP9A2>>EYZ*_Km~G_bLn5E!J#&lU?-H6`PFRCNRCRRlA9Js$94Endq;uoiMb!% z9Us9v0~1CFt+ki6heg?egjQI@*GHnipR5zi@ifLOZbz`vHTba_8^(EUhW=NAfUl`0 zsn_%wl1pH~93w1MUgK@B18WkF4?pg+;QHS+0s)ZaCiU+3KxZHAR>id>UM?Sc^8i$^ zl%y8kwHood1_*L4s3G398!e;`H?_I#&5k3lMQ!t%s@AwBL z8UP7!5sdJgR{f*ulv(-C4s!?(xMf(r2ND4iB=vI3fV&4EK(x{PFwJwHK;H-n#oHE8 zPzM))M-)_eq?HN~yns)<-sYgIIV7Q(k$&Yg+SMG8V_Zq<7xJTBYa=;DbV~ezjox>s zfR6?vVg9hj@T(^vq^&^lGRlx^K#I;M95(6Yl)e}8fG$9N9OgUTp?iCzl_0oIm7v~3 z)idu6z$KU90eX+nu&f@~E1|R@+Ix%!rYBJi8>|HJbNaxvdTtWU@FmBA#EM>*%MupJ z1$9qr2X-25o?ZToj=};90R7VShNE?I;6e!F{8Zj>VR#}hv6^0Iy6NG)Ho3sv_Xd)4$3Qg_}4X7 za>oKiYjBND@J|=(vpAn%aHRS9tZILW{`HO@{u};!lPs&cSk12!_ZwI!KoS9~ZeXxt?$M)M#FW^>?y}Eal`uGNx$bv-M&Xs~2 zNz=>(#2N+hj34iI6>CE=mw(;RKDs z`i+MKO-6u6D9dK$$81t&Shs05udcFbv4YhsmXoq*ZfLLac5)F+8)Izp7Hvi{OfOGU zOS>-e>7vM-FoM6$iW-P_w3n47AEDb<&dOr?yle2$RkjQt5&W0#k4UB@Raw3Q9g&?C zO`EN1)FPAJ>GxZD$_bXbIV7etEY?A}TY9_S;F!4NiNPx_^ih(Wi40pt!OycrzFgW!2elz-~>3A@2#U!|=1lC}FpEmXK@#=cLRw z3_TWGfnG&@cSz109H9(5O=Xy22ewGo^|USO$2r`Lr(7M5iNO+J{I9{s(H)yLSzBdj z3JhKa@a=C=wQ2Y|iK7@CO&t+}=;ny{SqClQ5*(@@~~u))XGP%u1Eo`4kM56L|S@+6Ke1feOqno+eEAN+1y?cUNi|Yo%DRaBK*Im-PTkIGdiP#xxAiJaY zJ?QP0)(@Z*rYAaTIwk6+&teqCHzCL9iPim2df^)Ma7NNUTSyvy!?FFAi;?ut7SYHt zN{?>fz;hi-|7;=UUgi2BNP1T}j-Y=wz&hRCmcIIDi$=NXuQFJgaDv_l|1>HC=-JK$ zXefUomC=z@m>l|MS+-?9cMiof%j%y+)|bGPo)k79>^%>llO)9lp!h`dHD>Z}w8Bj- z&%T%%3%dW*H4{%>?5updUAdm%#Mm6xSJiy(wj5lk+bR#7sz78|2SYHk@Rb$Oh&*JV zoWV)K$`7w>C){b&=I8D?Mi>&snlakm_kO>&r>qNYN0^HSph2>$gA;gruu-_%S7)1f z2dnW8RpUMU26b6nZS9#}vj%fqJ;>q1d$*2Kke|0914gx9fA13rRj-x^xa;T+kEC85 zq_OqNL)B~d?OtoOVd}L*lvIqOUM-wR$!JYdwva*&tn9r-(z~lSih6axAb&>a{kBCD zq<3X+z+eg1;*mSg*~r3nB>DNPlr#1VG6Md?Ni@lqR=<@Xkrvf-JujlI;yZ-{dW!nC zy(k-CscIEiI)5_~E}eJYjfF4b_-_$H-EJ7QDB3)(mTd(Z;rifjcQf?~)u3#{2L*!E zUXS05>i1r=lXN#JkuRvWsapG}wm4@wi{@ZieD~p*YvqZ#+r?+mODhOYF7x`?lf72m zPdA|kPj5enoB=`#de1bc1U9F?5+;$NJ5;)6zWAEh)_@SN5bNdgcuI6|? zoko@;TKDI&#XkW~9i84-NvA!=taqMt$(>YL-D;i7VG)cx+Sg& zIh@cPk(hx5$g%u2G5kyl%BE8+MPp{C(K9eyq9+geT@XFfb%E!bM0jCtdt2Ed1W&}K zo&Tr%_yjEU&DdSCY7vF&TYOjoM#iR%7y|$DJqF2|W}A&Rb+NMIqs>>F77+<3LhNYB zRNMM*-&ai={Z^et6ZmquUWEb?PNl>M&ClKC_b*!`5;OX`tq2CiH}PQn?m$E|5iHPJ zMp`rc4yG zsn7-3x=*9CVumcV$UoRT#(8xH9_#rwm2l%1g^yg>)?2RX^=`mmTGrrU;-3Jpy(sL< z2AtfrCEJalqn?997B%^-xcMw}BqB|1i-5bgxT6!t9o;xa{ecvdn}G(|Lc!ysN~1OS zu2@u;xK=;>@b{=2>wZ3GFfzlQ5cke}UQO)8QMtk_;E2I$fFQEB`Kt&^UA#}4=Hit@ z2_A)%953|m9!0Z0>x>pWhWP|LHo>y5fom>7SHWK=AQl6L`}Aq+ z>{Vhv+WY4B-=mW!p;J}dUDs^45?kAWpljfI3a_h2N*z-GHe)XyXwn*|cuFNy2`y57g^47;t0KUB)h# zllgjDyqSTmYCG&ktEyZ&+pz)*x0u4u%Y5E!MvP8Ku;ewU9FOwbLl^qj?O@+yZaT=q zuy_a#L{o;&WrtMGHb7ynC4QBx@-`sdWJ90XNcaf!?QgYpQ8X|9x$jsExpOS;%)Qtq z8x5H+XP80sKpMm6WQbHpZKqoY?Yv{TKj=8nln{wwg=6NDeb02mgE5=X*;El0=OiUD zCt?~KIhC=PPP8Ck;7DZ>H_PCmP%70?p?SBs8g1SF6bpd4@%P(gk8pH`o_d;sU5n!N z&#;Qh7|iVW)W_)78vYp;gQSw_hcCwQBQ=^pri2Rn>~{m{*5}i&3?rNmmo~@O!g;g6jx#V0&R{U*O#a_4e17u8i<)#Z7) zJZ?1CVClC-;(jez*YnU}g9KA%*MYplDL5MM8L+%hLkB8S*QVs3_MtBZW; za^ji>9G5R`(s=Z4-L%n{)ntts1U8VdPvHOl_y4)O*5x*i<@`I(kUtxmJ|rbAF!u|O z>0?>eu~a2XDob(_OE$AuEXa*O0LFzfeXP7%UM<}-yTF1ZAc`|9ol2FeY1tj| z$}+4`$gpd2M}*Qc*cj=_%NJpEBvI%e#9Ix})rxFTP@$AvXAno}Fj=?-N!?@y(q*o+ z*t$*Ub$i2cuj>cPM7JSZ zxB7zrTlZD<6_DXamj>K9>jw%iB?f`~)lyxyA0c>!Bb;^T*~xMMU$Vb9h7c&9a+vXm zTUSf0-n-no6^6p0gp%i&%r)~3aKpG**HrKUIH?HBKTKSCu`rqnp)o%vKY;Vw&*s@- zf|=lfBq_a85Fm^oS9tZmD8T9iqjtQQ=CGaa`@IuL8q5mBMox$y5C%$vZ;$xKx}!nOq4CbS zS07@R&^BJozd2WX$^MH+_YeC87)m)Sz-%J@3@j)@YylEeF&M~!6hh~P;r;$Wh>QkX z^|-{JEi(4!XeQFj$4$sCjRJ)$*` zpz)cSv)O4nn@`JpUgZRak!767n(VRyRVYpEvv4M8z|Gd83Q=SR{p{A#p?2a>RMKG! zXRbnVS|123P*Ua?ohM_h=u#Vk}i&4G~cWd#I0n$H`X) z9LNZcD&Mq3C2;+OOF*g67==D9M_|&waz&u_&a&AYk%O=~aop${;$x%xrL1#~r`ae! z?IbTTS)nQ$Rek9Ghd`;6!DeG#s(08(%%532Qak97?O^^GUKOC_A!2>8xeh>lf$6a6CZ@ zdwBkemJwFoI0z=CiZk zATEBta4p)(JUPjxJ=`a`FH{j|@dHmS)W#XUgaSIrJ19sJu z7nK{hnBc^n1`-Fii`^(YDToc1GCXPFN~%(jNXk;W5u^OiowpP+ZYjA|+mo1r$Scaj zlhQsWqbW#QE40|CAO3%(H-*fTq1I>?p6e7O0~)R$_SL#$LuCl*;OsKFF!7qsx(XX3Lp*m)j4ojeo zW{?YSM4LFRi7x{ejUqmkMbs5QP+Gdt*ti63N%}r~7Q|5@R{JA!!kl9PhF39i(A`e0 z&mRP9U#q0$N;mf60~{=FL|@Yh73Ls}gQ9XvE@VBo=@Kx5X6<;IgI@gBpXg0WE=Vkm zp)XJ*FW!})m3`f_ELo|PDjpulLkR3#utw2^R~Hla%sS2Tx5-{(NPC?ihFDblO7Z$2 zf?$kqcE+(zZA$%mon`Z*FCT?5Q`ph+=Y(dmr&V6$vX5PGOwI>XS>5ZgKIwtWX?0G8 z9t6Y$u@VZPAUjDB-5!YZ^pq?k9w%7|c$h?y5Zf|`f~y=M?Si9ifU6vN zBX}F&Dp%?bK2v5 zE#An)l?@DIB&A&SS1t$1>(@#8N3y$O^~*1R#VaocFJD!E+1=m^eA;@n=q3Fz81}B< z-^gEb|5ayUczh&Qn4K)AG^IcKA{j;B49L)X(f5}e9yAd~w<*pR}a@=0NC*^4IdvbIHHc*(Prn3dXbm&aYQ@2`}_O5`&jipNzc>W{TVFv-mL2_Kw(%h^F!5&VaV6k9{tgRbtXg9F$zUnje36@DWOO_Q}_R}5!G z1gb`0fa1mzmb(A!?EYi z=l#7TKl;!F-b>26_D*?cpOQP}S4a85T}%A0)$c9MrQyvJ8s*e0urjyqsMSifGbAbW z>N1RQY&De}F_YJ~Zb(OC@W0)268=2Rv719WPA27~7-Z$pb#@42uOy-xg53*EHM84W zDut1UGOAvI;)Jz|Byy(sN!TWz&;lK0DgocVK4(&4K3jA9lO< zy2<;$g4f~IS2{}e;C%bP?!je)e;O67C}m?gYxEuU!eT*kU^rUJn|pazpKlO(`2~H-Yl<0`g}0Ja=V-&_XxmfF z77^a@<35&g?i+km=lw7Vq$db*E4Vt3;Uu+)7Ln#1iv8;~yQ1SUsJ^>uV-&~&3oJCW z`X%C_Cmt982fD6gR^-9(ZuDbHcXsi=RVSvM_e}-#@aa$CvPjAK#)i#pYad{*1it8n zY9Am31}<$&`yhF{t?hFdZBU%;MV%=D-J?Zp?E_>f=OW_KrV9Mht5Ii6SaIcQyhR0m zvhPqH$V-cc6TF#A@ByRXNj4mPApCDjt1vyU`TgbVZofG1juyjV95)}dByB1g(CLfm z&_A=z$I0H23oJWpa5y`s9p>Kuv2hP2^^aJ~VKglU3k?7HH)&*!h)B=~qj?RDbWLaQ z84xu>9k8CAcKq0|NARBxnu|}n4%On4N>&eS$!Lr)n2QKw=33G#PJ2Ulqi$O>;-24s z`6vQkp&MbgaBm&^=ec0`XHv@)l%0y9mZ>$e*}^xnTBg==|6mtiEi*#2{6K5uF#5P{ zqN`>2P~NPoV;C1MV*2isQz_SgD0P=q2bHK5MXjZ)c)HVO?ie6?>db@8$0@#?r3=@z zH&7-m=;~O9OB1XXVA8GleAXHlU>Qg76_JqUtTna31(!;+qZYUjs>L84(3dJcMgYhv zp=jG`feX#mvVHEW=f}@sVIRMQrG5UNFjG(OBh!HLO^$_OBqG>>VZITXf(W(*U$3es zgPHrIVwm`YJ9Cu;{5em5H|ki4bTN868owK*Lwu-BPeuWSb$N$du+q7yr5Ga5dBq&t zvDg0(RZfz+R0{1_*&)fkw1#L!Rk8Y_2q?G5JA#0w!Ng%N&$Lc5TDCu|B^VD@!Ga_o zmXk>Uz!&*~JBr)?qxhm`9-IZ#U_-`=aYH|Lz@aAwW?2(|LHjGMf+0I&L5M(+z zCJ03SJf>5{Uc1h(A*Pcj1PY^__s@zvr!v0jRMED&r7*jvZ}^&38I5dJFtRPvsp8`L zdqlATuN+Y0Yk(jpnki0dMZ0iCl8WEU+1w`!d)K8+v&IWyEL+~H^^sn=P#9XDk6RYO zN@~J1Ash)Xh}ey8gOM-#bi?}~P%29B=`f6l6;i9KujZ~Y4_G+)Y4NtWy-`=OsCuN= z@8s*mLzO!)kgg#|#o)X9FCQN5?)&M#yL8WMi^l!b8_x=iOX5O}3Pd;ln!CO$4!(Do zyxC{OVXxf3-W;TedZcT=rM&)i*8w2k@a|;qAi`i2H!DEsqx&x&-F`5qaJ_-DiX@n3 zf?j^ouhtMmbRX>EA;>c85#|#2)Qm5?Of?d z-tZP6O`bM$$+j(B=}2FhxaDXovet^0uXL=r?d*|gS2|+18M&gWtGq8gA@98YHmHGF`gV{Y?gar?w;81wulSw*z;o$Qw^SzGBp9GPcygprYS~$p~a!%j0$xqh_2nwR*$uI1H$QS&^19 z7GBE{1Qp0HKiMx8p;v#tghnzgPRiLlpyp1+PiS>j z*MxSIy62)nr7vOYKQs4|v#eh%LtXCEz|34Pj&ZU={0=H>j@ZRF!>F3VzeoKNvZ^0*uy*M*Iu-7toL zN^a&H!`N;T>^PYglO+8u+52@`_jROiI_kBtzr5+}MH7xny`C*QA20ILXtVg|pDyn^ zf}mh;+BHENH}%%taa0|h$d@-L2iFg&k&PR;vPwr?S*|ao_5QQ2tl*a2Ze_7bOY~(G zhDA&%_B16W(Xt&KMX&+X=d<%*hFp?ju$>2lq-ifS2;^-`uYEwiDl>kA+xR6A`{EAp<(TO0a9XuDQL zB(}Z)`pTH-`a)nlqHQP|a#^e37*StfXIvVqf-e$OaHpUf-!jw+_%H_mgL9fqFAf8N z&=o}oidqBeECr{7j7dQ>6TLw%%cj(SQYQdlb?R5k?$}ktV262h_J?>`(@|J9nqve&V7bh zznEZZY&IOGgCd(RrU7kPJWnuIR?lW1<`#UT!Iothqq_b^D}t{qJQ6Gd)Cvh%*4~w+ z6=SlUcQd4|O-qSqo<65GZkd3JtR!v6Q;>_1?U=Y)2^y_+ImHfmnj7l^Vlo;*7cN;k(+$298mw7dkeo<^$v3 zIAMe=a9TNqYjW!d7o##C_lvZ|W=eh+u|S@}BZ)Wje3uR1_RA@rkswPFl2^^oGM6j(YV09!|by*P}#*$+a**2DE#aVvp zmM`e71bJb5rmg$aO(vLJj_eGlln3&}Zr^Mx&(Rnz4H#M|BPA@|s!sdY>9nw05xh;M zw~p?&1vXA{TSbJObcvOBwrO^Y zarBCoifmWaxLaaedtj!4@$@maOa$9PYo-3Lx$A#!8#nepzy1}R>r8#uh74JPw2%8bmGR;8uqYpbN*3h1fgt;J~!6Ia8la z_-BcRuWAlj=5@rmO~54!56+S)j92?RuY$B@2o9*1{G(cw;*V6n!-OWu7DmVhOV!}t)W)5IMuRKp(5HpI~rUCwjm#br5=++9<*al!Y@Dq33AF0A-PB)#Q}C=)pg4ctTk8R zP#nY|1nKPH9_}ux#Pg;EyKx%!|Kt@aTb*MyxetBQo3FcX@TG?ch_tw0BLh&! zFpBqluE=K5osQsQIiFea3pPUDc$TwJX|Afa>egL? z{qqZm_0LuJmrIK{Si{K(3)R|nFz+cPRNvJxO^v6^=A)`DZyF)pIPA+mE>_tL^l1t^ z&r|Pu9&D*|(+*%wJ!{iCzj*4cC2UJRF|Xy)*{u0ARrAI2YHDt1KK1lN7)$zum?nN7 zm0C=3X?2e1MyA1o?q@x1+Qmg$JE9z$f(z!I|MXY&@d|GjIw38jz6ID3e2lA*nsyY@$GQq>=u_$V_P!w- zNAaUxg)}JQF`kwh(o(wW`hf(oY$s0SFE3xcIQ`(g>%flQ*7AOMTl;;h(qeA6U?mU9 zP6gEGtNFsTwztz$AS}f7@V?grdTVw&tPNmEoDe+5=!w5JyV7?qHzkQBvu{8=cetxc zypMg2@El*k*80mD#gaAzMPx(!a7EMS@2g?fcZ(?hVwbh_+U?YG0*uWi4{-FtS7SWS&+t zd$NLF&!m~ZpH+wE$eJ`GcsX$`<<)HFm^{NEvnT&HJ$E8y#u?cr>WtZBYxYjGTlb^( zyS%TT8&B+2V?OxCjS_-)dToD?P5&8Nk`bd@Mn!vg&$NT*#J>Z87#q*q5|Od9%Cnjv z=)`uduQP=`8q(GB!qefKEp>ry+U;AE`)P-Qq-W(!95$Y7iMi{SKwMbjF zjtsHDzH#|2VJOF^(2ruElaPX!h=!?eM)_g$XbX!5BV z(LC+I+dTp^gmqiI8k_>%>jQs5#44TjlN`W|RNbatVL$Wu1xv0HfrYCKSD)}ojOmMk z?LNK&pRN816&yk$FA73nqJi12%j$ducJwBfE*O#&gFM3!FtO?o1k-KU;_9kdwOaMt zb$C#z?uqy5q5^Quez#gITGU!Rmhk#K#uHnf^bKsoQx2?HrF4*9Le@gJqC~?`XL}82 z|E0GCJw0Ig&u2{r6N3{J^lD?GPVc{Qmi}xD%&|I;lOag#W^aaoZgHnyyf^ji3UsmQ zlgUulbqUf5TbJVgnnh+Z0Vr%$<={x7t)CZXBgtN^QYkW zdW&><(Rt_9rCmG;WTp1^*KvPuSMR-k)~7;t%p{Z$XC<->L4Hdt{{6;i*-@7;cs{h` zTNAXjP9hSHU3jn%72ffHExG|83zoORfNj!1*e!BSlbBdU9~L*b5gn@y-X+YmD0*q$ zH;A_5ElD_*z`C6`WJ(u_c`ybIgD{5Jix2M~W{U+!ybW}O^0BGY_TiqEMnn&ojzLtI zIPM|;f}eD--eC`zT?N;3V8sh2tNT2W{mM54X9_g1kY6-7c7KeFON#XrN}Z`gc+D9DRRO@`S24*WGW#IMXMe` zq6>E$!tTv3FqbaiK%obw-1$Ya9#F~wE!YLr4-eaks#o*iC;0gUH*OaUaC5b4ECXd7 z;7`*&B6GokpmLPV_c*}S?*!hIVZS$g+syHnn+q1ePYUnBf-~jAy!btEyQ)xG+WOT} z%^esIDZ){z?|jkGkc(0B-u&QpRxL9b&^ITFmoI5P97YqVXTYB-3|MxX^RdGUzex# z6f*@b1|7$Il<_g>&elFB`hw_4SjQ6QrorP_aHDDOObOS&!)Go8AR;y(fKH`tei3L< zPr_0V6%~1&rm_9NMG_5cvOAzOgAgK(q|B4j9yA#=vpb+HD#|#N1xv%o0jMdQeHUm9 z;S-CAP=XSBP#Mty1F{2}&Z@MVq=HithdBhaxdUh!j+t2OT>m;xgB0G$2Fk^N8nf`hl{&?-6lH zhE{F6|4wdJWkKs?XoAH<0=$W;!%SDeQV!|N4d6{;-wpI|IDZpbdwPy=WhjP*j8yVK zYtrQS^!5L|dHMYhZ+%f_gp^DL%g=TRY%EVVxFc<2zo^>u=caD_pH)$<=6=FU5$8!B zd}_+_;RimN2Nvq8ZsU1)qs~_|aPAYZjGRj5gb6~$SV5enb?3ia!tRj!2`Ta@E|eP{ zBpoXV&BISGk568mo%ms#X1vG>dwpOu9!f3U&O)ybilk{&hN%-VM7n;!eR=37B+pBs zSmZ`2SU8mGx}B4?W`?C=N$N-|nBYTZX`6|)GCX_z?D&ThKjooPX+j+6M67#Dnw zh$P9Fg}TefW*^BQ$WtPAC6zcNe2f9x<>pMytE`z7ewtF5GRfVsBL^d%o9vuH>{l}X zJLH5HtSnd_I^%@gM{j#hUSbE(C?i4@&iJ5wD1$o8$7UJRyewo92Xnh?7fBpNqjr}a zF4j8BGYDgqOq_{?g~|Ap+%vOLSq70giPGF1H+<-9afh2Sy{z)41_CTokt9KP`S7b; z205mKDY@H_Bl9FwKcvN3_`Z?@wT;(4Y*3l?Gup@sLL@6Vv`i{rGyYW z0*5c}tg-oE6upbx!RKlFQMDVDQ|kX5n7Aa0MlQJ>_%l^ISZ^{6M=IPdxW1OL zYSWoR+atLcsRX;cT-Ek|lnB|-CEJ7G@9ITW+oO}w>wXWtOH1X52O17tmEPxJk=9i{ zS=FDax|kvdJv(ny`A&a$2A?@#DB&ab{vn23a>K&NRU6ixASdy56^~VSp`V`jZyoTt zd}0e znKQx1R1h$QZ<~7$&+1W_!Hy`_H1q!G{c`={_Ja}8Mr8h<-RP?zJV}JRH4Ov>iLYPV zKGPSE@Kqr`_$y)RuW)fM@ExmFzB5S}MkxRAUN`XNUz^O!XDY2%i-%W;0jg5Ol99pZ zf_+ ze?Q?e0zHUxKQ4&yS;z`MOGEAh#Vjg{lIMyZd6_D)gU~$1^4VTrgJSDVpLv00rH?QS zF0eVqvqH1I_4>Z3T9xacmW|#cmi#y0drQe6#@uqQ8m&5n* z?Iywp*IeK0$+;D?k|YFA-LbsUB}(z^?1y#9icN_ZP0S7k%hwJT)!E2o*5q0wc=gWL zl|MjEYU#J?Tzy`6=)1fRy6)oGP_bi)Q{7tSc9yzT1`gC}rt9#iRM*gg*;&CFyi^`I z0)D-Qm$0BD2;oD4Mt3vgKfHbW z+Ebsw1N1Hw-lno7pGY!XsBEOPa3_5q=>7El)Ay72{g(P3_f+^b(CoV47M#jqZrv-v zJ;pUkKaf&zi`%hF$X;m}-cSp-l%}e6S&5gWhj*1L@G=%nrTZ43$%POE`~N|l+pXEx z`Bpm~B;oXKyL!hI{O^YI_vn6M&3P-Xu;UJ1ZSKid<(lgK%7);#0=#o;$&5@eG30<5 z{a4FYsc!N|2aHj?UU?g1Mrk!#w@7p4N)nVX3>QZNx*n~h=qQiW)boBde;FziQ|-MB zxqkrSZ!K@1#zaMBq5QB2L!T+e{fy+&PhxPoCUJNk&AvRnlDOzB8)IQ^BCH4i+%ZmMxKNa zjScSchoiAuWz$_WpZq>$KNu01EO21{ELgP`Pija&LY{EG9bVrO3k~MhECm1Xx@*Wa zjhHS)dyIKe|qTD%`#H3c3sc{KF;>VCSIYoQh}t5^Zh z)LJbY?9`#_J8u0I$__tkYHyoPU3l~KN>}AZv0`oyrdZ`b?djoeYeAoW?o9dMOqmW@ zx|}6|nL=|LD24|n>mtnE63=vjnIBiR@@TE=N>LtcbG9am%6178sP{ zCzO4Qjd8HMRn;~%Duj2Lwv|SPA#D$^#hR$PZZpnI33IA*0)cm0=W0i?3YNbzm9Tpe zKfy2R3cJhglr^xA^-N(Z`fGFEn!jg1{dD^G>ASO&>AP2FC&%yJoM?Whe>-_|27jQr z4zNbQH^($Jb|`G;sX2a7>goPTP6iIz#H6h8CzzSo52a6}zt&dxrr(r)4a4wh2v){7 WU-j3jX2j4MkGBi0jGdMRj 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 B7N-CJ literal 0 HcmV?d00001 diff --git a/docs/case-studies/issue-38/raw-data/downstream-meta-ontology-issue-3.json b/docs/case-studies/issue-38/raw-data/downstream-meta-ontology-issue-3.json new file mode 100644 index 0000000..17d78f5 --- /dev/null +++ b/docs/case-studies/issue-38/raw-data/downstream-meta-ontology-issue-3.json @@ -0,0 +1 @@ +{"author":{"id":"MDQ6VXNlcjE0MzE5MDQ=","is_bot":false,"login":"konard","name":"Konstantin Diachenko"},"body":"Use all the best practices from CI/CD templates (check full file tree to compare for all GitHub workflow and CI/CD scripts file), if the same issue is found in template report issue also in templates:\n- https://github.com/link-foundation/js-ai-driven-development-pipeline-template\n- https://github.com/link-foundation/rust-ai-driven-development-pipeline-template\n\nWe should compare all files, so we don't have more CI/CD errors in the future and reuse all the best practices from these templates.\n\nWe need to download all logs and data related about the issue to this repository, make sure we compile that data to `./docs/case-studies/issue-{id}` folder, and use it to do deep case study analysis (also make sure to search online for additional facts and data), in which we will reconstruct timeline/sequence of events, list of each and all requirements from the issue, find root causes of the each problem, and propose possible solutions and solution plans for each requirement (we should also check known existing components/libraries, that solve similar problem or can help in solutions).\n\nIf there is not enough data to find actual root cause, add debug output and verbose mode if not present, that will allow us to find root cause on next iteration.\n\nIf issue related to any other repository/project, where we can report issues on GitHub, please do so. Each issue must contain reproducible examples, workarounds and suggestions for fix the issue in code.","comments":[],"createdAt":"2026-04-27T08:25:26Z","labels":[{"id":"LA_kwDOSNlfVs8AAAACg6iIJA","name":"bug","description":"Something isn't working","color":"d73a4a"}],"number":3,"state":"CLOSED","title":"Release to website at https://link-foundation.github.io/meta-ontology/ should work regardless of success of package release","updatedAt":"2026-04-27T08:59:28Z","url":"https://github.com/link-foundation/meta-ontology/issues/3"} diff --git a/docs/case-studies/issue-38/raw-data/downstream-meta-ontology-pr-4.json b/docs/case-studies/issue-38/raw-data/downstream-meta-ontology-pr-4.json new file mode 100644 index 0000000..281cc7e --- /dev/null +++ b/docs/case-studies/issue-38/raw-data/downstream-meta-ontology-pr-4.json @@ -0,0 +1 @@ +{"author":{"id":"MDQ6VXNlcjE0MzE5MDQ=","is_bot":false,"login":"konard","name":"Konstantin Diachenko"},"baseRefName":"main","body":"## Summary\n\n- Decouple GitHub Pages documentation deployment from package/GitHub release publication.\n- Gate `deploy-docs` on successful `build` for main pushes and manual instant releases, so docs can still publish when package release fails later.\n- Fix the observed `rust-script` release failure under `RUSTFLAGS=-Dwarnings`.\n- Add an issue-3 case study with preserved CI run metadata, compressed failed-run logs, template workflow snapshots, and upstream-template follow-up.\n\nFixes #3\n\n## Reproduction\n\nThe preserved main-branch runs `24979900862` and `24983875003` both showed this sequence:\n\n1. `Build Package` succeeded.\n2. `Auto Release` failed in `Check if version already released or no fragments`.\n3. `Deploy Rust Documentation` was skipped because it depended on release jobs.\n\nThe detailed case study and raw data are in `docs/case-studies/issue-3/`.\n\n## Verification\n\n- `cargo fmt --check`\n- `cargo test --all-features --verbose`\n- `cargo test --doc --verbose`\n- `cargo test --test unit ci_cd::workflow_release::documentation_deploy_is_independent_from_release_publication`\n- `cargo clippy --all-targets --all-features`\n- `rust-script scripts/check-file-size.rs`\n- `RUSTFLAGS=-Dwarnings HAS_FRAGMENTS=true rust-script scripts/check-release-needed.rs`\n- `cargo build --release --verbose`\n- `cargo package --list --allow-dirty`\n\n## Template Follow-Up\n\nThe Rust CI/CD template has the same `deploy-docs` dependency issue. I filed:\n\nhttps://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/38\n","comments":[{"id":"IC_kwDOSNlfVs8AAAABAdHtoA","author":{"login":"konard"},"authorAssociation":"MEMBER","body":"## Solution summary\n\nImplemented and pushed the fix to PR 4: https://github.com/link-foundation/meta-ontology/pull/4\n\nWhat changed:\n- `deploy-docs` now depends only on `build`, so GitHub Pages docs are no longer blocked by package/GitHub release jobs.\n- Fixed the release helper scripts that were tripping `RUSTFLAGS=-Dwarnings`.\n- Added a regression test for docs deployment independence.\n- Added the requested case study/log/template evidence under `docs/case-studies/issue-3/`.\n- Filed upstream template issue: https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/38\n- Follow-up fixed the Windows CI failure by making the workflow test line-ending independent.\n\nVerification:\n- Local: `cargo fmt --check`\n- Local: targeted workflow release test\n- Local: `cargo test --all-features --verbose`\n- Local: `cargo clippy --all-targets --all-features`\n- GitHub Actions: fresh CI run `24985181894` passed on SHA `afa8dab3ecbd272506681b510ed545c302d24291`\n\nPR is ready, not draft, merge state is clean, and the working tree is clean.\n\n---\n*This summary was automatically extracted from the AI working session output.*","createdAt":"2026-04-27T08:46:12Z","includesCreatedEdit":false,"isMinimized":false,"minimizedReason":"","reactionGroups":[],"url":"https://github.com/link-foundation/meta-ontology/pull/4#issuecomment-4325502368","viewerDidAuthor":true},{"id":"IC_kwDOSNlfVs8AAAABAdHzTg","author":{"login":"konard"},"authorAssociation":"MEMBER","body":"## 🤖 Solution Draft Log\nThis log file contains the complete execution trace of the AI solution draft process.\n\n### 💰 **Cost estimation:**\n- Model: GPT-5.5\n- Provider: OpenAI\n- Public pricing estimate: $19.219414\n\n### 📊 **Context and tokens usage:**\n- 14.5M / 1.1M (1380%) input tokens, 34.5K / 128K (27%) output tokens\n\nTotal: (353.6K + 14.1M cached) input tokens, 34.5K output tokens, $19.219414 cost\n\n### 🤖 **Models used:**\n- Tool: OpenAI Codex\n- Requested: `gpt-5.5`\n- **Model: GPT-5.5** (`gpt-5.5`)\n\n### 📎 **Log file uploaded as Repository** (39271KB)\n- [View complete solution draft log](https://raw.githubusercontent.com/konard/public-logs/main/log-tmp-solution-draft-log-pr-1777279576864.txt/tmp-solution-draft-log-pr-1777279576864.txt)\n\n---\n*Now working session is ended, feel free to review and add any feedback on the solution draft.*","createdAt":"2026-04-27T08:46:25Z","includesCreatedEdit":false,"isMinimized":false,"minimizedReason":"","reactionGroups":[],"url":"https://github.com/link-foundation/meta-ontology/pull/4#issuecomment-4325503822","viewerDidAuthor":true},{"id":"IC_kwDOSNlfVs8AAAABAdIwFg","author":{"login":"konard"},"authorAssociation":"MEMBER","body":"## ✅ Ready to merge\n\nThis pull request is now ready to be merged:\n- All CI checks have passed\n- No merge conflicts\n- No pending changes\n\n---\n*Monitored by hive-mind with --auto-restart-until-mergeable flag*","createdAt":"2026-04-27T08:48:46Z","includesCreatedEdit":false,"isMinimized":false,"minimizedReason":"","reactionGroups":[],"url":"https://github.com/link-foundation/meta-ontology/pull/4#issuecomment-4325519382","viewerDidAuthor":true}],"createdAt":"2026-04-27T08:26:10Z","headRefName":"issue-3-296ff7e963a4","headRefOid":"afa8dab3ecbd272506681b510ed545c302d24291","isDraft":false,"mergedAt":"2026-04-27T08:59:27Z","number":4,"state":"MERGED","title":"Fix GitHub Pages docs deployment independence","updatedAt":"2026-04-27T08:59:27Z","url":"https://github.com/link-foundation/meta-ontology/pull/4"} diff --git a/docs/case-studies/issue-38/raw-data/issue-38-comments.json b/docs/case-studies/issue-38/raw-data/issue-38-comments.json new file mode 100644 index 0000000..48ae41e --- /dev/null +++ b/docs/case-studies/issue-38/raw-data/issue-38-comments.json @@ -0,0 +1 @@ +[{"url":"https://api.github.com/repos/link-foundation/rust-ai-driven-development-pipeline-template/issues/comments/4359023289","html_url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/38#issuecomment-4359023289","issue_url":"https://api.github.com/repos/link-foundation/rust-ai-driven-development-pipeline-template/issues/38","id":4359023289,"node_id":"IC_kwDOQvQFhs8AAAABA9FquQ","user":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"created_at":"2026-05-01T11:11:24Z","updated_at":"2026-05-01T11:11:24Z","body":"Use all the best practices from CI/CD templates (check full file tree to compare for all GitHub workflow and CI/CD scripts file), if the same issue is found in template report issue also in templates:\n- https://github.com/link-foundation/js-ai-driven-development-pipeline-template\n- https://github.com/link-foundation/rust-ai-driven-development-pipeline-template\n\nWe should compare all files, so we don't have more CI/CD errors in the future and reuse all the best practices from these templates.\n\nWe need to download all logs and data related about the issue to this repository, make sure we compile that data to `./docs/case-studies/issue-{id}` folder, and use it to do deep case study analysis (also make sure to search online for additional facts and data), in which we will reconstruct timeline/sequence of events, list of each and all requirements from the issue, find root causes of the each problem, and propose possible solutions and solution plans for each requirement (we should also check known existing components/libraries, that solve similar problem or can help in solutions).\n\nIf there is not enough data to find actual root cause, add debug output and verbose mode if not present, that will allow us to find root cause on next iteration.\n\nIf issue related to any other repository/project, where we can report issues on GitHub, please do so. Each issue must contain reproducible examples, workarounds and suggestions for fix the issue in code.\n\nPlease plan and execute everything in a single pull request, you have unlimited time and context, as context autocompacts and you can continue indefinetely, do as much as possible in one go, until it is each and every requirement fully addressed, and everything is totally done.","author_association":"MEMBER","pin":null,"reactions":{"url":"https://api.github.com/repos/link-foundation/rust-ai-driven-development-pipeline-template/issues/comments/4359023289/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"performed_via_github_app":null}] \ No newline at end of file diff --git a/docs/case-studies/issue-38/raw-data/issue-38.json b/docs/case-studies/issue-38/raw-data/issue-38.json new file mode 100644 index 0000000..1e08848 --- /dev/null +++ b/docs/case-studies/issue-38/raw-data/issue-38.json @@ -0,0 +1 @@ +{"author":{"id":"MDQ6VXNlcjE0MzE5MDQ=","is_bot":false,"login":"konard","name":"Konstantin Diachenko"},"body":"## Problem\n\nThe Rust CI/CD template's `deploy-docs` job depends on release publication jobs:\n\n```yaml\ndeploy-docs:\n needs: [auto-release, manual-release]\n```\n\nand only runs when `auto-release` or `manual-release` succeeds. This couples GitHub Pages documentation deployment to package/GitHub release publication.\n\n## Reproducible Example\n\n1. Use the current `release.yml` from `link-foundation/rust-ai-driven-development-pipeline-template`.\n2. Run the workflow on `main` with a package build that succeeds.\n3. Make the release publication path fail, for example by missing/invalid crates.io credentials, a crates.io publish failure, or a release script failure.\n4. Observe that `Deploy Rust Documentation` is skipped because it needs the failed release job.\n\nThe same pattern reproduced in `link-foundation/meta-ontology`:\n\n- `Build Package` succeeded.\n- `Auto Release` failed.\n- `Deploy Rust Documentation` was skipped.\n\nSee downstream issue: https://github.com/link-foundation/meta-ontology/issues/3\n\n## Workaround\n\nAfter a release failure, manually rerun a documentation deployment job or manually build and publish `target/doc` to GitHub Pages.\n\n## Suggested Fix\n\nMake documentation deployment depend on the successful validation/build job instead of release publication jobs. For example:\n\n```yaml\ndeploy-docs:\n needs: [build]\n if: |\n !cancelled() &&\n needs.build.result == 'success' && (\n (github.event_name == 'push' && github.ref == 'refs/heads/main') ||\n (github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'instant')\n )\n```\n\nThis keeps the website release tied to a successful build while allowing it to proceed when package publication or GitHub release creation fails.\n","comments":[{"id":"IC_kwDOQvQFhs8AAAABA9FquQ","author":{"login":"konard"},"authorAssociation":"MEMBER","body":"Use all the best practices from CI/CD templates (check full file tree to compare for all GitHub workflow and CI/CD scripts file), if the same issue is found in template report issue also in templates:\n- https://github.com/link-foundation/js-ai-driven-development-pipeline-template\n- https://github.com/link-foundation/rust-ai-driven-development-pipeline-template\n\nWe should compare all files, so we don't have more CI/CD errors in the future and reuse all the best practices from these templates.\n\nWe need to download all logs and data related about the issue to this repository, make sure we compile that data to `./docs/case-studies/issue-{id}` folder, and use it to do deep case study analysis (also make sure to search online for additional facts and data), in which we will reconstruct timeline/sequence of events, list of each and all requirements from the issue, find root causes of the each problem, and propose possible solutions and solution plans for each requirement (we should also check known existing components/libraries, that solve similar problem or can help in solutions).\n\nIf there is not enough data to find actual root cause, add debug output and verbose mode if not present, that will allow us to find root cause on next iteration.\n\nIf issue related to any other repository/project, where we can report issues on GitHub, please do so. Each issue must contain reproducible examples, workarounds and suggestions for fix the issue in code.\n\nPlease plan and execute everything in a single pull request, you have unlimited time and context, as context autocompacts and you can continue indefinetely, do as much as possible in one go, until it is each and every requirement fully addressed, and everything is totally done.","createdAt":"2026-05-01T11:11:24Z","includesCreatedEdit":false,"isMinimized":false,"minimizedReason":"","reactionGroups":[],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/38#issuecomment-4359023289","viewerDidAuthor":true}],"createdAt":"2026-04-27T08:34:11Z","labels":[{"id":"LA_kwDOQvQFhs8AAAACTZwT0w","name":"bug","description":"Something isn't working","color":"d73a4a"}],"number":38,"state":"OPEN","title":"Decouple documentation deployment from package release publication","updatedAt":"2026-05-01T11:11:28Z","url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/38"} diff --git a/docs/case-studies/issue-38/raw-data/js-template-issue-search.json b/docs/case-studies/issue-38/raw-data/js-template-issue-search.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/docs/case-studies/issue-38/raw-data/js-template-issue-search.json @@ -0,0 +1 @@ +[] diff --git a/docs/case-studies/issue-38/raw-data/main-run-24465255225.json b/docs/case-studies/issue-38/raw-data/main-run-24465255225.json new file mode 100644 index 0000000..0912727 --- /dev/null +++ b/docs/case-studies/issue-38/raw-data/main-run-24465255225.json @@ -0,0 +1 @@ +{"conclusion":"failure","createdAt":"2026-04-15T16:12:07Z","databaseId":24465255225,"displayTitle":"Merge pull request #37 from link-foundation/issue-36-5fb511c4472f","headSha":"353d89396fd420339f2dab0e548ca2caf6198cd5","jobs":[{"completedAt":"2026-04-15T16:12:56Z","conclusion":"success","databaseId":71490396484,"name":"Detect Changes","startedAt":"2026-04-15T16:12:10Z","status":"completed","steps":[{"completedAt":"2026-04-15T16:12:11Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-15T16:12:10Z","status":"completed"},{"completedAt":"2026-04-15T16:12:12Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-15T16:12:11Z","status":"completed"},{"completedAt":"2026-04-15T16:12:12Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-15T16:12:12Z","status":"completed"},{"completedAt":"2026-04-15T16:12:46Z","conclusion":"success","name":"Install rust-script","number":4,"startedAt":"2026-04-15T16:12:12Z","status":"completed"},{"completedAt":"2026-04-15T16:12:54Z","conclusion":"success","name":"Detect changes","number":5,"startedAt":"2026-04-15T16:12:46Z","status":"completed"},{"completedAt":"2026-04-15T16:12:54Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":10,"startedAt":"2026-04-15T16:12:54Z","status":"completed"},{"completedAt":"2026-04-15T16:12:55Z","conclusion":"success","name":"Complete job","number":11,"startedAt":"2026-04-15T16:12:54Z","status":"completed"}],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490396484"},{"completedAt":"2026-04-15T16:12:08Z","conclusion":"skipped","databaseId":71490396957,"name":"Create Changelog PR","startedAt":"2026-04-15T16:12:08Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490396957"},{"completedAt":"2026-04-15T16:12:08Z","conclusion":"skipped","databaseId":71490396985,"name":"Version Modification Check","startedAt":"2026-04-15T16:12:08Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490396985"},{"completedAt":"2026-04-15T16:13:24Z","conclusion":"success","databaseId":71490525776,"name":"Code Coverage","startedAt":"2026-04-15T16:12:58Z","status":"completed","steps":[{"completedAt":"2026-04-15T16:13:03Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-15T16:12:59Z","status":"completed"},{"completedAt":"2026-04-15T16:13:04Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-15T16:13:03Z","status":"completed"},{"completedAt":"2026-04-15T16:13:10Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-15T16:13:04Z","status":"completed"},{"completedAt":"2026-04-15T16:13:12Z","conclusion":"success","name":"Cache cargo registry","number":4,"startedAt":"2026-04-15T16:13:10Z","status":"completed"},{"completedAt":"2026-04-15T16:13:12Z","conclusion":"success","name":"Install cargo-llvm-cov","number":5,"startedAt":"2026-04-15T16:13:12Z","status":"completed"},{"completedAt":"2026-04-15T16:13:17Z","conclusion":"success","name":"Generate code coverage","number":6,"startedAt":"2026-04-15T16:13:12Z","status":"completed"},{"completedAt":"2026-04-15T16:13:19Z","conclusion":"success","name":"Upload coverage to Codecov","number":7,"startedAt":"2026-04-15T16:13:17Z","status":"completed"},{"completedAt":"2026-04-15T16:13:23Z","conclusion":"success","name":"Post Cache cargo registry","number":13,"startedAt":"2026-04-15T16:13:19Z","status":"completed"},{"completedAt":"2026-04-15T16:13:23Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":14,"startedAt":"2026-04-15T16:13:23Z","status":"completed"},{"completedAt":"2026-04-15T16:13:23Z","conclusion":"success","name":"Complete job","number":15,"startedAt":"2026-04-15T16:13:23Z","status":"completed"}],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490525776"},{"completedAt":"2026-04-15T16:13:52Z","conclusion":"success","databaseId":71490525783,"name":"Lint and Format Check","startedAt":"2026-04-15T16:12:59Z","status":"completed","steps":[{"completedAt":"2026-04-15T16:13:02Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-15T16:13:00Z","status":"completed"},{"completedAt":"2026-04-15T16:13:03Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-15T16:13:02Z","status":"completed"},{"completedAt":"2026-04-15T16:13:04Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-15T16:13:03Z","status":"completed"},{"completedAt":"2026-04-15T16:13:40Z","conclusion":"success","name":"Install rust-script","number":4,"startedAt":"2026-04-15T16:13:04Z","status":"completed"},{"completedAt":"2026-04-15T16:13:44Z","conclusion":"success","name":"Cache cargo registry","number":5,"startedAt":"2026-04-15T16:13:40Z","status":"completed"},{"completedAt":"2026-04-15T16:13:45Z","conclusion":"success","name":"Check formatting","number":6,"startedAt":"2026-04-15T16:13:44Z","status":"completed"},{"completedAt":"2026-04-15T16:13:48Z","conclusion":"success","name":"Run Clippy","number":7,"startedAt":"2026-04-15T16:13:45Z","status":"completed"},{"completedAt":"2026-04-15T16:13:49Z","conclusion":"success","name":"Check file size limit","number":8,"startedAt":"2026-04-15T16:13:48Z","status":"completed"},{"completedAt":"2026-04-15T16:13:50Z","conclusion":"success","name":"Post Cache cargo registry","number":15,"startedAt":"2026-04-15T16:13:49Z","status":"completed"},{"completedAt":"2026-04-15T16:13:50Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":16,"startedAt":"2026-04-15T16:13:50Z","status":"completed"},{"completedAt":"2026-04-15T16:13:50Z","conclusion":"success","name":"Complete job","number":17,"startedAt":"2026-04-15T16:13:50Z","status":"completed"}],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490525783"},{"completedAt":"2026-04-15T16:13:50Z","conclusion":"success","databaseId":71490526007,"name":"Test (windows-latest)","startedAt":"2026-04-15T16:12:59Z","status":"completed","steps":[{"completedAt":"2026-04-15T16:13:02Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-15T16:13:00Z","status":"completed"},{"completedAt":"2026-04-15T16:13:06Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-15T16:13:02Z","status":"completed"},{"completedAt":"2026-04-15T16:13:11Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-15T16:13:06Z","status":"completed"},{"completedAt":"2026-04-15T16:13:17Z","conclusion":"success","name":"Cache cargo registry","number":4,"startedAt":"2026-04-15T16:13:11Z","status":"completed"},{"completedAt":"2026-04-15T16:13:36Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-04-15T16:13:17Z","status":"completed"},{"completedAt":"2026-04-15T16:13:37Z","conclusion":"success","name":"Run doc tests","number":6,"startedAt":"2026-04-15T16:13:36Z","status":"completed"},{"completedAt":"2026-04-15T16:13:46Z","conclusion":"success","name":"Post Cache cargo registry","number":11,"startedAt":"2026-04-15T16:13:37Z","status":"completed"},{"completedAt":"2026-04-15T16:13:48Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":12,"startedAt":"2026-04-15T16:13:46Z","status":"completed"},{"completedAt":"2026-04-15T16:13:48Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-04-15T16:13:48Z","status":"completed"}],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490526007"},{"completedAt":"2026-04-15T16:13:29Z","conclusion":"success","databaseId":71490526009,"name":"Test (macos-latest)","startedAt":"2026-04-15T16:12:59Z","status":"completed","steps":[{"completedAt":"2026-04-15T16:13:03Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-15T16:12:59Z","status":"completed"},{"completedAt":"2026-04-15T16:13:07Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-15T16:13:03Z","status":"completed"},{"completedAt":"2026-04-15T16:13:09Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-15T16:13:07Z","status":"completed"},{"completedAt":"2026-04-15T16:13:14Z","conclusion":"success","name":"Cache cargo registry","number":4,"startedAt":"2026-04-15T16:13:09Z","status":"completed"},{"completedAt":"2026-04-15T16:13:20Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-04-15T16:13:14Z","status":"completed"},{"completedAt":"2026-04-15T16:13:21Z","conclusion":"success","name":"Run doc tests","number":6,"startedAt":"2026-04-15T16:13:20Z","status":"completed"},{"completedAt":"2026-04-15T16:13:25Z","conclusion":"success","name":"Post Cache cargo registry","number":11,"startedAt":"2026-04-15T16:13:21Z","status":"completed"},{"completedAt":"2026-04-15T16:13:27Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":12,"startedAt":"2026-04-15T16:13:25Z","status":"completed"},{"completedAt":"2026-04-15T16:13:27Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-04-15T16:13:27Z","status":"completed"}],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490526009"},{"completedAt":"2026-04-15T16:13:27Z","conclusion":"success","databaseId":71490526188,"name":"Test (ubuntu-latest)","startedAt":"2026-04-15T16:12:58Z","status":"completed","steps":[{"completedAt":"2026-04-15T16:13:01Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-15T16:12:59Z","status":"completed"},{"completedAt":"2026-04-15T16:13:01Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-15T16:13:01Z","status":"completed"},{"completedAt":"2026-04-15T16:13:02Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-15T16:13:01Z","status":"completed"},{"completedAt":"2026-04-15T16:13:03Z","conclusion":"success","name":"Cache cargo registry","number":4,"startedAt":"2026-04-15T16:13:02Z","status":"completed"},{"completedAt":"2026-04-15T16:13:18Z","conclusion":"success","name":"Run tests","number":5,"startedAt":"2026-04-15T16:13:03Z","status":"completed"},{"completedAt":"2026-04-15T16:13:18Z","conclusion":"success","name":"Run doc tests","number":6,"startedAt":"2026-04-15T16:13:18Z","status":"completed"},{"completedAt":"2026-04-15T16:13:25Z","conclusion":"success","name":"Post Cache cargo registry","number":11,"startedAt":"2026-04-15T16:13:18Z","status":"completed"},{"completedAt":"2026-04-15T16:13:25Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":12,"startedAt":"2026-04-15T16:13:25Z","status":"completed"},{"completedAt":"2026-04-15T16:13:25Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-04-15T16:13:25Z","status":"completed"}],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490526188"},{"completedAt":"2026-04-15T16:12:56Z","conclusion":"skipped","databaseId":71490526268,"name":"Changelog Fragment Check","startedAt":"2026-04-15T16:12:56Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490526268"},{"completedAt":"2026-04-15T16:14:09Z","conclusion":"success","databaseId":71490675382,"name":"Build Package","startedAt":"2026-04-15T16:13:54Z","status":"completed","steps":[{"completedAt":"2026-04-15T16:13:57Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-15T16:13:55Z","status":"completed"},{"completedAt":"2026-04-15T16:13:58Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-15T16:13:57Z","status":"completed"},{"completedAt":"2026-04-15T16:13:59Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-15T16:13:58Z","status":"completed"},{"completedAt":"2026-04-15T16:14:00Z","conclusion":"success","name":"Cache cargo registry","number":4,"startedAt":"2026-04-15T16:13:59Z","status":"completed"},{"completedAt":"2026-04-15T16:14:05Z","conclusion":"success","name":"Build release","number":5,"startedAt":"2026-04-15T16:14:00Z","status":"completed"},{"completedAt":"2026-04-15T16:14:06Z","conclusion":"success","name":"Check package","number":6,"startedAt":"2026-04-15T16:14:05Z","status":"completed"},{"completedAt":"2026-04-15T16:14:07Z","conclusion":"success","name":"Post Cache cargo registry","number":11,"startedAt":"2026-04-15T16:14:06Z","status":"completed"},{"completedAt":"2026-04-15T16:14:07Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":12,"startedAt":"2026-04-15T16:14:07Z","status":"completed"},{"completedAt":"2026-04-15T16:14:07Z","conclusion":"success","name":"Complete job","number":13,"startedAt":"2026-04-15T16:14:07Z","status":"completed"}],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490675382"},{"completedAt":"2026-04-15T16:15:21Z","conclusion":"failure","databaseId":71490719762,"name":"Auto Release","startedAt":"2026-04-15T16:14:11Z","status":"completed","steps":[{"completedAt":"2026-04-15T16:14:13Z","conclusion":"success","name":"Set up job","number":1,"startedAt":"2026-04-15T16:14:12Z","status":"completed"},{"completedAt":"2026-04-15T16:14:14Z","conclusion":"success","name":"Run actions/checkout@v6","number":2,"startedAt":"2026-04-15T16:14:13Z","status":"completed"},{"completedAt":"2026-04-15T16:14:14Z","conclusion":"success","name":"Setup Rust","number":3,"startedAt":"2026-04-15T16:14:14Z","status":"completed"},{"completedAt":"2026-04-15T16:14:48Z","conclusion":"success","name":"Install rust-script","number":4,"startedAt":"2026-04-15T16:14:14Z","status":"completed"},{"completedAt":"2026-04-15T16:14:48Z","conclusion":"success","name":"Configure git","number":5,"startedAt":"2026-04-15T16:14:48Z","status":"completed"},{"completedAt":"2026-04-15T16:14:56Z","conclusion":"success","name":"Determine bump type from changelog fragments","number":6,"startedAt":"2026-04-15T16:14:48Z","status":"completed"},{"completedAt":"2026-04-15T16:15:20Z","conclusion":"failure","name":"Check if version already released or no fragments","number":7,"startedAt":"2026-04-15T16:14:56Z","status":"completed"},{"completedAt":"2026-04-15T16:15:20Z","conclusion":"skipped","name":"Collect changelog and bump version","number":8,"startedAt":"2026-04-15T16:15:20Z","status":"completed"},{"completedAt":"2026-04-15T16:15:20Z","conclusion":"skipped","name":"Get current version","number":9,"startedAt":"2026-04-15T16:15:20Z","status":"completed"},{"completedAt":"2026-04-15T16:15:20Z","conclusion":"skipped","name":"Build release","number":10,"startedAt":"2026-04-15T16:15:20Z","status":"completed"},{"completedAt":"2026-04-15T16:15:20Z","conclusion":"skipped","name":"Publish to Crates.io","number":11,"startedAt":"2026-04-15T16:15:20Z","status":"completed"},{"completedAt":"2026-04-15T16:15:20Z","conclusion":"skipped","name":"Create GitHub Release","number":12,"startedAt":"2026-04-15T16:15:20Z","status":"completed"},{"completedAt":"2026-04-15T16:15:20Z","conclusion":"success","name":"Post Run actions/checkout@v6","number":24,"startedAt":"2026-04-15T16:15:20Z","status":"completed"},{"completedAt":"2026-04-15T16:15:20Z","conclusion":"success","name":"Complete job","number":25,"startedAt":"2026-04-15T16:15:20Z","status":"completed"}],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490719762"},{"completedAt":"2026-04-15T16:14:09Z","conclusion":"skipped","databaseId":71490720731,"name":"Instant Release","startedAt":"2026-04-15T16:14:09Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490720731"},{"completedAt":"2026-04-15T16:15:22Z","conclusion":"skipped","databaseId":71490914832,"name":"Deploy Rust Documentation","startedAt":"2026-04-15T16:15:22Z","status":"completed","steps":[],"url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225/job/71490914832"}],"status":"completed","url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/actions/runs/24465255225","workflowName":"CI/CD Pipeline"} diff --git a/docs/case-studies/issue-38/raw-data/main-run-24465255225.log.gz b/docs/case-studies/issue-38/raw-data/main-run-24465255225.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..5abf2592c57f21a5fb30eb2ba1372653fca772e2 GIT binary patch literal 89367 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%0D!1?dk0hp=!EUyGN5T}DoF0)GJr+9)TcY3 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 literal 0 HcmV?d00001 diff --git a/docs/case-studies/issue-38/raw-data/rust-template-issue-search.json b/docs/case-studies/issue-38/raw-data/rust-template-issue-search.json new file mode 100644 index 0000000..cc8a938 --- /dev/null +++ b/docs/case-studies/issue-38/raw-data/rust-template-issue-search.json @@ -0,0 +1 @@ +[{"createdAt":"2026-04-27T08:34:11Z","number":38,"state":"OPEN","title":"Decouple documentation deployment from package release publication","updatedAt":"2026-05-01T11:11:28Z","url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/38"}] diff --git a/docs/case-studies/issue-38/template-data/js-template-ci-tree.txt b/docs/case-studies/issue-38/template-data/js-template-ci-tree.txt new file mode 100644 index 0000000..371d093 --- /dev/null +++ b/docs/case-studies/issue-38/template-data/js-template-ci-tree.txt @@ -0,0 +1,22 @@ +/tmp/js-ai-driven-development-pipeline-template/.github/workflows/links.yml +/tmp/js-ai-driven-development-pipeline-template/.github/workflows/release.yml +/tmp/js-ai-driven-development-pipeline-template/scripts/changeset-version.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/check-changesets.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/check-file-line-limits.sh +/tmp/js-ai-driven-development-pipeline-template/scripts/check-mjs-syntax.sh +/tmp/js-ai-driven-development-pipeline-template/scripts/check-release-needed.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/check-version.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/check-web-archive.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/create-github-release.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/create-manual-changeset.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/detect-code-changes.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/format-github-release.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/format-release-notes.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/instant-version-bump.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/js-paths.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/merge-changesets.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/publish-to-npm.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/setup-npm.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/simulate-fresh-merge.sh +/tmp/js-ai-driven-development-pipeline-template/scripts/validate-changeset.mjs +/tmp/js-ai-driven-development-pipeline-template/scripts/version-and-commit.mjs diff --git a/docs/case-studies/issue-38/template-data/js-template-links.yml b/docs/case-studies/issue-38/template-data/js-template-links.yml new file mode 100644 index 0000000..3b7271b --- /dev/null +++ b/docs/case-studies/issue-38/template-data/js-template-links.yml @@ -0,0 +1,81 @@ +name: Broken Link Checker + +on: + push: + branches: + - main + paths: + - '**.md' + - '**.html' + - '.github/workflows/links.yml' + pull_request: + types: [opened, synchronize, reopened] + paths: + - '**.md' + - '**.html' + - '.github/workflows/links.yml' + workflow_dispatch: + +jobs: + link-checker: + name: Check Links + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v6 + + - name: Check links with lychee + id: lychee + uses: lycheeverse/lychee-action@v2 + with: + # Check all Markdown and HTML files + # Exclude case-studies directory - these are research documents from + # external repos with references to files and issues that don't exist + # in this repository (similar exclusion pattern as eslint.config.js) + args: >- + --verbose + --no-progress + --cache + --max-cache-age 1d + --max-retries 3 + --timeout 30 + --exclude-path docs/case-studies + './**/*.md' + './**/*.html' + # Don't fail the workflow immediately - we want to check web archive first + fail: false + # Output file for broken links report (used by check-web-archive.mjs) + output: lychee/out.md + # Write a job summary + jobSummary: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Check broken links against Web Archive + if: steps.lychee.outputs.exit_code != 0 + id: webarchive + run: node scripts/check-web-archive.mjs + env: + LYCHEE_OUTPUT: lychee/out.md + + - name: Fail if broken links found and no web archive fallback + if: steps.lychee.outputs.exit_code != 0 && steps.webarchive.outputs.all_archived != 'true' + run: | + echo "::error::Broken links were detected with no Web Archive fallback available." + echo "" + echo "What happened:" + echo " lychee found one or more broken links in the *.md and *.html files of this repository." + echo " The Web Archive (Wayback Machine) check found no archived versions for some of them." + echo "" + echo "How to fix:" + echo " 1. Review the 'Check links with lychee' step above for a full list of broken links." + echo " 2. For links marked with a '::notice::' annotation above, a Web Archive version exists." + echo " Replace those broken links with the suggested archive.org URL." + echo " 3. For links with no archive version, either:" + echo " a. Find an updated URL that points to the same or equivalent content." + echo " b. Remove the link if the content is no longer relevant." + echo " c. Add the URL to .lycheeignore if it is a known false positive." + echo "" + echo "Report location: lychee/out.md (available as a workflow artifact if configured)." + exit 1 diff --git a/docs/case-studies/issue-38/template-data/js-template-release.yml b/docs/case-studies/issue-38/template-data/js-template-release.yml new file mode 100644 index 0000000..7090eb5 --- /dev/null +++ b/docs/case-studies/issue-38/template-data/js-template-release.yml @@ -0,0 +1,537 @@ +name: Checks and release + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + # Manual release support - consolidated here to work with npm trusted publishing + # npm only allows ONE workflow file as trusted publisher, so all publishing + # must go through this workflow (release.yml) + workflow_dispatch: + inputs: + release_mode: + description: 'Manual release mode' + required: true + type: choice + default: 'instant' + options: + - instant + - changeset-pr + bump_type: + description: 'Manual release type' + required: true + type: choice + options: + - patch + - minor + - major + description: + description: 'Manual release description (optional)' + required: false + type: string + +# Concurrency: Only one workflow run per branch at a time +# - For main branch (releases): cancel older runs to prevent blocking newer releases +# When multiple commits are pushed quickly, we want the latest to release, not wait +# - For PR branches: queue runs to avoid cancelling checks on force-pushes +# See: docs/case-studies/issue-25/DETAILED-COMPARISON.md for context +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} + +jobs: + # === DETECT CHANGES - determines which jobs should run === + detect-changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name != 'workflow_dispatch' + outputs: + mjs-changed: ${{ steps.changes.outputs.mjs-changed }} + js-changed: ${{ steps.changes.outputs.js-changed }} + package-changed: ${{ steps.changes.outputs.package-changed }} + docs-changed: ${{ steps.changes.outputs.docs-changed }} + workflow-changed: ${{ steps.changes.outputs.workflow-changed }} + any-code-changed: ${{ steps.changes.outputs.any-code-changed }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Detect changes + id: changes + run: node scripts/detect-code-changes.mjs + + # === FAST CHECKS - run before slow tests for fastest feedback === + # See: hive-mind CI/CD best practices principle #5 (fast-fail job ordering) + + # Syntax check all .mjs files with node --check (~7s) + test-compilation: + name: Test Compilation + runs-on: ubuntu-latest + needs: [detect-changes] + if: | + github.event_name == 'push' || + needs.detect-changes.outputs.mjs-changed == 'true' || + needs.detect-changes.outputs.js-changed == 'true' + steps: + - uses: actions/checkout@v6 + + - name: Check .mjs syntax + run: bash scripts/check-mjs-syntax.sh + + # Enforce 1500-line limit on .mjs files and release.yml + check-file-line-limits: + name: Check File Line Limits + runs-on: ubuntu-latest + needs: [detect-changes] + if: | + github.event_name == 'push' || + needs.detect-changes.outputs.mjs-changed == 'true' || + needs.detect-changes.outputs.js-changed == 'true' || + needs.detect-changes.outputs.workflow-changed == 'true' + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Simulate fresh merge with base branch (PR only) + if: github.event_name == 'pull_request' + env: + BASE_REF: ${{ github.base_ref }} + run: bash scripts/simulate-fresh-merge.sh + + - name: Check file line limits + run: bash scripts/check-file-line-limits.sh + + # === VERSION CHANGE CHECK === + # Prohibit manual version changes in package.json - versions should only be changed by CI/CD + version-check: + name: Check for Manual Version Changes + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Check for version changes in package.json + env: + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_BASE_REF: ${{ github.base_ref }} + run: node scripts/check-version.mjs + + # === CHANGESET CHECK - only runs on PRs with code changes === + # Docs-only PRs (./docs folder, markdown files) don't require changesets + changeset-check: + name: Check for Changesets + runs-on: ubuntu-latest + needs: [detect-changes] + if: github.event_name == 'pull_request' && needs.detect-changes.outputs.any-code-changed == 'true' + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '24.x' + + - name: Install dependencies + run: npm install + + - name: Check for changesets + env: + # Pass PR context to the validation script + GITHUB_BASE_REF: ${{ github.base_ref }} + GITHUB_BASE_SHA: ${{ github.event.pull_request.base.sha }} + GITHUB_HEAD_SHA: ${{ github.event.pull_request.head.sha }} + run: | + # Skip changeset check for automated version PRs + if [[ "${{ github.head_ref }}" == "changeset-release/"* ]]; then + echo "Skipping changeset check for automated release PR" + exit 0 + fi + + # Run changeset validation script + # This validates that exactly ONE changeset was ADDED by this PR + # Pre-existing changesets from other merged PRs are ignored + node scripts/validate-changeset.mjs + + # === LINT AND FORMAT CHECK === + # Lint runs independently of changeset-check - it's a fast check that should always run + # See: https://github.com/link-assistant/hive-mind/pull/1024 for why this dependency was removed + # IMPORTANT: ESLint includes max-lines rule (1500 lines) to ensure files stay maintainable + # See docs/case-studies/issue-23 for why fresh merge simulation is critical + lint: + name: Lint and Format Check + runs-on: ubuntu-latest + needs: [detect-changes] + if: | + github.event_name == 'push' || + needs.detect-changes.outputs.mjs-changed == 'true' || + needs.detect-changes.outputs.js-changed == 'true' || + needs.detect-changes.outputs.docs-changed == 'true' || + needs.detect-changes.outputs.package-changed == 'true' || + needs.detect-changes.outputs.workflow-changed == 'true' + steps: + - uses: actions/checkout@v6 + with: + # For PRs, fetch enough history to merge with base branch + fetch-depth: 0 + + - name: Simulate fresh merge with base branch (PR only) + if: github.event_name == 'pull_request' + env: + BASE_REF: ${{ github.base_ref }} + run: bash scripts/simulate-fresh-merge.sh + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '24.x' + + - name: Install dependencies + run: npm install + + - name: Run ESLint + run: npm run lint + + - name: Check formatting + run: npm run format:check + + - name: Check code duplication + run: npm run check:duplication + + - name: Check for secrets + run: npx --yes -p secretlint -p @secretlint/secretlint-rule-preset-recommend secretlint "**/*" + + # Test matrix: 3 runtimes (Node.js, Bun, Deno) x 3 OS (Ubuntu, macOS, Windows) + # IMPORTANT: Tests must validate the ACTUAL merge result, not a stale merge preview. + # See docs/case-studies/issue-23 for why this is critical. + # Fast-fail: slow test matrix only runs after fast checks pass (hive-mind principle #5) + test: + name: Test (${{ matrix.runtime }} on ${{ matrix.os }}) + runs-on: ${{ matrix.os }} + needs: + [ + detect-changes, + changeset-check, + test-compilation, + lint, + check-file-line-limits, + ] + # Use !cancelled() instead of always() so cancellation propagates correctly (hive-mind issue #1278) + # Run if: push event, OR changeset-check succeeded, OR changeset-check was skipped (docs-only PR) + # AND all fast checks passed (or were skipped for irrelevant changes) + if: | + !cancelled() && + (github.event_name == 'push' || needs.changeset-check.result == 'success' || needs.changeset-check.result == 'skipped') && + (needs.test-compilation.result == 'success' || needs.test-compilation.result == 'skipped') && + (needs.lint.result == 'success' || needs.lint.result == 'skipped') && + (needs.check-file-line-limits.result == 'success' || needs.check-file-line-limits.result == 'skipped') + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + runtime: [node, bun, deno] + steps: + - uses: actions/checkout@v6 + with: + # For PRs, fetch enough history to merge with base branch + fetch-depth: 0 + + - name: Simulate fresh merge with base branch (PR only) + if: github.event_name == 'pull_request' + env: + BASE_REF: ${{ github.base_ref }} + shell: bash + run: bash scripts/simulate-fresh-merge.sh + + - name: Setup Node.js + if: matrix.runtime == 'node' + uses: actions/setup-node@v6 + with: + node-version: '24.x' + + - name: Install dependencies (Node.js) + if: matrix.runtime == 'node' + run: npm install + + - name: Run tests (Node.js) + if: matrix.runtime == 'node' + run: npm test + + - name: Setup Bun + if: matrix.runtime == 'bun' + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies (Bun) + if: matrix.runtime == 'bun' + run: bun install + + - name: Run tests (Bun) + if: matrix.runtime == 'bun' + run: bun test + + - name: Setup Deno + if: matrix.runtime == 'deno' + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + + - name: Run tests (Deno) + if: matrix.runtime == 'deno' + run: deno test --allow-read + + # === DOCUMENTATION VALIDATION === + # Validate documentation files when docs change (hive-mind principle #12) + validate-docs: + name: Validate Documentation + runs-on: ubuntu-latest + needs: [detect-changes] + if: | + github.event_name == 'push' || + needs.detect-changes.outputs.docs-changed == 'true' + steps: + - uses: actions/checkout@v6 + + - name: Check documentation file sizes + run: | + LIMIT=2500 + FAILURES=() + + echo "Checking that documentation files are under ${LIMIT} lines..." + + while IFS= read -r -d '' file; do + line_count=$(wc -l < "$file") + if [ "$line_count" -gt "$LIMIT" ]; then + echo "ERROR: $file has $line_count lines (limit: ${LIMIT})" + echo "::error file=$file::Documentation file has $line_count lines (limit: ${LIMIT})" + FAILURES+=("$file") + fi + done < <(find docs -name "*.md" -type f -print0 2>/dev/null) + + if [ "${#FAILURES[@]}" -gt 0 ]; then + echo "The following docs exceed the ${LIMIT} line limit:" + printf ' %s\n' "${FAILURES[@]}" + exit 1 + else + echo "All documentation files are within the ${LIMIT} line limit." + fi + + - name: Check required documentation files exist + run: | + REQUIRED_FILES=( + "docs/BEST-PRACTICES.md" + "docs/CONTRIBUTING.md" + "README.md" + "CHANGELOG.md" + ) + + MISSING=() + for file in "${REQUIRED_FILES[@]}"; do + if [ ! -f "$file" ]; then + echo "ERROR: Required documentation file missing: $file" + MISSING+=("$file") + else + echo "Found: $file" + fi + done + + if [ "${#MISSING[@]}" -gt 0 ]; then + echo "" + echo "Missing required documentation files:" + printf ' %s\n' "${MISSING[@]}" + exit 1 + else + echo "All required documentation files present." + fi + + # Release - only runs on main after tests pass (for push events) + release: + name: Release + needs: [lint, test] + # Use !cancelled() instead of always() so cancellation propagates correctly (hive-mind issue #1278) + # This is needed because lint/test jobs have a transitive dependency on changeset-check + if: | + !cancelled() && + github.ref == 'refs/heads/main' && + github.event_name == 'push' && + needs.lint.result == 'success' && + needs.test.result == 'success' + runs-on: ubuntu-latest + # Permissions required for npm OIDC trusted publishing + permissions: + contents: write + pull-requests: write + id-token: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '24.x' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: npm install + + - name: Update npm for OIDC trusted publishing + run: node scripts/setup-npm.mjs + + - name: Check for changesets + id: check_changesets + run: node scripts/check-changesets.mjs + + - name: Check if release is needed + id: check_release + env: + HAS_CHANGESETS: ${{ steps.check_changesets.outputs.has_changesets }} + run: node scripts/check-release-needed.mjs + + - name: Merge multiple changesets + if: steps.check_changesets.outputs.has_changesets == 'true' && steps.check_changesets.outputs.changeset_count > 1 + run: | + echo "Multiple changesets detected, merging..." + node scripts/merge-changesets.mjs + + - name: Version packages and commit to main + if: steps.check_changesets.outputs.has_changesets == 'true' + id: version + run: node scripts/version-and-commit.mjs --mode changeset + + - name: Publish to npm + # Run if version was committed, if a previous attempt already committed (for re-runs), + # or if check-release-needed detected an unpublished version (self-healing, issue #36) + if: >- + steps.version.outputs.version_committed == 'true' || + steps.version.outputs.already_released == 'true' || + (steps.check_release.outputs.should_release == 'true' && steps.check_release.outputs.skip_bump == 'true') + id: publish + run: node scripts/publish-to-npm.mjs --should-pull + + - name: Create GitHub Release + if: steps.publish.outputs.published == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node scripts/create-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" + + - name: Format GitHub release notes + if: steps.publish.outputs.published == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node scripts/format-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --commit-sha "${{ github.sha }}" + + # Manual Instant Release - triggered via workflow_dispatch with instant mode + # This job is in release.yml because npm trusted publishing + # only allows one workflow file to be registered as a trusted publisher + instant-release: + name: Instant Release + if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'instant' + runs-on: ubuntu-latest + # Permissions required for npm OIDC trusted publishing + permissions: + contents: write + pull-requests: write + id-token: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '24.x' + registry-url: 'https://registry.npmjs.org' + + - name: Install dependencies + run: npm install + + - name: Update npm for OIDC trusted publishing + run: node scripts/setup-npm.mjs + + - name: Version packages and commit to main + id: version + run: node scripts/version-and-commit.mjs --mode instant --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" + + - name: Publish to npm + # Run if version was committed OR if a previous attempt already committed (for re-runs) + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + id: publish + run: node scripts/publish-to-npm.mjs + + - name: Create GitHub Release + if: steps.publish.outputs.published == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node scripts/create-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" + + - name: Format GitHub release notes + if: steps.publish.outputs.published == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: node scripts/format-github-release.mjs --release-version "${{ steps.publish.outputs.published_version }}" --repository "${{ github.repository }}" --commit-sha "${{ github.sha }}" + + # Manual Changeset PR - creates a pull request with the changeset for review + changeset-pr: + name: Create Changeset PR + if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'changeset-pr' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '24.x' + + - name: Install dependencies + run: npm install + + - name: Create changeset file + run: node scripts/create-manual-changeset.mjs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" + + - name: Format changeset with Prettier + run: | + # Run Prettier on the changeset file to ensure it matches project style + npx prettier --write ".changeset/*.md" || true + + echo "Formatted changeset files" + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore: add changeset for manual ${{ github.event.inputs.bump_type }} release' + branch: changeset-manual-release-${{ github.run_id }} + delete-branch: true + title: 'chore: manual ${{ github.event.inputs.bump_type }} release' + body: | + ## Manual Release Request + + This PR was created by a manual workflow trigger to prepare a **${{ github.event.inputs.bump_type }}** release. + + ### Release Details + - **Type:** ${{ github.event.inputs.bump_type }} + - **Description:** ${{ github.event.inputs.description || 'Manual release' }} + - **Triggered by:** @${{ github.actor }} + + ### Next Steps + 1. Review the changeset in this PR + 2. Merge this PR to main + 3. The automated release workflow will create a version PR + 4. Merge the version PR to publish to npm and create a GitHub release diff --git a/docs/case-studies/issue-38/template-data/rust-template-ci-tree.txt b/docs/case-studies/issue-38/template-data/rust-template-ci-tree.txt new file mode 100644 index 0000000..bcabbb2 --- /dev/null +++ b/docs/case-studies/issue-38/template-data/rust-template-ci-tree.txt @@ -0,0 +1,16 @@ +.github/workflows/release.yml +scripts/bump-version.rs +scripts/check-changelog-fragment.rs +scripts/check-file-size.rs +scripts/check-release-needed.rs +scripts/check-version-modification.rs +scripts/collect-changelog.rs +scripts/create-changelog-fragment.rs +scripts/create-github-release.rs +scripts/detect-code-changes.rs +scripts/get-bump-type.rs +scripts/get-version.rs +scripts/git-config.rs +scripts/publish-crate.rs +scripts/rust-paths.rs +scripts/version-and-commit.rs diff --git a/docs/case-studies/issue-38/template-data/rust-template-release-after.yml b/docs/case-studies/issue-38/template-data/rust-template-release-after.yml new file mode 100644 index 0000000..1bc4e06 --- /dev/null +++ b/docs/case-studies/issue-38/template-data/rust-template-release-after.yml @@ -0,0 +1,491 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: + inputs: + release_mode: + description: 'Manual release mode' + required: true + type: choice + default: 'instant' + options: + - instant + - changelog-pr + bump_type: + description: 'Version bump type' + required: true + type: choice + options: + - patch + - minor + - major + description: + description: 'Release description (optional)' + required: false + type: string + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref == 'refs/heads/main' }} + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -Dwarnings + # Support both CARGO_REGISTRY_TOKEN (cargo's native env var) and CARGO_TOKEN (for backwards compatibility) + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + +jobs: + # === DETECT CHANGES - determines which jobs should run === + detect-changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name != 'workflow_dispatch' + outputs: + rs-changed: ${{ steps.changes.outputs.rs-changed }} + toml-changed: ${{ steps.changes.outputs.toml-changed }} + docs-changed: ${{ steps.changes.outputs.docs-changed }} + workflow-changed: ${{ steps.changes.outputs.workflow-changed }} + any-code-changed: ${{ steps.changes.outputs.any-code-changed }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Detect changes + id: changes + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + run: rust-script scripts/detect-code-changes.rs + + # === CHANGELOG CHECK - only runs on PRs with code changes === + # Docs-only PRs (./docs folder, markdown files) don't require changelog fragments + changelog: + name: Changelog Fragment Check + runs-on: ubuntu-latest + needs: [detect-changes] + if: github.event_name == 'pull_request' && needs.detect-changes.outputs.any-code-changed == 'true' + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Check for changelog fragments + env: + GITHUB_BASE_REF: ${{ github.base_ref }} + run: rust-script scripts/check-changelog-fragment.rs + + # === VERSION CHECK - prevents manual version modification in PRs === + # This ensures versions are only modified by the automated release pipeline + version-check: + name: Version Modification Check + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Check for manual version changes + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_BASE_REF: ${{ github.base_ref }} + run: rust-script scripts/check-version-modification.rs + + # === LINT AND FORMAT CHECK === + # Lint runs independently of changelog check - it's a fast check that should always run + # See: https://github.com/link-assistant/hive-mind/pull/1024 for why this dependency was removed + lint: + name: Lint and Format Check + runs-on: ubuntu-latest + needs: [detect-changes] + # Note: always() is required because detect-changes is skipped on workflow_dispatch, + # and without always(), this job would also be skipped even though its condition includes workflow_dispatch. + # See: https://github.com/actions/runner/issues/491 + if: | + always() && !cancelled() && ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' || + needs.detect-changes.outputs.toml-changed == 'true' || + needs.detect-changes.outputs.docs-changed == 'true' || + needs.detect-changes.outputs.workflow-changed == 'true' + ) + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Install rust-script + run: cargo install rust-script + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Run Clippy + run: cargo clippy --all-targets --all-features + + - name: Check file size limit + run: rust-script scripts/check-file-size.rs + + # === TEST === + # Test runs independently of changelog check + test: + name: Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + needs: [detect-changes, changelog] + # Run if: push event, OR changelog succeeded, OR changelog was skipped (docs-only PR) + if: always() && !cancelled() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changelog.result == 'success' || needs.changelog.result == 'skipped') + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Run tests + run: cargo test --all-features --verbose + + - name: Run doc tests + run: cargo test --doc --verbose + + # === CODE COVERAGE === + # Generate and upload code coverage using cargo-llvm-cov + coverage: + name: Code Coverage + runs-on: ubuntu-latest + needs: [detect-changes] + if: | + always() && !cancelled() && ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' || + needs.detect-changes.outputs.toml-changed == 'true' + ) + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: llvm-tools-preview + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-coverage- + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Generate code coverage + run: cargo llvm-cov --all-features --lcov --output-path lcov.info + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: lcov.info + fail_ci_if_error: false + + # === BUILD === + # Build package - only runs if lint and test pass + build: + name: Build Package + runs-on: ubuntu-latest + needs: [lint, test] + if: always() && !cancelled() && needs.lint.result == 'success' && needs.test.result == 'success' + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-build- + + - name: Build release + run: cargo build --release --verbose + + - name: Check package + run: cargo package --list --allow-dirty + + # === AUTO RELEASE === + # Automatic release on push to main using changelog fragments + # This job automatically bumps version based on fragments in changelog.d/ + auto-release: + name: Auto Release + needs: [lint, test, build] + # Note: always() ensures consistent behavior with other jobs that depend on jobs using always(). + if: | + always() && !cancelled() && + github.event_name == 'push' && + github.ref == 'refs/heads/main' && + needs.build.result == 'success' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Configure git + run: rust-script scripts/git-config.rs + + - name: Determine bump type from changelog fragments + id: bump_type + run: rust-script scripts/get-bump-type.rs + + - name: Check if version already released or no fragments + id: check + env: + HAS_FRAGMENTS: ${{ steps.bump_type.outputs.has_fragments }} + run: rust-script scripts/check-release-needed.rs + + - name: Collect changelog and bump version + id: version + if: steps.check.outputs.should_release == 'true' && steps.check.outputs.skip_bump != 'true' + run: | + rust-script scripts/version-and-commit.rs \ + --bump-type "${{ steps.bump_type.outputs.bump_type }}" + + - name: Get current version + id: current_version + if: steps.check.outputs.should_release == 'true' + run: rust-script scripts/get-version.rs + + - name: Build release + if: steps.check.outputs.should_release == 'true' + run: cargo build --release + + - name: Publish to Crates.io + if: steps.check.outputs.should_release == 'true' + id: publish-crate + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + run: rust-script scripts/publish-crate.rs + + - name: Create GitHub Release + if: steps.check.outputs.should_release == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Use new_version from version-and-commit when available (tag-checked), else fall back to Cargo.toml version + RELEASE_VERSION="${{ steps.version.outputs.new_version }}" + if [ -z "$RELEASE_VERSION" ]; then + RELEASE_VERSION="${{ steps.current_version.outputs.version }}" + fi + rust-script scripts/create-github-release.rs --release-version "$RELEASE_VERSION" --repository "${{ github.repository }}" + + # === MANUAL INSTANT RELEASE === + # Manual release via workflow_dispatch - only after CI passes + manual-release: + name: Instant Release + needs: [lint, test, build] + # Note: always() is required to evaluate the condition when dependencies use always(). + # The build job ensures lint and test passed before this job runs. + if: | + always() && !cancelled() && + github.event_name == 'workflow_dispatch' && + github.event.inputs.release_mode == 'instant' && + needs.build.result == 'success' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Configure git + run: rust-script scripts/git-config.rs + + - name: Collect changelog fragments + run: rust-script scripts/collect-changelog.rs + + - name: Version and commit + id: version + env: + BUMP_TYPE: ${{ github.event.inputs.bump_type }} + DESCRIPTION: ${{ github.event.inputs.description }} + run: rust-script scripts/version-and-commit.rs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" + + - name: Build release + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + run: cargo build --release + + - name: Publish to Crates.io + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + id: publish-crate + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + run: rust-script scripts/publish-crate.rs + + - name: Create GitHub Release + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: rust-script scripts/create-github-release.rs --release-version "${{ steps.version.outputs.new_version }}" --repository "${{ github.repository }}" + + # === MANUAL CHANGELOG PR === + changelog-pr: + name: Create Changelog PR + if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'changelog-pr' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Create changelog fragment + env: + BUMP_TYPE: ${{ github.event.inputs.bump_type }} + DESCRIPTION: ${{ github.event.inputs.description }} + run: rust-script scripts/create-changelog-fragment.rs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore: add changelog for manual ${{ github.event.inputs.bump_type }} release' + branch: changelog-manual-release-${{ github.run_id }} + delete-branch: true + title: 'chore: manual ${{ github.event.inputs.bump_type }} release' + body: | + ## Manual Release Request + + This PR was created by a manual workflow trigger to prepare a **${{ github.event.inputs.bump_type }}** release. + + ### Release Details + - **Type:** ${{ github.event.inputs.bump_type }} + - **Description:** ${{ github.event.inputs.description || 'Manual release' }} + - **Triggered by:** @${{ github.actor }} + + ### Next Steps + 1. Review the changelog fragment in this PR + 2. Merge this PR to main + 3. The automated release workflow will publish to crates.io and create a GitHub release + + # === DEPLOY DOCUMENTATION === + # Deploy Rust API documentation to GitHub Pages after a successful package build. + # Keep this independent from package/GitHub release publication so the website + # still updates when the release path fails. + deploy-docs: + name: Deploy Rust Documentation + needs: [build] + if: | + !cancelled() && + needs.build.result == 'success' && ( + (github.event_name == 'push' && github.ref == 'refs/heads/main') || + (github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'instant') + ) + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + ref: main + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build documentation + run: cargo doc --no-deps --all-features + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: target/doc diff --git a/docs/case-studies/issue-38/template-data/rust-template-release-before.yml b/docs/case-studies/issue-38/template-data/rust-template-release-before.yml new file mode 100644 index 0000000..e8b6fb7 --- /dev/null +++ b/docs/case-studies/issue-38/template-data/rust-template-release-before.yml @@ -0,0 +1,488 @@ +name: CI/CD Pipeline + +on: + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] + workflow_dispatch: + inputs: + release_mode: + description: 'Manual release mode' + required: true + type: choice + default: 'instant' + options: + - instant + - changelog-pr + bump_type: + description: 'Version bump type' + required: true + type: choice + options: + - patch + - minor + - major + description: + description: 'Release description (optional)' + required: false + type: string + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -Dwarnings + # Support both CARGO_REGISTRY_TOKEN (cargo's native env var) and CARGO_TOKEN (for backwards compatibility) + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + +jobs: + # === DETECT CHANGES - determines which jobs should run === + detect-changes: + name: Detect Changes + runs-on: ubuntu-latest + if: github.event_name != 'workflow_dispatch' + outputs: + rs-changed: ${{ steps.changes.outputs.rs-changed }} + toml-changed: ${{ steps.changes.outputs.toml-changed }} + docs-changed: ${{ steps.changes.outputs.docs-changed }} + workflow-changed: ${{ steps.changes.outputs.workflow-changed }} + any-code-changed: ${{ steps.changes.outputs.any-code-changed }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Detect changes + id: changes + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + run: rust-script scripts/detect-code-changes.rs + + # === CHANGELOG CHECK - only runs on PRs with code changes === + # Docs-only PRs (./docs folder, markdown files) don't require changelog fragments + changelog: + name: Changelog Fragment Check + runs-on: ubuntu-latest + needs: [detect-changes] + if: github.event_name == 'pull_request' && needs.detect-changes.outputs.any-code-changed == 'true' + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Check for changelog fragments + env: + GITHUB_BASE_REF: ${{ github.base_ref }} + run: rust-script scripts/check-changelog-fragment.rs + + # === VERSION CHECK - prevents manual version modification in PRs === + # This ensures versions are only modified by the automated release pipeline + version-check: + name: Version Modification Check + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Check for manual version changes + env: + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_BASE_REF: ${{ github.base_ref }} + run: rust-script scripts/check-version-modification.rs + + # === LINT AND FORMAT CHECK === + # Lint runs independently of changelog check - it's a fast check that should always run + # See: https://github.com/link-assistant/hive-mind/pull/1024 for why this dependency was removed + lint: + name: Lint and Format Check + runs-on: ubuntu-latest + needs: [detect-changes] + # Note: always() is required because detect-changes is skipped on workflow_dispatch, + # and without always(), this job would also be skipped even though its condition includes workflow_dispatch. + # See: https://github.com/actions/runner/issues/491 + if: | + always() && !cancelled() && ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' || + needs.detect-changes.outputs.toml-changed == 'true' || + needs.detect-changes.outputs.docs-changed == 'true' || + needs.detect-changes.outputs.workflow-changed == 'true' + ) + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Install rust-script + run: cargo install rust-script + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Run Clippy + run: cargo clippy --all-targets --all-features + + - name: Check file size limit + run: rust-script scripts/check-file-size.rs + + # === TEST === + # Test runs independently of changelog check + test: + name: Test (${{ matrix.os }}) + runs-on: ${{ matrix.os }} + needs: [detect-changes, changelog] + # Run if: push event, OR changelog succeeded, OR changelog was skipped (docs-only PR) + if: always() && !cancelled() && (github.event_name == 'push' || github.event_name == 'workflow_dispatch' || needs.changelog.result == 'success' || needs.changelog.result == 'skipped') + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo- + + - name: Run tests + run: cargo test --all-features --verbose + + - name: Run doc tests + run: cargo test --doc --verbose + + # === CODE COVERAGE === + # Generate and upload code coverage using cargo-llvm-cov + coverage: + name: Code Coverage + runs-on: ubuntu-latest + needs: [detect-changes] + if: | + always() && !cancelled() && ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' || + needs.detect-changes.outputs.toml-changed == 'true' + ) + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + with: + components: llvm-tools-preview + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-coverage-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-coverage- + + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + + - name: Generate code coverage + run: cargo llvm-cov --all-features --lcov --output-path lcov.info + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v5 + with: + files: lcov.info + fail_ci_if_error: false + + # === BUILD === + # Build package - only runs if lint and test pass + build: + name: Build Package + runs-on: ubuntu-latest + needs: [lint, test] + if: always() && !cancelled() && needs.lint.result == 'success' && needs.test.result == 'success' + steps: + - uses: actions/checkout@v6 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo registry + uses: actions/cache@v5 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-build-${{ hashFiles('**/Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-build- + + - name: Build release + run: cargo build --release --verbose + + - name: Check package + run: cargo package --list --allow-dirty + + # === AUTO RELEASE === + # Automatic release on push to main using changelog fragments + # This job automatically bumps version based on fragments in changelog.d/ + auto-release: + name: Auto Release + needs: [lint, test, build] + # Note: always() ensures consistent behavior with other jobs that depend on jobs using always(). + if: | + always() && !cancelled() && + github.event_name == 'push' && + github.ref == 'refs/heads/main' && + needs.build.result == 'success' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Configure git + run: rust-script scripts/git-config.rs + + - name: Determine bump type from changelog fragments + id: bump_type + run: rust-script scripts/get-bump-type.rs + + - name: Check if version already released or no fragments + id: check + env: + HAS_FRAGMENTS: ${{ steps.bump_type.outputs.has_fragments }} + run: rust-script scripts/check-release-needed.rs + + - name: Collect changelog and bump version + id: version + if: steps.check.outputs.should_release == 'true' && steps.check.outputs.skip_bump != 'true' + run: | + rust-script scripts/version-and-commit.rs \ + --bump-type "${{ steps.bump_type.outputs.bump_type }}" + + - name: Get current version + id: current_version + if: steps.check.outputs.should_release == 'true' + run: rust-script scripts/get-version.rs + + - name: Build release + if: steps.check.outputs.should_release == 'true' + run: cargo build --release + + - name: Publish to Crates.io + if: steps.check.outputs.should_release == 'true' + id: publish-crate + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + run: rust-script scripts/publish-crate.rs + + - name: Create GitHub Release + if: steps.check.outputs.should_release == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Use new_version from version-and-commit when available (tag-checked), else fall back to Cargo.toml version + RELEASE_VERSION="${{ steps.version.outputs.new_version }}" + if [ -z "$RELEASE_VERSION" ]; then + RELEASE_VERSION="${{ steps.current_version.outputs.version }}" + fi + rust-script scripts/create-github-release.rs --release-version "$RELEASE_VERSION" --repository "${{ github.repository }}" + + # === MANUAL INSTANT RELEASE === + # Manual release via workflow_dispatch - only after CI passes + manual-release: + name: Instant Release + needs: [lint, test, build] + # Note: always() is required to evaluate the condition when dependencies use always(). + # The build job ensures lint and test passed before this job runs. + if: | + always() && !cancelled() && + github.event_name == 'workflow_dispatch' && + github.event.inputs.release_mode == 'instant' && + needs.build.result == 'success' + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Configure git + run: rust-script scripts/git-config.rs + + - name: Collect changelog fragments + run: rust-script scripts/collect-changelog.rs + + - name: Version and commit + id: version + env: + BUMP_TYPE: ${{ github.event.inputs.bump_type }} + DESCRIPTION: ${{ github.event.inputs.description }} + run: rust-script scripts/version-and-commit.rs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" + + - name: Build release + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + run: cargo build --release + + - name: Publish to Crates.io + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + id: publish-crate + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN || secrets.CARGO_TOKEN }} + CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} + run: rust-script scripts/publish-crate.rs + + - name: Create GitHub Release + if: steps.version.outputs.version_committed == 'true' || steps.version.outputs.already_released == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: rust-script scripts/create-github-release.rs --release-version "${{ steps.version.outputs.new_version }}" --repository "${{ github.repository }}" + + # === MANUAL CHANGELOG PR === + changelog-pr: + name: Create Changelog PR + if: github.event_name == 'workflow_dispatch' && github.event.inputs.release_mode == 'changelog-pr' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Install rust-script + run: cargo install rust-script + + - name: Create changelog fragment + env: + BUMP_TYPE: ${{ github.event.inputs.bump_type }} + DESCRIPTION: ${{ github.event.inputs.description }} + run: rust-script scripts/create-changelog-fragment.rs --bump-type "${{ github.event.inputs.bump_type }}" --description "${{ github.event.inputs.description }}" + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: 'chore: add changelog for manual ${{ github.event.inputs.bump_type }} release' + branch: changelog-manual-release-${{ github.run_id }} + delete-branch: true + title: 'chore: manual ${{ github.event.inputs.bump_type }} release' + body: | + ## Manual Release Request + + This PR was created by a manual workflow trigger to prepare a **${{ github.event.inputs.bump_type }}** release. + + ### Release Details + - **Type:** ${{ github.event.inputs.bump_type }} + - **Description:** ${{ github.event.inputs.description || 'Manual release' }} + - **Triggered by:** @${{ github.actor }} + + ### Next Steps + 1. Review the changelog fragment in this PR + 2. Merge this PR to main + 3. The automated release workflow will publish to crates.io and create a GitHub release + + # === DEPLOY DOCUMENTATION === + # Deploy Rust API documentation to GitHub Pages after a successful release + deploy-docs: + name: Deploy Rust Documentation + needs: [auto-release, manual-release] + if: | + always() && !cancelled() && ( + needs.auto-release.result == 'success' || + needs.manual-release.result == 'success' + ) + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v6 + with: + ref: main + + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable + + - name: Build documentation + run: cargo doc --no-deps --all-features + + - name: Deploy to GitHub Pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: target/doc diff --git a/docs/case-studies/issue-52/README.md b/docs/case-studies/issue-52/README.md new file mode 100644 index 0000000..1dedbb6 --- /dev/null +++ b/docs/case-studies/issue-52/README.md @@ -0,0 +1,80 @@ +# Case Study: Issue #52 - Track Parity for browser-commander Preview-Regeneration Pattern + +## Summary + +Issue [#52](https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/52) is a **tracking placeholder**, not a code change. The primary host for the pattern is [`link-foundation/js-ai-driven-development-pipeline-template#62`](https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/62). The original real-world implementation lives in [`konard/vk-bot-desktop#52`](https://github.com/konard/vk-bot-desktop/pull/52), which closed [`konard/vk-bot-desktop#51`](https://github.com/konard/vk-bot-desktop/issues/51). + +The pattern automates preview-image regeneration (README screenshots, the Pages site, and the `og:image`) at release time using [`browser-commander`](https://www.npmjs.com/package/browser-commander) and Playwright, then commits the drift back to `main` with `[skip ci]`. This Rust template currently ships no example-app surface that would render those screenshots, so the pattern cannot be applied here today. This case study captures the recipe so that the **next** PR that adds an example-app surface (a renderer, a Pages site with screenshots, or anything visual) can adopt the recipe verbatim instead of re-discovering it. + +## Why this is a tracking issue, not an implementation + +The upstream survey ([`docs/case-studies/issue-51/data/templates/survey.md`](https://github.com/konard/vk-bot-desktop/blob/issue-51-60ec0489f01f/docs/case-studies/issue-51/data/templates/survey.md) in `vk-bot-desktop`) confirmed that none of the four `link-foundation` AI-driven-development-pipeline-template repos ship browser automation or screenshot tooling. For this Rust template specifically: + +- There is no example-app frontend surface in `src/`, `examples/`, or `docs/`. +- The Pages site deployed by `deploy-docs` is `cargo doc` output, which is not screenshot-driven. +- The `og:image` referenced from `README.md` is a static image, not a generated artifact. +- The `scripts/` directory is Rust-script-based (`rust-script`), with no Node.js toolchain wired in. + +Because there is nothing to screenshot, adding a `preview-regen` job today would either run against an empty surface or hard-code fake fixtures. Either choice would drift from the upstream recipe rather than mirror it. The right move is to **register the pattern** so the next contributor adding visual surface picks it up without redoing the audit. + +## The pattern in one paragraph + +A release-time GitHub Actions job boots the example app's built site behind a static HTTP server (no Electron, no devserver), drives it through a locale × theme matrix using `browser.newContext({ locale })` + `commander.emulateMedia({ colorScheme })` + a `localStorage` theme key, captures fresh screenshots via `commander.page.screenshot()` (because `browser-commander@0.8` exposes the raw Playwright page; the package has no native screenshot method as of 0.10.1), then uses `git status --porcelain` drift detection and `git commit -m "... [skip ci]"` to push the drift back to `main`. `PREVIEW_VERBOSE=1` dumps DOM probes (`data-theme`, `lang`, `h1` contents) so CI failures are diagnosable from logs alone. + +## Reference implementation + +The canonical implementation to mirror (do not re-derive): + +- Script: [`scripts/update-preview-images.mjs`](https://github.com/konard/vk-bot-desktop/blob/issue-51-60ec0489f01f/scripts/update-preview-images.mjs) in `vk-bot-desktop`. +- Workflow job: the [`preview-regen` job](https://github.com/konard/vk-bot-desktop/blob/issue-51-60ec0489f01f/.github/workflows/js.yml#L657) in `.github/workflows/js.yml`. +- Triggers: push to `main`, release tag pushes, and `workflow_dispatch`. + +For this Rust template the screenshot script does **not** need to be Rust-native. The `scripts/` directory can shell out to a Node-only script identical to the JavaScript one. Keeping the script in Node sidesteps re-implementing Playwright bindings in Rust and matches the rest of the `link-foundation` ecosystem. + +## Activation checklist (apply when an example-app surface lands) + +When a future PR adds a visual surface to this template, follow this checklist in order. Each item maps directly to a building block in the upstream recipe. + +1. **Identify the screenshot target.** Decide whether the screenshot source is the example app's built site (preferred), a static Pages site, or a headless renderer. The site must be servable over plain HTTP. +2. **Add a Node-only screenshot script.** Copy [`scripts/update-preview-images.mjs`](https://github.com/konard/vk-bot-desktop/blob/issue-51-60ec0489f01f/scripts/update-preview-images.mjs) verbatim, adjust the input/output paths, and keep it in `scripts/` next to the Rust scripts. Do not port it to Rust. +3. **Pin `browser-commander` and Playwright.** Match the pin already used by sibling repos. As of the reference implementation, `browser-commander` is on `0.8` and exposes the raw Playwright page via `commander.page` — so screenshots go through `commander.page.screenshot()`, not a wrapper. +4. **Add a `preview-regen` job to `.github/workflows/release.yml`** (or a new `example-app.yml` if/when the Rust template grows one). The job runs on push to `main`, on release tag pushes, and on `workflow_dispatch`. +5. **Drive the locale × theme matrix from the workflow.** Use `browser.newContext({ locale })` for locale and `commander.emulateMedia({ colorScheme })` + `localStorage` for theme. Do not toggle theme through a UI segmented control — emulation is deterministic, UI toggling is not. +6. **Serve, do not devserver.** Spin up a static HTTP server over the built site (`npx serve` or equivalent) inside the job. Avoid devservers and avoid Electron, which the upstream recipe explicitly sidesteps. +7. **Drift detection + push-back.** Use `git status --porcelain` to detect changed bytes. When changes exist, `git add`, `git commit -m "chore(preview): regenerate preview images [skip ci]"`, and `git push`. The `[skip ci]` token is what keeps the loop from re-triggering itself. +8. **Diagnostic verbosity.** Wire a `PREVIEW_VERBOSE=1` env flag through the script that dumps `data-theme`, `lang`, and visible `h1` contents at capture time. Without this, CI failures in headless mode are nearly undebuggable from logs alone. +9. **Concurrency guard.** Add `concurrency: { group: preview-regen-${{ github.ref }}, cancel-in-progress: false }` to the job so back-to-back pushes don't race each other on the `[skip ci]` commit. +10. **Backfill a changelog fragment.** Use `bump: minor` because the new job is a feature, not a fix. Reference this case study from the fragment so the connection to issue #52 survives the changelog collection step. + +## Why each building block matters + +- **Static HTTP server, not Electron or devserver.** The upstream `vk-bot-desktop` recipe is intentionally renderer-only at screenshot time. This keeps the job runnable on a vanilla `ubuntu-latest` runner without GPU, display server, or Electron build artifacts. +- **`browser.newContext({ locale })` for locale.** Driving locale through the browser context is deterministic, parallelizable across contexts, and does not depend on the app exposing a locale switcher. Switching locale via in-UI controls forces serial state and adds a class of "wrong locale was captured" bugs. +- **`emulateMedia({ colorScheme })` + `localStorage` for theme.** `emulateMedia` covers system-driven theme detection. `localStorage` covers apps that persist explicit user choice. Both together cover the matrix of apps that use either signal or both. +- **`commander.page.screenshot()`, not a wrapper.** `browser-commander@0.8` (and through `0.10.1`) does not expose a screenshot method on the commander itself — only on the raw Playwright page. Calling `commander.page.screenshot()` is the documented escape hatch; future versions may add a wrapper, but this one is stable today. +- **`[skip ci]` on the drift commit.** Without `[skip ci]`, the drift commit re-triggers the workflow, which captures the same (now stable) screenshots, finds no drift, and exits. That is non-fatal but wastes a runner. With `[skip ci]`, the loop is self-terminating. +- **`PREVIEW_VERBOSE=1` DOM probes.** Headless Playwright failures often surface as "screenshot looks wrong" with no log signal. Dumping `data-theme`, `lang`, and `h1` contents at capture time turns those failures into single-glance diagnosis: "wrong theme was applied", "locale didn't load", "page didn't render". + +## Parity with sibling templates + +The same tracking issue exists for the C# template ([`csharp-ai-driven-development-pipeline-template#17`](https://github.com/link-foundation/csharp-ai-driven-development-pipeline-template/issues/17)) and the Python template ([`python-ai-driven-development-pipeline-template#9`](https://github.com/link-foundation/python-ai-driven-development-pipeline-template/issues/9)). All four templates (JS, Rust, C#, Python) are expected to converge on the same `preview-regen` job shape, so when one of them ships the first real implementation the other three should mirror it. + +When this template adopts the pattern, also: + +1. Comment on the upstream JS issue ([#62](https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/62)) linking the implementation PR here. +2. Cross-link the sibling C# and Python tracking issues. +3. Update this case study from "tracking" to "implemented", adding before/after CI run links and a link to the implementation PR. + +## Collected Data + +Raw GitHub data is stored in `raw-data/`: + +- `issue-52.json` — the tracking issue at the time of registration. +- `issue-52-comments.json` — issue comments at the time of registration (empty until the implementation PR lands). +- `js-issue-62.json` — the primary upstream tracking issue on the JS template. +- `vk-bot-desktop-issue-51.json` — the original problem statement that prompted the recipe. +- `vk-bot-desktop-pr-52.json` — the reference implementation PR. + +## Status + +**Tracking — no implementation in this template yet.** The pattern is registered here and ready to be applied as soon as an example-app surface lands. The pull request that registers this case study makes documentation-only changes and does not modify any workflow, script, or runtime code. diff --git a/docs/case-studies/issue-52/raw-data/issue-52-comments.json b/docs/case-studies/issue-52/raw-data/issue-52-comments.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/docs/case-studies/issue-52/raw-data/issue-52-comments.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/docs/case-studies/issue-52/raw-data/issue-52.json b/docs/case-studies/issue-52/raw-data/issue-52.json new file mode 100644 index 0000000..cb53916 --- /dev/null +++ b/docs/case-studies/issue-52/raw-data/issue-52.json @@ -0,0 +1 @@ +{"url":"https://api.github.com/repos/link-foundation/rust-ai-driven-development-pipeline-template/issues/52","repository_url":"https://api.github.com/repos/link-foundation/rust-ai-driven-development-pipeline-template","labels_url":"https://api.github.com/repos/link-foundation/rust-ai-driven-development-pipeline-template/issues/52/labels{/name}","comments_url":"https://api.github.com/repos/link-foundation/rust-ai-driven-development-pipeline-template/issues/52/comments","events_url":"https://api.github.com/repos/link-foundation/rust-ai-driven-development-pipeline-template/issues/52/events","html_url":"https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/52","id":4450135754,"node_id":"I_kwDOQvQFhs8AAAABCT-uyg","number":52,"title":"Track parity for browser-commander preview-regeneration pattern","user":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"labels":[],"state":"open","locked":false,"assignees":[],"milestone":null,"comments":0,"created_at":"2026-05-14T23:44:26Z","updated_at":"2026-05-14T23:44:26Z","closed_at":null,"assignee":null,"author_association":"MEMBER","type":null,"active_lock_reason":null,"sub_issues_summary":{"total":0,"completed":0,"percent_completed":0},"issue_dependencies_summary":{"blocked_by":0,"total_blocked_by":0,"blocking":0,"total_blocking":0},"body":"## Context\n\nCross-link to [link-foundation/js-ai-driven-development-pipeline-template#62](https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/62) which is the primary host for the pattern.\n\n`konard/vk-bot-desktop` recently shipped [PR #52](https://github.com/konard/vk-bot-desktop/pull/52) (closes [issue #51](https://github.com/konard/vk-bot-desktop/issues/51)) which adds a release-time pipeline that auto-regenerates every preview image (README + Pages site + `og:image`) using [`browser-commander`](https://www.npmjs.com/package/browser-commander) + Playwright, then commits the drift back to `main`.\n\nWhile auditing CI parity I checked the four `link-foundation` AI-driven-development-pipeline-template repos and **none** ship browser automation or screenshot tooling. Full per-template survey: [`docs/case-studies/issue-51/data/templates/survey.md`](https://github.com/konard/vk-bot-desktop/blob/issue-51-60ec0489f01f/docs/case-studies/issue-51/data/templates/survey.md).\n\n## Relevance to this template\n\nThere is no example-app surface here today; this is a **tracking issue** so the pattern is registered if/when one is added. The Rust-script-based `scripts/` ecosystem could shell out to a Node-only screenshot script identical to the JS one, since the screenshot job doesn't need to be Rust-native.\n\n## Suggested action\n\n1. Track this issue as a placeholder.\n2. When an example-app surface lands here, mirror the recipe from the primary upstream issue.\n\n## Related\n\n- konard/vk-bot-desktop#51\n- konard/vk-bot-desktop#52\n- link-foundation/js-ai-driven-development-pipeline-template#62 — primary upstream issue\n","closed_by":null,"reactions":{"url":"https://api.github.com/repos/link-foundation/rust-ai-driven-development-pipeline-template/issues/52/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"timeline_url":"https://api.github.com/repos/link-foundation/rust-ai-driven-development-pipeline-template/issues/52/timeline","performed_via_github_app":null,"state_reason":null,"pinned_comment":null} \ No newline at end of file diff --git a/docs/case-studies/issue-52/raw-data/js-issue-62.json b/docs/case-studies/issue-52/raw-data/js-issue-62.json new file mode 100644 index 0000000..14cea1e --- /dev/null +++ b/docs/case-studies/issue-52/raw-data/js-issue-62.json @@ -0,0 +1 @@ +{"url":"https://api.github.com/repos/link-foundation/js-ai-driven-development-pipeline-template/issues/62","repository_url":"https://api.github.com/repos/link-foundation/js-ai-driven-development-pipeline-template","labels_url":"https://api.github.com/repos/link-foundation/js-ai-driven-development-pipeline-template/issues/62/labels{/name}","comments_url":"https://api.github.com/repos/link-foundation/js-ai-driven-development-pipeline-template/issues/62/comments","events_url":"https://api.github.com/repos/link-foundation/js-ai-driven-development-pipeline-template/issues/62/events","html_url":"https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/62","id":4450133908,"node_id":"I_kwDOQmfsR88AAAABCT-nlA","number":62,"title":"Add release-time hook that regenerates example-app screenshots with browser-commander","user":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"labels":[],"state":"open","locked":false,"assignees":[],"milestone":null,"comments":0,"created_at":"2026-05-14T23:43:55Z","updated_at":"2026-05-14T23:43:55Z","closed_at":null,"assignee":null,"author_association":"MEMBER","type":null,"active_lock_reason":null,"sub_issues_summary":{"total":0,"completed":0,"percent_completed":0},"issue_dependencies_summary":{"blocked_by":0,"total_blocked_by":0,"blocking":0,"total_blocking":0},"body":"## Context\n\n`konard/vk-bot-desktop` recently shipped [PR #52](https://github.com/konard/vk-bot-desktop/pull/52) (closes [issue #51](https://github.com/konard/vk-bot-desktop/issues/51)) which adds a release-time pipeline that auto-regenerates every preview image (README + Pages site + `og:image`) using [`browser-commander`](https://www.npmjs.com/package/browser-commander) + Playwright, then commits the drift back to `main`.\n\nWhile auditing CI parity I checked the four `link-foundation` AI-driven-development-pipeline-template repos and **none** ship browser automation or screenshot tooling. Full per-template survey: [`docs/case-studies/issue-51/data/templates/survey.md`](https://github.com/konard/vk-bot-desktop/blob/issue-51-60ec0489f01f/docs/case-studies/issue-51/data/templates/survey.md).\n\nSince this template already has `.github/workflows/example-app.yml` and the richest `scripts/` ecosystem of the four, it's the natural primary host for the pattern.\n\n## Problem\n\nAny example app shipped from this template will accumulate stale README/site screenshots between releases. There is no automation that:\n\n1. Boots the example app (or its built site) in a real browser headlessly.\n2. Captures fresh screenshots across the locale × theme matrix.\n3. Commits drift back to `main` (or surfaces it on PRs).\n\n## Reproducible recipe\n\nThe full implementation lives at [`scripts/update-preview-images.mjs`](https://github.com/konard/vk-bot-desktop/blob/issue-51-60ec0489f01f/scripts/update-preview-images.mjs) and [the `preview-regen` job](https://github.com/konard/vk-bot-desktop/blob/issue-51-60ec0489f01f/.github/workflows/js.yml#L657) in `vk-bot-desktop`. Key building blocks worth pulling upstream:\n\n- Static HTTP server over the renderer dist (no Electron / no devserver needed for capture).\n- `browser.newContext({ locale })` to drive locale (no in-UI segmented-control toggling).\n- `commander.emulateMedia({ colorScheme })` + `localStorage` to drive theme.\n- `commander.page.screenshot()` because `browser-commander@0.8` exposes the raw Playwright page (no native screenshot method as of 0.10.1).\n- `git status --porcelain` drift detection + `git commit -m \"... [skip ci]\"` push-back, so the loop is self-healing without an extra workflow.\n- `PREVIEW_VERBOSE=1` to dump DOM probes (data-theme, lang, h1 contents) so CI failures are diagnosable from logs alone.\n\n## Suggested fix\n\nAdd a `preview-regen` job to `example-app.yml` that:\n\n1. Runs on push to `main`, on release tag pushes, and on `workflow_dispatch`.\n2. Installs the existing `browser-commander` + `playwright` pin already used elsewhere.\n3. Drives the example app's site through a locale × theme matrix.\n4. Commits any drift to `main` with `[skip ci]`, or surfaces a workflow artifact on PRs.\n\nHappy to send a PR if you'd like.\n\n## Related\n\n- konard/vk-bot-desktop#51 — original issue\n- konard/vk-bot-desktop#52 — implementation PR\n","closed_by":null,"reactions":{"url":"https://api.github.com/repos/link-foundation/js-ai-driven-development-pipeline-template/issues/62/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"timeline_url":"https://api.github.com/repos/link-foundation/js-ai-driven-development-pipeline-template/issues/62/timeline","performed_via_github_app":null,"state_reason":null,"pinned_comment":null} \ No newline at end of file diff --git a/docs/case-studies/issue-52/raw-data/vk-bot-desktop-issue-51.json b/docs/case-studies/issue-52/raw-data/vk-bot-desktop-issue-51.json new file mode 100644 index 0000000..30729ab --- /dev/null +++ b/docs/case-studies/issue-52/raw-data/vk-bot-desktop-issue-51.json @@ -0,0 +1 @@ +{"url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/51","repository_url":"https://api.github.com/repos/konard/vk-bot-desktop","labels_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/51/labels{/name}","comments_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/51/comments","events_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/51/events","html_url":"https://github.com/konard/vk-bot-desktop/issues/51","id":4450030976,"node_id":"I_kwDOSYpSBs8AAAABCT4VgA","number":51,"title":"We need to make sure we use browser-commander library to actually update all preview images of the application","user":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"labels":[{"id":10905687240,"node_id":"LA_kwDOSYpSBs8AAAACigeUyA","url":"https://api.github.com/repos/konard/vk-bot-desktop/labels/bug","name":"bug","color":"d73a4a","default":true,"description":"Something isn't working"}],"state":"closed","locked":false,"assignees":[],"milestone":null,"comments":0,"created_at":"2026-05-14T23:18:50Z","updated_at":"2026-05-14T23:58:19Z","closed_at":"2026-05-14T23:58:19Z","assignee":null,"author_association":"OWNER","active_lock_reason":null,"sub_issues_summary":{"total":0,"completed":0,"percent_completed":0},"issue_dependencies_summary":{"blocked_by":0,"total_blocked_by":0,"blocking":0,"total_blocking":0},"body":"So each release ends with all images in README.md, other docs and the website to be exactly as it looks actually. At the moment images are obsolete, and we should not regenerate them manually, everything should be done by Ci/CD at each release.\n\nUse all the best practices from CI/CD templates (check full file tree to compare for all GitHub workflow and CI/CD scripts file), if the same issue is found in template report issue also in templates:\n- https://github.com/link-foundation/js-ai-driven-development-pipeline-template\n- https://github.com/link-foundation/rust-ai-driven-development-pipeline-template\n- https://github.com/link-foundation/python-ai-driven-development-pipeline-template\n- https://github.com/link-foundation/csharp-ai-driven-development-pipeline-template\n\nWe should compare all files, so we don't have more CI/CD errors in the future and reuse all the best practices from these templates.\n\nWe need to download all logs and data related about the issue to this repository, make sure we compile that data to `./docs/case-studies/issue-{id}` folder, and use it to do deep case study analysis (also make sure to search online for additional facts and data), in which we will reconstruct timeline/sequence of events, list of each and all requirements from the issue, find root causes of the each problem, and propose possible solutions and solution plans for each requirement (we should also check known existing components/libraries, that solve similar problem or can help in solutions).\n\nIf there is not enough data to find actual root cause, add debug output and verbose mode if not present, that will allow us to find root cause on next iteration.\n\nIf issue related to any other repository/project, where we can report issues on GitHub, please do so. Each issue must contain reproducible examples, workarounds and suggestions for fix the issue in code.\n\nPlease plan and execute everything in this single pull request, you have unlimited time and context, as context auto-compacts and you can continue indefinitely, until it is each and every requirement fully addressed, and everything is totally done.\n\n","closed_by":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"reactions":{"url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/51/reactions","total_count":0,"+1":0,"-1":0,"laugh":0,"hooray":0,"confused":0,"heart":0,"rocket":0,"eyes":0},"timeline_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/51/timeline","performed_via_github_app":null,"state_reason":"completed","pinned_comment":null} \ No newline at end of file diff --git a/docs/case-studies/issue-52/raw-data/vk-bot-desktop-pr-52.json b/docs/case-studies/issue-52/raw-data/vk-bot-desktop-pr-52.json new file mode 100644 index 0000000..b5693aa --- /dev/null +++ b/docs/case-studies/issue-52/raw-data/vk-bot-desktop-pr-52.json @@ -0,0 +1 @@ +{"url":"https://api.github.com/repos/konard/vk-bot-desktop/pulls/52","id":3686303313,"node_id":"PR_kwDOSYpSBs7buIZR","html_url":"https://github.com/konard/vk-bot-desktop/pull/52","diff_url":"https://github.com/konard/vk-bot-desktop/pull/52.diff","patch_url":"https://github.com/konard/vk-bot-desktop/pull/52.patch","issue_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/52","number":52,"state":"closed","locked":false,"title":"feat(preview): auto-regenerate preview images on release via browser-commander","user":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"body":"## Summary\n\nCloses #51.\n\nAdds an end-to-end pipeline that keeps every preview image in the repo current at each release, with **zero manual recapture**, driven by [`browser-commander`](https://www.npmjs.com/package/browser-commander) as the issue requested.\n\n- **`scripts/update-preview-images.mjs`** (new): builds the renderer, serves it from a local static HTTP server, and uses `browser-commander` + Playwright to recapture the four locale × theme tiles + the share-image fallback + the README landing screenshot.\n- **`preview-regen` job** in `.github/workflows/js.yml` (new): runs on push to `main`, on release tag pushes (`refs/tags/v*`), and on `workflow_dispatch` with `release_mode=checks`. Commits any drift back to `main` with `[skip ci]`.\n- **`fix(renderer): hoist flushSave/applyAndSave`** in `electron/renderer/App.jsx`: pre-existing temporal-dead-zone bug that prevented the renderer from rendering at all when the bundle was loaded outside Electron. Blocking for the screenshot pipeline; in scope for #51.\n- **`docs/case-studies/issue-51/`**: full inventory, root-cause analysis, link-foundation template parity survey, and acceptance criteria.\n\n## What the new script does\n\n| Surface | File regenerated | Strategy |\n| ---------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Site landing tile (en/light) | `site/assets/app-preview-en-light.png` | Renderer bundle served statically; `browser.newContext({ locale: 'en-US' })` + `localStorage['vk-bot-desktop:theme']='light'` + `emulateMedia({ colorScheme: 'light' })`; capture via `commander.page.screenshot()`. |\n| Same, en/dark, ru/light, ru/dark | `site/assets/app-preview-{en,ru}-{light,dark}.png` | Same recipe, looped over `LOCALES × THEMES`. |\n| Share-image fallback | `site/assets/app-preview.png` | Copy of `app-preview-en-light.png`. |\n| README landing | `docs/screenshots/issue-26-pages-en-dark.png` | Same renderer-based capture path with a `1440 × 950` viewport to match the original aspect ratio. |\n\nVerbose mode is wired (`PREVIEW_VERBOSE=1`) so a future regression is diagnosable from CI logs alone (per **R7** in the case study).\n\n## What's intentionally **not** automated\n\n`docs/screenshots/issue-31-macos-*.png` are macOS Gatekeeper system dialogs — they reflect Apple's OS UI, not ours, so they're explicitly out of scope. Documented in `docs/case-studies/issue-51/README.md` § 4.\n\n## Template parity (R5/R8)\n\nI surveyed all four `link-foundation/*-ai-driven-development-pipeline-template` repos. **None** ship browser automation or screenshot tooling. Detailed per-template findings live in `docs/case-studies/issue-51/data/templates/survey.md`. Upstream tracking issues filed:\n\n- [link-foundation/js-ai-driven-development-pipeline-template#62](https://github.com/link-foundation/js-ai-driven-development-pipeline-template/issues/62) — primary host for the pattern\n- [link-foundation/csharp-ai-driven-development-pipeline-template#17](https://github.com/link-foundation/csharp-ai-driven-development-pipeline-template/issues/17) — secondary\n- [link-foundation/rust-ai-driven-development-pipeline-template#52](https://github.com/link-foundation/rust-ai-driven-development-pipeline-template/issues/52) — tracking\n- [link-foundation/python-ai-driven-development-pipeline-template#9](https://github.com/link-foundation/python-ai-driven-development-pipeline-template/issues/9) — tracking\n\n## Test plan\n\n- [x] `npm run lint` → clean\n- [x] `npm run format:check` → clean\n- [x] `npm run check:duplication` → clean\n- [x] `npm test` → 270/270 passing (including the updated `tests/ci-timeouts.test.js` that now enforces `preview-regen: 20`)\n- [x] `npm run preview:update --skip-build` regenerates all six target PNGs against the post-fix renderer bundle, and the resulting images render correctly in both themes and locales\n- [ ] After merge: confirm `preview-regen` job is green on first push to `main`, and that a no-op run (no drift) exits cleanly\n\n## Regenerated preview images\n\n![en/light](https://github.com/konard/vk-bot-desktop/blob/issue-51-60ec0489f01f/site/assets/app-preview-en-light.png?raw=true)\n\n(See also `site/assets/app-preview-{en,ru}-{light,dark}.png` in this PR for the other three tiles.)\n\n## Case study\n\nFull timeline, requirements list, root-cause analysis, library research, and acceptance checklist:\n[`docs/case-studies/issue-51/README.md`](https://github.com/konard/vk-bot-desktop/blob/issue-51-60ec0489f01f/docs/case-studies/issue-51/README.md)\n","created_at":"2026-05-14T23:19:32Z","updated_at":"2026-05-14T23:58:19Z","closed_at":"2026-05-14T23:58:18Z","merged_at":"2026-05-14T23:58:18Z","merge_commit_sha":"9212c1126cb5f072edb92b66bb0455b36caf0720","assignees":[{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false}],"requested_reviewers":[],"requested_teams":[],"labels":[],"milestone":null,"draft":false,"commits_url":"https://api.github.com/repos/konard/vk-bot-desktop/pulls/52/commits","review_comments_url":"https://api.github.com/repos/konard/vk-bot-desktop/pulls/52/comments","review_comment_url":"https://api.github.com/repos/konard/vk-bot-desktop/pulls/comments{/number}","comments_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/52/comments","statuses_url":"https://api.github.com/repos/konard/vk-bot-desktop/statuses/01fbb47c84c57d320e3f91aad3a187dbd37b8fdc","head":{"label":"konard:issue-51-60ec0489f01f","ref":"issue-51-60ec0489f01f","sha":"01fbb47c84c57d320e3f91aad3a187dbd37b8fdc","user":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"repo":{"id":1233801734,"node_id":"R_kgDOSYpSBg","name":"vk-bot-desktop","full_name":"konard/vk-bot-desktop","private":false,"owner":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"html_url":"https://github.com/konard/vk-bot-desktop","description":"A simple application for personal growth","fork":false,"url":"https://api.github.com/repos/konard/vk-bot-desktop","forks_url":"https://api.github.com/repos/konard/vk-bot-desktop/forks","keys_url":"https://api.github.com/repos/konard/vk-bot-desktop/keys{/key_id}","collaborators_url":"https://api.github.com/repos/konard/vk-bot-desktop/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/konard/vk-bot-desktop/teams","hooks_url":"https://api.github.com/repos/konard/vk-bot-desktop/hooks","issue_events_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/events{/number}","events_url":"https://api.github.com/repos/konard/vk-bot-desktop/events","assignees_url":"https://api.github.com/repos/konard/vk-bot-desktop/assignees{/user}","branches_url":"https://api.github.com/repos/konard/vk-bot-desktop/branches{/branch}","tags_url":"https://api.github.com/repos/konard/vk-bot-desktop/tags","blobs_url":"https://api.github.com/repos/konard/vk-bot-desktop/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/konard/vk-bot-desktop/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/konard/vk-bot-desktop/git/refs{/sha}","trees_url":"https://api.github.com/repos/konard/vk-bot-desktop/git/trees{/sha}","statuses_url":"https://api.github.com/repos/konard/vk-bot-desktop/statuses/{sha}","languages_url":"https://api.github.com/repos/konard/vk-bot-desktop/languages","stargazers_url":"https://api.github.com/repos/konard/vk-bot-desktop/stargazers","contributors_url":"https://api.github.com/repos/konard/vk-bot-desktop/contributors","subscribers_url":"https://api.github.com/repos/konard/vk-bot-desktop/subscribers","subscription_url":"https://api.github.com/repos/konard/vk-bot-desktop/subscription","commits_url":"https://api.github.com/repos/konard/vk-bot-desktop/commits{/sha}","git_commits_url":"https://api.github.com/repos/konard/vk-bot-desktop/git/commits{/sha}","comments_url":"https://api.github.com/repos/konard/vk-bot-desktop/comments{/number}","issue_comment_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/comments{/number}","contents_url":"https://api.github.com/repos/konard/vk-bot-desktop/contents/{+path}","compare_url":"https://api.github.com/repos/konard/vk-bot-desktop/compare/{base}...{head}","merges_url":"https://api.github.com/repos/konard/vk-bot-desktop/merges","archive_url":"https://api.github.com/repos/konard/vk-bot-desktop/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/konard/vk-bot-desktop/downloads","issues_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues{/number}","pulls_url":"https://api.github.com/repos/konard/vk-bot-desktop/pulls{/number}","milestones_url":"https://api.github.com/repos/konard/vk-bot-desktop/milestones{/number}","notifications_url":"https://api.github.com/repos/konard/vk-bot-desktop/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/konard/vk-bot-desktop/labels{/name}","releases_url":"https://api.github.com/repos/konard/vk-bot-desktop/releases{/id}","deployments_url":"https://api.github.com/repos/konard/vk-bot-desktop/deployments","created_at":"2026-05-09T11:29:18Z","updated_at":"2026-05-14T23:59:31Z","pushed_at":"2026-05-15T07:41:06Z","git_url":"git://github.com/konard/vk-bot-desktop.git","ssh_url":"git@github.com:konard/vk-bot-desktop.git","clone_url":"https://github.com/konard/vk-bot-desktop.git","svn_url":"https://github.com/konard/vk-bot-desktop","homepage":"https://konard.github.io/vk-bot-desktop/","size":21924,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"has_discussions":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":2,"license":{"key":"unlicense","name":"The Unlicense","spdx_id":"Unlicense","url":"https://api.github.com/licenses/unlicense","node_id":"MDc6TGljZW5zZTE1"},"allow_forking":true,"is_template":false,"web_commit_signoff_required":false,"has_pull_requests":true,"pull_request_creation_policy":"all","topics":[],"visibility":"public","forks":0,"open_issues":2,"watchers":0,"default_branch":"main"}},"base":{"label":"konard:main","ref":"main","sha":"046f065b02e9db0ee2056d2b2943a8262a46e078","user":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"repo":{"id":1233801734,"node_id":"R_kgDOSYpSBg","name":"vk-bot-desktop","full_name":"konard/vk-bot-desktop","private":false,"owner":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"html_url":"https://github.com/konard/vk-bot-desktop","description":"A simple application for personal growth","fork":false,"url":"https://api.github.com/repos/konard/vk-bot-desktop","forks_url":"https://api.github.com/repos/konard/vk-bot-desktop/forks","keys_url":"https://api.github.com/repos/konard/vk-bot-desktop/keys{/key_id}","collaborators_url":"https://api.github.com/repos/konard/vk-bot-desktop/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/konard/vk-bot-desktop/teams","hooks_url":"https://api.github.com/repos/konard/vk-bot-desktop/hooks","issue_events_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/events{/number}","events_url":"https://api.github.com/repos/konard/vk-bot-desktop/events","assignees_url":"https://api.github.com/repos/konard/vk-bot-desktop/assignees{/user}","branches_url":"https://api.github.com/repos/konard/vk-bot-desktop/branches{/branch}","tags_url":"https://api.github.com/repos/konard/vk-bot-desktop/tags","blobs_url":"https://api.github.com/repos/konard/vk-bot-desktop/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/konard/vk-bot-desktop/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/konard/vk-bot-desktop/git/refs{/sha}","trees_url":"https://api.github.com/repos/konard/vk-bot-desktop/git/trees{/sha}","statuses_url":"https://api.github.com/repos/konard/vk-bot-desktop/statuses/{sha}","languages_url":"https://api.github.com/repos/konard/vk-bot-desktop/languages","stargazers_url":"https://api.github.com/repos/konard/vk-bot-desktop/stargazers","contributors_url":"https://api.github.com/repos/konard/vk-bot-desktop/contributors","subscribers_url":"https://api.github.com/repos/konard/vk-bot-desktop/subscribers","subscription_url":"https://api.github.com/repos/konard/vk-bot-desktop/subscription","commits_url":"https://api.github.com/repos/konard/vk-bot-desktop/commits{/sha}","git_commits_url":"https://api.github.com/repos/konard/vk-bot-desktop/git/commits{/sha}","comments_url":"https://api.github.com/repos/konard/vk-bot-desktop/comments{/number}","issue_comment_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues/comments{/number}","contents_url":"https://api.github.com/repos/konard/vk-bot-desktop/contents/{+path}","compare_url":"https://api.github.com/repos/konard/vk-bot-desktop/compare/{base}...{head}","merges_url":"https://api.github.com/repos/konard/vk-bot-desktop/merges","archive_url":"https://api.github.com/repos/konard/vk-bot-desktop/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/konard/vk-bot-desktop/downloads","issues_url":"https://api.github.com/repos/konard/vk-bot-desktop/issues{/number}","pulls_url":"https://api.github.com/repos/konard/vk-bot-desktop/pulls{/number}","milestones_url":"https://api.github.com/repos/konard/vk-bot-desktop/milestones{/number}","notifications_url":"https://api.github.com/repos/konard/vk-bot-desktop/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/konard/vk-bot-desktop/labels{/name}","releases_url":"https://api.github.com/repos/konard/vk-bot-desktop/releases{/id}","deployments_url":"https://api.github.com/repos/konard/vk-bot-desktop/deployments","created_at":"2026-05-09T11:29:18Z","updated_at":"2026-05-14T23:59:31Z","pushed_at":"2026-05-15T07:41:06Z","git_url":"git://github.com/konard/vk-bot-desktop.git","ssh_url":"git@github.com:konard/vk-bot-desktop.git","clone_url":"https://github.com/konard/vk-bot-desktop.git","svn_url":"https://github.com/konard/vk-bot-desktop","homepage":"https://konard.github.io/vk-bot-desktop/","size":21924,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":true,"has_discussions":false,"forks_count":0,"mirror_url":null,"archived":false,"disabled":false,"open_issues_count":2,"license":{"key":"unlicense","name":"The Unlicense","spdx_id":"Unlicense","url":"https://api.github.com/licenses/unlicense","node_id":"MDc6TGljZW5zZTE1"},"allow_forking":true,"is_template":false,"web_commit_signoff_required":false,"has_pull_requests":true,"pull_request_creation_policy":"all","topics":[],"visibility":"public","forks":0,"open_issues":2,"watchers":0,"default_branch":"main"}},"_links":{"self":{"href":"https://api.github.com/repos/konard/vk-bot-desktop/pulls/52"},"html":{"href":"https://github.com/konard/vk-bot-desktop/pull/52"},"issue":{"href":"https://api.github.com/repos/konard/vk-bot-desktop/issues/52"},"comments":{"href":"https://api.github.com/repos/konard/vk-bot-desktop/issues/52/comments"},"review_comments":{"href":"https://api.github.com/repos/konard/vk-bot-desktop/pulls/52/comments"},"review_comment":{"href":"https://api.github.com/repos/konard/vk-bot-desktop/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/konard/vk-bot-desktop/pulls/52/commits"},"statuses":{"href":"https://api.github.com/repos/konard/vk-bot-desktop/statuses/01fbb47c84c57d320e3f91aad3a187dbd37b8fdc"}},"author_association":"OWNER","auto_merge":null,"assignee":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"active_lock_reason":null,"merged":true,"mergeable":null,"rebaseable":null,"mergeable_state":"unknown","merged_by":{"login":"konard","id":1431904,"node_id":"MDQ6VXNlcjE0MzE5MDQ=","avatar_url":"https://avatars.githubusercontent.com/u/1431904?v=4","gravatar_id":"","url":"https://api.github.com/users/konard","html_url":"https://github.com/konard","followers_url":"https://api.github.com/users/konard/followers","following_url":"https://api.github.com/users/konard/following{/other_user}","gists_url":"https://api.github.com/users/konard/gists{/gist_id}","starred_url":"https://api.github.com/users/konard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/konard/subscriptions","organizations_url":"https://api.github.com/users/konard/orgs","repos_url":"https://api.github.com/users/konard/repos","events_url":"https://api.github.com/users/konard/events{/privacy}","received_events_url":"https://api.github.com/users/konard/received_events","type":"User","user_view_type":"public","site_admin":false},"comments":3,"review_comments":0,"maintainer_can_modify":false,"commits":6,"additions":1032,"deletions":33,"changed_files":18} \ No newline at end of file diff --git a/docs/ci-cd/troubleshooting.md b/docs/ci-cd/troubleshooting.md new file mode 100644 index 0000000..b38e5dd --- /dev/null +++ b/docs/ci-cd/troubleshooting.md @@ -0,0 +1,258 @@ +# CI/CD Troubleshooting Guide + +This guide covers common CI/CD issues and their solutions for Rust projects using this template. + +## Table of Contents + +1. [Release Jobs Skipped](#release-jobs-skipped) +2. [Version Already Released (False Positive)](#version-already-released-false-positive) +3. [Crates.io Publishing Fails](#cratesio-publishing-fails) +4. [Docker Hub Publishing Fails](#docker-hub-publishing-fails) +5. [Secret Configuration Issues](#secret-configuration-issues) +6. [Multi-Language Repository Issues](#multi-language-repository-issues) + +--- + +## Release Jobs Skipped + +### Symptom +Release jobs (auto-release or manual-release) are skipped even though you expected them to run. + +### Common Causes + +#### 1. Upstream job was skipped +When a job like `detect-changes` is skipped (e.g., on `workflow_dispatch`), all dependent jobs are also skipped by default. + +**Solution:** Ensure dependent jobs use `always() && !cancelled()` in their conditions: +```yaml +if: | + always() && !cancelled() && ( + github.event_name == 'push' || + github.event_name == 'workflow_dispatch' || + needs.detect-changes.outputs.rs-changed == 'true' + ) +``` + +#### 2. Build or test failed +Release jobs depend on `build` which depends on `lint` and `test`. If any of these fail, release jobs won't run. + +**Solution:** Check the logs for lint, test, and build jobs. Fix any failures before releasing. + +#### 3. Wrong trigger condition +The job condition may not match your trigger event. + +**Solution:** Verify the job's `if` condition matches your trigger: +- `github.event_name == 'push'` for automatic releases on merge +- `github.event_name == 'workflow_dispatch'` for manual triggers + +### Reference +- [GitHub Actions Runner Issue #491](https://github.com/actions/runner/issues/491) + +--- + +## Version Already Released (False Positive) + +### Symptom +The release workflow says "version already released" but the package is not actually on crates.io. + +### Root Cause +The workflow was checking git tags instead of crates.io. Git tags can exist without the package being published (e.g., from previous GitHub-only releases). + +### Solution +This template now checks crates.io directly using the API: +```javascript +const response = await fetch( + `https://crates.io/api/v1/crates/${crateName}/${version}` +); +const isPublished = response.ok && (await response.json()).version; +``` + +### Verification +Check if your package exists on crates.io: +```bash +curl -s "https://crates.io/api/v1/crates/YOUR_CRATE_NAME" | jq +``` + +### Reference +- [browser-commander Issue #29](https://github.com/link-foundation/browser-commander/issues/29) + +--- + +## Crates.io Publishing Fails + +### Symptom +The "Publish to Crates.io" step fails with an error. + +### Common Errors + +#### "please provide a non-empty token" +**Cause:** The `CARGO_REGISTRY_TOKEN` environment variable is empty or not set. + +**Solution:** +1. Ensure you have a secret configured (either `CARGO_REGISTRY_TOKEN` or `CARGO_TOKEN`) +2. Map the secret correctly in your workflow: +```yaml +- name: Publish to Crates.io + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} + run: node scripts/publish-crate.mjs +``` + +#### "already uploaded" or "already exists" +**Cause:** This version was already published to crates.io. + +**Note:** This is handled gracefully by the script and is not a failure. + +#### "unauthorized" or authentication errors +**Cause:** Invalid or expired token. + +**Solution:** +1. Generate a new token at https://crates.io/settings/tokens +2. Update the secret in your repository or organization settings + +### Reference +- [browser-commander Issue #33](https://github.com/link-foundation/browser-commander/issues/33) +- [Cargo Publishing Documentation](https://doc.rust-lang.org/cargo/reference/publishing.html) + +--- + +## Docker Hub Publishing Fails + +### Symptom +The crates.io publish succeeds, but the release workflow fails before or during Docker Hub publishing. + +### Required Configuration + +Docker Hub publishing is optional. It runs only when all of these are true: + +- A root `Dockerfile` exists +- Repository variable `DOCKERHUB_IMAGE` is set to `namespace/repository` +- `DOCKERHUB_USERNAME` is set as a repository variable or secret +- Repository secret `DOCKERHUB_TOKEN` is set + +### Common Errors + +#### "Docker Hub publishing requires DOCKERHUB_USERNAME and DOCKERHUB_TOKEN" +**Cause:** `DOCKERHUB_IMAGE` and `Dockerfile` enabled Docker publishing, but credentials are incomplete. + +**Solution:** Set `DOCKERHUB_USERNAME` and create a Docker Hub access token stored as `DOCKERHUB_TOKEN`. + +#### Docker tag is missing after crates.io already published +**Cause:** A previous release run published the crate, then failed before Docker Hub or GitHub Release completed. + +**Solution:** Re-run the release workflow after fixing the Docker Hub configuration. The release check treats the version as incomplete and recreates missing artifacts without bumping the Cargo version again. + +### Verification +Check whether a Docker Hub tag exists: + +```bash +curl -fsSL "https://hub.docker.com/v2/repositories/NAMESPACE/REPOSITORY/tags/VERSION" +``` + +### Reference +- [Docker GitHub Actions guide](https://docs.docker.com/build/ci/github-actions/) + +--- + +## Secret Configuration Issues + +### Required Secrets + +| Secret Name | Purpose | Where to Get | +|------------|---------|--------------| +| `CARGO_REGISTRY_TOKEN` or `CARGO_TOKEN` | Publish to crates.io | https://crates.io/settings/tokens | +| `DOCKERHUB_TOKEN` | Publish to Docker Hub when `DOCKERHUB_IMAGE` is configured | https://app.docker.com/settings/personal-access-tokens | +| `GITHUB_TOKEN` | Create GitHub releases | Automatic (provided by GitHub) | + +### Organization vs Repository Secrets + +If using organization secrets with different names, map them in your workflow: +```yaml +env: + # Map organization secret to the expected variable name + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} +``` + +### Checking Secret Values + +Secrets are masked in logs, but you can verify they're set: +```yaml +- name: Debug secrets + run: | + if [ -n "$CARGO_REGISTRY_TOKEN" ]; then + echo "CARGO_REGISTRY_TOKEN is set (value masked)" + else + echo "WARNING: CARGO_REGISTRY_TOKEN is NOT set" + fi + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_TOKEN }} +``` + +### Reference +- [GitHub Actions Secrets Documentation](https://docs.github.com/actions/security-guides/using-secrets-in-github-actions) + +--- + +## Multi-Language Repository Issues + +### Symptom +Scripts fail to find `Cargo.toml` or run in the wrong directory. + +### Solution +This template auto-detects the repository structure: +- **Single-language:** `Cargo.toml` in repository root +- **Multi-language:** `Cargo.toml` in `rust/` subfolder + +If auto-detection fails, you can explicitly configure the Rust root: +```bash +# Via environment variable +RUST_ROOT=rust node scripts/publish-crate.mjs + +# Via CLI argument +node scripts/publish-crate.mjs --rust-root rust +``` + +### Workflow Configuration +For multi-language repos, ensure your workflow has the correct `working-directory`: +```yaml +defaults: + run: + working-directory: rust + +steps: + - name: Publish to Crates.io + working-directory: . # Override for scripts that handle paths themselves + run: node rust/scripts/publish-crate.mjs +``` + +### Reference +- [browser-commander Issue #31](https://github.com/link-foundation/browser-commander/issues/31) + +--- + +## General Debugging Tips + +### 1. Check Job Dependencies +View the workflow graph in GitHub Actions to see which jobs depend on which. + +### 2. Download Full Logs +```bash +gh run view --repo owner/repo --log > ci-logs.txt +``` + +### 3. Enable Debug Logging +Add this secret to enable debug logging: +- Name: `ACTIONS_STEP_DEBUG` +- Value: `true` + +### 4. Check crates.io Status +Sometimes crates.io has issues. Check: https://status.crates.io/ + +### 5. Verify Package Locally +Before pushing, verify your package builds and passes checks: +```bash +cargo fmt --all -- --check +cargo clippy --all-targets --all-features +cargo test --all-features +cargo package --list +``` diff --git a/examples/basic_usage.rs b/examples/basic_usage.rs new file mode 100644 index 0000000..be9a37f --- /dev/null +++ b/examples/basic_usage.rs @@ -0,0 +1,7 @@ +use example_sum_package_name::sum; + +fn main() { + println!("2 + 3 = {}", sum(2, 3)); + println!("-5 + 10 = {}", sum(-5, 10)); + println!("1000 + 2000 = {}", sum(1000, 2000)); +} diff --git a/experiments/test-changelog-parsing.rs b/experiments/test-changelog-parsing.rs new file mode 100644 index 0000000..bf19a45 --- /dev/null +++ b/experiments/test-changelog-parsing.rs @@ -0,0 +1,100 @@ +#!/usr/bin/env rust-script +//! Test script to verify changelog parsing without look-ahead regex +//! +//! ```cargo +//! [dependencies] +//! regex = "1" +//! ``` + +use regex::Regex; + +fn get_changelog_for_version(content: &str, version: &str) -> String { + let escaped_version = regex::escape(version); + let header_pattern = format!(r"(?m)^## \[{}\]", escaped_version); + let header_re = Regex::new(&header_pattern).unwrap(); + + if let Some(m) = header_re.find(content) { + let after_header = &content[m.end()..]; + let body_start = after_header.find('\n').map_or(after_header.len(), |i| i + 1); + let body = &after_header[body_start..]; + + let next_section_re = Regex::new(r"(?m)^## \[").unwrap(); + let section_body = if let Some(next) = next_section_re.find(body) { + &body[..next.start()] + } else { + body + }; + + let trimmed = section_body.trim(); + if trimmed.is_empty() { + format!("Release v{}", version) + } else { + trimmed.to_string() + } + } else { + format!("Release v{}", version) + } +} + +fn main() { + let changelog = r#"# Changelog + +## [0.3.0] - 2026-04-13 + +### Added +- Feature A +- Feature B + +### Fixed +- Bug fix C + +## [0.2.0] - 2026-03-11 + +### Added +- Feature D + +## [0.1.0] - 2025-01-01 + +### Added +- Initial release +"#; + + // Test 1: Extract middle version (has next section) + let result = get_changelog_for_version(changelog, "0.3.0"); + assert!(result.contains("Feature A"), "Should contain Feature A, got: {}", result); + assert!(result.contains("Bug fix C"), "Should contain Bug fix C, got: {}", result); + assert!(!result.contains("Feature D"), "Should NOT contain Feature D, got: {}", result); + println!("PASS: Test 1 - Middle version extraction"); + + // Test 2: Extract last version (no next section) + let result = get_changelog_for_version(changelog, "0.1.0"); + assert!(result.contains("Initial release"), "Should contain Initial release, got: {}", result); + println!("PASS: Test 2 - Last version extraction"); + + // Test 3: Non-existent version + let result = get_changelog_for_version(changelog, "9.9.9"); + assert_eq!(result, "Release v9.9.9", "Should return default, got: {}", result); + println!("PASS: Test 3 - Non-existent version"); + + // Test 4: Version with special regex chars + let result = get_changelog_for_version(changelog, "0.2.0"); + assert!(result.contains("Feature D"), "Should contain Feature D, got: {}", result); + assert!(!result.contains("Initial release"), "Should NOT contain Initial release, got: {}", result); + println!("PASS: Test 4 - Version with dots (regex escape)"); + + // Test 5: Empty section + let changelog_empty = r#"# Changelog + +## [1.0.0] - 2026-01-01 + +## [0.9.0] - 2025-12-01 + +### Added +- Something +"#; + let result = get_changelog_for_version(changelog_empty, "1.0.0"); + assert_eq!(result, "Release v1.0.0", "Empty section should return default, got: {}", result); + println!("PASS: Test 5 - Empty section fallback"); + + println!("\nAll tests passed!"); +} diff --git a/experiments/test-crates-io-check.rs b/experiments/test-crates-io-check.rs new file mode 100644 index 0000000..0741c6f --- /dev/null +++ b/experiments/test-crates-io-check.rs @@ -0,0 +1,104 @@ +#!/usr/bin/env rust-script +//! Test script for crates.io version check logic +//! Validates that the check_version_on_crates_io function works correctly +//! +//! ```cargo +//! [dependencies] +//! ureq = "2" +//! serde = { version = "1", features = ["derive"] } +//! serde_json = "1" +//! ``` + +use serde::Deserialize; + +#[derive(Deserialize)] +struct CratesIoVersion { + version: Option, +} + +#[derive(Deserialize)] +struct CratesIoVersionInfo { + #[allow(dead_code)] + num: String, +} + +fn check_version_on_crates_io(crate_name: &str, version: &str) -> bool { + let url = format!("https://crates.io/api/v1/crates/{}/{}", crate_name, version); + + match ureq::get(&url) + .set("User-Agent", "rust-script-version-and-commit") + .call() + { + Ok(response) => { + if response.status() == 200 { + if let Ok(body) = response.into_string() { + if let Ok(data) = serde_json::from_str::(&body) { + return data.version.is_some(); + } + } + } + false + } + Err(ureq::Error::Status(404, _)) => false, + Err(e) => { + eprintln!("Warning: Could not check crates.io: {}", e); + false + } + } +} + +fn main() { + let mut passed = 0; + let mut failed = 0; + + // Test 1: Known published crate version (serde 1.0.0 is guaranteed to exist) + print!("Test 1: Known published version (serde 1.0.0)... "); + let result = check_version_on_crates_io("serde", "1.0.0"); + if result { + println!("PASS"); + passed += 1; + } else { + println!("FAIL (expected true, got false)"); + failed += 1; + } + + // Test 2: Non-existent version of a known crate + print!("Test 2: Non-existent version (serde 999.999.999)... "); + let result = check_version_on_crates_io("serde", "999.999.999"); + if !result { + println!("PASS"); + passed += 1; + } else { + println!("FAIL (expected false, got true)"); + failed += 1; + } + + // Test 3: Completely non-existent crate + print!("Test 3: Non-existent crate (this-crate-definitely-does-not-exist-12345 0.1.0)... "); + let result = check_version_on_crates_io("this-crate-definitely-does-not-exist-12345", "0.1.0"); + if !result { + println!("PASS"); + passed += 1; + } else { + println!("FAIL (expected false, got true)"); + failed += 1; + } + + // Test 4: Another known version (regex 1.0.0) + print!("Test 4: Known published version (regex 1.0.0)... "); + let result = check_version_on_crates_io("regex", "1.0.0"); + if result { + println!("PASS"); + passed += 1; + } else { + println!("FAIL (expected true, got false)"); + failed += 1; + } + + println!(); + println!("Results: {} passed, {} failed", passed, failed); + + if failed > 0 { + std::process::exit(1); + } +} diff --git a/experiments/test-detect-code-changes.sh b/experiments/test-detect-code-changes.sh new file mode 100644 index 0000000..b63302f --- /dev/null +++ b/experiments/test-detect-code-changes.sh @@ -0,0 +1,114 @@ +#!/bin/bash +# Test script for detect-code-changes.rs merge commit detection logic +# +# This script creates a temporary git repository with a synthetic merge +# commit (similar to GitHub Actions' pull_request checkout) and verifies +# that the detect-code-changes script correctly uses per-commit diff. +# +# Usage: bash experiments/test-detect-code-changes.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +TEMP_DIR=$(mktemp -d) +trap "rm -rf $TEMP_DIR" EXIT + +echo "=== Test: detect-code-changes merge commit detection ===" +echo "Temp dir: $TEMP_DIR" +echo "" + +cd "$TEMP_DIR" +git init -b main test-repo +cd test-repo +git config user.email "test@test.com" +git config user.name "Test" + +# --- Setup: Create a main branch with initial files --- +echo "fn main() {}" > main.rs +echo "[package]" > Cargo.toml +git add . +git commit -m "Initial commit" + +# --- Create a PR branch with two commits --- +git checkout -b pr-branch + +# Commit 1: touches code files +echo "fn foo() {}" >> main.rs +git add main.rs +git commit -m "Add foo function" + +# Commit 2: touches only non-code files (like .gitkeep) +touch .gitkeep +git add .gitkeep +git commit -m "Add .gitkeep only" + +PR_HEAD=$(git rev-parse HEAD) +PR_HEAD_PARENT=$(git rev-parse HEAD^) + +# --- Go back to main and create a synthetic merge commit --- +git checkout main +git merge --no-ff pr-branch -m "Merge PR" + +echo "" +echo "=== Git log ===" +git log --oneline --graph --all +echo "" + +echo "=== Merge commit verification ===" +PARENT_COUNT=$(git cat-file -p HEAD | grep "^parent " | wc -l) +echo "HEAD parent count: $PARENT_COUNT (expected: 2)" + +HEAD_SECOND_PARENT=$(git rev-parse HEAD^2) +echo "HEAD^2 (PR head): $HEAD_SECOND_PARENT" +echo "Expected PR head: $PR_HEAD" + +if [ "$HEAD_SECOND_PARENT" = "$PR_HEAD" ]; then + echo "PASS: HEAD^2 correctly points to PR head" +else + echo "FAIL: HEAD^2 does not point to PR head" + exit 1 +fi + +echo "" +echo "=== Diff comparisons ===" + +echo "" +echo "--- Full PR diff (HEAD^ to HEAD) - WRONG for per-commit ---" +git diff --name-only HEAD^ HEAD +FULL_DIFF_COUNT=$(git diff --name-only HEAD^ HEAD | wc -l) +echo "Files: $FULL_DIFF_COUNT (includes main.rs + .gitkeep = both commits)" + +echo "" +echo "--- Per-commit diff (HEAD^2^ to HEAD^2) - CORRECT ---" +git diff --name-only HEAD^2^ HEAD^2 +PERCOMMIT_DIFF_COUNT=$(git diff --name-only HEAD^2^ HEAD^2 | wc -l) +echo "Files: $PERCOMMIT_DIFF_COUNT (should be only .gitkeep = last commit only)" + +echo "" +echo "=== Assertions ===" + +if [ "$FULL_DIFF_COUNT" -eq 2 ]; then + echo "PASS: Full PR diff shows 2 files (both commits merged together)" +else + echo "FAIL: Expected 2 files in full PR diff, got $FULL_DIFF_COUNT" + exit 1 +fi + +if [ "$PERCOMMIT_DIFF_COUNT" -eq 1 ]; then + echo "PASS: Per-commit diff shows 1 file (only latest commit)" +else + echo "FAIL: Expected 1 file in per-commit diff, got $PERCOMMIT_DIFF_COUNT" + exit 1 +fi + +PERCOMMIT_FILE=$(git diff --name-only HEAD^2^ HEAD^2) +if [ "$PERCOMMIT_FILE" = ".gitkeep" ]; then + echo "PASS: Per-commit diff correctly shows only .gitkeep" +else + echo "FAIL: Expected .gitkeep, got $PERCOMMIT_FILE" + exit 1 +fi + +echo "" +echo "=== All tests passed ===" diff --git a/experiments/test-version-check-dependencies.sh b/experiments/test-version-check-dependencies.sh new file mode 100755 index 0000000..17b269a --- /dev/null +++ b/experiments/test-version-check-dependencies.sh @@ -0,0 +1,22 @@ +#!/bin/bash +# Test script for version check - dependency changes only (no version change) + +echo "=== Test: Cargo.toml changed but NOT version ===" +# Save original +cp Cargo.toml Cargo.toml.bak + +# Create a temporary commit with non-version change +git checkout -b test-deps-change-branch 2>/dev/null || git checkout test-deps-change-branch 2>/dev/null +echo '# Test comment' >> Cargo.toml +git add Cargo.toml +git commit -m "Test dependency change" --no-verify 2>/dev/null || true + +# Run the check +GITHUB_EVENT_NAME=pull_request GITHUB_HEAD_REF=test-deps-change-branch GITHUB_BASE_REF=main node scripts/check-version-modification.mjs +echo "Exit code: $?" + +# Restore +git checkout issue-14-9d4fe6371f90 2>/dev/null +cp Cargo.toml.bak Cargo.toml +rm Cargo.toml.bak +git branch -D test-deps-change-branch 2>/dev/null || true diff --git a/experiments/test-version-check.sh b/experiments/test-version-check.sh new file mode 100755 index 0000000..57c317d --- /dev/null +++ b/experiments/test-version-check.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# Test script for version check + +echo "=== Test 1: No version change in Cargo.toml ===" +GITHUB_EVENT_NAME=pull_request GITHUB_HEAD_REF=test-branch GITHUB_BASE_REF=main node scripts/check-version-modification.mjs +echo "Exit code: $?" +echo "" + +echo "=== Test 2: Automated release branch (should skip) ===" +GITHUB_EVENT_NAME=pull_request GITHUB_HEAD_REF=changelog-manual-release-12345 GITHUB_BASE_REF=main node scripts/check-version-modification.mjs +echo "Exit code: $?" +echo "" + +echo "=== Test 3: Non-PR event (should skip) ===" +GITHUB_EVENT_NAME=push GITHUB_HEAD_REF=main GITHUB_BASE_REF=main node scripts/check-version-modification.mjs +echo "Exit code: $?" +echo "" + +echo "=== Test 4: Simulating version change ===" +# Save original +cp Cargo.toml Cargo.toml.bak + +# Create a temporary commit with version change +git checkout -b test-version-change-branch 2>/dev/null || git checkout test-version-change-branch 2>/dev/null +sed -i 's/version = "0.1.0"/version = "0.2.0"/' Cargo.toml +git add Cargo.toml +git commit -m "Test version change" --no-verify 2>/dev/null || true + +# Run the check +GITHUB_EVENT_NAME=pull_request GITHUB_HEAD_REF=test-version-change-branch GITHUB_BASE_REF=main node scripts/check-version-modification.mjs +echo "Exit code: $?" + +# Restore +git checkout issue-14-9d4fe6371f90 2>/dev/null +cp Cargo.toml.bak Cargo.toml +rm Cargo.toml.bak +git branch -D test-version-change-branch 2>/dev/null || true diff --git a/scripts/bump-version.rs b/scripts/bump-version.rs new file mode 100644 index 0000000..fe1daee --- /dev/null +++ b/scripts/bump-version.rs @@ -0,0 +1,165 @@ +#!/usr/bin/env rust-script +//! Bump version in Cargo.toml +//! +//! Usage: rust-script scripts/bump-version.rs --bump-type [--dry-run] [--rust-root ] +//! +//! Supports both single-language and multi-language repository structures: +//! - Single-language: Cargo.toml in repository root +//! - Multi-language: Cargo.toml in rust/ subfolder +//! +//! ```cargo +//! [dependencies] +//! regex = "1" +//! ``` + +use std::env; +use std::fs; +use std::process::exit; +use regex::Regex; + +#[path = "rust-paths.rs"] +mod rust_paths; + +#[derive(Debug, Clone, Copy, PartialEq)] +enum BumpType { + Major, + Minor, + Patch, +} + +impl BumpType { + fn from_str(s: &str) -> Option { + match s.to_lowercase().as_str() { + "major" => Some(BumpType::Major), + "minor" => Some(BumpType::Minor), + "patch" => Some(BumpType::Patch), + _ => None, + } + } +} + +struct Version { + major: u32, + minor: u32, + patch: u32, +} + +impl Version { + fn bump(&self, bump_type: BumpType) -> String { + match bump_type { + BumpType::Major => format!("{}.0.0", self.major + 1), + BumpType::Minor => format!("{}.{}.0", self.major, self.minor + 1), + BumpType::Patch => format!("{}.{}.{}", self.major, self.minor, self.patch + 1), + } + } + + fn to_string(&self) -> String { + format!("{}.{}.{}", self.major, self.minor, self.patch) + } +} + +fn get_arg(name: &str) -> Option { + let args: Vec = env::args().collect(); + let flag = format!("--{}", name); + + if let Some(idx) = args.iter().position(|a| a == &flag) { + return args.get(idx + 1).cloned(); + } + + // Check environment variable (convert dashes to underscores) + let env_name = name.to_uppercase().replace('-', "_"); + env::var(&env_name).ok().filter(|s| !s.is_empty()) +} + +fn has_flag(name: &str) -> bool { + let args: Vec = env::args().collect(); + let flag = format!("--{}", name); + args.contains(&flag) +} + +fn get_current_version(cargo_toml_path: &str) -> Result { + let content = fs::read_to_string(cargo_toml_path) + .map_err(|e| format!("Failed to read {}: {}", cargo_toml_path, e))?; + + let re = Regex::new(r#"(?m)^version\s*=\s*"(\d+)\.(\d+)\.(\d+)""#).unwrap(); + + if let Some(caps) = re.captures(&content) { + let major: u32 = caps.get(1).unwrap().as_str().parse().unwrap(); + let minor: u32 = caps.get(2).unwrap().as_str().parse().unwrap(); + let patch: u32 = caps.get(3).unwrap().as_str().parse().unwrap(); + Ok(Version { major, minor, patch }) + } else { + Err(format!("Could not parse version from {}", cargo_toml_path)) + } +} + +fn update_cargo_toml(cargo_toml_path: &str, new_version: &str) -> Result<(), String> { + let content = fs::read_to_string(cargo_toml_path) + .map_err(|e| format!("Failed to read {}: {}", cargo_toml_path, e))?; + + let re = Regex::new(r#"(?m)^(version\s*=\s*")[^"]+(")"#).unwrap(); + let new_content = re.replace(&content, format!("${{1}}{}${{2}}", new_version).as_str()); + + fs::write(cargo_toml_path, new_content.as_ref()) + .map_err(|e| format!("Failed to write {}: {}", cargo_toml_path, e))?; + + Ok(()) +} + +fn main() { + let bump_type_str = match get_arg("bump-type") { + Some(s) => s, + None => { + eprintln!("Usage: rust-script scripts/bump-version.rs --bump-type [--dry-run] [--rust-root ]"); + exit(1); + } + }; + + let bump_type = match BumpType::from_str(&bump_type_str) { + Some(bt) => bt, + None => { + eprintln!("Invalid bump type: {}. Must be major, minor, or patch.", bump_type_str); + exit(1); + } + }; + + let dry_run = has_flag("dry-run"); + let rust_root = match rust_paths::get_rust_root(None, true) { + Ok(root) => root, + Err(e) => { + eprintln!("Error: {}", e); + exit(1); + } + }; + let cargo_toml = rust_paths::get_cargo_toml_path(&rust_root); + let package_manifest = match rust_paths::get_package_manifest_path(&cargo_toml) { + Ok(path) => path, + Err(e) => { + eprintln!("Error: {}", e); + exit(1); + } + }; + + let current = match get_current_version(package_manifest.to_string_lossy().as_ref()) { + Ok(v) => v, + Err(e) => { + eprintln!("Error: {}", e); + exit(1); + } + }; + + let new_version = current.bump(bump_type); + + println!("Current version: {}", current.to_string()); + println!("New version: {}", new_version); + + if dry_run { + println!("Dry run - no changes made"); + } else { + if let Err(e) = update_cargo_toml(package_manifest.to_string_lossy().as_ref(), &new_version) { + eprintln!("Error: {}", e); + exit(1); + } + println!("Updated {}", package_manifest.display()); + } +} diff --git a/scripts/check-changelog-fragment.rs b/scripts/check-changelog-fragment.rs new file mode 100644 index 0000000..70faf74 --- /dev/null +++ b/scripts/check-changelog-fragment.rs @@ -0,0 +1,164 @@ +#!/usr/bin/env rust-script +//! Check if a changelog fragment was added in the current PR +//! +//! This script validates that a changelog fragment is added in the PR diff, +//! not just checking if any fragments exist in the directory. This prevents +//! the check from incorrectly passing when there are leftover fragments +//! from previous PRs that haven't been released yet. +//! +//! Usage: rust-script scripts/check-changelog-fragment.rs +//! +//! Environment variables (set by GitHub Actions): +//! - GITHUB_BASE_REF: Base branch name for PR (e.g., "main") +//! +//! Exit codes: +//! - 0: Check passed (fragment added or no source changes) +//! - 1: Check failed (source changes without changelog fragment) +//! +//! ```cargo +//! [dependencies] +//! regex = "1" +//! ``` + +use std::env; +use std::path::Path; +use std::process::{Command, exit}; +use regex::Regex; + +fn exec(command: &str, args: &[&str]) -> String { + match Command::new(command).args(args).output() { + Ok(output) => { + if output.status.success() { + String::from_utf8_lossy(&output.stdout).trim().to_string() + } else { + eprintln!("Error executing {} {:?}", command, args); + eprintln!("{}", String::from_utf8_lossy(&output.stderr)); + String::new() + } + } + Err(e) => { + eprintln!("Failed to execute {} {:?}: {}", command, args, e); + String::new() + } + } +} + +fn get_rust_root() -> String { + if let Ok(root) = env::var("RUST_ROOT") { + if !root.is_empty() { + return root; + } + } + + if Path::new("./Cargo.toml").exists() { + return ".".to_string(); + } + + if Path::new("./rust/Cargo.toml").exists() { + return "rust".to_string(); + } + + ".".to_string() +} + +fn get_changed_files() -> Vec { + let base_ref = env::var("GITHUB_BASE_REF").unwrap_or_else(|_| "main".to_string()); + eprintln!("Comparing against origin/{}...HEAD", base_ref); + + let output = exec( + "git", + &["diff", "--name-only", &format!("origin/{}...HEAD", base_ref)], + ); + + if output.is_empty() { + return Vec::new(); + } + + output.lines().filter(|s| !s.is_empty()).map(String::from).collect() +} + +fn is_source_file(file_path: &str, rust_root: &str) -> bool { + let prefix = if rust_root == "." { String::new() } else { format!("{}/", rust_root) }; + + let source_patterns = [ + Regex::new(&format!(r"^{}src/", regex::escape(&prefix))).unwrap(), + Regex::new(&format!(r"^{}tests/", regex::escape(&prefix))).unwrap(), + Regex::new(&format!(r"^{}?scripts/", regex::escape(&prefix))).unwrap(), + Regex::new(&format!(r"^{}Cargo\.toml$", regex::escape(&prefix))).unwrap(), + ]; + + source_patterns.iter().any(|pattern| pattern.is_match(file_path)) +} + +fn is_changelog_fragment(file_path: &str, rust_root: &str) -> bool { + let changelog_dir = if rust_root == "." { "changelog.d/".to_string() } else { format!("{}/changelog.d/", rust_root) }; + + (file_path.starts_with(&changelog_dir) || file_path.starts_with("changelog.d/")) + && file_path.ends_with(".md") + && !file_path.ends_with("README.md") +} + +fn main() { + println!("Checking for changelog fragment in PR diff...\n"); + + let rust_root = get_rust_root(); + if rust_root != "." { + println!("Detected multi-language repository (Rust root: {})", rust_root); + } + + let changed_files = get_changed_files(); + + if changed_files.is_empty() { + println!("No changed files found"); + exit(0); + } + + println!("Changed files:"); + for file in &changed_files { + println!(" {}", file); + } + println!(); + + // Count source files changed + let source_changes: Vec<&String> = changed_files.iter().filter(|f| is_source_file(f, &rust_root)).collect(); + let source_changed_count = source_changes.len(); + + println!("Source files changed: {}", source_changed_count); + if source_changed_count > 0 { + for file in &source_changes { + println!(" {}", file); + } + } + println!(); + + // Count changelog fragments added in this PR + let fragments_added: Vec<&String> = changed_files + .iter() + .filter(|f| is_changelog_fragment(f, &rust_root)) + .collect(); + let fragment_added_count = fragments_added.len(); + + println!("Changelog fragments added: {}", fragment_added_count); + if fragment_added_count > 0 { + for file in &fragments_added { + println!(" {}", file); + } + } + println!(); + + // Check if source files changed but no fragment was added + if source_changed_count > 0 && fragment_added_count == 0 { + eprintln!("::error::No changelog fragment found in this PR. Please add a changelog entry in changelog.d/"); + eprintln!(); + eprintln!("To create a changelog fragment:"); + eprintln!(" Create a new .md file in changelog.d/ with your changes"); + eprintln!(); + eprintln!("See changelog.d/README.md for more information."); + exit(1); + } + + println!( + "Changelog check passed (source files changed: {}, fragments added: {})", + source_changed_count, fragment_added_count + ); +} diff --git a/scripts/check-file-size.rs b/scripts/check-file-size.rs new file mode 100644 index 0000000..64d3eb1 --- /dev/null +++ b/scripts/check-file-size.rs @@ -0,0 +1,293 @@ +#!/usr/bin/env rust-script +//! Check Rust files for maximum and warning line-count thresholds +//! Exits with error code 1 if any files exceed the hard limit +//! +//! Usage: rust-script scripts/check-file-size.rs +//! +//! ```cargo +//! [dependencies] +//! walkdir = "2" +//! ``` + +use std::fs; +use std::path::Path; +#[cfg(not(test))] +use std::process::exit; +use walkdir::WalkDir; + +const MAX_LINES: usize = 1000; +const WARN_LINES: usize = 900; +const FILE_EXTENSIONS: &[&str] = &[".rs"]; +const EXCLUDE_PATTERNS: &[&str] = &["target", ".git", "node_modules"]; + +fn should_exclude(path: &Path) -> bool { + let path_str = path.to_string_lossy(); + EXCLUDE_PATTERNS + .iter() + .any(|pattern| path_str.contains(pattern)) +} + +fn has_valid_extension(path: &Path) -> bool { + let Some(ext) = path.extension().and_then(|ext| ext.to_str()) else { + return false; + }; + + FILE_EXTENSIONS + .iter() + .any(|valid_ext| valid_ext.strip_prefix('.') == Some(ext)) +} + +fn count_lines(path: &Path) -> Result { + let content = fs::read_to_string(path)?; + Ok(content.lines().count()) +} + +#[derive(Debug, PartialEq, Eq)] +struct Finding { + file: String, + lines: usize, +} + +#[derive(Debug, PartialEq, Eq)] +struct CheckResult { + warnings: Vec, + violations: Vec, +} + +#[derive(Debug, PartialEq, Eq)] +enum LineStatus { + WithinLimit, + Warning, + Violation, +} + +const fn classify_line_count(line_count: usize) -> LineStatus { + if line_count > MAX_LINES { + LineStatus::Violation + } else if line_count > WARN_LINES { + LineStatus::Warning + } else { + LineStatus::WithinLimit + } +} + +fn relative_path(path: &Path, cwd: &Path) -> String { + let relative = path + .strip_prefix(cwd) + .unwrap_or(path) + .to_string_lossy() + .to_string(); + + relative.replace(std::path::MAIN_SEPARATOR, "/") +} + +fn check_directory(cwd: &Path) -> CheckResult { + let mut result = CheckResult { + warnings: Vec::new(), + violations: Vec::new(), + }; + + for entry in WalkDir::new(cwd) + .into_iter() + .filter_map(std::result::Result::ok) + .filter(|e| e.file_type().is_file()) + { + let path = entry.path(); + + if should_exclude(path) { + continue; + } + + if !has_valid_extension(path) { + continue; + } + + match count_lines(path) { + Ok(line_count) => { + let finding = Finding { + file: relative_path(path, cwd), + lines: line_count, + }; + + match classify_line_count(line_count) { + LineStatus::Violation => result.violations.push(finding), + LineStatus::Warning => result.warnings.push(finding), + LineStatus::WithinLimit => {} + } + } + Err(error) => { + eprintln!("Warning: Could not read {}: {error}", path.display()); + } + } + } + + result +} + +fn escape_annotation_property(value: &str) -> String { + value + .replace('%', "%25") + .replace('\r', "%0D") + .replace('\n', "%0A") + .replace(':', "%3A") + .replace(',', "%2C") +} + +fn escape_annotation_message(value: &str) -> String { + value + .replace('%', "%25") + .replace('\r', "%0D") + .replace('\n', "%0A") +} + +fn warning_annotation(finding: &Finding) -> String { + let message = format!( + "File has {} lines (approaching limit of {MAX_LINES}). Consider extracting code to keep at or below {WARN_LINES} lines and prevent concurrent PR merge limit violations.", + finding.lines + ); + + format!( + "::warning file={}::{}", + escape_annotation_property(&finding.file), + escape_annotation_message(&message) + ) +} + +#[cfg(not(test))] +fn print_warnings(warnings: &[Finding]) { + if warnings.is_empty() { + return; + } + + for warning in warnings { + let annotation = warning_annotation(warning); + println!("{annotation}"); + println!( + "WARNING: {} has {} lines (approaching limit of {MAX_LINES}, warning threshold: {WARN_LINES})", + warning.file, warning.lines + ); + } + + println!(); + println!( + "The following files are approaching the {MAX_LINES} line limit (>{WARN_LINES} lines):" + ); + for warning in warnings { + println!(" {}", warning.file); + } + println!("\nConsider extracting code to prevent concurrent PR merge limit violations.\n"); +} + +#[cfg(not(test))] +fn print_violations(violations: &[Finding]) { + if violations.is_empty() { + return; + } + + println!("Found files exceeding the line limit:\n"); + for violation in violations { + println!( + " {}: {} lines (exceeds {MAX_LINES})", + violation.file, violation.lines + ); + } + println!("\nPlease refactor these files to be under {MAX_LINES} lines\n"); +} + +#[cfg(not(test))] +fn main() { + println!( + "\nChecking Rust files for maximum {MAX_LINES} lines (warning above {WARN_LINES})...\n" + ); + + let cwd = std::env::current_dir().expect("Failed to get current directory"); + let result = check_directory(&cwd); + + print_warnings(&result.warnings); + + if result.violations.is_empty() { + println!("All files are within the line limit\n"); + exit(0); + } else { + print_violations(&result.violations); + exit(1); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::fmt::Write as _; + use std::path::PathBuf; + use std::time::{SystemTime, UNIX_EPOCH}; + + fn temp_dir(name: &str) -> PathBuf { + let nanos = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos(); + let path = std::env::temp_dir().join(format!("check-file-size-{name}-{nanos}")); + fs::create_dir_all(&path).unwrap(); + path + } + + fn write_rust_file_with_lines(path: &Path, line_count: usize) { + let mut content = String::new(); + for line in 1..=line_count { + writeln!(&mut content, "// line {line}").unwrap(); + } + fs::write(path, content).unwrap(); + } + + #[test] + fn classifies_warning_band_without_blocking() { + assert_eq!(classify_line_count(WARN_LINES), LineStatus::WithinLimit); + assert_eq!(classify_line_count(WARN_LINES + 1), LineStatus::Warning); + assert_eq!(classify_line_count(MAX_LINES), LineStatus::Warning); + } + + #[test] + fn classifies_hard_limit_violations() { + assert_eq!(classify_line_count(MAX_LINES + 1), LineStatus::Violation); + } + + #[test] + fn check_directory_reports_warning_and_violation_separately() { + let repo = temp_dir("thresholds"); + let src_dir = repo.join("src"); + fs::create_dir_all(&src_dir).unwrap(); + write_rust_file_with_lines(&src_dir.join("near_limit.rs"), WARN_LINES + 1); + write_rust_file_with_lines(&src_dir.join("over_limit.rs"), MAX_LINES + 1); + write_rust_file_with_lines(&src_dir.join("small.rs"), WARN_LINES); + + let result = check_directory(&repo); + + assert_eq!( + result.warnings, + vec![Finding { + file: "src/near_limit.rs".to_string(), + lines: WARN_LINES + 1, + }] + ); + assert_eq!( + result.violations, + vec![Finding { + file: "src/over_limit.rs".to_string(), + lines: MAX_LINES + 1, + }] + ); + } + + #[test] + fn warning_annotation_uses_github_actions_format() { + let finding = Finding { + file: "src/near_limit.rs".to_string(), + lines: WARN_LINES + 1, + }; + + assert_eq!( + warning_annotation(&finding), + "::warning file=src/near_limit.rs::File has 901 lines (approaching limit of 1000). Consider extracting code to keep at or below 900 lines and prevent concurrent PR merge limit violations." + ); + } +} diff --git a/scripts/check-release-needed.rs b/scripts/check-release-needed.rs new file mode 100644 index 0000000..b97c5e4 --- /dev/null +++ b/scripts/check-release-needed.rs @@ -0,0 +1,395 @@ +#!/usr/bin/env rust-script +//! Check if a release is needed based on changelog fragments and version state +//! +//! This script checks: +//! 1. If there are changelog fragments to process +//! 2. If the current version has already been published to crates.io +//! 3. If the matching GitHub release and configured Docker Hub image tag exist +//! +//! IMPORTANT: This script checks external release artifacts, NOT git tags. +//! This is critical because: +//! - Git tags can exist without the package being published +//! - GitHub releases create tags but do not publish to crates.io or Docker Hub +//! - A crates.io publish can succeed while later Docker/GitHub release steps fail +//! +//! Supports both single-language and multi-language repository structures: +//! - Single-language: Cargo.toml in repository root +//! - Multi-language: Cargo.toml in rust/ subfolder +//! +//! Usage: rust-script scripts/check-release-needed.rs [--rust-root ] +//! +//! Environment variables: +//! - HAS_FRAGMENTS: 'true' if changelog fragments exist (from get-bump-type.rs) +//! - DOCKERHUB_IMAGE: Optional Docker Hub image name to verify (namespace/repository) +//! - GITHUB_REPOSITORY: GitHub repository to verify (owner/repository) +//! +//! Outputs (written to GITHUB_OUTPUT): +//! - should_release: 'true' if a release should be created +//! - skip_bump: 'true' if version bump should be skipped while missing artifacts are recreated +//! - crate_published: 'true' if the current version already exists on crates.io +//! - dockerhub_required: 'true' if Docker Hub publishing is configured and a Dockerfile exists +//! - dockerhub_published: 'true' if the configured Docker Hub tag exists +//! - github_release_published: 'true' if the matching GitHub release exists +//! - max_published_version: the highest non-yanked version on crates.io (for downstream use) +//! +//! ```cargo +//! [dependencies] +//! regex = "1" +//! ureq = "2" +//! serde = { version = "1", features = ["derive"] } +//! serde_json = "1" +//! ``` + +use serde::Deserialize; +use std::env; +use std::fs; +use std::path::Path; +use std::process::exit; + +#[path = "rust-paths.rs"] +mod rust_paths; + +fn get_arg(name: &str) -> Option { + let args: Vec = env::args().collect(); + let flag = format!("--{}", name); + + if let Some(idx) = args.iter().position(|a| a == &flag) { + return args.get(idx + 1).cloned(); + } + + let env_name = name.to_uppercase().replace('-', "_"); + env::var(&env_name).ok().filter(|s| !s.is_empty()) +} + +fn set_output(key: &str, value: &str) { + if let Ok(output_file) = env::var("GITHUB_OUTPUT") { + if let Err(e) = fs::OpenOptions::new() + .create(true) + .append(true) + .open(&output_file) + .and_then(|mut f| { + use std::io::Write; + writeln!(f, "{}={}", key, value) + }) + { + eprintln!("Warning: Could not write to GITHUB_OUTPUT: {}", e); + } + } + println!("Output: {}={}", key, value); +} + +#[derive(Deserialize)] +struct CratesIoVersion { + version: Option, +} + +#[derive(Deserialize)] +struct CratesIoVersionInfo { + #[allow(dead_code)] + num: String, +} + +#[derive(Deserialize)] +struct CratesIoCrate { + versions: Option>, +} + +#[derive(Deserialize)] +struct CratesIoVersionEntry { + num: String, + yanked: bool, +} + +fn check_version_on_crates_io(crate_name: &str, version: &str) -> bool { + let url = format!("https://crates.io/api/v1/crates/{}/{}", crate_name, version); + + match ureq::get(&url) + .set("User-Agent", "rust-script-check-release") + .call() + { + Ok(response) => { + if response.status() == 200 { + if let Ok(body) = response.into_string() { + if let Ok(data) = serde_json::from_str::(&body) { + return data.version.is_some(); + } + } + } + false + } + Err(ureq::Error::Status(404, _)) => false, + Err(e) => { + eprintln!("Warning: Could not check crates.io: {}", e); + false + } + } +} + +fn split_docker_image(image: &str) -> Option<(&str, &str)> { + let mut parts = image.split('/'); + let namespace = parts.next()?; + let repository = parts.next()?; + + if parts.next().is_some() || namespace.is_empty() || repository.is_empty() { + None + } else { + Some((namespace, repository)) + } +} + +fn check_docker_hub_tag(image: &str, version: &str) -> bool { + let Some((namespace, repository)) = split_docker_image(image) else { + eprintln!( + "Warning: Could not parse Docker Hub image '{}'; expected namespace/repository", + image + ); + return false; + }; + + let url = format!( + "https://hub.docker.com/v2/repositories/{}/{}/tags/{}", + namespace, repository, version + ); + + match ureq::get(&url) + .set("User-Agent", "rust-script-check-release") + .call() + { + Ok(response) => response.status() == 200, + Err(ureq::Error::Status(404, _)) => false, + Err(e) => { + eprintln!("Warning: Could not check Docker Hub tag: {}", e); + false + } + } +} + +fn check_github_release(repository: &str, tag_prefix: &str, version: &str) -> bool { + let url = format!( + "https://api.github.com/repos/{}/releases/tags/{}{}", + repository, tag_prefix, version + ); + + let mut request = ureq::get(&url) + .set("User-Agent", "rust-script-check-release") + .set("Accept", "application/vnd.github+json"); + + if let Ok(token) = env::var("GITHUB_TOKEN") { + if !token.is_empty() { + let auth_header = format!("Bearer {}", token); + request = request.set("Authorization", &auth_header); + } + } + + match request.call() { + Ok(response) => response.status() == 200, + Err(ureq::Error::Status(404, _)) => false, + Err(e) => { + eprintln!("Warning: Could not check GitHub release: {}", e); + false + } + } +} + +fn docker_hub_image_to_check() -> Option { + get_arg("dockerhub-image") + .or_else(|| get_arg("docker-hub-image")) + .or_else(|| get_arg("dockerhub_image")) + .filter(|image| Path::new("Dockerfile").exists() && !image.trim().is_empty()) +} + +fn release_is_complete( + crate_published: bool, + dockerhub_required: bool, + dockerhub_published: bool, + github_release_published: bool, +) -> bool { + crate_published && (!dockerhub_required || dockerhub_published) && github_release_published +} + +fn parse_semver(version: &str) -> Option<(u32, u32, u32)> { + let parts: Vec<&str> = version.split('-').next()?.split('.').collect(); + if parts.len() != 3 { + return None; + } + Some(( + parts[0].parse().ok()?, + parts[1].parse().ok()?, + parts[2].parse().ok()?, + )) +} + +fn get_max_published_version(crate_name: &str) -> Option { + let url = format!("https://crates.io/api/v1/crates/{}", crate_name); + + match ureq::get(&url) + .set("User-Agent", "rust-script-check-release") + .call() + { + Ok(response) => { + if response.status() == 200 { + if let Ok(body) = response.into_string() { + if let Ok(data) = serde_json::from_str::(&body) { + if let Some(versions) = data.versions { + let mut max_version: Option<(u32, u32, u32, String)> = None; + for v in &versions { + if v.yanked { + continue; + } + if let Some(parsed) = parse_semver(&v.num) { + match &max_version { + None => { + max_version = + Some((parsed.0, parsed.1, parsed.2, v.num.clone())); + } + Some(current) => { + if parsed > (current.0, current.1, current.2) { + max_version = Some(( + parsed.0, + parsed.1, + parsed.2, + v.num.clone(), + )); + } + } + } + } + } + return max_version.map(|v| v.3); + } + } + } + } + None + } + Err(ureq::Error::Status(404, _)) => None, + Err(e) => { + eprintln!("Warning: Could not query crates.io for versions: {}", e); + None + } + } +} + +fn main() { + let rust_root = match rust_paths::get_rust_root(None, true) { + Ok(root) => root, + Err(e) => { + eprintln!("Error: {}", e); + exit(1); + } + }; + let cargo_toml = rust_paths::get_cargo_toml_path(&rust_root); + let package_manifest = match rust_paths::get_package_manifest_path(&cargo_toml) { + Ok(path) => path, + Err(e) => { + eprintln!("Error: {}", e); + exit(1); + } + }; + + let has_fragments = env::var("HAS_FRAGMENTS") + .map(|v| v == "true") + .unwrap_or(false); + + let package_info = match rust_paths::read_package_info(&package_manifest) { + Ok(info) => info, + Err(e) => { + eprintln!("Error: {}", e); + exit(1); + } + }; + let crate_name = package_info.name; + let current_version = package_info.version; + + let max_published = get_max_published_version(&crate_name); + if let Some(ref max_ver) = max_published { + println!("Max published version on crates.io: {}", max_ver); + set_output("max_published_version", max_ver); + } else { + println!("No versions published on crates.io yet (or crate not found)"); + set_output("max_published_version", ""); + } + + if !has_fragments { + let crate_published = check_version_on_crates_io(&crate_name, ¤t_version); + let tag_prefix = get_arg("tag-prefix").unwrap_or_else(|| "v".to_string()); + let dockerhub_image = docker_hub_image_to_check(); + let dockerhub_required = dockerhub_image.is_some(); + let dockerhub_published = dockerhub_image + .as_deref() + .map(|image| { + check_docker_hub_tag(image, ¤t_version) + && check_docker_hub_tag(image, "latest") + }) + .unwrap_or(false); + let github_release_published = get_arg("repository") + .or_else(|| env::var("GITHUB_REPOSITORY").ok().filter(|s| !s.is_empty())) + .map(|repository| check_github_release(&repository, &tag_prefix, ¤t_version)) + .unwrap_or_else(|| { + eprintln!("Warning: GITHUB_REPOSITORY not set; assuming GitHub release is missing"); + false + }); + + set_output( + "crate_published", + if crate_published { "true" } else { "false" }, + ); + set_output( + "dockerhub_required", + if dockerhub_required { "true" } else { "false" }, + ); + set_output( + "dockerhub_published", + if dockerhub_published { "true" } else { "false" }, + ); + set_output( + "github_release_published", + if github_release_published { + "true" + } else { + "false" + }, + ); + + println!( + "Crate: {}, Version: {}, Published on crates.io: {}", + crate_name, current_version, crate_published + ); + if let Some(image) = dockerhub_image { + println!( + "Docker image: {}, version/latest tags published on Docker Hub: {}", + image, dockerhub_published + ); + } else { + println!("Docker Hub artifact check skipped: DOCKERHUB_IMAGE or Dockerfile is not configured"); + } + println!( + "GitHub release {}{} published: {}", + tag_prefix, current_version, github_release_published + ); + + if release_is_complete( + crate_published, + dockerhub_required, + dockerhub_published, + github_release_published, + ) { + println!( + "No changelog fragments and v{} is fully published", + current_version + ); + set_output("should_release", "false"); + } else { + println!( + "No changelog fragments but v{} is missing at least one release artifact", + current_version + ); + set_output("should_release", "true"); + set_output("skip_bump", "true"); + } + } else { + println!("Found changelog fragments, proceeding with release"); + set_output("should_release", "true"); + set_output("skip_bump", "false"); + } +} diff --git a/scripts/check-version-modification.rs b/scripts/check-version-modification.rs new file mode 100644 index 0000000..09ebb33 --- /dev/null +++ b/scripts/check-version-modification.rs @@ -0,0 +1,163 @@ +#!/usr/bin/env rust-script +//! Check for manual version modification in Cargo.toml +//! +//! This script prevents manual version changes in pull requests. +//! Versions should be managed automatically by the CI/CD pipeline +//! using changelog fragments in changelog.d/. +//! +//! Key behavior: +//! - Detects if `version = "..."` line has changed in Cargo.toml +//! - Fails the CI check if manual version change is detected +//! - Skips check for automated release branches (changelog-manual-release-*) +//! +//! Usage: rust-script scripts/check-version-modification.rs +//! +//! Environment variables (set by GitHub Actions): +//! - GITHUB_HEAD_REF: The head branch name for PRs +//! - GITHUB_BASE_REF: The base branch name for PRs +//! - GITHUB_EVENT_NAME: Should be 'pull_request' +//! +//! Exit codes: +//! - 0: No manual version changes detected (or check skipped) +//! - 1: Manual version changes detected +//! +//! ```cargo +//! [dependencies] +//! regex = "1" +//! ``` + +use std::env; +use std::path::Path; +use std::process::{Command, exit}; +use regex::Regex; + +fn exec(command: &str, args: &[&str]) -> String { + match Command::new(command).args(args).output() { + Ok(output) => { + String::from_utf8_lossy(&output.stdout).trim().to_string() + } + Err(_) => String::new(), + } +} + +fn exec_ignore_error(command: &str, args: &[&str]) { + let _ = Command::new(command) + .args(args) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::null()) + .status(); +} + +fn should_skip_version_check() -> bool { + let head_ref = env::var("GITHUB_HEAD_REF").unwrap_or_default(); + + // Skip for automated release PRs + let automated_branch_prefixes = [ + "changelog-manual-release-", + "changeset-release/", + "release/", + "automated-release/", + ]; + + for prefix in &automated_branch_prefixes { + if head_ref.starts_with(prefix) { + println!("Skipping version check for automated branch: {}", head_ref); + return true; + } + } + + false +} + +fn get_rust_root() -> String { + if let Ok(root) = env::var("RUST_ROOT") { + if !root.is_empty() { + return root; + } + } + + if Path::new("./Cargo.toml").exists() { + return ".".to_string(); + } + + if Path::new("./rust/Cargo.toml").exists() { + return "rust".to_string(); + } + + ".".to_string() +} + +fn get_cargo_toml_path(rust_root: &str) -> String { + if rust_root == "." { + "Cargo.toml".to_string() + } else { + format!("{}/Cargo.toml", rust_root) + } +} + +fn get_cargo_toml_diff(cargo_toml_path: &str) -> String { + let base_ref = env::var("GITHUB_BASE_REF").unwrap_or_else(|_| "main".to_string()); + + // Ensure we have the base branch + exec_ignore_error("git", &["fetch", "origin", &base_ref, "--depth=1"]); + + // Get the diff for Cargo.toml + exec( + "git", + &["diff", &format!("origin/{}...HEAD", base_ref), "--", cargo_toml_path], + ) +} + +fn has_version_change(diff: &str) -> bool { + if diff.is_empty() { + return false; + } + + // Look for changes to the version line + // Match lines that start with + or - followed by version = "..." + let version_change_pattern = Regex::new(r#"(?m)^[+-]version\s*=\s*""#).unwrap(); + version_change_pattern.is_match(diff) +} + +fn main() { + println!("Checking for manual version modifications in Cargo.toml...\n"); + + // Only run on pull requests + let event_name = env::var("GITHUB_EVENT_NAME").unwrap_or_default(); + if event_name != "pull_request" { + println!("Skipping: Not a pull request event (event: {})", event_name); + exit(0); + } + + // Skip for automated release branches + if should_skip_version_check() { + exit(0); + } + + // Get and check the diff + let rust_root = get_rust_root(); + let cargo_toml_path = get_cargo_toml_path(&rust_root); + let diff = get_cargo_toml_diff(&cargo_toml_path); + + if diff.is_empty() { + println!("No changes to Cargo.toml detected."); + println!("Version check passed."); + exit(0); + } + + // Check for version changes + if has_version_change(&diff) { + eprintln!("Error: Manual version change detected in Cargo.toml!\n"); + eprintln!("Versions are managed automatically by the CI/CD pipeline."); + eprintln!("Please do not modify the version field directly.\n"); + eprintln!("To trigger a release, add a changelog fragment to changelog.d/"); + eprintln!("with the appropriate bump type (major, minor, or patch).\n"); + eprintln!("See changelog.d/README.md for more information.\n"); + eprintln!("If you need to undo your version change, run:"); + eprintln!(" git checkout origin/main -- Cargo.toml"); + exit(1); + } + + println!("Cargo.toml was modified but version field was not changed."); + println!("Version check passed."); +} diff --git a/scripts/collect-changelog.rs b/scripts/collect-changelog.rs new file mode 100644 index 0000000..63b7d8c --- /dev/null +++ b/scripts/collect-changelog.rs @@ -0,0 +1,234 @@ +#!/usr/bin/env rust-script +//! Collect changelog fragments into CHANGELOG.md +//! +//! This script collects all .md files from changelog.d/ (except README.md) +//! and prepends them to CHANGELOG.md, then removes the processed fragments. +//! +//! Supports both single-language and multi-language repository structures: +//! - Single-language: Cargo.toml and changelog.d/ in repository root +//! - Multi-language: Cargo.toml and changelog.d/ in rust/ subfolder +//! +//! Usage: rust-script scripts/collect-changelog.rs [--rust-root ] +//! +//! ```cargo +//! [dependencies] +//! regex = "1" +//! chrono = "0.4" +//! ``` + +use std::env; +use std::fs; +use std::path::Path; +use std::process::exit; +use chrono::Utc; +use regex::Regex; + +const INSERT_MARKER: &str = ""; + +fn get_arg(name: &str) -> Option { + let args: Vec = env::args().collect(); + let flag = format!("--{}", name); + + if let Some(idx) = args.iter().position(|a| a == &flag) { + return args.get(idx + 1).cloned(); + } + + let env_name = name.to_uppercase().replace('-', "_"); + env::var(&env_name).ok().filter(|s| !s.is_empty()) +} + +fn get_rust_root() -> String { + if let Some(root) = get_arg("rust-root") { + eprintln!("Using explicitly configured Rust root: {}", root); + return root; + } + + if Path::new("./Cargo.toml").exists() { + eprintln!("Detected single-language repository (Cargo.toml in root)"); + return ".".to_string(); + } + + if Path::new("./rust/Cargo.toml").exists() { + eprintln!("Detected multi-language repository (Cargo.toml in rust/)"); + return "rust".to_string(); + } + + eprintln!("Error: Could not find Cargo.toml in expected locations"); + exit(1); +} + +fn get_cargo_toml_path(rust_root: &str) -> String { + if rust_root == "." { + "./Cargo.toml".to_string() + } else { + format!("{}/Cargo.toml", rust_root) + } +} + +fn get_changelog_dir(rust_root: &str) -> String { + if rust_root == "." { + "./changelog.d".to_string() + } else { + format!("{}/changelog.d", rust_root) + } +} + +fn get_changelog_path(rust_root: &str) -> String { + if rust_root == "." { + "./CHANGELOG.md".to_string() + } else { + format!("{}/CHANGELOG.md", rust_root) + } +} + +fn get_version_from_cargo(cargo_toml_path: &str) -> Result { + let content = fs::read_to_string(cargo_toml_path) + .map_err(|e| format!("Failed to read {}: {}", cargo_toml_path, e))?; + + let re = Regex::new(r#"(?m)^version\s*=\s*"([^"]+)""#).unwrap(); + + if let Some(caps) = re.captures(&content) { + Ok(caps.get(1).unwrap().as_str().to_string()) + } else { + Err(format!("Could not find version in {}", cargo_toml_path)) + } +} + +fn strip_frontmatter(content: &str) -> String { + let re = Regex::new(r"(?s)^---\s*\n.*?\n---\s*\n(.*)$").unwrap(); + if let Some(caps) = re.captures(content) { + caps.get(1).unwrap().as_str().trim().to_string() + } else { + content.trim().to_string() + } +} + +fn collect_fragments(changelog_dir: &str) -> String { + let dir_path = Path::new(changelog_dir); + if !dir_path.exists() { + return String::new(); + } + + let mut files: Vec<_> = match fs::read_dir(dir_path) { + Ok(entries) => entries + .filter_map(|e| e.ok()) + .map(|e| e.path()) + .filter(|p| { + p.extension().map_or(false, |ext| ext == "md") + && p.file_name().map_or(false, |name| name != "README.md") + }) + .collect(), + Err(_) => return String::new(), + }; + + files.sort(); + + let mut fragments = Vec::new(); + for file in &files { + if let Ok(raw_content) = fs::read_to_string(file) { + let content = strip_frontmatter(&raw_content); + if !content.is_empty() { + fragments.push(content); + } + } + } + + fragments.join("\n\n") +} + +fn update_changelog(changelog_file: &str, version: &str, fragments: &str) { + let date_str = Utc::now().format("%Y-%m-%d").to_string(); + let new_entry = format!("\n## [{}] - {}\n\n{}\n", version, date_str, fragments); + + if Path::new(changelog_file).exists() { + let mut content = fs::read_to_string(changelog_file).unwrap_or_default(); + + if content.contains(INSERT_MARKER) { + content = content.replace(INSERT_MARKER, &format!("{}{}", INSERT_MARKER, new_entry)); + } else { + // Insert after the first ## heading + let lines: Vec<&str> = content.lines().collect(); + let mut insert_index = None; + + for (i, line) in lines.iter().enumerate() { + if line.starts_with("## [") { + insert_index = Some(i); + break; + } + } + + if let Some(idx) = insert_index { + let mut new_lines: Vec = lines[..idx].iter().map(|s| s.to_string()).collect(); + new_lines.push(new_entry.clone()); + new_lines.extend(lines[idx..].iter().map(|s| s.to_string())); + content = new_lines.join("\n"); + } else { + // Append after the main heading + content.push_str(&new_entry); + } + } + + fs::write(changelog_file, content).expect("Failed to write changelog"); + } else { + let content = format!( + "# Changelog\n\n\ + All notable changes to this project will be documented in this file.\n\n\ + The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\n\ + and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n\ + {}\n{}\n", + INSERT_MARKER, new_entry + ); + fs::write(changelog_file, content).expect("Failed to write changelog"); + } + + println!("Updated CHANGELOG.md with version {}", version); +} + +fn remove_fragments(changelog_dir: &str) { + let dir_path = Path::new(changelog_dir); + if !dir_path.exists() { + return; + } + + if let Ok(entries) = fs::read_dir(dir_path) { + for entry in entries.filter_map(|e| e.ok()) { + let path = entry.path(); + if path.extension().map_or(false, |ext| ext == "md") + && path.file_name().map_or(false, |name| name != "README.md") + { + if fs::remove_file(&path).is_ok() { + println!("Removed {}", path.display()); + } + } + } + } +} + +fn main() { + let rust_root = get_rust_root(); + let cargo_toml = get_cargo_toml_path(&rust_root); + let changelog_dir = get_changelog_dir(&rust_root); + let changelog_file = get_changelog_path(&rust_root); + + let version = match get_version_from_cargo(&cargo_toml) { + Ok(v) => v, + Err(e) => { + eprintln!("Error: {}", e); + exit(1); + } + }; + + println!("Collecting changelog fragments for version {}", version); + + let fragments = collect_fragments(&changelog_dir); + + if fragments.is_empty() { + println!("No changelog fragments found"); + exit(0); + } + + update_changelog(&changelog_file, &version, &fragments); + remove_fragments(&changelog_dir); + + println!("Changelog collection complete"); +} diff --git a/scripts/create-changelog-fragment.rs b/scripts/create-changelog-fragment.rs new file mode 100644 index 0000000..08e145b --- /dev/null +++ b/scripts/create-changelog-fragment.rs @@ -0,0 +1,119 @@ +#!/usr/bin/env rust-script +//! Create a changelog fragment for manual release PR +//! +//! This script creates a changelog fragment with the appropriate +//! category based on the bump type. +//! +//! Usage: rust-script scripts/create-changelog-fragment.rs --bump-type [--description ] +//! +//! ```cargo +//! [dependencies] +//! chrono = "0.4" +//! ``` + +use std::env; +use std::fs; +use std::path::Path; +use std::process::exit; +use chrono::Utc; + +fn get_arg(name: &str) -> Option { + let args: Vec = env::args().collect(); + let flag = format!("--{}", name); + + if let Some(idx) = args.iter().position(|a| a == &flag) { + return args.get(idx + 1).cloned(); + } + + let env_name = name.to_uppercase().replace('-', "_"); + env::var(&env_name).ok().filter(|s| !s.is_empty()) +} + +fn get_rust_root() -> String { + if let Some(root) = get_arg("rust-root") { + return root; + } + + if let Ok(root) = env::var("RUST_ROOT") { + if !root.is_empty() { + return root; + } + } + + if Path::new("./Cargo.toml").exists() { + return ".".to_string(); + } + + if Path::new("./rust/Cargo.toml").exists() { + return "rust".to_string(); + } + + ".".to_string() +} + +fn get_changelog_dir(rust_root: &str) -> String { + if rust_root == "." { + "changelog.d".to_string() + } else { + format!("{}/changelog.d", rust_root) + } +} + +fn get_category(bump_type: &str) -> &'static str { + match bump_type { + "major" => "### Breaking Changes", + "minor" => "### Added", + "patch" => "### Fixed", + _ => "### Changed", + } +} + +fn generate_timestamp() -> String { + Utc::now().format("%Y%m%d%H%M%S").to_string() +} + +fn main() { + let bump_type = get_arg("bump-type").unwrap_or_else(|| "patch".to_string()); + let description = get_arg("description"); + + // Validate bump type + if !["major", "minor", "patch"].contains(&bump_type.as_str()) { + eprintln!("Invalid bump type: {}. Must be major, minor, or patch.", bump_type); + exit(1); + } + + let rust_root = get_rust_root(); + let changelog_dir = get_changelog_dir(&rust_root); + let timestamp = generate_timestamp(); + let fragment_file = format!("{}/{}-manual-{}.md", changelog_dir, timestamp, bump_type); + + // Determine changelog category based on bump type + let category = get_category(&bump_type); + + // Create changelog fragment with frontmatter + let description_text = description.unwrap_or_else(|| format!("Manual {} release", bump_type)); + let fragment_content = format!( + "---\nbump: {}\n---\n\n{}\n\n- {}\n", + bump_type, category, description_text + ); + + // Ensure changelog directory exists + let dir_path = Path::new(&changelog_dir); + if !dir_path.exists() { + if let Err(e) = fs::create_dir_all(dir_path) { + eprintln!("Error creating directory {}: {}", changelog_dir, e); + exit(1); + } + } + + // Write the fragment file + if let Err(e) = fs::write(&fragment_file, &fragment_content) { + eprintln!("Error writing fragment file: {}", e); + exit(1); + } + + println!("Created changelog fragment: {}", fragment_file); + println!(); + println!("Content:"); + println!("{}", fragment_content); +} diff --git a/scripts/create-github-release.rs b/scripts/create-github-release.rs new file mode 100644 index 0000000..2a914a0 --- /dev/null +++ b/scripts/create-github-release.rs @@ -0,0 +1,413 @@ +#!/usr/bin/env rust-script +//! Create GitHub Release from CHANGELOG.md +//! +//! Automatically includes crates.io and docs.rs badges in release notes +//! when the crate name can be detected from Cargo.toml. +//! +//! Usage: rust-script scripts/create-github-release.rs --release-version --repository [--tag-prefix ] [--language ] [--release-label

BmF>5@vvd(2bfXRjPwJjIFyee{Qz0B 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