Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 70 additions & 28 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ name: Publish SDKs
# this workflow deliberately does NOT touch the TS flow.
#
# ─────────────────────────────────────────────────────────────────────────────
# REQUIRED GITHUB ACTIONS SECRETS (add under repo Settings → Secrets → Actions):
# SECRETS — these are ORG-level and already exist. Nothing to create.
#
# CARGO_REGISTRY_TOKEN crates.io API token (Rust publish)
# https://crates.io/settings/tokens
# SMOOAI_CARGO_REGISTRY_TOKEN crates.io API token (Rust publish)
# SMOOAI_PYPI_TOKEN PyPI API token (Python publish)
# SMOOAI_NUGET_API_KEY nuget.org API key (.NET publish)
#
# NUGET_API_KEY nuget.org API key (.NET publish)
# https://www.nuget.org/account/apikeys
#
# PYPI_API_TOKEN PyPI API token (Python publish — ONLY needed if
# NOT using PyPI Trusted Publishing / OIDC; see the
# python job notes below)
# These names are not cosmetic. This workflow previously read
# `secrets.CARGO_REGISTRY_TOKEN`, `secrets.NUGET_API_KEY` and
# `secrets.PYPI_API_TOKEN` — none of which exist for this repo, at either the
# org or the repo level. A missing secret is the empty string in an expression,
# so every publish job would have run its full test suite, packaged cleanly, and
# then failed at the upload with an auth error, or worse pushed an unauthorized
# request. The names above are the ones `gh api .../actions/organization-secrets`
# actually reports, and match what SmooAI/logger uses to publish today.
#
# Go needs NO secret: pkg.go.dev pulls modules from the Go module proxy
# automatically once a SemVer git tag exists.
Expand Down Expand Up @@ -159,6 +162,20 @@ jobs:
with:
workspaces: rust -> target

# Fail on a missing credential BEFORE the suite runs, not after a
# clean package at the upload step. A missing secret is the empty
# string in a GitHub expression — silent until it isn't.
- name: Require the crates.io credential
if: ${{ !inputs.dry_run }}
env:
TOKEN: ${{ secrets.SMOOAI_CARGO_REGISTRY_TOKEN }}
run: |
if [ -z "$TOKEN" ]; then
echo "::error::crates.io credential is empty — the secret is missing or not visible to this repo. Nothing published."
exit 1
fi
echo "crates.io credential present."

# Gate publish on the same checks the PR lane runs, so a broken SDK
# can't ship.
- name: Format check
Expand All @@ -184,7 +201,7 @@ jobs:
if: ${{ !inputs.dry_run }}
run: cargo publish --locked -p smooai-observability
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
CARGO_REGISTRY_TOKEN: ${{ secrets.SMOOAI_CARGO_REGISTRY_TOKEN }}

# ── Python → PyPI ────────────────────────────────────────────────────────
python:
Expand All @@ -194,17 +211,13 @@ jobs:
(github.event_name == 'workflow_dispatch' && inputs.language == 'python')
runs-on: ubuntu-latest
timeout-minutes: 20
# PyPI Trusted Publishing (OIDC) — no PYPI_API_TOKEN needed once the
# publisher is configured at https://pypi.org/manage/account/publishing/
# for this repo + workflow (publish.yml) + environment (pypi). If you
# would rather use a classic token, delete the `environment:` block,
# remove `id-token: write`, and set `password: ${{ secrets.PYPI_API_TOKEN }}`
# on the publish step below.
environment:
name: pypi
url: https://pypi.org/p/smooai-observability
permissions:
id-token: write # OIDC token for Trusted Publishing
# Token auth, not OIDC Trusted Publishing. The OIDC path this job used
# to declare needed three things that are not true here: a `pypi`
# environment (this repo has ZERO environments configured), a Trusted
# Publisher registered on PyPI for repo+workflow+environment, and — for
# a package that does not exist yet — a PENDING publisher created by
# hand first. `SMOOAI_PYPI_TOKEN` already exists at the org level and is
# how SmooAI/logger publishes smooai-logger today.
defaults:
run:
working-directory: python
Expand All @@ -216,6 +229,20 @@ jobs:
with:
enable-cache: true

# Fail on a missing credential BEFORE the suite runs, not after a
# clean package at the upload step. A missing secret is the empty
# string in a GitHub expression — silent until it isn't.
- name: Require the PyPI credential
if: ${{ !inputs.dry_run }}
env:
TOKEN: ${{ secrets.SMOOAI_PYPI_TOKEN }}
run: |
if [ -z "$TOKEN" ]; then
echo "::error::PyPI credential is empty — the secret is missing or not visible to this repo. Nothing published."
exit 1
fi
echo "PyPI credential present."

- name: Set up Python
run: uv python install 3.13

Expand All @@ -235,13 +262,11 @@ jobs:
- name: Build sdist + wheel
run: uv build --wheel --sdist

- name: Publish to PyPI (Trusted Publishing)
- name: Publish to PyPI
if: ${{ !inputs.dry_run }}
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: python/dist
# If using a classic token instead of OIDC, uncomment:
# password: ${{ secrets.PYPI_API_TOKEN }}
run: uv publish
env:
UV_PUBLISH_TOKEN: ${{ secrets.SMOOAI_PYPI_TOKEN }}

# ── .NET → NuGet ─────────────────────────────────────────────────────────
dotnet:
Expand All @@ -262,6 +287,20 @@ jobs:
with:
global-json-file: dotnet/global.json

# Fail on a missing credential BEFORE the suite runs, not after a
# clean package at the upload step. A missing secret is the empty
# string in a GitHub expression — silent until it isn't.
- name: Require the NuGet credential
if: ${{ !inputs.dry_run }}
env:
TOKEN: ${{ secrets.NUGET_API_KEY || secrets.SMOOAI_NUGET_API_KEY }}
run: |
if [ -z "$TOKEN" ]; then
echo "::error::NuGet credential is empty — the secret is missing or not visible to this repo. Nothing published."
exit 1
fi
echo "NuGet credential present."

- name: Restore
run: dotnet restore

Expand All @@ -285,7 +324,10 @@ jobs:
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
env:
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY }}
# Repo-level NUGET_API_KEY first (that is where the sibling repos
# keep theirs) with the org secret as the fallback, so this works
# whichever one is present.
NUGET_API_KEY: ${{ secrets.NUGET_API_KEY || secrets.SMOOAI_NUGET_API_KEY }}

# ── Go → pkg.go.dev (no publish step) ────────────────────────────────────
#
Expand Down
21 changes: 13 additions & 8 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ Nothing to do by hand.
`publish.yml` is dormant until a language-prefixed tag is pushed. Pushing to a
branch publishes nothing.

| tag | goes to | secret needed |
| -------------------- | --------------------------------------- | ------------------------------ |
| `rust-v<semver>` | crates.io (`smooai-observability`) | `CARGO_REGISTRY_TOKEN` |
| `python-v<semver>` | PyPI (`smooai-observability`) | none — OIDC Trusted Publishing |
| `dotnet-v<semver>` | NuGet (`SmooAI.Observability`) | `NUGET_API_KEY` |
| `go/v<semver>` | pkg.go.dev (`…/observability/go`) | none — the module proxy |
| `go/fiber/v<semver>` | pkg.go.dev (`…/observability/go/fiber`) | none |
| `go/gin/v<semver>` | pkg.go.dev (`…/observability/go/gin`) | none |
| tag | goes to | secret needed |
| -------------------- | --------------------------------------- | ----------------------------- |
| `rust-v<semver>` | crates.io (`smooai-observability`) | `SMOOAI_CARGO_REGISTRY_TOKEN` |
| `python-v<semver>` | PyPI (`smooai-observability`) | `SMOOAI_PYPI_TOKEN` |
| `dotnet-v<semver>` | NuGet (`SmooAI.Observability`) | `SMOOAI_NUGET_API_KEY` |
| `go/v<semver>` | pkg.go.dev (`…/observability/go`) | none — the module proxy |
| `go/fiber/v<semver>` | pkg.go.dev (`…/observability/go/fiber`) | none |
| `go/gin/v<semver>` | pkg.go.dev (`…/observability/go/gin`) | none |

All three secrets are **org-level and already present** — nothing to create.
Each publish job also refuses to start if its credential resolves to the empty
string, so a missing or invisible secret fails on a bare runner instead of after
a clean package at the upload step.

Every job depends on a `verify` gate that asserts (a) all version-bearing files
agree with `packages/core/package.json` and (b) the tag names that same version.
Expand Down
Loading