mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-14 16:16:48 +00:00
0.20.0 failed to upload with "Certificate's Build Config URI ... does not match expected Trusted Publisher". Authentication was fine: trusted publishing matches the called workflow, pypi-publish.yml, which is what PyPI is configured with. The PEP 740 attestation the publish action attaches by default records the workflow that STARTED the run instead, backend-release.yml, and PyPI verifies that against the same publisher entry. The two claims can never agree while the publish runs as a called workflow, and no publisher configuration satisfies both. Attest only when this file is the entry point, which covers the manual dispatch and a release published by hand. The release chain uploads unattested rather than failing. The earlier rehearsal went to TestPyPI and passed, so this only appeared on the real index.
153 lines
5.6 KiB
YAML
153 lines
5.6 KiB
YAML
name: Publish to PyPI
|
|
|
|
# Trusted publishing: PyPI trusts this workflow file in arc53/DocsGPT running
|
|
# in the `pypi` environment, so no API token is stored. The backend-release
|
|
# workflow calls this one after it tags a version bump on main (a release it
|
|
# creates with GITHUB_TOKEN never fires the release trigger); a release created
|
|
# by hand publishes through that trigger; a manual run publishes to TestPyPI
|
|
# by default.
|
|
#
|
|
# Attestations are on only when this file is the workflow that STARTED the run.
|
|
# A PEP 740 attestation records the entry workflow in its certificate, while
|
|
# trusted publishing matches the called workflow, so when backend-release calls
|
|
# this one the two disagree and PyPI rejects the upload with "Certificate's
|
|
# Build Config URI ... does not match expected Trusted Publisher". No publisher
|
|
# configuration satisfies both, so the release chain publishes unattested.
|
|
|
|
on:
|
|
release:
|
|
types: [published]
|
|
workflow_call:
|
|
inputs:
|
|
version:
|
|
description: Version the archives must carry (empty skips the check)
|
|
type: string
|
|
default: ''
|
|
target:
|
|
description: Package index to publish to (pypi or testpypi)
|
|
type: string
|
|
default: pypi
|
|
workflow_dispatch:
|
|
inputs:
|
|
target:
|
|
description: Package index to publish to
|
|
type: choice
|
|
options: [testpypi, pypi]
|
|
default: testpypi
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
# The version being released: passed in by the caller, or the release's tag;
|
|
# empty for a manual run, which publishes whatever main builds.
|
|
RELEASE_VERSION: ${{ inputs.version || github.event.release.tag_name }}
|
|
|
|
jobs:
|
|
build:
|
|
if: github.repository == 'arc53/DocsGPT'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e # v6.8.0
|
|
with:
|
|
# A publishing workflow must not restore a cache another job could have poisoned.
|
|
enable-cache: false
|
|
|
|
- name: The publish target is one this workflow knows
|
|
# A caller's typo would otherwise skip both publish jobs and upload nothing.
|
|
if: inputs.target != '' && inputs.target != 'pypi' && inputs.target != 'testpypi'
|
|
env:
|
|
TARGET: ${{ inputs.target }}
|
|
run: |
|
|
echo "::error::unknown target '$TARGET': expected pypi or testpypi"
|
|
exit 1
|
|
|
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: 22
|
|
# A publishing workflow must not restore a cache another job could have poisoned.
|
|
package-manager-cache: false
|
|
|
|
- name: Build the web UI into the package
|
|
env:
|
|
CI: "true"
|
|
run: bash scripts/build_frontend.sh
|
|
|
|
- name: Build the sdist and wheel
|
|
run: uv build
|
|
|
|
- name: The wheel carries the web UI and no generated runtime config
|
|
run: |
|
|
python3 - <<'PY'
|
|
import glob
|
|
import zipfile
|
|
|
|
wheel = zipfile.ZipFile(glob.glob("dist/*.whl")[0])
|
|
names = set(wheel.namelist())
|
|
assert "docsgpt/static/index.html" in names, "the UI build is missing from the wheel"
|
|
assert any(n.startswith("docsgpt/static/assets/") for n in names), "no bundle under docsgpt/static/assets/"
|
|
assert "docsgpt/static/config.js" not in names, "config.js is generated by the API at runtime"
|
|
assert b'src="/config.js"' in wheel.read("docsgpt/static/index.html"), "index.html must load /config.js"
|
|
print("ui ok:", sum(n.startswith("docsgpt/static/") for n in names), "files")
|
|
PY
|
|
|
|
- name: The archives carry the released version
|
|
if: env.RELEASE_VERSION != ''
|
|
run: |
|
|
ls "dist/docsgpt-${RELEASE_VERSION}.tar.gz" "dist/docsgpt-${RELEASE_VERSION}-py3-none-any.whl"
|
|
|
|
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: dist
|
|
path: dist/
|
|
if-no-files-found: error
|
|
|
|
publish-pypi:
|
|
# A release event, or a call or manual run aimed at PyPI (the call default).
|
|
if: github.event_name == 'release' || inputs.target == 'pypi'
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
environment:
|
|
name: pypi
|
|
url: https://pypi.org/project/docsgpt/
|
|
permissions:
|
|
id-token: write
|
|
steps:
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: dist
|
|
path: dist/
|
|
|
|
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
with:
|
|
# Only when this workflow is the entry point: see the note at the top.
|
|
attestations: ${{ contains(github.workflow_ref, '/pypi-publish.yml@') }}
|
|
|
|
publish-testpypi:
|
|
if: inputs.target == 'testpypi'
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
environment:
|
|
name: testpypi
|
|
url: https://test.pypi.org/project/docsgpt/
|
|
permissions:
|
|
id-token: write
|
|
steps:
|
|
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: dist
|
|
path: dist/
|
|
|
|
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
with:
|
|
# Only when this workflow is the entry point: see the note at the top.
|
|
attestations: ${{ contains(github.workflow_ref, '/pypi-publish.yml@') }}
|
|
repository-url: https://test.pypi.org/legacy/
|
|
# TestPyPI is a rehearsal target: a version that is already there
|
|
# must not fail the run.
|
|
skip-existing: true
|