Metadata-Version: 2.4
Name: blueline
Version: 2026.10.23.1909.dev1
Summary: Dry-run a PyPI release against the pinned pypa/gh-action-pypi-publish toolchain
Author-email: Moshe Zadka <moshez@zadka.club>
License: 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.
        
Project-URL: Homepage, https://github.com/moshez/blueline
Requires-Python: >=3.12
Description-Content-Type: text/x-rst
Requires-Dist: build
Requires-Dist: commander-data
Requires-Dist: gather
Provides-Extra: tests
Requires-Dist: virtue; extra == "tests"
Requires-Dist: pyhamcrest; extra == "tests"
Requires-Dist: coverage; extra == "tests"
Provides-Extra: mypy
Requires-Dist: mypy; extra == "mypy"
Provides-Extra: lint
Requires-Dist: flake8; extra == "lint"
Requires-Dist: black; extra == "lint"
Requires-Dist: stolid; extra == "lint"
Requires-Dist: placard; extra == "lint"
Requires-Dist: pydoclint; extra == "lint"
Provides-Extra: docs
Requires-Dist: sphinx; extra == "docs"

blueline
========

A blueline is the printer's last proof before a job goes to press.
``blueline`` is the last proof before a package goes to PyPI.

It builds a project's sdist and wheel and runs ``twine check --strict`` on
them with the same Python and the same ``twine`` (and ``readme-renderer``,
``docutils``, ...) versions that the pinned
`pypa/gh-action-pypi-publish <https://github.com/pypa/gh-action-pypi-publish>`_
action uploads with. Anything the real upload would reject fails in a pull
request first.

Usage
-----

Pin the action to a full commit SHA in your release workflow::

    - uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0

Then run, from the project root, on the Python the action uses::

    python -m blueline dry-release --no-dry-run

``blueline`` reads the pin from ``.github/workflows/release.yml``, fetches the
action's ``Dockerfile`` and ``requirements/runtime.txt`` at that commit, and:

1. fails if it is not running on the action's Python (the Dockerfile's
   ``FROM python:X.Y``);
2. installs ``twine`` into the running environment with ``pip``, constrained
   to the action's runtime pins;
3. builds an sdist and a wheel into a temporary directory;
4. runs ``twine check --strict`` on them.

Steps 2 and 3 change things, so, like other
`gather <https://pypi.org/project/gather/>`_ commands, they only run with
``--no-dry-run``; without it, ``blueline`` fetches the action's files, checks
the Python version, and logs the commands it would run.

``--workflow PATH`` reads the pin from another workflow file, and a positional
argument builds a project other than the current directory.

Every command runs through gather's runner. ``curl`` for the action's files,
the version check, and ``twine check`` are read-only and use ``safe_run``;
``pip`` and ``build`` use ``run``, which only runs them with ``--no-dry-run``. Tests drive the whole command with
a fake ``subprocess.run``.

With nox
--------

.. code-block:: python

    @nox.session(python="3.13")
    def dry_release(session):
        session.install("blueline")
        session.run("python", "-m", "blueline", "dry-release", "--no-dry-run")

When the action moves to a new Python, ``blueline`` fails and says so: update
``python=`` and the CI job that runs the session.

``blueline`` needs ``pip`` in the session's environment, which nox's default
``virtualenv`` backend provides. The ``uv`` backend is not supported. It also
needs ``curl`` on ``PATH``, which GitHub's runners have.
