mirror of
https://gitcode.com/gh_mirrors/gh/gh-action-pypi-publish.git
synced 2026-07-08 20:30:50 +00:00
Compare commits
112 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f8c70e705f | |||
| 68276eb3e4 | |||
| a5d57af63c | |||
| e90e853e89 | |||
| be695966b0 | |||
| 54d67ed3c5 | |||
| d32e2fab32 | |||
| a8d92e9876 | |||
| f5622bde02 | |||
| 3be882c473 | |||
| 775be49481 | |||
| 5684530096 | |||
| 135d0d5353 | |||
| 110f54a387 | |||
| c803c91ef0 | |||
| f9ed8ba9ad | |||
| 30639668ca | |||
| a56da0b891 | |||
| e4b9031741 | |||
| 5a085bf49e | |||
| 0811f991bd | |||
| f47b34707f | |||
| 7a1a355fb5 | |||
| 3b6670b0bd | |||
| c008c2f40a | |||
| fe431ff9ad | |||
| c542b72dc6 | |||
| f437f577c3 | |||
| ba7045370c | |||
| 6cbdb5439a | |||
| 82695c57c9 | |||
| 0bf742be3e | |||
| 30c382209e | |||
| 89ddbeae04 | |||
| a0f29a5690 | |||
| 0b567d5b01 | |||
| 4372cb5585 | |||
| 69efb8cbfb | |||
| dfde872acc | |||
| 3d567f44ce | |||
| 67b747a9c8 | |||
| 29930c9cf5 | |||
| 9c859e9a77 | |||
| 65bf8a81de | |||
| 486ec8dd23 | |||
| 48b317d84d | |||
| ae295504b3 | |||
| f3ce18f699 | |||
| ea29ccc08c | |||
| a3a3bafbb3 | |||
| d5417dc8be | |||
| 8ef2b3d46c | |||
| 2b46bad8cb | |||
| 22b4d1f125 | |||
| 7104b6e981 | |||
| f131721e84 | |||
| 32b5e93709 | |||
| efcb9babc8 | |||
| d2ce3ec872 | |||
| 0eaf3a11fd | |||
| 6a2da9bc3b | |||
| 7eb3b701d1 | |||
| 91e612128c | |||
| c13a06eb2f | |||
| a260c7e54e | |||
| 5a2ea379cb | |||
| c7f29f7ade | |||
| 644926c972 | |||
| e71a4a4c1d | |||
| e56e8212f4 | |||
| c879b84594 | |||
| 57e7d53102 | |||
| ce291dce5b | |||
| 102d8ab13f | |||
| 3a9eaef3ef | |||
| a01fa7442e | |||
| ee892fd7f2 | |||
| 47622d7eb0 | |||
| 54b250c7bf | |||
| 967acbb201 | |||
| 5755482491 | |||
| dfc70e7dc8 | |||
| 1c4183fd91 | |||
| efa83bf521 | |||
| 3f27ae2c90 | |||
| ee83abeb35 | |||
| 08af49986e | |||
| 713d81cca0 | |||
| 3a5c774287 | |||
| 819df810af | |||
| fedca4a9a8 | |||
| 1350b8bd72 | |||
| dfae161eab | |||
| b3f93a1ad6 | |||
| 595429bec5 | |||
| cde4774fb1 | |||
| a737e68aed | |||
| f68ac3ea4e | |||
| 7ef975b955 | |||
| 9022aae148 | |||
| 5d1679fa6b | |||
| d2a2496a01 | |||
| d7edd4c957 | |||
| 8d5f27cca4 | |||
| b0dc178d8e | |||
| bbf6e0b2f0 | |||
| 0b69a8c2df | |||
| c54db9c2b7 | |||
| 480ec4ed58 | |||
| 5fb2f047e2 | |||
| 7bbdccd64f | |||
| 328cf89e05 |
+15
-5
@@ -1,6 +1,16 @@
|
||||
github: [webknjaz]
|
||||
patreon: webknjaz
|
||||
# open_collective: # Replace with a single Open Collective username
|
||||
---
|
||||
|
||||
custom:
|
||||
- https://www.comebackalive.in.ua/donate
|
||||
- https://github.com/vshymanskyy/StandWithUkraine#for-maintainers-and-authors
|
||||
- https://www.paypal.me/webknjazCZ
|
||||
- https://webknjaz.me
|
||||
|
||||
github:
|
||||
- webknjaz
|
||||
|
||||
ko_fi: webknjaz
|
||||
# tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
custom: https://www.paypal.me/webknjazCZ
|
||||
|
||||
liberapay: webknjaz
|
||||
|
||||
...
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
|
||||
name: 🧪
|
||||
|
||||
on: # yamllint disable-line rule:truthy
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
devpi-password: abcd1234
|
||||
devpi-username: root
|
||||
devpi-port: 3141
|
||||
|
||||
FORCE_COLOR: 1 # Request colored output from CLI tools supporting it
|
||||
MYPY_FORCE_COLOR: 1 # MyPy's color enforcement
|
||||
PIP_DISABLE_PIP_VERSION_CHECK: 1
|
||||
PIP_NO_PYTHON_VERSION_WARNING: 1
|
||||
PIP_NO_WARN_SCRIPT_LOCATION: 1
|
||||
PY_COLORS: 1 # Recognized by the `py` package, dependency of `pytest`
|
||||
TOX_PARALLEL_NO_SPINNER: 1
|
||||
TOX_TESTENV_PASSENV: >- # Make tox-wrapped tools see color requests
|
||||
FORCE_COLOR
|
||||
MYPY_FORCE_COLOR
|
||||
NO_COLOR
|
||||
PY_COLORS
|
||||
PYTEST_THEME
|
||||
PYTEST_THEME_MODE
|
||||
|
||||
jobs:
|
||||
smoke-test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
services:
|
||||
devpi:
|
||||
image: muccg/devpi
|
||||
env:
|
||||
DEVPI_PASSWORD: ${{ env.devpi-password }}
|
||||
ports:
|
||||
- 3141
|
||||
|
||||
timeout-minutes: 2
|
||||
|
||||
steps:
|
||||
- name: Check out the action locally
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
path: test
|
||||
- name: Install the packaging-related tools
|
||||
run: python3 -m pip install build twine
|
||||
env:
|
||||
PIP_CONSTRAINT: test/requirements/runtime.txt
|
||||
- name: Create the stub package importable directory
|
||||
run: mkdir -pv src/test_package
|
||||
- name: Populate the stub package `__init__.py`
|
||||
run: echo '__version__ = "0.1"' > src/test_package/__init__.py
|
||||
- name: Populate the stub package `README.md`
|
||||
run: echo "# Test Package" > README.md
|
||||
- name: Populate the stub package `pyproject.toml`
|
||||
run: echo "$CONTENTS" > pyproject.toml
|
||||
env:
|
||||
CONTENTS: |
|
||||
[build-system]
|
||||
requires = [
|
||||
"setuptools == 65.6.3",
|
||||
]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "test-package"
|
||||
version = "0.1"
|
||||
readme = "README.md"
|
||||
- name: Build the stub package sdist and wheel distributions
|
||||
run: python3 -m build
|
||||
- name: Register the stub package in devpi
|
||||
run: twine register dist/*.tar.gz
|
||||
env:
|
||||
TWINE_USERNAME: ${{ env.devpi-username }}
|
||||
TWINE_PASSWORD: ${{ env.devpi-password }}
|
||||
TWINE_REPOSITORY_URL: >-
|
||||
http://localhost:${{
|
||||
job.services.devpi.ports[env.devpi-port]
|
||||
}}/${{
|
||||
env.devpi-username
|
||||
}}/public/
|
||||
- name: ✅ Smoke-test the locally checked out action
|
||||
uses: ./test
|
||||
env:
|
||||
DEBUG: >-
|
||||
true
|
||||
PATH: utter-nonsense
|
||||
with:
|
||||
user: ${{ env.devpi-username }}
|
||||
password: ${{ env.devpi-password }}
|
||||
repository-url: >-
|
||||
http://devpi:${{ env.devpi-port }}/${{ env.devpi-username }}/public/
|
||||
|
||||
...
|
||||
@@ -0,0 +1,184 @@
|
||||
---
|
||||
ci:
|
||||
autoupdate_schedule: quarterly
|
||||
default_language_version: python3.11
|
||||
|
||||
repos:
|
||||
- repo: https://github.com/asottile/add-trailing-comma.git
|
||||
rev: v3.0.0
|
||||
hooks:
|
||||
- id: add-trailing-comma
|
||||
|
||||
- repo: https://github.com/PyCQA/isort.git
|
||||
rev: 5.12.0
|
||||
hooks:
|
||||
- id: isort
|
||||
args:
|
||||
- --honor-noqa
|
||||
|
||||
- repo: https://github.com/Lucas-C/pre-commit-hooks.git
|
||||
rev: v1.5.1
|
||||
hooks:
|
||||
- id: remove-tabs
|
||||
|
||||
- repo: https://github.com/python-jsonschema/check-jsonschema.git
|
||||
rev: 0.23.2
|
||||
hooks:
|
||||
- id: check-github-actions
|
||||
- id: check-github-workflows
|
||||
- id: check-jsonschema
|
||||
name: Check GitHub Workflows set timeout-minutes
|
||||
args:
|
||||
- --builtin-schema
|
||||
- github-workflows-require-timeout
|
||||
files: ^\.github/workflows/[^/]+$
|
||||
types:
|
||||
- yaml
|
||||
- id: check-readthedocs
|
||||
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks.git
|
||||
rev: v4.4.0
|
||||
hooks:
|
||||
# Side-effects:
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
- id: mixed-line-ending
|
||||
# Non-modifying checks:
|
||||
- id: name-tests-test
|
||||
files: >-
|
||||
^tests/[^_].*\.py$
|
||||
- id: check-added-large-files
|
||||
- id: check-byte-order-marker
|
||||
- id: check-case-conflict
|
||||
- id: check-executables-have-shebangs
|
||||
- id: check-merge-conflict
|
||||
- id: check-json
|
||||
- id: check-symlinks
|
||||
- id: check-yaml
|
||||
- id: detect-private-key
|
||||
# Heavy checks:
|
||||
- id: check-ast
|
||||
- id: debug-statements
|
||||
language_version: python3
|
||||
|
||||
- repo: https://github.com/codespell-project/codespell
|
||||
rev: v2.2.5
|
||||
hooks:
|
||||
- id: codespell
|
||||
|
||||
- repo: https://github.com/adrienverge/yamllint.git
|
||||
rev: v1.32.0
|
||||
hooks:
|
||||
- id: yamllint
|
||||
files: \.(yaml|yml)$
|
||||
types:
|
||||
- file
|
||||
- yaml
|
||||
args:
|
||||
- --strict
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8.git
|
||||
rev: 6.0.0
|
||||
hooks:
|
||||
- id: flake8
|
||||
alias: flake8-no-wps
|
||||
name: flake8 WPS-excluded
|
||||
args:
|
||||
- --ignore
|
||||
- >-
|
||||
D100,
|
||||
D101,
|
||||
D103,
|
||||
D107,
|
||||
E402,
|
||||
E501,
|
||||
additional_dependencies:
|
||||
- flake8-2020 ~= 1.7.0
|
||||
- flake8-pytest-style ~= 1.6.0
|
||||
|
||||
- repo: https://github.com/PyCQA/flake8.git
|
||||
# NOTE: This is kept at v4 for until WPS starts supporting flake v5.
|
||||
rev: 4.0.1 # enforce-version: 4.0.1
|
||||
hooks:
|
||||
- id: flake8
|
||||
alias: flake8-only-wps
|
||||
name: flake8 WPS-only
|
||||
args:
|
||||
- --ignore
|
||||
# NOTE: WPS326: Found implicit string concatenation
|
||||
# NOTE: WPS332: Found walrus operator
|
||||
- >-
|
||||
WPS102,
|
||||
WPS110,
|
||||
WPS111,
|
||||
WPS305,
|
||||
WPS326,
|
||||
WPS332,
|
||||
WPS347,
|
||||
WPS360,
|
||||
WPS421,
|
||||
WPS422,
|
||||
WPS432,
|
||||
WPS433,
|
||||
WPS437,
|
||||
WPS440,
|
||||
WPS441,
|
||||
WPS453,
|
||||
- --select
|
||||
- WPS
|
||||
additional_dependencies:
|
||||
- wemake-python-styleguide ~= 0.17.0
|
||||
|
||||
- repo: https://github.com/PyCQA/pylint.git
|
||||
rev: v3.0.0a6
|
||||
hooks:
|
||||
- id: pylint
|
||||
args:
|
||||
- --disable
|
||||
- >-
|
||||
import-error,
|
||||
invalid-name,
|
||||
line-too-long,
|
||||
missing-class-docstring,
|
||||
missing-function-docstring,
|
||||
missing-module-docstring,
|
||||
protected-access,
|
||||
super-init-not-called,
|
||||
unused-argument,
|
||||
wrong-import-position,
|
||||
- --output-format
|
||||
- colorized
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: enforced-flake8-version
|
||||
name: Verify that enforced flake8 version stays unchanged
|
||||
description: >-
|
||||
This is a sanity check and fixer that makes sure that
|
||||
the `flake8` version in this file remains matching the
|
||||
corresponding request in the `# enforce-version` comment.
|
||||
# Using Python here because using
|
||||
# shell test does not always work in CIs:
|
||||
entry: >-
|
||||
python -c 'import pathlib, re, sys;
|
||||
pre_commit_config = pathlib.Path(sys.argv[1]);
|
||||
cfg_txt = pre_commit_config.read_text();
|
||||
new_cfg_txt = re.sub(
|
||||
r"(?P<spaces>\s+)rev:\s(?:\d+\.\d+\.\d+)\s{0,2}"
|
||||
r"#\senforce-version:\s(?P<enforced_version>\d+\.\d+\.\d+)"
|
||||
r"[ \t\f\v]*",
|
||||
r"\g<spaces>rev: \g<enforced_version> "
|
||||
r"# enforce-version: \g<enforced_version>",
|
||||
cfg_txt,
|
||||
);
|
||||
cfg_txt != new_cfg_txt and
|
||||
pre_commit_config.write_text(new_cfg_txt)
|
||||
'
|
||||
pass_filenames: true
|
||||
language: system
|
||||
files: >-
|
||||
^\.pre-commit-config\.ya?ml$
|
||||
types:
|
||||
- yaml
|
||||
|
||||
...
|
||||
@@ -1,2 +1,10 @@
|
||||
indentation:
|
||||
indent-sequences: false
|
||||
---
|
||||
|
||||
extends: default
|
||||
|
||||
rules:
|
||||
indentation:
|
||||
level: error
|
||||
indent-sequences: false
|
||||
|
||||
...
|
||||
|
||||
+15
-3
@@ -1,4 +1,4 @@
|
||||
FROM python:3.9-slim
|
||||
FROM python:3.11-slim
|
||||
|
||||
LABEL "maintainer" "Sviatoslav Sydorenko <wk+pypa@sydorenko.org.ua>"
|
||||
LABEL "repository" "https://github.com/pypa/gh-action-pypi-publish"
|
||||
@@ -7,14 +7,26 @@ LABEL "homepage" "https://github.com/pypa/gh-action-pypi-publish"
|
||||
ENV PYTHONDONTWRITEBYTECODE 1
|
||||
ENV PYTHONUNBUFFERED 1
|
||||
|
||||
ENV PIP_NO_CACHE_DIR 1
|
||||
ENV PIP_ROOT_USER_ACTION ignore
|
||||
|
||||
ENV PATH "/root/.local/bin:${PATH}"
|
||||
ENV PYTHONPATH "/root/.local/lib/python3.11/site-packages"
|
||||
|
||||
COPY requirements requirements
|
||||
RUN \
|
||||
pip install --upgrade --no-cache-dir pip-with-requires-python && \
|
||||
pip install --upgrade --no-cache-dir --prefer-binary twine
|
||||
PIP_CONSTRAINT=requirements/runtime-prerequisites.txt \
|
||||
pip install --user --upgrade --no-cache-dir \
|
||||
-r requirements/runtime-prerequisites.in && \
|
||||
PIP_CONSTRAINT=requirements/runtime.txt \
|
||||
pip install --user --upgrade --no-cache-dir --prefer-binary \
|
||||
-r requirements/runtime.in
|
||||
|
||||
WORKDIR /app
|
||||
COPY LICENSE.md .
|
||||
COPY twine-upload.sh .
|
||||
COPY print-hash.py .
|
||||
COPY oidc-exchange.py .
|
||||
|
||||
RUN chmod +x twine-upload.sh
|
||||
ENTRYPOINT ["/app/twine-upload.sh"]
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
[![SWUbanner]][SWUdocs]
|
||||
|
||||
[![🧪 GitHub Actions CI/CD workflow tests badge]][GHA workflow runs list]
|
||||
[![pre-commit.ci status badge]][pre-commit.ci results page]
|
||||
|
||||
# PyPI publish GitHub Action
|
||||
|
||||
This action allows you to upload your [Python distribution packages]
|
||||
@@ -7,6 +10,9 @@ in the `dist/` directory to PyPI.
|
||||
This text suggests a minimalistic usage overview. For more detailed
|
||||
walkthrough check out the [PyPA guide].
|
||||
|
||||
If you have any feedback regarding specific action versions, please leave
|
||||
comments in the corresponding [per-release announcement discussions].
|
||||
|
||||
|
||||
## 🌇 `master` branch sunset ❗
|
||||
|
||||
@@ -17,49 +23,72 @@ tag, or a full Git commit SHA.
|
||||
|
||||
## Usage
|
||||
|
||||
To use the action add the following step to your workflow file (e.g.
|
||||
`.github/workflows/main.yml`)
|
||||
### Trusted publishing
|
||||
|
||||
> **NOTE**: Trusted publishing is sometimes referred to by its
|
||||
> underlying technology -- OpenID Connect, or OIDC for short.
|
||||
> If you see references to "OIDC publishing" in the context of PyPI,
|
||||
> this is what they're referring to.
|
||||
|
||||
```yml
|
||||
- name: Publish a Python distribution to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
This example jumps right into the current best practice. If you want to
|
||||
use API tokens directly or a less secure username and password, check out
|
||||
[how to specify username and password].
|
||||
|
||||
This action supports PyPI's [trusted publishing]
|
||||
implementation, which allows authentication to PyPI without a manually
|
||||
configured API token or username/password combination. To perform
|
||||
[trusted publishing] with this action, your project's
|
||||
publisher must already be configured on PyPI.
|
||||
|
||||
To enter the trusted publishing flow, configure this action's job with the
|
||||
`id-token: write` permission and **without** an explicit username or password:
|
||||
|
||||
```yaml
|
||||
# .github/workflows/ci-cd.yml
|
||||
jobs:
|
||||
pypi-publish:
|
||||
name: Upload release to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/p/<your-pypi-project-name>
|
||||
permissions:
|
||||
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
|
||||
steps:
|
||||
# retrieve your distributions here
|
||||
|
||||
- name: Publish package distributions to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
```
|
||||
|
||||
> **Pro tip**: instead of using branch pointers, like `master`, pin versions of
|
||||
Actions that you use to tagged versions or sha1 commit identifiers. This will
|
||||
make your workflows more secure and better reproducible, saving you from sudden
|
||||
and unpleasant surprises.
|
||||
> **Pro tip**: instead of using branch pointers, like `unstable/v1`, pin
|
||||
versions of Actions that you use to tagged versions or sha1 commit identifiers.
|
||||
This will make your workflows more secure and better reproducible, saving you
|
||||
from sudden and unpleasant surprises.
|
||||
|
||||
Other indices that support trusted publishing can also be used, like TestPyPI:
|
||||
|
||||
```yaml
|
||||
- name: Publish package distributions to TestPyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
```
|
||||
_(don't forget to update the environment name to `testpypi` or similar!)_
|
||||
|
||||
> **Pro tip**: only set the `id-token: write` permission in the job that does
|
||||
> publishing, not globally. Also, try to separate building from publishing
|
||||
> — this makes sure that any scripts maliciously injected into the build
|
||||
> or test environment won't be able to elevate privileges while flying under
|
||||
> the radar.
|
||||
|
||||
A common use case is to upload packages only on a tagged commit, to do so add a
|
||||
filter to the step:
|
||||
|
||||
filter to the job:
|
||||
|
||||
```yml
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
```
|
||||
|
||||
So the full step would look like:
|
||||
|
||||
|
||||
```yml
|
||||
- name: Publish package
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
```
|
||||
|
||||
The example above uses the new [API token][PyPI API token] feature of
|
||||
PyPI, which is recommended to restrict the access the action has.
|
||||
|
||||
The secret used in `${{ secrets.PYPI_API_TOKEN }}` needs to be created on the
|
||||
settings page of your project on GitHub. See [Creating & using secrets].
|
||||
|
||||
|
||||
## Non-goals
|
||||
|
||||
@@ -95,26 +124,28 @@ by putting them into the `dist/` folder prior to running this Action.
|
||||
For best results, figure out what kind of workflow fits your
|
||||
project's specific needs.
|
||||
|
||||
For example, you could implement a parallel workflow that
|
||||
For example, you could implement a parallel job that
|
||||
pushes every commit to TestPyPI or your own index server,
|
||||
like `devpi`. For this, you'd need to (1) specify a custom
|
||||
`repository_url` value and (2) generate a unique version
|
||||
`repository-url` value and (2) generate a unique version
|
||||
number for each upload so that they'd not create a conflict.
|
||||
The latter is possible if you use `setuptools_scm` package but
|
||||
you could also invent your own solution based on the distance
|
||||
to the latest tagged commit.
|
||||
|
||||
You'll need to create another token for a separate host and then
|
||||
[save it as a GitHub repo secret][Creating & using secrets].
|
||||
You'll need to create another token for a separate host and then [save it as a
|
||||
GitHub repo secret][Creating & using secrets] under an environment used in
|
||||
your job. Though, passing a password would disable the secretless [trusted
|
||||
publishing] so it's better to configure it instead, when publishing to TestPyPI
|
||||
and not something custom.
|
||||
|
||||
The action invocation in this case would look like:
|
||||
```yml
|
||||
- name: Publish package to TestPyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
|
||||
repository_url: https://test.pypi.org/legacy/
|
||||
repository-url: https://test.pypi.org/legacy/
|
||||
```
|
||||
|
||||
### Customizing target package dists directory
|
||||
@@ -127,9 +158,7 @@ would now look like:
|
||||
- name: Publish package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
user: __token__
|
||||
password: ${{ secrets.PYPI_API_TOKEN }}
|
||||
packages_dir: custom-dir/
|
||||
packages-dir: custom-dir/
|
||||
```
|
||||
|
||||
### Disabling metadata verification
|
||||
@@ -140,7 +169,7 @@ check with:
|
||||
|
||||
```yml
|
||||
with:
|
||||
verify_metadata: false
|
||||
verify-metadata: false
|
||||
```
|
||||
|
||||
### Tolerating release package file duplicates
|
||||
@@ -150,12 +179,12 @@ may hit race conditions. For example, when publishing from multiple CIs
|
||||
or even having workflows with the same steps triggered within GitHub
|
||||
Actions CI/CD for different events concerning the same high-level act.
|
||||
|
||||
To facilitate this use-case, you may use `skip_existing` (disabled by
|
||||
To facilitate this use-case, you may use `skip-existing` (disabled by
|
||||
default) setting as follows:
|
||||
|
||||
```yml
|
||||
with:
|
||||
skip_existing: true
|
||||
skip-existing: true
|
||||
```
|
||||
|
||||
> **Pro tip**: try to avoid enabling this setting where possible. If you
|
||||
@@ -178,15 +207,52 @@ It will show SHA256, MD5, BLAKE2-256 values of files to be uploaded.
|
||||
|
||||
```yml
|
||||
with:
|
||||
print_hash: true
|
||||
print-hash: true
|
||||
```
|
||||
|
||||
### Specifying a different username
|
||||
|
||||
The default username value is `__token__`. If you publish to a custom
|
||||
registry that does not provide API tokens, like `devpi`, you may need to
|
||||
specify a custom username and password pair. This is how it's done.
|
||||
|
||||
```yml
|
||||
with:
|
||||
user: guido
|
||||
password: ${{ secrets.DEVPI_PASSWORD }}
|
||||
```
|
||||
|
||||
The secret used in `${{ secrets.DEVPI_PASSWORD }}` needs to be created on the
|
||||
environment page under the settings of your project on GitHub.
|
||||
See [Creating & using secrets].
|
||||
|
||||
In the past, when publishing to PyPI, the most secure way of the access scoping
|
||||
for automatic publishing was to use the [API tokens][PyPI API token] feature of
|
||||
PyPI. One would make it project-scoped and save as an environment-bound secret
|
||||
in their GitHub repository settings, naming it `${{ secrets.PYPI_API_TOKEN }}`,
|
||||
for example. See [Creating & using secrets]. While still secure,
|
||||
[trusted publishing] is now encouraged over API tokens as a best practice
|
||||
on supported platforms (like GitHub).
|
||||
|
||||
## License
|
||||
|
||||
The Dockerfile and associated scripts and documentation in this project
|
||||
are released under the [BSD 3-clause license](LICENSE.md).
|
||||
|
||||
|
||||
[🧪 GitHub Actions CI/CD workflow tests badge]:
|
||||
https://github.com/pypa/gh-action-pypi-publish/actions/workflows/self-smoke-test-action.yml/badge.svg?branch=unstable%2Fv1&event=push
|
||||
[GHA workflow runs list]:
|
||||
https://github.com/pypa/gh-action-pypi-publish/actions/workflows/self-smoke-test-action.yml?query=branch%3Aunstable%2Fv1
|
||||
|
||||
[pre-commit.ci results page]:
|
||||
https://results.pre-commit.ci/latest/github/pypa/gh-action-pypi-publish/unstable/v1
|
||||
[pre-commit.ci status badge]:
|
||||
https://results.pre-commit.ci/badge/github/pypa/gh-action-pypi-publish/unstable/v1.svg
|
||||
|
||||
[per-release announcement discussions]:
|
||||
https://github.com/pypa/gh-action-pypi-publish/discussions/categories/announcements
|
||||
|
||||
[Creating & using secrets]:
|
||||
https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets
|
||||
[has nothing to do with _building package distributions_]:
|
||||
@@ -195,8 +261,13 @@ https://github.com/pypa/gh-action-pypi-publish/issues/11#issuecomment-530480449
|
||||
https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/
|
||||
[PyPI API token]: https://pypi.org/help/#apitoken
|
||||
[Python distribution packages]:
|
||||
https://packaging.python.org/glossary/#term-distribution-package
|
||||
https://packaging.python.org/glossary/#term-Distribution-Package
|
||||
[SWUbanner]:
|
||||
https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner-direct-single.svg
|
||||
[SWUdocs]:
|
||||
https://github.com/vshymanskyy/StandWithUkraine/blob/main/docs/README.md
|
||||
|
||||
[warehouse#12965]: https://github.com/pypi/warehouse/issues/12965
|
||||
[trusted publishing]: https://docs.pypi.org/trusted-publishers/
|
||||
|
||||
[how to specify username and password]: #specifying-a-different-username
|
||||
|
||||
+61
-15
@@ -8,32 +8,78 @@ inputs:
|
||||
default: __token__
|
||||
password:
|
||||
description: Password for your PyPI user or an access token
|
||||
required: true
|
||||
repository_url:
|
||||
required: false
|
||||
repository-url: # Canonical alias for `repository_url`
|
||||
description: The repository URL to use
|
||||
required: false
|
||||
packages_dir:
|
||||
repository_url: # DEPRECATED ALIAS; TODO: Remove in v3+
|
||||
description: >-
|
||||
[DEPRECATED]
|
||||
The repository URL to use
|
||||
deprecationMessage: >-
|
||||
The inputs have been normalized to use kebab-case.
|
||||
Use `repository-url` instead.
|
||||
required: false
|
||||
default: https://upload.pypi.org/legacy/
|
||||
packages-dir: # Canonical alias for `packages_dir`
|
||||
description: The target directory for distribution
|
||||
required: false
|
||||
# default: dist # TODO: uncomment once alias removed
|
||||
packages_dir: # DEPRECATED ALIAS; TODO: Remove in v3+
|
||||
description: >-
|
||||
[DEPRECATED]
|
||||
The target directory for distribution
|
||||
deprecationMessage: >-
|
||||
The inputs have been normalized to use kebab-case.
|
||||
Use `packages-dir` instead.
|
||||
required: false
|
||||
default: dist
|
||||
verify_metadata:
|
||||
verify-metadata: # Canonical alias for `verify_metadata`
|
||||
description: Check metadata before uploading
|
||||
required: false
|
||||
default: true
|
||||
skip_existing:
|
||||
# default: 'true' # TODO: uncomment once alias removed
|
||||
verify_metadata: # DEPRECATED ALIAS; TODO: Remove in v3+
|
||||
description: >-
|
||||
[DEPRECATED]
|
||||
Check metadata before uploading
|
||||
deprecationMessage: >-
|
||||
The inputs have been normalized to use kebab-case.
|
||||
Use `verify-metadata` instead.
|
||||
required: false
|
||||
default: 'true'
|
||||
skip-existing: # Canonical alias for `skip_existing`
|
||||
description: >-
|
||||
Do not fail if a Python package distribution
|
||||
exists in the target package index
|
||||
required: false
|
||||
default: false
|
||||
# default: 'false' # TODO: uncomment once alias removed
|
||||
skip_existing: # DEPRECATED ALIAS; TODO: Remove in v3+
|
||||
description: >-
|
||||
[DEPRECATED]
|
||||
Do not fail if a Python package distribution
|
||||
exists in the target package index
|
||||
deprecationMessage: >-
|
||||
The inputs have been normalized to use kebab-case.
|
||||
Use `skip-existing` instead.
|
||||
required: false
|
||||
default: 'false'
|
||||
verbose:
|
||||
description: Show verbose output.
|
||||
required: false
|
||||
default: false
|
||||
print_hash:
|
||||
default: 'false'
|
||||
print-hash: # Canonical alias for `print_hash`
|
||||
description: Show hash values of files to be uploaded
|
||||
required: false
|
||||
default: false
|
||||
# default: 'false' # TODO: uncomment once alias removed
|
||||
print_hash: # DEPRECATED ALIAS; TODO: Remove in v3+
|
||||
description: >-
|
||||
[DEPRECATED]
|
||||
Show hash values of files to be uploaded
|
||||
deprecationMessage: >-
|
||||
The inputs have been normalized to use kebab-case.
|
||||
Use `print-hash` instead.
|
||||
required: false
|
||||
default: 'false'
|
||||
branding:
|
||||
color: yellow
|
||||
icon: upload-cloud
|
||||
@@ -43,9 +89,9 @@ runs:
|
||||
args:
|
||||
- ${{ inputs.user }}
|
||||
- ${{ inputs.password }}
|
||||
- ${{ inputs.repository_url }}
|
||||
- ${{ inputs.packages_dir }}
|
||||
- ${{ inputs.verify_metadata }}
|
||||
- ${{ inputs.skip_existing }}
|
||||
- ${{ inputs.repository-url }}
|
||||
- ${{ inputs.packages-dir }}
|
||||
- ${{ inputs.verify-metadata }}
|
||||
- ${{ inputs.skip-existing }}
|
||||
- ${{ inputs.verbose }}
|
||||
- ${{ inputs.print_hash }}
|
||||
- ${{ inputs.print-hash }}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import os
|
||||
import sys
|
||||
from http import HTTPStatus
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
from urllib.parse import urlparse
|
||||
|
||||
import id # pylint: disable=redefined-builtin
|
||||
import requests
|
||||
|
||||
_GITHUB_STEP_SUMMARY = Path(os.getenv("GITHUB_STEP_SUMMARY"))
|
||||
|
||||
# The top-level error message that gets rendered.
|
||||
# This message wraps one of the other templates/messages defined below.
|
||||
_ERROR_SUMMARY_MESSAGE = """
|
||||
Trusted publishing exchange failure:
|
||||
|
||||
{message}
|
||||
|
||||
You're seeing this because the action wasn't given the inputs needed to
|
||||
perform password-based or token-based authentication. If you intended to
|
||||
perform one of those authentication methods instead of trusted
|
||||
publishing, then you should double-check your secret configuration and variable
|
||||
names.
|
||||
|
||||
Read more about trusted publishers at https://docs.pypi.org/trusted-publishers/
|
||||
|
||||
Read more about how this action uses trusted publishers at
|
||||
https://github.com/marketplace/actions/pypi-publish#trusted-publishing
|
||||
"""
|
||||
|
||||
# Rendered if OIDC identity token retrieval fails for any reason.
|
||||
_TOKEN_RETRIEVAL_FAILED_MESSAGE = """
|
||||
OpenID Connect token retrieval failed: {identity_error}
|
||||
|
||||
This generally indicates a workflow configuration error, such as insufficient
|
||||
permissions. Make sure that your workflow has `id-token: write` configured
|
||||
at the job level, e.g.:
|
||||
|
||||
```yaml
|
||||
permissions:
|
||||
id-token: write
|
||||
```
|
||||
|
||||
Learn more at https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#adding-permissions-settings.
|
||||
"""
|
||||
|
||||
# Rendered if the package index refuses the given OIDC token.
|
||||
_SERVER_REFUSED_TOKEN_EXCHANGE_MESSAGE = """
|
||||
Token request failed: the server refused the request for the following reasons:
|
||||
|
||||
{reasons}
|
||||
"""
|
||||
|
||||
# Rendered if the package index's token response isn't valid JSON.
|
||||
_SERVER_TOKEN_RESPONSE_MALFORMED_JSON = """
|
||||
Token request failed: the index produced an unexpected
|
||||
{status_code} response.
|
||||
|
||||
This strongly suggests a server configuration or downtime issue; wait
|
||||
a few minutes and try again.
|
||||
"""
|
||||
|
||||
# Rendered if the package index's token response isn't a valid API token payload.
|
||||
_SERVER_TOKEN_RESPONSE_MALFORMED_MESSAGE = """
|
||||
Token response error: the index gave us an invalid response.
|
||||
|
||||
This strongly suggests a server configuration or downtime issue; wait
|
||||
a few minutes and try again.
|
||||
"""
|
||||
|
||||
|
||||
def die(msg: str) -> NoReturn:
|
||||
with _GITHUB_STEP_SUMMARY.open("a", encoding="utf-8") as io:
|
||||
print(_ERROR_SUMMARY_MESSAGE.format(message=msg), file=io)
|
||||
|
||||
# HACK: GitHub Actions' annotations don't work across multiple lines naively;
|
||||
# translating `\n` into `%0A` (i.e., HTML percent-encoding) is known to work.
|
||||
# See: https://github.com/actions/toolkit/issues/193
|
||||
msg = msg.replace("\n", "%0A")
|
||||
print(f"::error::Trusted publishing exchange failure: {msg}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def debug(msg: str):
|
||||
print(f"::debug::{msg.title()}", file=sys.stderr)
|
||||
|
||||
|
||||
def get_normalized_input(name: str) -> str | None:
|
||||
name = f"INPUT_{name.upper()}"
|
||||
if val := os.getenv(name):
|
||||
return val
|
||||
return os.getenv(name.replace("-", "_"))
|
||||
|
||||
|
||||
def assert_successful_audience_call(resp: requests.Response, domain: str):
|
||||
if resp.ok:
|
||||
return
|
||||
|
||||
match resp.status_code:
|
||||
case HTTPStatus.FORBIDDEN:
|
||||
# This index supports OIDC, but forbids the client from using
|
||||
# it (either because it's disabled, ratelimited, etc.)
|
||||
die(
|
||||
f"audience retrieval failed: repository at {domain} has trusted publishing disabled",
|
||||
)
|
||||
case HTTPStatus.NOT_FOUND:
|
||||
# This index does not support OIDC.
|
||||
die(
|
||||
"audience retrieval failed: repository at "
|
||||
f"{domain} does not indicate trusted publishing support",
|
||||
)
|
||||
case other:
|
||||
status = HTTPStatus(other)
|
||||
# Unknown: the index may or may not support OIDC, but didn't respond with
|
||||
# something we expect. This can happen if the index is broken, in maintenance mode,
|
||||
# misconfigured, etc.
|
||||
die(
|
||||
"audience retrieval failed: repository at "
|
||||
f"{domain} responded with unexpected {other}: {status.phrase}",
|
||||
)
|
||||
|
||||
|
||||
repository_url = get_normalized_input("repository-url")
|
||||
repository_domain = urlparse(repository_url).netloc
|
||||
token_exchange_url = f"https://{repository_domain}/_/oidc/github/mint-token"
|
||||
|
||||
# Indices are expected to support `https://{domain}/_/oidc/audience`,
|
||||
# which tells OIDC exchange clients which audience to use.
|
||||
audience_url = f"https://{repository_domain}/_/oidc/audience"
|
||||
audience_resp = requests.get(audience_url)
|
||||
assert_successful_audience_call(audience_resp, repository_domain)
|
||||
|
||||
oidc_audience = audience_resp.json()["audience"]
|
||||
|
||||
debug(f"selected trusted publishing exchange endpoint: {token_exchange_url}")
|
||||
|
||||
try:
|
||||
oidc_token = id.detect_credential(audience=oidc_audience)
|
||||
except id.IdentityError as identity_error:
|
||||
die(_TOKEN_RETRIEVAL_FAILED_MESSAGE.format(identity_error=identity_error))
|
||||
|
||||
# Now we can do the actual token exchange.
|
||||
mint_token_resp = requests.post(
|
||||
token_exchange_url,
|
||||
json={"token": oidc_token},
|
||||
)
|
||||
|
||||
try:
|
||||
mint_token_payload = mint_token_resp.json()
|
||||
except requests.JSONDecodeError:
|
||||
# Token exchange failure normally produces a JSON error response, but
|
||||
# we might have hit a server error instead.
|
||||
die(
|
||||
_SERVER_TOKEN_RESPONSE_MALFORMED_JSON.format(
|
||||
status_code=mint_token_resp.status_code,
|
||||
),
|
||||
)
|
||||
|
||||
# On failure, the JSON response includes the list of errors that
|
||||
# occurred during minting.
|
||||
if not mint_token_resp.ok:
|
||||
reasons = "\n".join(
|
||||
f"* `{error['code']}`: {error['description']}"
|
||||
for error in mint_token_payload["errors"]
|
||||
)
|
||||
|
||||
die(_SERVER_REFUSED_TOKEN_EXCHANGE_MESSAGE.format(reasons=reasons))
|
||||
|
||||
pypi_token = mint_token_payload.get("token")
|
||||
if pypi_token is None:
|
||||
die(_SERVER_TOKEN_RESPONSE_MALFORMED_MESSAGE)
|
||||
|
||||
# Mask the newly minted PyPI token, so that we don't accidentally leak it in logs.
|
||||
print(f"::add-mask::{pypi_token}", file=sys.stderr)
|
||||
|
||||
# This final print will be captured by the subshell in `twine-upload.sh`.
|
||||
print(pypi_token)
|
||||
Executable → Regular
@@ -0,0 +1 @@
|
||||
pip-with-requires-python
|
||||
@@ -0,0 +1,12 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/runtime-prerequisites.txt --resolver=backtracking --strip-extras requirements/runtime-prerequisites.in
|
||||
#
|
||||
pip-with-requires-python==1.0.1
|
||||
# via -r requirements/runtime-prerequisites.in
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
pip==22.3.1
|
||||
# via pip-with-requires-python
|
||||
@@ -0,0 +1,9 @@
|
||||
twine
|
||||
|
||||
# NOTE: Used to detect an ambient OIDC credential for OIDC publishing.
|
||||
id ~= 1.0
|
||||
|
||||
# NOTE: This is pulled in transitively through `twine`, but we also declare
|
||||
# NOTE: it explicitly here because `oidc-exchange.py` uses it.
|
||||
# Ref: https://github.com/di/id
|
||||
requests
|
||||
@@ -0,0 +1,86 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --allow-unsafe --output-file=requirements/runtime.txt --resolver=backtracking --strip-extras requirements/runtime.in
|
||||
#
|
||||
annotated-types==0.5.0
|
||||
# via pydantic
|
||||
bleach==6.0.0
|
||||
# via readme-renderer
|
||||
certifi==2023.5.7
|
||||
# via requests
|
||||
cffi==1.15.1
|
||||
# via cryptography
|
||||
charset-normalizer==3.2.0
|
||||
# via requests
|
||||
cryptography==41.0.2
|
||||
# via secretstorage
|
||||
docutils==0.20.1
|
||||
# via readme-renderer
|
||||
id==1.0.0
|
||||
# via -r requirements/runtime.in
|
||||
idna==3.4
|
||||
# via requests
|
||||
importlib-metadata==6.8.0
|
||||
# via
|
||||
# keyring
|
||||
# twine
|
||||
jaraco-classes==3.3.0
|
||||
# via keyring
|
||||
jeepney==0.8.0
|
||||
# via
|
||||
# keyring
|
||||
# secretstorage
|
||||
keyring==24.2.0
|
||||
# via twine
|
||||
markdown-it-py==3.0.0
|
||||
# via rich
|
||||
mdurl==0.1.2
|
||||
# via markdown-it-py
|
||||
more-itertools==9.1.0
|
||||
# via jaraco-classes
|
||||
pkginfo==1.9.6
|
||||
# via twine
|
||||
pycparser==2.21
|
||||
# via cffi
|
||||
pydantic==2.0.2
|
||||
# via id
|
||||
pydantic-core==2.1.2
|
||||
# via pydantic
|
||||
pygments==2.15.1
|
||||
# via
|
||||
# readme-renderer
|
||||
# rich
|
||||
readme-renderer==40.0
|
||||
# via twine
|
||||
requests==2.31.0
|
||||
# via
|
||||
# -r requirements/runtime.in
|
||||
# id
|
||||
# requests-toolbelt
|
||||
# twine
|
||||
requests-toolbelt==1.0.0
|
||||
# via twine
|
||||
rfc3986==2.0.0
|
||||
# via twine
|
||||
rich==13.4.2
|
||||
# via twine
|
||||
secretstorage==3.3.3
|
||||
# via keyring
|
||||
six==1.16.0
|
||||
# via bleach
|
||||
twine==4.0.2
|
||||
# via -r requirements/runtime.in
|
||||
typing-extensions==4.7.1
|
||||
# via
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
urllib3==2.0.3
|
||||
# via
|
||||
# requests
|
||||
# twine
|
||||
webencodings==0.5.1
|
||||
# via bleach
|
||||
zipp==3.16.0
|
||||
# via importlib-metadata
|
||||
+91
-10
@@ -1,21 +1,102 @@
|
||||
#! /usr/bin/env bash
|
||||
#! /bin/bash
|
||||
|
||||
if [[ -n "${DEBUG}" ]]
|
||||
then
|
||||
set -x
|
||||
fi
|
||||
|
||||
set -Eeuo pipefail
|
||||
|
||||
|
||||
# NOTE: These variables are needed to combat GitHub passing broken env vars
|
||||
# NOTE: from the runner VM host runtime.
|
||||
# Ref: https://github.com/pypa/gh-action-pypi-publish/issues/112
|
||||
export HOME="/root" # So that `python -m site` doesn't get confused
|
||||
export PATH="/usr/bin:${PATH}" # To find `id`
|
||||
. /etc/profile # Makes python and other executables findable
|
||||
export PATH="$(python -m site --user-base)/bin:${PATH}"
|
||||
export PYTHONPATH="$(python -m site --user-site):${PYTHONPATH}"
|
||||
|
||||
|
||||
function get-normalized-input() {
|
||||
local var_name=${1}
|
||||
python -c \
|
||||
'
|
||||
from os import getenv
|
||||
from sys import argv
|
||||
envvar_name = f"INPUT_{argv[1].upper()}"
|
||||
print(
|
||||
getenv(envvar_name) or getenv(envvar_name.replace("-", "_")) or "",
|
||||
end="",
|
||||
)
|
||||
' \
|
||||
"${var_name}"
|
||||
}
|
||||
|
||||
|
||||
INPUT_REPOSITORY_URL="$(get-normalized-input 'repository-url')"
|
||||
INPUT_PACKAGES_DIR="$(get-normalized-input 'packages-dir')"
|
||||
INPUT_VERIFY_METADATA="$(get-normalized-input 'verify-metadata')"
|
||||
INPUT_SKIP_EXISTING="$(get-normalized-input 'skip-existing')"
|
||||
INPUT_PRINT_HASH="$(get-normalized-input 'print-hash')"
|
||||
|
||||
TRUSTED_PUBLISHING_NUDGE="::warning title=Upgrade to Trusted Publishing::\
|
||||
Trusted Publishers allows publishing packages to PyPI from automated \
|
||||
environments like GitHub Actions without needing to use username/password \
|
||||
combinations or API tokens to authenticate with PyPI. Read more: \
|
||||
https://docs.pypi.org/trusted-publishers"
|
||||
|
||||
if [[ "${INPUT_USER}" == "__token__" && -z "${INPUT_PASSWORD}" ]] ; then
|
||||
# No password supplied by the user implies that we're in the OIDC flow;
|
||||
# retrieve the OIDC credential and exchange it for a PyPI API token.
|
||||
echo \
|
||||
'::notice::Attempting to perform trusted publishing exchange' \
|
||||
'to retrieve a temporary short-lived API token for authentication' \
|
||||
"against ${INPUT_REPOSITORY_URL} due to __token__ username with no" \
|
||||
'supplied password field'
|
||||
INPUT_PASSWORD="$(python /app/oidc-exchange.py)"
|
||||
elif [[ "${INPUT_USER}" == '__token__' ]]; then
|
||||
echo \
|
||||
'::notice::Using a user-provided API token for authentication' \
|
||||
"against ${INPUT_REPOSITORY_URL}"
|
||||
|
||||
if [[ "${INPUT_REPOSITORY_URL}" =~ pypi\.org ]]; then
|
||||
echo "${TRUSTED_PUBLISHING_NUDGE}"
|
||||
fi
|
||||
else
|
||||
echo \
|
||||
'::notice::Using a username + password pair for authentication' \
|
||||
"against ${INPUT_REPOSITORY_URL}"
|
||||
|
||||
if [[ "${INPUT_REPOSITORY_URL}" =~ pypi\.org ]]; then
|
||||
echo "${TRUSTED_PUBLISHING_NUDGE}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[
|
||||
"$INPUT_USER" == "__token__" &&
|
||||
! "$INPUT_PASSWORD" =~ ^pypi-
|
||||
]]
|
||||
then
|
||||
echo \
|
||||
::warning file='# >>' PyPA publish to PyPI GHA'%3A' \
|
||||
POTENTIALLY INVALID TOKEN \
|
||||
'<< ':: \
|
||||
It looks like you are trying to use an API token to \
|
||||
authenticate in the package index and your token value does \
|
||||
not start with '"pypi-"' as it typically should. This may \
|
||||
cause an authentication error. Please verify that you have \
|
||||
copied your token properly if such an error occurs.
|
||||
if [[ -z "$INPUT_PASSWORD" ]]; then
|
||||
echo \
|
||||
::warning file='# >>' PyPA publish to PyPI GHA'%3A' \
|
||||
EMPTY TOKEN \
|
||||
'<< ':: \
|
||||
It looks like you have not passed a password or it \
|
||||
is otherwise empty. Please verify that you have passed it \
|
||||
directly or, preferably, through a secret.
|
||||
else
|
||||
echo \
|
||||
::warning file='# >>' PyPA publish to PyPI GHA'%3A' \
|
||||
POTENTIALLY INVALID TOKEN \
|
||||
'<< ':: \
|
||||
It looks like you are trying to use an API token to \
|
||||
authenticate in the package index and your token value does \
|
||||
not start with '"pypi-"' as it typically should. This may \
|
||||
cause an authentication error. Please verify that you have \
|
||||
copied your token properly if such an error occurs.
|
||||
fi
|
||||
fi
|
||||
|
||||
if ( ! ls -A ${INPUT_PACKAGES_DIR%%/}/*.tar.gz &> /dev/null && \
|
||||
|
||||
Reference in New Issue
Block a user