diff --git a/.config/dictionary.txt b/.config/dictionary.txt new file mode 100644 index 0000000..f7ec258 --- /dev/null +++ b/.config/dictionary.txt @@ -0,0 +1,16 @@ +PYSCAFFOLD +REQPASS +Sbarnea +Sorin +TOXENV +autofix +autoupdate +capsys +codespell +commitlint +coveragerc +dists +notest +pycontribs +pypa +setuptools diff --git a/.config/requirements.txt b/.config/requirements.txt new file mode 100644 index 0000000..3f382dd --- /dev/null +++ b/.config/requirements.txt @@ -0,0 +1 @@ +rich diff --git a/.config/test-requirements.txt b/.config/test-requirements.txt new file mode 100644 index 0000000..e079f8a --- /dev/null +++ b/.config/test-requirements.txt @@ -0,0 +1 @@ +pytest diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..fac88a7 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,28 @@ +# .coveragerc to control coverage.py +[run] +branch = True +source = gh_pre +# omit = bad_file.py + +[paths] +source = + src/ + */site-packages/ + +[report] +# Regexes for lines to exclude from consideration +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain about missing debug-only code: + def __repr__ + if self\.debug + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if 0: + if __name__ == .__main__.: diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f04740a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,22 @@ +--- +version: 2 +updates: + - package-ecosystem: pip + directory: /.config/ + schedule: + day: sunday + interval: monthly + labels: + - dependabot-deps-updates + - skip-changelog + groups: + dependencies: + patterns: + - "*" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: monthly + labels: + - "dependencies" + - "skip-changelog" diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..b2c18a9 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,3 @@ +--- +# see https://github.com/ansible/team-devtools +_extends: ansible/team-devtools diff --git a/.github/workflows/ack.yml b/.github/workflows/ack.yml new file mode 100644 index 0000000..122e764 --- /dev/null +++ b/.github/workflows/ack.yml @@ -0,0 +1,10 @@ +--- +# See https://github.com/ansible/devtools/blob/main/.github/workflows/ack.yml +name: ack +"on": + pull_request_target: + types: [opened, labeled, unlabeled, synchronize] + +jobs: + ack: + uses: ansible/team-devtools/.github/workflows/ack.yml@main diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 0000000..751e431 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,13 @@ +--- +# See https://github.com/ansible/devtools/blob/main/.github/workflows/push.yml +name: push +"on": + push: + branches: + - main + - "releases/**" + - "stable/**" + +jobs: + ack: + uses: ansible/team-devtools/.github/workflows/push.yml@main diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..c4efa93 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,57 @@ +--- +name: release + +"on": + release: + types: [published] + workflow_dispatch: + +jobs: + # https://github.com/marketplace/actions/actions-tagger + actions-tagger: + needs: pypi # do not move the mobile tag until we publish + runs-on: windows-latest + permissions: + # Give the default GITHUB_TOKEN write permission. + # https://github.blog/changelog/2023-02-02-github-actions-updating-the-default-github_token-permissions-to-read-only/ + contents: write + steps: + - uses: Actions-R-Us/actions-tagger@latest + with: + token: "${{ github.token }}" + # Do not activate latest tag because it seems to affect RTD builds + # publish_latest_tag: true + pypi: + name: Publish to PyPI registry + environment: release + runs-on: ubuntu-22.04 + permissions: + id-token: write + + env: + FORCE_COLOR: 1 + PY_COLORS: 1 + TOXENV: pkg + + steps: + - name: Switch to using Python 3.10 by default + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install tox + run: python3 -m pip install --user "tox>=4.0.0" + + - name: Check out src from Git + uses: actions/checkout@v4 + with: + fetch-depth: 0 # needed by setuptools-scm + submodules: true + + - name: Build dists + run: python -m tox + + - name: Publish to pypi.org + if: >- # "create" workflows run separately from "push" & "pull_request" + github.event_name == 'release' + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml new file mode 100644 index 0000000..5e016b5 --- /dev/null +++ b/.github/workflows/tox.yml @@ -0,0 +1,142 @@ +--- +name: tox + +on: + push: # only publishes pushes to the main branch to TestPyPI + branches: # any integration branch but not tag + - "main" + pull_request: + branches: + - "main" + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +env: + FORCE_COLOR: 1 # tox, pytest, ansible-lint + PY_COLORS: 1 + +jobs: + prepare: + name: prepare + runs-on: ubuntu-22.04 + outputs: + matrix: ${{ steps.generate_matrix.outputs.matrix }} + steps: + - name: Determine matrix + id: generate_matrix + uses: coactions/dynamic-matrix@v1 + with: + min_python: "3.10" + max_python: "3.12" + other_names: | + lint + pkg + platforms: linux,macos + build: + name: ${{ matrix.name }} + runs-on: ${{ matrix.os || 'ubuntu-22.04' }} + needs: + - prepare + defaults: + run: + shell: ${{ matrix.shell || 'bash'}} + strategy: + fail-fast: false + matrix: ${{ fromJson(needs.prepare.outputs.matrix) }} + # max-parallel: 5 + # The matrix testing goal is to cover the *most likely* environments + # which are expected to be used by users in production. Avoid adding a + # combination unless there are good reasons to test it, like having + # proof that we failed to catch a bug by not running it. Using + # distribution should be preferred instead of custom builds. + env: + # Number of expected test passes, safety measure for accidental skip of + # tests. Update value if you add/remove tests. + PYTEST_REQPASS: 850 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 # needed by setuptools-scm + submodules: true + + - name: Set pre-commit cache + uses: actions/cache@v3 + if: ${{ matrix.passed_name == 'lint' }} + with: + path: | + ~/.cache/pre-commit + key: pre-commit-${{ matrix.name || matrix.passed_name }}-${{ hashFiles('.pre-commit-config.yaml') }} + + - name: Set up Python ${{ matrix.python_version || '3.10' }} + if: "!contains(matrix.shell, 'wsl')" + uses: actions/setup-python@v5 + with: + cache: pip + python-version: ${{ matrix.python_version || '3.10' }} + + # - name: Run ./tools/test-setup.sh + # run: ./tools/test-setup.sh + + - name: Install tox + run: | + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade "tox>=4.0.0" + + - name: Log installed dists + run: python3 -m pip freeze --all + + - name: Initialize tox envs ${{ matrix.passed_name }} + run: python3 -m tox --notest --skip-missing-interpreters false -vv -e ${{ matrix.passed_name }} + timeout-minutes: 5 # average is under 1, but macos can be over 3 + + # sequential run improves browsing experience (almost no speed impact) + - name: tox -e ${{ matrix.passed_name }} + run: python3 -m tox -e ${{ matrix.passed_name }} + + - name: Combine coverage data + if: ${{ startsWith(matrix.passed_name, 'py') }} + # produce a single .coverage file at repo root + run: tox -e coverage + + - name: Upload coverage data + if: ${{ startsWith(matrix.passed_name, 'py') }} + uses: codecov/codecov-action@v3 + with: + name: ${{ matrix.passed_name }} + token: ${{ secrets.CODECOV_TOKEN }} + verbose: true # optional (default = false) + + - name: Archive logs + uses: actions/upload-artifact@v3 + with: + name: logs.zip + path: .tox/**/log/ + + - name: Report failure if git reports dirty status + run: | + if [[ -n $(git status -s) ]]; then + # shellcheck disable=SC2016 + echo -n '::error file=git-status::' + printf '### Failed as git reported modified and/or untracked files\n```\n%s\n```\n' "$(git status -s)" | tee -a "$GITHUB_STEP_SUMMARY" + exit 99 + fi + # https://github.com/actions/toolkit/issues/193 + check: # This job does nothing and is only used for the branch protection + if: always() + permissions: + pull-requests: write + needs: + - build + + runs-on: ubuntu-latest + + steps: + - name: Decide whether the needed jobs succeeded or failed + uses: re-actors/alls-green@release/v1 + with: + jobs: ${{ toJSON(needs) }} + + - name: Check out src from Git + uses: actions/checkout@v4 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9e1e9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,54 @@ +# Temporary and binary files +*~ +*.py[cod] +*.so +*.cfg +!.isort.cfg +!setup.cfg +*.orig +*.log +*.pot +__pycache__/* +.cache/* +.*.swp +*/.ipynb_checkpoints/* +.DS_Store + +# Project files +.ropeproject +.project +.pydevproject +.settings +.idea +.vscode +tags + +# Package files +*.egg +*.eggs/ +.installed.cfg +*.egg-info + +# Unittest and coverage +htmlcov/* +.coverage +.coverage.* +.tox +junit*.xml +coverage.xml +.pytest_cache/ + +# Build and docs folder/files +build/* +dist/* +sdist/* +docs/api/* +docs/_rst/* +docs/_build/* +cover/* +MANIFEST + +# Per-project virtualenvs +.venv*/ +.conda*/ +.python-version diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..0557ad2 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,106 @@ +--- +ci: + # format compatible with commitlint + autoupdate_commit_msg: "chore: pre-commit autoupdate" + autoupdate_schedule: monthly + autofix_commit_msg: | + chore: auto fixes from pre-commit.com hooks + + for more information, see https://pre-commit.ci + skip: + # https://github.com/pre-commit-ci/issues/issues/55 + - pip-compile + - schemas + submodules: true +exclude: > + (?x)^( + .config/.*requirements.*| + .vscode/extensions.json| + .vscode/settings.json| + )$ +repos: + - repo: meta + hooks: + - id: check-useless-excludes + - repo: https://github.com/pappasam/toml-sort + rev: v0.23.1 + hooks: + - id: toml-sort-fix + - repo: https://github.com/pre-commit/mirrors-prettier + # keep it before yamllint + rev: v4.0.0-alpha.8 + hooks: + - id: prettier + always_run: true + additional_dependencies: + - prettier + - prettier-plugin-toml + - prettier-plugin-sort-json + - repo: https://github.com/streetsidesoftware/cspell-cli + rev: v8.3.0 + hooks: + - id: cspell + # entry: codespell --relative + args: [--relative, --no-progress, --no-summary] + name: Spell check with cspell + - repo: https://github.com/pre-commit/pre-commit-hooks.git + rev: v4.5.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - id: mixed-line-ending + - id: fix-byte-order-marker + - id: check-executables-have-shebangs + - id: check-merge-conflict + - id: debug-statements + language_version: python3 + - repo: https://github.com/adrienverge/yamllint.git + rev: v1.33.0 + hooks: + - id: yamllint + files: \.(yaml|yml)$ + types: [file, yaml] + entry: yamllint --strict + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: "v0.1.14" + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - repo: https://github.com/psf/black + rev: 23.12.1 + hooks: + - id: black + language_version: python3 + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.8.0 + hooks: + - id: mypy + # empty args needed in order to match mypy cli behavior + args: [--strict] + additional_dependencies: + - pytest + - rich + - repo: https://github.com/pycqa/pylint + rev: v3.0.3 + hooks: + - id: pylint + args: + - --output-format=colorized + additional_dependencies: + - pytest + - rich + - repo: https://github.com/jazzband/pip-tools + rev: 7.3.0 + hooks: + - id: pip-compile + name: deps + alias: deps + always_run: true + entry: pip-compile --upgrade -q --no-annotate --output-file=.config/constraints.txt pyproject.toml --strip-extras --all-extras + files: ^.config\/.*requirements.*$ + language: python + language_version: "3.10" # minimal we support officially + pass_filenames: false + stages: [manual] + additional_dependencies: + - pip>=22.3.1 diff --git a/.yamllint b/.yamllint new file mode 100644 index 0000000..2772c3c --- /dev/null +++ b/.yamllint @@ -0,0 +1,15 @@ +--- +rules: + comments: + # prettier compatibility + min-spaces-from-content: 1 + document-start: + present: true + indentation: + level: error + indent-sequences: consistent + octal-values: + forbid-implicit-octal: true + forbid-explicit-octal: true +ignore: | + .tox diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..0325506 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024 Sorin Sbarnea + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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 OR COPYRIGHT HOLDERS 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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9854aff --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# gh-pre + +An experimental tool that helps with Project Release Engineering, focused on +repository clusters (groups of repositories which need to be released in +particular order). + +```shell +gh extension install pycontribs/gh-pre +``` + +Usage: + +``` +gh pre +``` + +It can also be installed and executed as a python module: + +``` +pip install gh-pre +pre +``` diff --git a/cspell.config.yaml b/cspell.config.yaml new file mode 100644 index 0000000..ffce57a --- /dev/null +++ b/cspell.config.yaml @@ -0,0 +1,15 @@ +--- +dictionaryDefinitions: + - name: words + path: .config/dictionary.txt + addWords: true +dictionaries: + # Use `cspell-cli trace word` to check where a work is defined + - en_US + - bash + - words + - python +ignorePaths: + - cspell.config.yaml + - tox.ini + - .gitignore diff --git a/gh-pre b/gh-pre new file mode 100755 index 0000000..16cc234 --- /dev/null +++ b/gh-pre @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -e + +command -v pipx || { + echo "pipx not found, please installing it. See https://github.com/pypa/pipx" + exit 9 +} + +command -v pre || { + echo "pre not found" + pipx install -e . +} + +pre "$@" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..86ff24a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,54 @@ +[build-system] +# AVOID CHANGING REQUIRES: IT WILL BE UPDATED BY PYSCAFFOLD! +requires = ["setuptools>=46.1.0", "setuptools_scm[toml]>=5"] +build-backend = "setuptools.build_meta" + +[project] +# https://peps.python.org/pep-0621/#readme +requires-python = ">=3.10" +dynamic = ["version", "dependencies", "optional-dependencies"] +name = "gh-pre" +description = "gh-pre" +readme = "README.md" +authors = [{"name" = "Sorin Sbarnea", "email" = "sorin.sbarnea@gmail.com"}] +maintainers = [{"name" = "Sorin Sbarnea", "email" = "sorin.sbarnea@gmail.com"}] +license = {text = "MIT"} +classifiers = [ + "Development Status :: 5 - Production/Stable", + "Environment :: Console", + "Intended Audience :: Developers", + "Intended Audience :: Information Technology", + "Intended Audience :: System Administrators", + "License :: OSI Approved :: MIT License", + "Operating System :: MacOS", + "Operating System :: POSIX", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python", + "Topic :: System :: Systems Administration", + "Topic :: Software Development :: Quality Assurance", + "Topic :: Software Development :: Testing", + "Topic :: Utilities" +] +keywords = ["gh", "github"] + +[project.scripts] +pre = "gh_pre.__main__:main" + +[project.urls] +homepage = "https://github.com/pycontribs/gh-pre" +repository = "https://github.com/pycontribs/gh-pre" +changelog = "https://github.com/pycontribs/gh-pre/releases" + +[tool.setuptools.dynamic] +dependencies = {file = [".config/requirements.txt"]} +optional-dependencies.test = {file = [".config/test-requirements.txt"]} + +[tool.setuptools_scm] +# For smarter version schemes and other configuration options, +# check out https://github.com/pypa/setuptools_scm +version_scheme = "no-guess-dev" diff --git a/src/gh_pre/__init__.py b/src/gh_pre/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/gh_pre/__main__.py b/src/gh_pre/__main__.py new file mode 100644 index 0000000..522b3af --- /dev/null +++ b/src/gh_pre/__main__.py @@ -0,0 +1,64 @@ +"""Expose features related to git repositories.""" +from __future__ import annotations +import json + +import datetime +from subprocess import run + +from rich.panel import Panel +from rich import box +from rich.console import Console + + +def main() -> None: + """Main entrypoint.""" + console = Console() + repos = [ + "ansible/ansible-compat", + "ansible/ansible-lint", + "ansible/ansible-navigator", + "ansible/ansible-creator", + "ansible/molecule", + "ansible/tox-ansible", + "ansible/pytest-ansible", + "ansible/ansible-development-environment", + "ansible/ansible-dev-tools", + "ansible/creator-ee", + ] + for repo in repos: + repo_link = f"[markdown.link][link=https://github.com/{repo}]{repo}[/][/]" + result = run( + f'gh api repos/{repo}/releases --jq "[.[] | select(.draft)"]', + text=True, + shell=True, + capture_output=True, + check=True, + ) + drafts = json.loads(result.stdout) + if not drafts or ( + isinstance(drafts, dict) and drafts["message"] == "Not Found" + ): + console.print(f"🟢 {repo_link} [dim]has no draft release.[/]") + continue + for draft in drafts: + created = datetime.datetime.fromisoformat(draft["created_at"]).replace( + tzinfo=None + ) + age = (datetime.datetime.now() - created).days + if not draft["body"].strip(): + console.print(f"🟢 {repo_link} [dim]has an empty draft release.[/]") + continue + + md = Panel(draft["body"].replace("\n\n", "\n").strip("\n"), box=box.MINIMAL) + msg = ( + f"🟠 {repo_link} draft release " + + f"[link={draft['html_url']}][markdown.link]{draft['tag_name']}[/][/]" + + f" created [repr.number]{age}[/] days ago:\n" + ) + console.print(msg, highlight=False, end="") + console.print(md, style="dim") + + +if __name__ == "__main__": + # execute only if run as a script + main() diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_app.py b/tests/test_app.py new file mode 100644 index 0000000..3c62ee6 --- /dev/null +++ b/tests/test_app.py @@ -0,0 +1,16 @@ +"""Tests""" +from pytest import CaptureFixture +from gh_pre.__main__ import main + +__author__ = "Sorin Sbarnea" +__copyright__ = "Sorin Sbarnea" +__license__ = "MIT" + + +def test_main(capsys: CaptureFixture[str]) -> None: + """CLI Tests""" + # capsys is a pytest fixture that allows asserts against stdout/stderr + # https://docs.pytest.org/en/stable/capture.html + main() + captured = capsys.readouterr() + assert "The 7-th Fibonacci number is 13" in captured.out diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..adf7e17 --- /dev/null +++ b/tox.ini @@ -0,0 +1,104 @@ +# Tox configuration file +# Read more under https://tox.wiki/ +# THIS SCRIPT IS SUPPOSED TO BE AN EXAMPLE. MODIFY IT ACCORDING TO YOUR NEEDS! + +[tox] +minversion = 4.0 +envlist = + pkg + lint + py +isolated_build = True + + +[testenv] +description = Invoke pytest to run automated tests +setenv = + TOXINIDIR = {toxinidir} +passenv = + HOME + SETUPTOOLS_* +extras = + test +commands = + pytest {posargs} +package = editable +allowlist_externals = + gh + sh + +[testenv:{build,clean}] +description = + build: Build the package in isolation according to PEP517, see https://github.com/pypa/build + clean: Remove old distribution files and temporary build artifacts (./build and ./dist) +# https://setuptools.pypa.io/en/stable/build_meta.html#how-to-use-it +skip_install = True +changedir = {toxinidir} +deps = + build: build[virtualenv] +passenv = + SETUPTOOLS_* +commands = + clean: python -c 'import shutil; [shutil.rmtree(p, True) for p in ("build", "dist", "docs/_build")]' + clean: python -c 'import pathlib, shutil; [shutil.rmtree(p, True) for p in pathlib.Path("src").glob("*.egg-info")]' + build: python -m build {posargs} +# By default, both `sdist` and `wheel` are built. If your sdist is too big or you don't want +# to make it available, consider running: `tox -e build -- --wheel` + +[testenv:lint] +description = Run all linters +# pip compile includes python version in output constraints, so we want to +# be sure that version does not change randomly. +basepython = python3.10 +deps = + pre-commit>=2.6.0 + setuptools>=51.1.1 + pytest>=7.2.2 # to updated schemas +skip_install = true +commands_pre = +commands = + {envpython} -m pre_commit run --all-files --show-diff-on-failure {posargs:} +passenv = + {[testenv]passenv} + PRE_COMMIT_HOME +setenv = + {[testenv]setenv} + # avoid messing pre-commit with out own constraints + PIP_CONSTRAINT= + +[testenv:pkg] +description = + Do packaging/distribution. If tag is not present or PEP440 compliant upload to + PYPI could fail +# `usedevelop = true` overrides `skip_install` instruction, it's unwanted +usedevelop = false +# don't install molecule itself in this env +skip_install = true +deps = + build >= 0.9.0 + twine >= 4.0.1 + pipx +setenv = +commands = + # build wheel and sdist using PEP-517 + {envpython} -c 'import os.path, shutil, sys; \ + dist_dir = os.path.join("{toxinidir}", "dist"); \ + os.path.isdir(dist_dir) or sys.exit(0); \ + print("Removing \{!s\} contents...".format(dist_dir), file=sys.stderr); \ + shutil.rmtree(dist_dir)' + {envpython} -m build --outdir {toxinidir}/dist/ {toxinidir} + # Validate metadata using twine + twine check --strict {toxinidir}/dist/* + # Install the wheel + sh -c 'python3 -m pip install "gh-pre @ file://$(echo {toxinidir}/dist/*.whl)"' + # call the tool + python3 -m gh_pre --help + # Uninstall it + python3 -m pip uninstall -y gh-pre + # Testing pipx compatibility + pipx install --verbose --editable . + sh -c "pipx list --short | grep gh-pre" + # Testing gh calling + sh -c "gh extension remove gh-pre || true" + gh extension install . + gh pre --version