From 1916dfd2a57fdf803b8ec699fd3f214af0afb229 Mon Sep 17 00:00:00 2001 From: bakebot Date: Wed, 12 Aug 2026 21:21:40 +0000 Subject: [PATCH 1/2] Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool Template: ``` { "template": "https://github.com/networktocode-llc/cookiecutter-ntc.git", "dir": "python", "ref": "main", "path": "/__w/cookiecutter-nautobot-app-drift-manager/cookiecutter-nautobot-app-drift-manager/cookiecutter-ntc/python" } ``` Cookie: ``` { "remote": "https://github.com/networktocode/jdiff.git", "path": "/tmp/tmpjukp_9wv/jdiff", "repository_path": "/tmp/tmpjukp_9wv/jdiff", "dir": "", "branch_prefix": "drift-manager/develop", "context": { "codeowner_github_usernames": "@lvrfrc87 @scetron @grelleum @jeffkala @pszulczewski", "full_name": "Network to Code, LLC", "email": "info@networktocode.com", "github_org": "networktocode", "description": "Python library for examining structured data.", "project_name": "jdiff", "project_slug": "jdiff", "repo_url": "https://github.com/networktocode/jdiff", "base_url": "jdiff", "project_python_name": "jdiff", "project_python_base_version": "3.10", "project_with_config_settings": "no", "generate_docs": "yes", "version": "1.0.1", "original_publish_year": "2022", "_template": "/__w/cookiecutter-nautobot-app-drift-manager/cookiecutter-nautobot-app-drift-manager/cookiecutter-ntc/python", "_output_dir": "/tmp/tmpjukp_9wv", "_repo_dir": "/__w/cookiecutter-nautobot-app-drift-manager/cookiecutter-nautobot-app-drift-manager/cookiecutter-ntc/python", "_checkout": null }, "drift_managed_branch": "develop", "remote_name": "origin", "pull_request_strategy": "PullRequestStrategy.UPDATE_OR_CREATE", "post_actions": [], "baked_commit_ref": "bc931311336d391d878202dba239389783ee9f09", "draft": false } ``` CLI Arguments: ``` { "cookie_dir": "", "input": false, "json_filename": "", "output_dir": "", "push": true, "template": "./cookiecutter-ntc", "template_dir": "python", "template_ref": "main", "pull_request": "update-or-create", "post_action": [], "disable_post_actions": true, "draft": null, "drift_managed_branch": "develop" } ``` --- .cookiecutter.json | 4 + .github/workflows/prepare_release.yml | 178 ++++++++++++++++++++++++ .github/workflows/release.yml | 74 ++++++++-- .gitignore | 1 + README.md | 10 ++ bin/ensure_release_notes.py | 97 +++++++++++++ changes/+main.housekeeping | 1 + docs/admin/release_notes/version_1.0.md | 4 + docs/generate_code_reference_pages.py | 4 +- pyproject.toml | 41 +++--- tasks.py | 44 ++++-- towncrier_header.txt | 9 ++ 12 files changed, 428 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/prepare_release.yml create mode 100644 bin/ensure_release_notes.py create mode 100644 changes/+main.housekeeping create mode 100644 towncrier_header.txt diff --git a/.cookiecutter.json b/.cookiecutter.json index 93bd9ed..ac30036 100644 --- a/.cookiecutter.json +++ b/.cookiecutter.json @@ -23,7 +23,11 @@ "pull_request_strategy": "update-or-create", "post_actions": [], "draft": false, +<<<<<<< HEAD "baked_commit_ref": "bc931311336d391d878202dba239389783ee9f09", +======= + "baked_commit_ref": "83a4975ef60cb34219557649eb08b8e599d5ec1c", +>>>>>>> d408d2a (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) "drift_managed_branch": "develop" } } diff --git a/.github/workflows/prepare_release.yml b/.github/workflows/prepare_release.yml new file mode 100644 index 0000000..727c32d --- /dev/null +++ b/.github/workflows/prepare_release.yml @@ -0,0 +1,178 @@ +--- +name: "Prepare Release" +on: # yamllint disable-line rule:truthy rule:comments + workflow_dispatch: + inputs: + bump_rule: + description: "Select the version bump type" + required: true + default: "patch" + type: "choice" + options: + - "prerelease" + - "patch" + - "minor" + - "major" + target_branch: + description: "Create the release from this branch (default: main)." + required: true + default: "main" + date: + description: "Date of the release YYYY-MM-DD (defaults to today's date in the US Eastern TZ)." + required: false + default: "" + +jobs: + prepare-release: + permissions: + contents: "write" + pull-requests: "write" + name: "Prepare Release" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout code" + uses: "actions/checkout@v4" + with: + # If target_branch is 'main', use 'develop' as the source branch. Otherwise, the source and target branch are the same. + ref: "${{ github.event.inputs['target_branch'] == 'main' && 'develop' || github.event.inputs['target_branch'] }}" + fetch-depth: 0 # Fetch all history for git tags + + - name: "Setup environment" + uses: "networktocode/gh-action-setup-poetry-environment@v6" + with: + poetry-version: "2.1.3" + poetry-install-options: "--with dev" + + - name: "Validate Branch and Tags" + run: | + # 1. Verify branch exists + if ! git rev-parse --verify origin/${{ github.event.inputs.target_branch }} > /dev/null 2>&1; then + echo "Error: Branch ${{ github.event.inputs.target_branch }} does not exist." + exit 1 + fi + + # 2. Try to get the previous version tag + # If it fails (no tags), get the hash of the first commit + if PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null); then + echo "PREVIOUS_TAG=$PREV_TAG" >> $GITHUB_ENV + echo "Found previous tag: $PREV_TAG" + else + # Fallback to the first commit in the repository + FIRST_COMMIT=$(git rev-list --max-parents=0 HEAD) + echo "PREVIOUS_TAG=$FIRST_COMMIT" >> $GITHUB_ENV + echo "No tags found. Falling back to initial commit: $FIRST_COMMIT" + fi + + - name: "Determine New Version" + id: "versioning" + run: | + # Perform the bump based on the user input + poetry version ${{ github.event.inputs.bump_rule }} + + # Capture the New version string for use in other steps + NEW_VER=$(poetry version --short) + echo "NEW_VERSION=$NEW_VER" >> $GITHUB_ENV + echo "RELEASE_BRANCH=release/$NEW_VER" >> $GITHUB_ENV + + - name: "Set Date Variable" + run: | + if [ -z "${{ github.event.inputs.date }}" ]; then + RELEASE_DATE=$(TZ=America/New_York date +%Y-%m-%d) + else + RELEASE_DATE="${{ github.event.inputs.date }}" + fi + echo "RELEASE_DATE=$RELEASE_DATE" >> $GITHUB_ENV + + - name: "Create Release Branch" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + git config user.name "${{ github.actor }}" + git config user.email "${{ github.actor }}@users.noreply.github.com" + + # Ensure release branch doesn't already exist + if git rev-parse --verify origin/${{ env.RELEASE_BRANCH }} > /dev/null 2>&1; then + echo "Error: Release branch ${{ env.RELEASE_BRANCH }} already exists." + exit 1 + fi + + # Create a new branch for the release + git checkout -b "${{ env.RELEASE_BRANCH }}" + + - name: "Regenerate poetry.lock" + run: "poetry lock --regenerate" + + - name: "Generate Github Release Notes" + env: + GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + # 1. Get Towncrier Draft + TOWNCRIER_NOTES=$(poetry run towncrier build --version "${{ env.NEW_VERSION }}" --date "${{ env.RELEASE_DATE }}" --draft) + + # 2. Call GitHub API to generate raw notes + RAW_GH_NOTES=$(gh api /repos/${{ github.repository }}/releases/generate-notes \ + -f tag_name="v${{ env.NEW_VERSION }}" \ + -f target_commitish="${{ github.event.inputs['target_branch'] == 'main' && 'develop' || github.event.inputs['target_branch'] }}" \ + -f previous_tag_name="${{ env.PREVIOUS_TAG }}" --jq '.body') + + # 3. Parse usernames (Regex match) + # We use grep to find "@user" patterns, sort, and uniq them + USERNAMES=$(echo "$RAW_GH_NOTES" | grep -oP 'by @\K[a-zA-Z0-9-]+' | sort -u | grep -vE 'dependabot|nautobot-bot|github-actions' || true) + + # 4. Format the Contributors section + CONTRIBUTORS_SECTION="## Contributors" + for user in $USERNAMES; do + CONTRIBUTORS_SECTION="$CONTRIBUTORS_SECTION"$'\n'"* @$user" + done + + # 5. Extract the "Full Changelog" or "New Contributors" part + # Using awk to grab everything from '## New Contributors' or '**Full Changelog**' to the end + GH_FOOTER=$(echo "$RAW_GH_NOTES" | awk '/## New Contributors/ || /\*\*Full Changelog\*\*/ {found=1} found {print}') + if [ -z "$GH_FOOTER" ]; then + GH_FOOTER=$(echo "$RAW_GH_NOTES" | sed -n '/**Full Changelog**/,$p') + fi + + # 6. Combine everything + FINAL_NOTES="$TOWNCRIER_NOTES"$'\n\n'"$CONTRIBUTORS_SECTION"$'\n\n'"$GH_FOOTER" + + # 7. Save to a temporary file to avoid shell argument length limits + echo "$FINAL_NOTES" > ../consolidated_notes.md + + - name: "Generate Release Notes" + run: "poetry run inv generate-release-notes --version '${{ env.NEW_VERSION }}' --date '${{ env.RELEASE_DATE }}'" + + - name: "Commit Changes and Push" + run: | + # Add all changes (pyproject.toml, poetry.lock, etc.) + git add . + git commit -m "prepare release v${{ env.NEW_VERSION }}" + git push origin "${{ env.RELEASE_BRANCH }}" + + - name: "Create Pull Request" + env: + GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + gh pr create \ + --title "Release v${{ env.NEW_VERSION }}" \ + --body-file "../consolidated_notes.md" \ + --base "${{ github.event.inputs.target_branch }}" \ + --head "${{ env.RELEASE_BRANCH }}" + + - name: "Create Draft Release" + env: + GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + if [[ "${{ github.event.inputs.bump_rule }}" == "prerelease" ]]; then + RELEASE_FLAGS="--prerelease" + elif [[ "${{ github.event.inputs.target_branch }}" == "main" ]]; then + RELEASE_FLAGS="--latest" + else + RELEASE_FLAGS="--latest=false" + fi + + gh release create "v${{ env.NEW_VERSION }}" \ + --draft \ + $RELEASE_FLAGS \ + --title "v${{ env.NEW_VERSION }} - ${{ env.RELEASE_DATE }}" \ + --notes-file "../consolidated_notes.md" \ + --target "${{ github.event.inputs.target_branch }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c6fc0dc..1afc038 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,6 @@ --- name: "Release" -on: # yamllint disable-line rule:truthy rule:comments +on: # yamllint disable-line rule:truthy rule:comments release: types: ["published"] @@ -12,10 +12,10 @@ jobs: steps: - uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v6" + uses: "networktocode/gh-action-setup-poetry-environment@v7" with: poetry-version: "2.1.3" - python-version: "3.13" + python-version: "3.12" poetry-install-options: "--no-root" - name: "Run Poetry Build" run: "poetry build" @@ -56,7 +56,9 @@ jobs: if: "startsWith(github.ref, 'refs/tags/v')" needs: "build" environment: "pypi" - # Steps to publish to PyPI. + # IMPORTANT: this permission is mandatory for trusted publishing. + permissions: + id-token: "write" steps: - name: "Retrieve built package from cache" uses: "actions/download-artifact@v4" @@ -65,11 +67,6 @@ jobs: path: "dist/" - name: "Publish package distributions to PyPI" uses: "pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e" # v1.13.0 - ## Used for networktocode org since trusted publisher isn't supported for GitHub Plan. - with: - user: "__token__" - password: "${{ secrets.PYPI_API_TOKEN }}" - # End publish to PyPI job. slack-notify: needs: @@ -104,3 +101,62 @@ jobs: } ] } + + create-pr-to-develop: + if: "github.event.release.target_commitish == 'main'" + permissions: + contents: "write" + pull-requests: "write" + name: "Create a PR from main into develop" + needs: + - "publish-github" + - "publish-pypi" + runs-on: "ubuntu-latest" + steps: + - name: "Checkout main" + uses: "actions/checkout@v4" + with: + ref: "main" + fetch-depth: 0 + + - name: "Setup environment" + uses: "networktocode/gh-action-setup-poetry-environment@v6" + with: + poetry-version: "2.1.3" + poetry-install-options: "--no-root" + + - name: "Create release branch from main" + id: "branch" + run: | + + git config user.name "${{ github.actor }}" + git config user.email "${{ github.actor }}@users.noreply.github.com" + + TAG_NAME="${{ github.event.release.tag_name }}" + VERSION="${TAG_NAME#v}" + + BRANCH_NAME="release-${VERSION}-to-develop" + + # Ensure release branch doesn't already exist + if git rev-parse --verify origin/$BRANCH_NAME > /dev/null 2>&1; then + echo "Error: Release branch $BRANCH_NAME already exists." + exit 1 + fi + + git checkout -b "$BRANCH_NAME" + + poetry version prepatch + git add pyproject.toml && git commit -m "Bump version" + git push origin "$BRANCH_NAME" + + echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT + + - name: "Create Pull Request to develop" + env: + GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + gh pr create \ + --title "Post release ${{ github.event.release.tag_name }} to develop" \ + --body "Please do a merge commit." \ + --base "develop" \ + --head "${{ steps.branch.outputs.branch_name }}" diff --git a/.gitignore b/.gitignore index fabcfd8..d3ac5be 100644 --- a/.gitignore +++ b/.gitignore @@ -308,3 +308,4 @@ docs/CHANGELOG.md public /compose.yaml /dump.sql +/jdiff/static/* diff --git a/README.md b/README.md index f5e6860..66b996f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ # Jdiff

+<<<<<<< HEAD +======= + +>>>>>>> d408d2a (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool)
@@ -18,9 +22,15 @@ Full documentation for this library can be found over on the [Jdiff Docs](https://jdiff.readthedocs.io/) website: +<<<<<<< HEAD - [User Guide](https://jdiff.readthedocs.io/en/latest/user/lib_overview/) - Overview, Using the library, Getting Started. - [Administrator Guide](https://jdiff.readthedocs.io/en/latest/admin/install/) - How to Install, Configure, Upgrade, or Uninstall the library. - [Developer Guide](https://jdiff.readthedocs.io/en/latest/dev/contributing/) - Extending the library, Code Reference, Contribution Guide. +======= +- [User Guide](https://jdiff.readthedocs.io/en/latest/user/app_overview/) - Overview, Using the Library, Getting Started. +- [Administrator Guide](https://jdiff.readthedocs.io/en/latest/admin/install/) - How to Install, Configure, Upgrade, or Uninstall the Library. +- [Developer Guide](https://jdiff.readthedocs.io/en/latest/dev/contributing/) - Extending the Library, Code Reference, Contribution Guide. +>>>>>>> d408d2a (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) - [Release Notes / Changelog](https://jdiff.readthedocs.io/en/latest/admin/release_notes/). - [Frequently Asked Questions](https://jdiff.readthedocs.io/en/latest/user/faq/). diff --git a/bin/ensure_release_notes.py b/bin/ensure_release_notes.py new file mode 100644 index 0000000..7479433 --- /dev/null +++ b/bin/ensure_release_notes.py @@ -0,0 +1,97 @@ +"""Ensure that release notes exist for a given version. + +This script will do the following: + Ensure a release notes file exists at `docs/admin/release_notes/version_{version}.md`. + Ensure the `mkdocs.yml` file is updated to add the release notes file to the navigation. + Ensure the `pyproject.toml` `tool.towncrier.filename` is updated to reference the release notes file. + +It shouldn't be necessary to run this file manually. It is automatically called by `invoke generate-release-notes`. + +Example: + $ python bin/ensure_release_notes.py --version '1.0' +""" + +import argparse + +try: + import tomllib +except ImportError: + import tomli as tomllib + +from pathlib import Path + + +def release_notes_pyproject_toml(version): + """Update the pyproject.toml file to set the towncrier filename for the given version.""" + pyproject_file = Path(__file__).parent.parent / "pyproject.toml" + pyproject_content = pyproject_file.read_text() + pyproject_data = tomllib.loads(pyproject_content) + release_notes_file = f"docs/admin/release_notes/version_{version}.md" + + # Update the towncrier filename + if pyproject_data["tool"]["towncrier"].get("filename", "") != release_notes_file: + pyproject_data["tool"]["towncrier"]["filename"] = release_notes_file + + # Write back the updated content to pyproject.toml + # tomllib is not used to write the file because it is not roundtrippable + new_pyproject_content = [] + in_towncrier_section = False + for line in pyproject_content.splitlines(): + if line.strip() == "[tool.towncrier]": + in_towncrier_section = True + new_pyproject_content.append(line) + continue + if in_towncrier_section: + if line.strip().startswith("filename"): + new_pyproject_content.append(f'filename = "docs/admin/release_notes/version_{version}.md"') + in_towncrier_section = False # Only replace the first occurrence + else: + new_pyproject_content.append(line) + else: + new_pyproject_content.append(line) + + pyproject_file.write_text("\n".join(new_pyproject_content)) + # Add a newline at the end of the file if it doesn't exist + if not pyproject_file.read_text().endswith("\n"): + pyproject_file.write_text(pyproject_file.read_text() + "\n") + # Remind the user to update the release notes file. + print( + f"\033[33mRemember to update the Release Overview section in the release notes file: {release_notes_file}\033[0m" + ) + + +def ensure_release_notes_file(version): + """Ensure that the release notes file for the given version exists and is referenced in mkdocs.yml.""" + release_notes_file = Path(__file__).parent.parent / "docs" / "admin" / "release_notes" / f"version_{version}.md" + if not release_notes_file.exists(): + # Create a new release notes file with a basic template from towncrier_header.txt + towncrier_header = Path(__file__).parent.parent / "towncrier_header.txt" + content = towncrier_header.read_text().format(version=version) + release_notes_file.write_text(content) + + +def ensure_mkdocs_version(version): + """Ensure that mkdocs.yml includes the new release notes file in the navigation.""" + mkdocs_yml_file = Path(__file__).parent.parent / "mkdocs.yml" + mkdocs_yml_content = mkdocs_yml_file.read_text() + release_notes_nav_entry = f' - v{version}: "admin/release_notes/version_{version}.md"\n' + if release_notes_nav_entry in mkdocs_yml_content: + return + + # Add the new release notes entry to the mkdocs.yml content + if "Release Notes:" in mkdocs_yml_content: + mkdocs_yml_content = mkdocs_yml_content.replace( + ' - "admin/release_notes/index.md"\n', + f' - "admin/release_notes/index.md"\n{release_notes_nav_entry}', + ) + + mkdocs_yml_file.write_text(mkdocs_yml_content) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Ensure release notes exist for a given version.") + parser.add_argument("--version", help="The version number (e.g. 2.2)") + args = parser.parse_args() + ensure_release_notes_file(args.version) + ensure_mkdocs_version(args.version) + release_notes_pyproject_toml(args.version) diff --git a/changes/+main.housekeeping b/changes/+main.housekeeping new file mode 100644 index 0000000..3433adf --- /dev/null +++ b/changes/+main.housekeeping @@ -0,0 +1 @@ +Rebaked from the cookie `main`. diff --git a/docs/admin/release_notes/version_1.0.md b/docs/admin/release_notes/version_1.0.md index e9ac835..534d672 100644 --- a/docs/admin/release_notes/version_1.0.md +++ b/docs/admin/release_notes/version_1.0.md @@ -9,7 +9,11 @@ This document describes all new features and changes in the release `1.0`. The f - Major features or milestones - Changes to compatibility with Nautobot and/or other apps, libraries etc. +<<<<<<< HEAD ## [v1.0.2] - 2026-03-23 +======= +## [v1.0.0] - 2026-08-12 +>>>>>>> d408d2a (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) ### Added diff --git a/docs/generate_code_reference_pages.py b/docs/generate_code_reference_pages.py index 636ab53..26f8637 100644 --- a/docs/generate_code_reference_pages.py +++ b/docs/generate_code_reference_pages.py @@ -14,7 +14,7 @@ parts = parts[:-1] with mkdocs_gen_files.open(full_doc_path, "w") as fd: - IDENTIFIER = ".".join(parts) - print(f"::: {IDENTIFIER}", file=fd) + identifier = ".".join(parts) + print(f"::: {identifier}", file=fd) mkdocs_gen_files.set_edit_path(full_doc_path, file_path) diff --git a/pyproject.toml b/pyproject.toml index 6662513..d01a2d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,50 +23,57 @@ include = [ "README.md", ] +packages = [ + { include = "jdiff" }, +] + [tool.poetry.dependencies] python = ">=3.10,<3.14" deepdiff = ">=8.6.1,<9.0.0" jmespath = "^1.0.1" [tool.poetry.group.dev.dependencies] +<<<<<<< HEAD requests_mock = "*" coverage = "*" +======= +coverage = "~7.15.4" +>>>>>>> d408d2a (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) pytest = "*" mock = "*" pyyaml = "^6.0.1" -pylint = "^3.1.0" -yamllint = "^1.35.1" -invoke = "^2.2.0" +pylint = "~4.0.6" +yamllint = "~1.38.0" +invoke = "~3.0.3" toml = "^0.10.2" attrs = "^23.2.0" -towncrier = ">=23.6.0,<=24.8.0" -ruff = "*" +towncrier = "~25.8.0" +ruff = "~0.16.2" Markdown = "*" [tool.poetry.group.docs.dependencies] # Rendering docs to HTML -mkdocs = "1.6.1" +mkdocs = "~1.6.1" # Embedding YAML files into Markdown documents as tables -markdown-data-tables = "1.0.0" +markdown-data-tables = "~1.0.0" # Render custom markdown for version added/changed/remove notes -markdown-version-annotations = "1.0.1" +markdown-version-annotations = "~1.0.1" # Automatically generate some files as part of mkdocs build -mkdocs-gen-files = "0.5.0" +mkdocs-gen-files = "~0.6.1" # Image lightboxing in mkdocs -mkdocs-glightbox = "0.4.0" +mkdocs-glightbox = "~0.5.2" # Use Jinja2 templating in docs - see settings.md -mkdocs-macros-plugin = "1.3.7" +mkdocs-macros-plugin = "~1.5.0" # Material for mkdocs theme -mkdocs-material = "9.6.15" +mkdocs-material = "~9.7.7" # Handle docs redirections -mkdocs-redirects = "1.2.2" +mkdocs-redirects = "~1.2.3" # Automatically handle index pages for docs sections -mkdocs-section-index = "0.3.10" +mkdocs-section-index = "~0.3.12" # Automatic documentation from sources, for MkDocs -mkdocstrings = "0.27.0" +mkdocstrings = "~1.0.6" # Python-specific extension to mkdocstrings -mkdocstrings-python = "1.13.0" -griffe = "1.1.1" +mkdocstrings-python = "~2.0.5" [tool.ruff] line-length = 120 diff --git a/tasks.py b/tasks.py index 009f8d5..b839c2a 100644 --- a/tasks.py +++ b/tasks.py @@ -66,13 +66,14 @@ def task_wrapper(function=None): return task_wrapper -def run_command(context, exec_cmd, port=None): +def run_command(context, exec_cmd, port=None, rm=True): """Wrapper to run the invoke task commands. Args: context ([invoke.task]): Invoke task object. exec_cmd ([str]): Command to run. port (int): Used to serve local docs. + rm (bool): Whether to remove the container after running the command. Returns: result (obj): Contains Invoke result from running task. @@ -84,12 +85,12 @@ def run_command(context, exec_cmd, port=None): print(f"DOCKER - Running command: {exec_cmd} container: {context.jdiff.image_name}:{context.jdiff.image_ver}") if port: result = context.run( - f"docker run -it -p {port} -v {context.jdiff.pwd}:/local {context.jdiff.image_name}:{context.jdiff.image_ver} sh -c '{exec_cmd}'", + f"docker run -it {'--rm' if rm else ''} -p {port} -v {context.jdiff.pwd}:/local {context.jdiff.image_name}:{context.jdiff.image_ver} sh -c '{exec_cmd}'", pty=True, ) else: result = context.run( - f"docker run -it -v {context.jdiff.pwd}:/local {context.jdiff.image_name}:{context.jdiff.image_ver} sh -c '{exec_cmd}'", + f"docker run -it {'--rm' if rm else ''} -v {context.jdiff.pwd}:/local {context.jdiff.image_name}:{context.jdiff.image_ver} sh -c '{exec_cmd}'", pty=True, ) @@ -165,12 +166,28 @@ def coverage(context): run_command(context, "coverage html") -@task -def pytest(context): +@task( + help={ + "pattern": "Only run tests which match the given substring. Can be used multiple times.", + "label": "Module path to run (e.g., tests/unit/test_foo.py). Can be used multiple times.", + }, + iterable=["pattern", "label"], +) +def pytest(context, pattern=None, label=None): """Run pytest test cases.""" exec_cmd = "pytest -vv --doctest-modules jdiff/ && coverage run --source=jdiff -m pytest && coverage report" run_command(context, exec_cmd) + doc_test_cmd = "pytest -vv --doctest-modules jdiff/" + pytest_cmd = "coverage run --source=jdiff -m pytest" + if pattern: + pytest_cmd += "".join([f" -k {_pattern}" for _pattern in pattern]) + if label: + pytest_cmd += "".join([f" {_label}" for _label in label]) + coverage_cmd = "coverage report" + exec_cmd = " && ".join([doc_test_cmd, pytest_cmd, coverage_cmd]) + run_command(context, exec_cmd) + @task(aliases=("a",)) def autoformat(context): @@ -308,14 +325,19 @@ def docs(context): @task( help={ "version": "Version of jdiff to generate the release notes for.", + "date": "Date of the release (default: today).", } ) -def generate_release_notes(context, version=""): +def generate_release_notes(context, version="", date=""): """Generate Release Notes using Towncrier.""" - command = "poetry run towncrier build" - if version: - command += f" --version {version}" - else: - command += " --version `poetry version -s`" + if not version: + version = context.run("poetry version --short", hide=True).stdout.strip() + + version_major_minor = ".".join(version.split(".")[:2]) + context.run(f"poetry run python bin/ensure_release_notes.py --version {version_major_minor}") + + command = f"poetry run towncrier build --version {version} --yes" + if date: + command += f" --date {date}" # Due to issues with git repo ownership in the containers, this must always run locally. context.run(command) diff --git a/towncrier_header.txt b/towncrier_header.txt new file mode 100644 index 0000000..a350a18 --- /dev/null +++ b/towncrier_header.txt @@ -0,0 +1,9 @@ +# v{version} Release Notes + +This document describes all new features and changes in the release. 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). + +## Release Overview + +- Major features or milestones + + From d9fcd0c6a8c4bf9579919e7c80204e83a437da91 Mon Sep 17 00:00:00 2001 From: bakebot Date: Fri, 14 Aug 2026 17:38:30 +0000 Subject: [PATCH 2/2] Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool Template: ``` { "template": "https://github.com/networktocode-llc/cookiecutter-ntc.git", "dir": "python", "ref": "main", "path": "/__w/cookiecutter-nautobot-app-drift-manager/cookiecutter-nautobot-app-drift-manager/cookiecutter-ntc/python" } ``` Cookie: ``` { "remote": "https://github.com/networktocode/jdiff.git", "path": "/tmp/tmppsxnld8b/jdiff", "repository_path": "/tmp/tmppsxnld8b/jdiff", "dir": "", "branch_prefix": "drift-manager/develop", "context": { "codeowner_github_usernames": "@lvrfrc87 @scetron @grelleum @jeffkala @pszulczewski", "full_name": "Network to Code, LLC", "email": "info@networktocode.com", "github_org": "networktocode", "description": "Python library for examining structured data.", "project_name": "jdiff", "project_slug": "jdiff", "repo_url": "https://github.com/networktocode/jdiff", "base_url": "jdiff", "project_python_name": "jdiff", "project_python_base_version": "3.10", "project_with_config_settings": "no", "generate_docs": "yes", "version": "1.0.1", "original_publish_year": "2022", "_template": "/__w/cookiecutter-nautobot-app-drift-manager/cookiecutter-nautobot-app-drift-manager/cookiecutter-ntc/python", "_output_dir": "/tmp/tmppsxnld8b", "_repo_dir": "/__w/cookiecutter-nautobot-app-drift-manager/cookiecutter-nautobot-app-drift-manager/cookiecutter-ntc/python", "_checkout": null }, "drift_managed_branch": "develop", "remote_name": "origin", "pull_request_strategy": "PullRequestStrategy.UPDATE_OR_CREATE", "post_actions": [], "baked_commit_ref": "bc931311336d391d878202dba239389783ee9f09", "draft": false } ``` CLI Arguments: ``` { "cookie_dir": "", "input": false, "json_filename": "", "output_dir": "", "push": true, "template": "./cookiecutter-ntc", "template_dir": "python", "template_ref": "main", "pull_request": "update-or-create", "post_action": [], "disable_post_actions": true, "draft": null, "drift_managed_branch": "develop" } ``` --- .cookiecutter.json | 4 + .github/workflows/ci.yml | 58 +++++++++-- .github/workflows/prepare_release.yml | 45 +++++++++ .github/workflows/release.yml | 76 ++++++++++++++ README.md | 10 ++ development/bin/ensure_release_notes.py | 99 +++++++++++++++++++ .../towncrier_template.j2 | 9 -- docs/admin/release_notes/version_1.0.md | 4 + docs/dev/release_checklist.md | 6 +- example.invoke.yml => invoke.example.yml | 0 pyproject.toml | 32 +++++- tasks.py | 21 +++- 12 files changed, 341 insertions(+), 23 deletions(-) create mode 100644 development/bin/ensure_release_notes.py rename towncrier_template.j2 => development/towncrier_template.j2 (66%) rename example.invoke.yml => invoke.example.yml (100%) diff --git a/.cookiecutter.json b/.cookiecutter.json index ac30036..daf6e23 100644 --- a/.cookiecutter.json +++ b/.cookiecutter.json @@ -23,11 +23,15 @@ "pull_request_strategy": "update-or-create", "post_actions": [], "draft": false, +<<<<<<< HEAD <<<<<<< HEAD "baked_commit_ref": "bc931311336d391d878202dba239389783ee9f09", ======= "baked_commit_ref": "83a4975ef60cb34219557649eb08b8e599d5ec1c", >>>>>>> d408d2a (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) +======= + "baked_commit_ref": "508587ce7cf88b2c7d0b4aa5e162b22c532d710c", +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) "drift_managed_branch": "develop" } } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cd03532..1a0cd2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - name: "Check out repository code" uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v6" + uses: "networktocode/gh-action-setup-poetry-environment@v7" with: poetry-version: "2.1.3" - name: "Linting: ruff format" @@ -36,7 +36,7 @@ jobs: - name: "Check out repository code" uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v6" + uses: "networktocode/gh-action-setup-poetry-environment@v7" with: poetry-version: "2.1.3" - name: "Linting: ruff" @@ -49,7 +49,7 @@ jobs: - name: "Check out repository code" uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v6" + uses: "networktocode/gh-action-setup-poetry-environment@v7" with: poetry-version: "2.1.3" poetry-install-options: "--only dev,docs" @@ -63,7 +63,7 @@ jobs: - name: "Check out repository code" uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v6" + uses: "networktocode/gh-action-setup-poetry-environment@v7" with: poetry-version: "2.1.3" - name: "Checking: poetry lock file" @@ -76,7 +76,7 @@ jobs: - name: "Check out repository code" uses: "actions/checkout@v4" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v6" + uses: "networktocode/gh-action-setup-poetry-environment@v7" with: poetry-version: "2.1.3" - name: "Linting: yamllint" @@ -91,13 +91,14 @@ jobs: strategy: fail-fast: true matrix: - python-version: ["3.10", "3.13"] + python-version: ["3.10"] env: INVOKE_JDIFF_PYTHON_VER: "${{ matrix.python-version }}" steps: - name: "Check out repository code" uses: "actions/checkout@v4" - name: "Setup environment" +<<<<<<< HEAD uses: "networktocode/gh-action-setup-poetry-environment@v6" with: poetry-version: "2.1.3" @@ -136,6 +137,9 @@ jobs: uses: "actions/checkout@v4" - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v6" +======= + uses: "networktocode/gh-action-setup-poetry-environment@v7" +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) with: poetry-version: "2.1.3" - name: "Get image version" @@ -156,6 +160,46 @@ jobs: cache-to: "type=gha,scope=${{ env.INVOKE_JDIFF_IMAGE_NAME }}-${{ env.INVOKE_JDIFF_IMAGE_VER }}-py${{ matrix.python-version }}" build-args: | PYTHON_VER=${{ matrix.python-version }} +<<<<<<< HEAD +======= + - name: "Linting: Pylint" + run: "poetry run invoke pylint" + pytest: + needs: + - "check-in-docker" + strategy: + fail-fast: true + matrix: + python-version: ["3.10", "3.14"] + runs-on: "ubuntu-latest" + env: + INVOKE_JDIFF_PYTHON_VER: "${{ matrix.python-version }}" + steps: + - name: "Check out repository code" + uses: "actions/checkout@v4" + - name: "Setup environment" + uses: "networktocode/gh-action-setup-poetry-environment@v7" + with: + poetry-version: "2.1.3" + - name: "Get image version" + run: "echo INVOKE_JDIFF_IMAGE_VER=`poetry version -s`-py${{ matrix.python-version }} >> $GITHUB_ENV" + - name: "Set up Docker Buildx" + id: "buildx" + uses: "docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2" # v3.10.0 + - name: "Build" + uses: "docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25" # v5.4.0 + with: + builder: "${{ steps.buildx.outputs.name }}" + context: "./" + push: false + load: true + tags: "${{ env.INVOKE_JDIFF_IMAGE_NAME }}:${{ env.INVOKE_JDIFF_IMAGE_VER }}" + file: "./Dockerfile" + cache-from: "type=gha,scope=${{ env.INVOKE_JDIFF_IMAGE_NAME }}-${{ env.INVOKE_JDIFF_IMAGE_VER }}-py${{ matrix.python-version }}" + cache-to: "type=gha,scope=${{ env.INVOKE_JDIFF_IMAGE_NAME }}-${{ env.INVOKE_JDIFF_IMAGE_VER }}-py${{ matrix.python-version }}" + build-args: | + PYTHON_VER=${{ matrix.python-version }} +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) - name: "Run Tests" run: "poetry run invoke pytest" changelog: @@ -169,7 +213,7 @@ jobs: with: fetch-depth: "0" - name: "Setup environment" - uses: "networktocode/gh-action-setup-poetry-environment@v6" + uses: "networktocode/gh-action-setup-poetry-environment@v7" with: poetry-version: "2.1.3" - name: "Check for changelog entry" diff --git a/.github/workflows/prepare_release.yml b/.github/workflows/prepare_release.yml index 727c32d..30e22bb 100644 --- a/.github/workflows/prepare_release.yml +++ b/.github/workflows/prepare_release.yml @@ -21,6 +21,13 @@ on: # yamllint disable-line rule:truthy rule:comments description: "Date of the release YYYY-MM-DD (defaults to today's date in the US Eastern TZ)." required: false default: "" +<<<<<<< HEAD +======= + previous_version: + description: "The previous version tag to use for generating release notes (defaults to the latest tag or initial commit if no tags exist)." + required: false + default: "" +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) jobs: prepare-release: @@ -38,7 +45,11 @@ jobs: fetch-depth: 0 # Fetch all history for git tags - name: "Setup environment" +<<<<<<< HEAD uses: "networktocode/gh-action-setup-poetry-environment@v6" +======= + uses: "networktocode/gh-action-setup-poetry-environment@v7" +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) with: poetry-version: "2.1.3" poetry-install-options: "--with dev" @@ -53,7 +64,15 @@ jobs: # 2. Try to get the previous version tag # If it fails (no tags), get the hash of the first commit +<<<<<<< HEAD if PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null); then +======= + if [ -n "${{ github.event.inputs.previous_version }}" ]; then + PREV_TAG="${{ github.event.inputs.previous_version }}" + echo "Using user-specified previous version: $PREV_TAG" + echo "PREVIOUS_TAG=$PREV_TAG" >> $GITHUB_ENV + elif PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null); then +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) echo "PREVIOUS_TAG=$PREV_TAG" >> $GITHUB_ENV echo "Found previous tag: $PREV_TAG" else @@ -65,9 +84,31 @@ jobs: - name: "Determine New Version" id: "versioning" +<<<<<<< HEAD run: | # Perform the bump based on the user input poetry version ${{ github.event.inputs.bump_rule }} +======= + env: + GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + # Bump the poetry version based on the user input + CURRENT_POETRY_VER=$(poetry version --short) + set +e # continue running if the next command fails + GITHUB_RELEASE_EXISTS=$(gh release view v$CURRENT_POETRY_VER --json publishedAt --jq '.publishedAt') + set -e + # Unconditionally bump the version if the current version is alpha0 or beta0 + if [[ $CURRENT_POETRY_VER == *a0 || $CURRENT_POETRY_VER == *b0 || $CURRENT_POETRY_VER == *rc0 ]]; then + poetry version ${{ github.event.inputs.bump_rule }} + # If the bump rule is prerelease and a release doesn't already exist in github we don't bump the version + + # This is because we've already bumped to the prerelease version after the previous release + elif [[ "${{ github.event.inputs.bump_rule }}" == "prerelease" && "$CURRENT_POETRY_VER" =~ (a|b|rc)[0-9]+$ && -z "$GITHUB_RELEASE_EXISTS" ]]; then + echo "Prerelease v$CURRENT_POETRY_VER does not exist in github, using this version" + else + poetry version ${{ github.event.inputs.bump_rule }} + fi +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) # Capture the New version string for use in other steps NEW_VER=$(poetry version --short) @@ -138,7 +179,11 @@ jobs: # 7. Save to a temporary file to avoid shell argument length limits echo "$FINAL_NOTES" > ../consolidated_notes.md +<<<<<<< HEAD - name: "Generate Release Notes" +======= + - name: "Generate App Release Notes and update mkdocs.yml" +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) run: "poetry run inv generate-release-notes --version '${{ env.NEW_VERSION }}' --date '${{ env.RELEASE_DATE }}'" - name: "Commit Changes and Push" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1afc038..c096b5e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,10 @@ on: # yamllint disable-line rule:truthy rule:comments release: types: ["published"] +env: + POETRY_VERSION: "2.1.3" + PYTHON_VERSION: "3.12" + jobs: build: name: "Build package with poetry" @@ -14,9 +18,16 @@ jobs: - name: "Setup environment" uses: "networktocode/gh-action-setup-poetry-environment@v7" with: +<<<<<<< HEAD poetry-version: "2.1.3" python-version: "3.12" +======= + poetry-version: "${{ env.POETRY_VERSION }}" + python-version: "${{ env.PYTHON_VERSION }}" +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) poetry-install-options: "--no-root" + - name: "Build Documentation" + run: "poetry run invoke build-and-check-docs" - name: "Run Poetry Build" run: "poetry build" @@ -56,8 +67,13 @@ jobs: if: "startsWith(github.ref, 'refs/tags/v')" needs: "build" environment: "pypi" +<<<<<<< HEAD # IMPORTANT: this permission is mandatory for trusted publishing. permissions: +======= + permissions: + # IMPORTANT: this permission is mandatory for Trusted Publishing +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) id-token: "write" steps: - name: "Retrieve built package from cache" @@ -65,6 +81,7 @@ jobs: with: name: "distfiles" path: "dist/" + - name: "Publish package distributions to PyPI" uses: "pypa/gh-action-pypi-publish@ed0c53931b1dc9bd32cbe73a98c7f6766f8a527e" # v1.13.0 @@ -107,12 +124,17 @@ jobs: permissions: contents: "write" pull-requests: "write" +<<<<<<< HEAD name: "Create a PR from main into develop" +======= + name: "${{ github.event.release.target_commitish == 'main' && 'Create a PR from main into develop' || format('Create a PR to merge into {0}', github.event.release.target_commitish) }}" +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) needs: - "publish-github" - "publish-pypi" runs-on: "ubuntu-latest" steps: +<<<<<<< HEAD - name: "Checkout main" uses: "actions/checkout@v4" with: @@ -126,16 +148,45 @@ jobs: poetry-install-options: "--no-root" - name: "Create release branch from main" +======= + - name: "Checkout source branch" + uses: "actions/checkout@v4" + with: + ref: "${{ github.event.release.target_commitish }}" + fetch-depth: 0 + + - name: "Setup environment" + uses: "networktocode/gh-action-setup-poetry-environment@v7" + with: + poetry-version: "${{ env.POETRY_VERSION }}" + poetry-install-options: "--no-root" + + - name: "Create release branch and bump version" +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) id: "branch" run: | git config user.name "${{ github.actor }}" git config user.email "${{ github.actor }}@users.noreply.github.com" +<<<<<<< HEAD TAG_NAME="${{ github.event.release.tag_name }}" VERSION="${TAG_NAME#v}" BRANCH_NAME="release-${VERSION}-to-develop" +======= + SOURCE_BRANCH="${{ github.event.release.target_commitish }}" + if [ "$SOURCE_BRANCH" = "main" ]; then + TARGET_BRANCH="develop" + else + TARGET_BRANCH="$SOURCE_BRANCH" + fi + + TAG_NAME="${{ github.event.release.tag_name }}" + VERSION="${TAG_NAME#v}" + + BRANCH_NAME="release-${VERSION}-to-${TARGET_BRANCH}" +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) # Ensure release branch doesn't already exist if git rev-parse --verify origin/$BRANCH_NAME > /dev/null 2>&1; then @@ -145,18 +196,43 @@ jobs: git checkout -b "$BRANCH_NAME" +<<<<<<< HEAD poetry version prepatch +======= + poetry version prerelease +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) git add pyproject.toml && git commit -m "Bump version" git push origin "$BRANCH_NAME" echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT +<<<<<<< HEAD - name: "Create Pull Request to develop" +======= + echo "target_branch=$TARGET_BRANCH" >> $GITHUB_OUTPUT + + - name: "Create Pull Request" +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) env: GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" run: | gh pr create \ +<<<<<<< HEAD --title "Post release ${{ github.event.release.tag_name }} to develop" \ +======= + --title "Post release ${{ github.event.release.tag_name }} to ${{ steps.branch.outputs.target_branch }}" \ + --body "Please do a merge commit." \ + --base "${{ steps.branch.outputs.target_branch }}" \ + --head "${{ steps.branch.outputs.branch_name }}" + + - name: "Create Pull Request" + if: "steps.branch.outputs.branch_name != ''" + env: + GH_TOKEN: "${{ secrets.GITHUB_TOKEN }}" + run: | + gh pr create \ + --title "Sync release notes from ${{ github.event.release.target_commitish }} (${{ github.event.release.tag_name }}) to develop" \ +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) --body "Please do a merge commit." \ --base "develop" \ --head "${{ steps.branch.outputs.branch_name }}" diff --git a/README.md b/README.md index 66b996f..c255783 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@ # Jdiff

+<<<<<<< HEAD <<<<<<< HEAD ======= >>>>>>> d408d2a (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) +======= + +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool)
@@ -22,6 +26,7 @@ Full documentation for this library can be found over on the [Jdiff Docs](https://jdiff.readthedocs.io/) website: +<<<<<<< HEAD <<<<<<< HEAD - [User Guide](https://jdiff.readthedocs.io/en/latest/user/lib_overview/) - Overview, Using the library, Getting Started. - [Administrator Guide](https://jdiff.readthedocs.io/en/latest/admin/install/) - How to Install, Configure, Upgrade, or Uninstall the library. @@ -31,6 +36,11 @@ Full documentation for this library can be found over on the [Jdiff Docs](https: - [Administrator Guide](https://jdiff.readthedocs.io/en/latest/admin/install/) - How to Install, Configure, Upgrade, or Uninstall the Library. - [Developer Guide](https://jdiff.readthedocs.io/en/latest/dev/contributing/) - Extending the Library, Code Reference, Contribution Guide. >>>>>>> d408d2a (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) +======= +- [User Guide](https://jdiff.readthedocs.io/en/latest/user/app_overview/) - Overview, Using the Library, Getting Started. +- [Administrator Guide](https://jdiff.readthedocs.io/en/latest/admin/install/) - How to Install, Configure, Upgrade, or Uninstall the Library. +- [Developer Guide](https://jdiff.readthedocs.io/en/latest/dev/contributing/) - Extending the Library, Code Reference, Contribution Guide. +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) - [Release Notes / Changelog](https://jdiff.readthedocs.io/en/latest/admin/release_notes/). - [Frequently Asked Questions](https://jdiff.readthedocs.io/en/latest/user/faq/). diff --git a/development/bin/ensure_release_notes.py b/development/bin/ensure_release_notes.py new file mode 100644 index 0000000..f038cc4 --- /dev/null +++ b/development/bin/ensure_release_notes.py @@ -0,0 +1,99 @@ +"""Ensure that release notes exist for a given version. + +This script will do the following: + Ensure a release notes file exists at `docs/admin/release_notes/version_{version}.md`. + Ensure the `mkdocs.yml` file is updated to add the release notes file to the navigation. + Ensure the `pyproject.toml` `tool.towncrier.filename` is updated to reference the release notes file. + +It shouldn't be necessary to run this file manually. It is automatically called by `invoke generate-release-notes`. + +Example: + $ python ensure_release_notes.py --version '1.0.0' +""" + +import argparse + +try: + import tomllib +except ImportError: + import tomli as tomllib + +from pathlib import Path + + +def release_notes_pyproject_toml(version): + """Update the pyproject.toml file to set the towncrier filename for the given version.""" + pyproject_file = Path(__file__).parent.parent.parent / "pyproject.toml" + pyproject_content = pyproject_file.read_text() + pyproject_data = tomllib.loads(pyproject_content) + release_notes_file = f"docs/admin/release_notes/version_{version}.md" + + # Update the towncrier filename + if pyproject_data["tool"]["towncrier"].get("filename", "") != release_notes_file: + pyproject_data["tool"]["towncrier"]["filename"] = release_notes_file + + # Write back the updated content to pyproject.toml + # tomllib is not used to write the file because it is not roundtrippable + new_pyproject_content = [] + in_towncrier_section = False + for line in pyproject_content.splitlines(): + if line.strip() == "[tool.towncrier]": + in_towncrier_section = True + new_pyproject_content.append(line) + continue + if in_towncrier_section: + if line.strip().startswith("filename"): + new_pyproject_content.append(f'filename = "docs/admin/release_notes/version_{version}.md"') + in_towncrier_section = False # Only replace the first occurrence + else: + new_pyproject_content.append(line) + else: + new_pyproject_content.append(line) + + pyproject_file.write_text("\n".join(new_pyproject_content)) + # Add a newline at the end of the file if it doesn't exist + if not pyproject_file.read_text().endswith("\n"): + pyproject_file.write_text(pyproject_file.read_text() + "\n") + # Remind the user to update the release notes file. + print( + f"\033[33mRemember to update the Release Overview section in the release notes file: {release_notes_file}\033[0m" + ) + + +def ensure_release_notes_file(version): + """Ensure that the release notes file for the given version exists and is referenced in mkdocs.yml.""" + release_notes_file = ( + Path(__file__).parent.parent.parent / "docs" / "admin" / "release_notes" / f"version_{version}.md" + ) + if not release_notes_file.exists(): + # Create a new release notes file with a basic template from towncrier_header.txt + towncrier_header = Path(__file__).parent.parent / "towncrier_header.txt" + content = towncrier_header.read_text().format(version=version) + release_notes_file.write_text(content) + + +def ensure_mkdocs_version(version): + """Ensure that mkdocs.yml includes the new release notes file in the navigation.""" + mkdocs_yml_file = Path(__file__).parent.parent.parent / "mkdocs.yml" + mkdocs_yml_content = mkdocs_yml_file.read_text() + release_notes_nav_entry = f' - v{version}: "admin/release_notes/version_{version}.md"\n' + if release_notes_nav_entry in mkdocs_yml_content: + return + + # Add the new release notes entry to the mkdocs.yml content + if "Release Notes:" in mkdocs_yml_content: + mkdocs_yml_content = mkdocs_yml_content.replace( + ' - "admin/release_notes/index.md"\n', + f' - "admin/release_notes/index.md"\n{release_notes_nav_entry}', + ) + + mkdocs_yml_file.write_text(mkdocs_yml_content) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Ensure release notes exist for a given version.") + parser.add_argument("--version", help="The version number (e.g. 2.2)") + args = parser.parse_args() + ensure_release_notes_file(args.version) + ensure_mkdocs_version(args.version) + release_notes_pyproject_toml(args.version) diff --git a/towncrier_template.j2 b/development/towncrier_template.j2 similarity index 66% rename from towncrier_template.j2 rename to development/towncrier_template.j2 index 4fdab78..1b302da 100644 --- a/towncrier_template.j2 +++ b/development/towncrier_template.j2 @@ -1,13 +1,4 @@ -# v{{ versiondata.version.split(".")[:2] | join(".") }} Release Notes - -This document describes all new features and changes in the release. 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). - -## Release Overview - -- Major features or milestones -- Changes to compatibility with Nautobot and/or other apps, libraries etc. - {% if render_title %} ## [v{{ versiondata.version }} ({{ versiondata.date }})](https://github.com/networktocode/jdiff/releases/tag/v{{ versiondata.version}}) diff --git a/docs/admin/release_notes/version_1.0.md b/docs/admin/release_notes/version_1.0.md index 534d672..99283a4 100644 --- a/docs/admin/release_notes/version_1.0.md +++ b/docs/admin/release_notes/version_1.0.md @@ -9,11 +9,15 @@ This document describes all new features and changes in the release `1.0`. The f - Major features or milestones - Changes to compatibility with Nautobot and/or other apps, libraries etc. +<<<<<<< HEAD <<<<<<< HEAD ## [v1.0.2] - 2026-03-23 ======= ## [v1.0.0] - 2026-08-12 >>>>>>> d408d2a (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) +======= +## [v1.0.0] - 2026-08-14 +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) ### Added diff --git a/docs/dev/release_checklist.md b/docs/dev/release_checklist.md index 168ddea..c887c22 100644 --- a/docs/dev/release_checklist.md +++ b/docs/dev/release_checklist.md @@ -12,8 +12,8 @@ This document is intended for library maintainers and outlines the steps to perf Choose your own adventure: -- Patch release from `develop`? Jump [here](#all-releases-from-develop). -- Minor release? Continue with [Minor Version Bumps](#minor-version-bumps) and then [All Releases from `develop`](#all-releases-from-develop). +- Patch release from `develop`? Jump [here](#all-release-branches-branch-off-of-develop). +- Minor release? Continue with [Minor Version Bumps](#minor-version-bumps) and then [All Releases from `develop`](#all-release-branches-branch-off-of-develop). ## Minor Version Bumps @@ -45,7 +45,7 @@ The goal of this step is to walk through the entire install process *as document --- -## All Releases from `develop` +## All `release` branches branch off of `develop` ### Verify CI Build Status diff --git a/example.invoke.yml b/invoke.example.yml similarity index 100% rename from example.invoke.yml rename to invoke.example.yml diff --git a/pyproject.toml b/pyproject.toml index d01a2d9..5debd59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,10 @@ [tool.poetry] name = "jdiff" +<<<<<<< HEAD version = "1.0.3a0" +======= +version = "0.1.0a0" +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) description = "Python library for examining structured data." authors = ["Network to Code, LLC "] readme = "README.md" @@ -17,10 +21,16 @@ classifiers = [ "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] + include = [ - "LICENSE", - "README.md", + # Poetry by default will exclude files that are in .gitignore + { path = "jdiff/static/jdiff/docs/**/*", format = ["sdist", "wheel"] } +] + +packages = [ + { include = "jdiff" }, ] packages = [ @@ -28,6 +38,7 @@ packages = [ ] [tool.poetry.dependencies] +<<<<<<< HEAD python = ">=3.10,<3.14" deepdiff = ">=8.6.1,<9.0.0" jmespath = "^1.0.1" @@ -39,6 +50,13 @@ coverage = "*" ======= coverage = "~7.15.4" >>>>>>> d408d2a (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) +======= +python = ">=3.10,<3.15" +click = "*" + +[tool.poetry.group.dev.dependencies] +coverage = "~7.15.4" +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) pytest = "*" mock = "*" pyyaml = "^6.0.1" @@ -74,6 +92,14 @@ mkdocs-section-index = "~0.3.12" mkdocstrings = "~1.0.6" # Python-specific extension to mkdocstrings mkdocstrings-python = "~2.0.5" +<<<<<<< HEAD +======= + +[tool.poetry.scripts] +jdiff = 'jdiff.cli:main' + + +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) [tool.ruff] line-length = 120 @@ -152,7 +178,7 @@ addopts = "-vv --doctest-modules -p no:warnings --ignore-glob='*mock*'" package = "jdiff" directory = "changes" filename = "docs/admin/release_notes/version_X.Y.md" -template = "towncrier_template.j2" +template = "development/towncrier_template.j2" start_string = "" issue_format = "[#{issue}](https://github.com/networktocode/jdiff/issues/{issue})" diff --git a/tasks.py b/tasks.py index b839c2a..52a81db 100644 --- a/tasks.py +++ b/tasks.py @@ -36,7 +36,7 @@ def is_truthy(arg): { "jdiff": { "project_name": "jdiff", - "python_ver": "3.10", + "python_ver": "3.14", "local": is_truthy(os.getenv("INVOKE_JDIFF_LOCAL", "false")), "image_name": "jdiff", "image_ver": os.getenv("INVOKE_JDIFF_IMAGE_VER", "latest"), @@ -326,6 +326,7 @@ def docs(context): help={ "version": "Version of jdiff to generate the release notes for.", "date": "Date of the release (default: today).", +<<<<<<< HEAD } ) def generate_release_notes(context, version="", date=""): @@ -339,5 +340,23 @@ def generate_release_notes(context, version="", date=""): command = f"poetry run towncrier build --version {version} --yes" if date: command += f" --date {date}" +======= + "keep": "Keep existing release notes files. Useful for testing. (default: False).", + } +) +def generate_release_notes(context, version="", date="", keep=False): + """Generate Release Notes using Towncrier.""" + command = "poetry run towncrier build" + if not version: + version = context.run("poetry version --short", hide=True).stdout.strip() + command += f" --version {version}" + if date: + command += f" --date {date}" + command += " --keep" if keep else " --yes" + + version_major_minor = ".".join(version.split(".")[:2]) + context.run(f"poetry run python development/bin/ensure_release_notes.py --version {version_major_minor}") + +>>>>>>> f3dca29 (Cookie updated targeting develop by NetworkToCode Cookie Drift Manager Tool) # Due to issues with git repo ownership in the containers, this must always run locally. context.run(command)