d2c3a8b80d
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>
174 lines
7.2 KiB
YAML
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"
|