45641f8e2c
docker / test (push) Successful in 36s
docker / lint (push) Successful in 41s
lint / lint (push) Successful in 44s
docker / test (pull_request) Successful in 37s
lint / lint (pull_request) Successful in 44s
test / test (push) Successful in 45s
docker / lint (pull_request) Successful in 42s
test / package (push) Successful in 1m6s
test / test (pull_request) Successful in 40s
test / package (pull_request) Successful in 57s
docker / docker (pull_request) Successful in 56s
docker / docker (push) Successful in 1m7s
Document the two publish channels (aegis-gitea-mcp from main, aegis-gitea-mcp-dev from dev), install commands for each, that both share the aegis_gitea_mcp module so only one installs per environment, and the merge-driven stable release flow (bump version -> PR into dev -> promote dev to main; re-pushing main at the same version is a --check-url no-op).
118 lines
4.6 KiB
Markdown
118 lines
4.6 KiB
Markdown
# Packaging & publishing
|
|
|
|
AegisGitea-MCP is built with [`uv`](https://docs.astral.sh/uv/) and published to
|
|
the self-hosted Gitea package registry on every merge. There are two channels:
|
|
|
|
| Channel | Package | Published on | Versioning |
|
|
|---------|---------|--------------|------------|
|
|
| **stable** | `aegis-gitea-mcp` | merge to `main` | `X.Y.Z` |
|
|
| **dev** | `aegis-gitea-mcp-dev` | merge to `dev` | `X.Y.Z.devN` (N = CI run number, always unique) |
|
|
|
|
Both channels build from the same source and install the **same import module**,
|
|
`aegis_gitea_mcp`, with the same two console scripts. They differ only in the
|
|
distribution name and the version. Install one or the other in a given
|
|
environment, not both — they would collide on the module.
|
|
|
|
## Distribution layout
|
|
|
|
Each channel ships one distribution with two console scripts and one optional
|
|
extra (the stable and dev packages are identical here — only the dist name and
|
|
version differ):
|
|
|
|
| Console script | Entry point | Requires |
|
|
|----------------|-------------|----------|
|
|
| `aegis-gitea-mcp` | `aegis_gitea_mcp.stdio_app:main` | core only |
|
|
| `aegis-gitea-mcp-server` | `aegis_gitea_mcp.server_entry:main` | `[server]` extra |
|
|
|
|
- **Core** (default install): `httpx`, `pydantic`, `pydantic-settings`, `PyYAML`,
|
|
`python-dotenv`, `structlog`, `mcp`. Enough to run the local stdio server.
|
|
- **`[server]` extra**: `fastapi`, `uvicorn[standard]`, `PyJWT[crypto]`,
|
|
`python-multipart`. The public HTTP/OAuth server.
|
|
|
|
The `aegis-gitea-mcp-server` entry point degrades gracefully: invoked without
|
|
the web stack it prints `install 'aegis-gitea-mcp[server]'` instead of a
|
|
`ModuleNotFoundError` traceback.
|
|
|
|
## Build locally
|
|
|
|
```bash
|
|
uv build
|
|
# -> dist/aegis_gitea_mcp-<version>-py3-none-any.whl
|
|
# -> dist/aegis_gitea_mcp-<version>.tar.gz
|
|
```
|
|
|
|
Smoke-test the local stdio server from the built wheel:
|
|
|
|
```bash
|
|
GITEA_URL=https://git.hiddenden.cafe GITEA_TOKEN=<pat> \
|
|
uvx --from ./dist/aegis_gitea_mcp-*.whl aegis-gitea-mcp
|
|
```
|
|
|
|
## Install from the Gitea registry
|
|
|
|
**Stable** (`aegis-gitea-mcp`, published from `main`):
|
|
|
|
```bash
|
|
uv pip install \
|
|
--index-url https://git.hiddenden.cafe/api/packages/Hiddenden/pypi/simple/ \
|
|
aegis-gitea-mcp
|
|
|
|
# or run it one-off without installing into the environment:
|
|
uvx --index https://git.hiddenden.cafe/api/packages/Hiddenden/pypi/simple/ aegis-gitea-mcp
|
|
```
|
|
|
|
**Dev** (`aegis-gitea-mcp-dev`, published from `dev` — newest pre-release build):
|
|
|
|
```bash
|
|
uv pip install \
|
|
--index-url https://git.hiddenden.cafe/api/packages/Hiddenden/pypi/simple/ \
|
|
aegis-gitea-mcp-dev
|
|
```
|
|
|
|
(With `pip`, use `--index-url` the same way.) Both packages expose the same
|
|
`aegis-gitea-mcp` / `aegis-gitea-mcp-server` console scripts and the same
|
|
`aegis_gitea_mcp` import module, so install one **or** the other per environment.
|
|
|
|
## Publishing channels
|
|
|
|
Publishing is merge-driven, not tag-driven. The publish workflow
|
|
(`.gitea/workflows/publish.yml`) triggers on a push to `dev` or `main`, runs
|
|
lint + tests first, then builds with `uv` and publishes to the Gitea PyPI
|
|
registry. The package name + version are patched into `pyproject.toml` **at build
|
|
time only** (never committed):
|
|
|
|
- **`dev` push** → `aegis-gitea-mcp-dev` at `X.Y.Z.dev<run_number>`. The CI run
|
|
number is monotonic, so every merge to `dev` yields a unique pre-release.
|
|
- **`main` push** → `aegis-gitea-mcp` at the plain `X.Y.Z` from `pyproject.toml`.
|
|
Publishing uses `uv publish --check-url`, so a `main` push that did **not** bump
|
|
the version is a clean no-op (the existing files are skipped) rather than a 409.
|
|
|
|
### Cutting a stable release
|
|
|
|
1. Bump `version` in `pyproject.toml` (e.g. `0.2.0` → `0.3.0`) on a branch off
|
|
`dev`.
|
|
2. Open a PR into `dev` and merge it (this publishes a fresh
|
|
`aegis-gitea-mcp-dev` build).
|
|
3. Promote `dev` → `main`. The push to `main` publishes the new stable
|
|
`aegis-gitea-mcp X.Y.Z`.
|
|
|
|
Re-pushing `main` at an unchanged version is harmless — `--check-url` skips the
|
|
already-published files.
|
|
|
|
### Required CI secrets
|
|
|
|
The publish job reuses the **existing** `REGISTRY_TOKEN` Actions secret — the same
|
|
PAT (`write:package`) that `docker.yml` uses to push images — so no new secret is
|
|
needed. The token authenticates as its owning Gitea user, so `GITHUB_ACTOR` is the
|
|
username and the token is the password.
|
|
|
|
| Secret | Purpose |
|
|
|--------|---------|
|
|
| `REGISTRY_TOKEN` | PAT with `write:package`; used for both image and package pushes |
|
|
|
|
If the secret is absent the job fails loudly rather than publishing anonymously.
|
|
|
|
> Publishing to public PyPI is intentionally **not** configured. A second,
|
|
> separately-gated `uv publish` step would be required and is left as a
|
|
> commented stub in the workflow.
|