Files
gmail-channel-molecule/.gitea/workflows/publish.yml
T
Hongming Wang d2c3a8b80d
ci / gmail channel — unit tests (push) Successful in 12s
auto-release / gate (push) Successful in 14s
auto-release / release (push) Successful in 4s
publish / publish (push) Successful in 42s
ci: wheel-publish pipeline (auto-release -> publish to org Gitea PyPI)
Mirrors plugins/lark-channel: a green merge to main cuts tag v<pyproject version>
(auto-release.yml, gate re-runs the stdlib unit tests — no private-index coupling),
and the tag triggers publish.yml — build wheel+sdist, fetch the Gitea PyPI publisher
token from the Infisical SSOT (/ci/gitea-pypi-publisher), twine-upload, and verify a
clean install of gmail-channel-molecule==<version> from the registry. Makes the
connect onboarding CLI 'pip install gmail-channel-molecule==0.1.0' resolve.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 19:24:54 -07:00

174 lines
7.2 KiB
YAML

name: publish
# Tag-triggered publish of gmail-channel-molecule to the org Gitea PyPI registry
# (mirrors plugins/lark-channel/publish.yml). The tag is the source of truth: its
# version is stamped into pyproject.toml at build time (ephemeral, in-CI, never
# committed). Publisher creds come from the Infisical SSOT
# (/ci/gitea-pypi-publisher, prod) via the org CI machine identity — no per-repo
# credential copies. A broken/empty fetch hard-fails loudly.
on:
push:
tags:
- "v*"
workflow_dispatch:
permissions:
contents: read
concurrency:
group: publish
cancel-in-progress: false
env:
GITEA_PYPI_SIMPLE_URL: https://git.moleculesai.app/api/packages/molecule-ai/pypi/simple/
GITEA_PYPI_UPLOAD_URL: https://git.moleculesai.app/api/packages/molecule-ai/pypi/
jobs:
publish:
runs-on: publish
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.11"
cache: pip
- name: Derive version + stamp pyproject (tag is source of truth)
id: version
run: |
set -eu
STAMP_FROM_TAG=0
if echo "$GITHUB_REF" | grep -q "^refs/tags/v"; then
VERSION="${GITHUB_REF#refs/tags/v}"
STAMP_FROM_TAG=1
else
VERSION=$(python - <<'PY'
import tomllib
with open("pyproject.toml", "rb") as f:
print(tomllib.load(f)["project"]["version"])
PY
)
fi
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+([a-z0-9.+-]+)?$'; then
echo "::error::version $VERSION does not look like a PEP 440 release"
exit 1
fi
if [ "$STAMP_FROM_TAG" = "1" ]; then
VERSION="$VERSION" python - <<'PY'
import os, re
version = os.environ["VERSION"]
with open("pyproject.toml", "r", encoding="utf-8") as f:
text = f.read()
new, n = re.subn(r'(?m)^(version\s*=\s*)"[^"]+"', r'\g<1>"' + version + '"', text, count=1)
if n != 1:
raise SystemExit('could not locate a single version = "..." line in pyproject.toml')
with open("pyproject.toml", "w", encoding="utf-8") as f:
f.write(new)
print(f"stamped pyproject [project].version = {version} (ephemeral build-time)")
PY
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Publishing gmail-channel-molecule $VERSION to the Gitea package registry"
- name: Build wheel + sdist
run: |
pip install build twine
python -m build
python -m twine check dist/*
- name: Fetch Gitea PyPI publisher creds from Infisical SSOT
env:
INFISICAL_CI_CLIENT_ID: ${{ secrets.INFISICAL_CI_CLIENT_ID }}
INFISICAL_CI_CLIENT_SECRET: ${{ secrets.INFISICAL_CI_CLIENT_SECRET }}
INFISICAL_PROJECT_ID: ${{ secrets.INFISICAL_CI_PROJECT_ID }}
run: |
set -uo pipefail
BASE="https://key.moleculesai.app"
if [ -z "${INFISICAL_CI_CLIENT_ID:-}" ] || [ -z "${INFISICAL_CI_CLIENT_SECRET:-}" ] || [ -z "${INFISICAL_PROJECT_ID:-}" ]; then
echo "::error::Infisical CI machine identity is not set -- cannot fetch publisher creds from the SSOT"
exit 1
fi
TOK=$(curl -fsS -X POST "$BASE/api/v1/auth/universal-auth/login" \
-H 'Content-Type: application/json' \
-d "{\"clientId\":\"$INFISICAL_CI_CLIENT_ID\",\"clientSecret\":\"$INFISICAL_CI_CLIENT_SECRET\"}" \
| python3 -c 'import sys,json; d=json.load(sys.stdin); v=d.get("accessToken"); print(v if isinstance(v,str) and v else "")')
if [ -z "${TOK}" ]; then
echo "::error::Infisical universal-auth login returned no accessToken"
exit 1
fi
MOL_PACKAGE_TOKEN=$(curl -fsS "$BASE/api/v3/secrets/raw/MOL_PACKAGE_TOKEN?workspaceId=$INFISICAL_PROJECT_ID&environment=prod&secretPath=%2Fci%2Fgitea-pypi-publisher" \
-H "Authorization: Bearer $TOK" \
| python3 -c 'import sys,json; d=json.load(sys.stdin); v=(d.get("secret") or {}).get("secretValue"); print(v if isinstance(v,str) and v else "")')
if [ -z "${MOL_PACKAGE_TOKEN}" ]; then
echo "::error::Infisical returned empty MOL_PACKAGE_TOKEN at /ci/gitea-pypi-publisher (prod) -- failing loudly"
exit 1
fi
echo "::add-mask::$MOL_PACKAGE_TOKEN"
echo "MOL_PACKAGE_TOKEN=$MOL_PACKAGE_TOKEN" >> "$GITHUB_ENV"
echo "Gitea package-publisher token loaded from Infisical (token len=${#MOL_PACKAGE_TOKEN})"
- name: Publish to Gitea package registry
env:
MOL_PACKAGE_TOKEN: ${{ env.MOL_PACKAGE_TOKEN }}
run: |
set -eu
PUBLISHER_USER="pypi-publisher"
if [ -z "${MOL_PACKAGE_TOKEN:-}" ]; then
echo "::error::MOL_PACKAGE_TOKEN was not exported by the Infisical fetch step"
exit 1
fi
# Idempotent publish: the Gitea registry rejects twine --skip-existing,
# so pre-check the simple index and skip when everything is present.
SIMPLE_BASE="${GITEA_PYPI_UPLOAD_URL%/}/simple"
missing=0
for f in dist/*; do
base=$(basename "$f")
pkg=$(printf %s "$base" | cut -d- -f1 | tr _ -)
if curl -fsS -u "$PUBLISHER_USER:$MOL_PACKAGE_TOKEN" "$SIMPLE_BASE/$pkg/" 2>/dev/null | grep -qF "$base"; then
echo "::notice::$base already in registry"
else
missing=1
fi
done
if [ "$missing" = "0" ]; then
echo "::notice::all dist files already published (re-run) -- skipping upload"
else
python -m twine upload \
--repository-url "$GITEA_PYPI_UPLOAD_URL" \
--username "$PUBLISHER_USER" \
--password "$MOL_PACKAGE_TOKEN" \
dist/*
fi
- name: Verify install from Gitea registry
env:
PKG_VERSION: ${{ steps.version.outputs.version }}
run: |
set -eu
python -m venv /tmp/gmail-registry-smoke
PIP=/tmp/gmail-registry-smoke/bin/pip
PY=/tmp/gmail-registry-smoke/bin/python
$PIP install --upgrade --quiet pip
$PIP install \
--index-url "$GITEA_PYPI_SIMPLE_URL" \
--extra-index-url "https://pypi.org/simple/" \
--no-cache-dir \
"gmail-channel-molecule==$PKG_VERSION"
INSTALLED=$($PIP show gmail-channel-molecule | awk -F': ' '/^Version:/{print $2}')
if [ "$INSTALLED" != "$PKG_VERSION" ]; then
echo "::error::installed $INSTALLED, want $PKG_VERSION"
exit 1
fi
$PY -c "import gmail_channel_molecule; from gmail_channel_molecule import brand, bridge, daemon; print('gmail-channel registry smoke OK')"
- name: Publish summary
if: always()
run: |
{
echo "## publish gmail-channel-molecule"
echo
echo "**Version:** \`${{ steps.version.outputs.version }}\`"
echo "**Registry:** \`$GITEA_PYPI_SIMPLE_URL\`"
} >> "$GITHUB_STEP_SUMMARY"