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
171 changes: 171 additions & 0 deletions .github/workflows/release.yml
Comment thread
JosiahBull marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
name: Release

on:
push:
tags:
- "v*"

permissions:
contents: read

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: 1

jobs:
# Verify the pushed tag matches every artifact's declared version:
# workspace.package.version, each member crate's manifest, and the
# ts-client package.json. `shared-version = true` in release.toml is
# meant to keep these aligned but doesn't enforce it on `cargo
# release` failures or hand-edits, so check defensively.
version-check:
name: Verify tag matches every artifact version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Check all manifest versions match the tag
run: |
set -euo pipefail
tag="${GITHUB_REF_NAME#v}"
fail=0

check() {
local label="$1" version="$2"
if [ "${version}" != "${tag}" ]; then
echo "::error::${label}: '${version}' does not match tag '${tag}'"
fail=1
else
echo "ok ${label}: ${version}"
fi
}

ws=$(awk '/^\[workspace\.package\]/{f=1; next} f && /^version = /{gsub(/"/, "", $3); print $3; exit}' Cargo.toml)
check "workspace.package" "${ws}"

for manifest in crates/*/Cargo.toml; do
crate=$(awk -F'"' '/^name = /{print $2; exit}' "${manifest}")
# `version.workspace = true` carries the workspace version
# and matches by construction; only check explicit values.
if grep -Eq '^version = "' "${manifest}"; then
v=$(awk -F'"' '/^version = "/{print $2; exit}' "${manifest}")
check "${crate}" "${v}"
fi
done

tsv=$(awk -F'"' '/"version"/{print $4; exit}' ts-client/package.json)
check "ts-client/package.json" "${tsv}"

exit "${fail}"

# Publish in dependency order so each crate's verification build can
# resolve the previous one from the index. partly-proxy-echo is
# publish=false and skipped automatically.
publish-crates:
name: Publish to crates.io
needs: [version-check]
runs-on: ubuntu-latest
# `environment: release` lets the repo gate this job behind approval
# rules and bind crates.io Trusted Publishers to this scope.
environment: release
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2

- name: Authenticate to crates.io (OIDC)
id: crates-io-auth
uses: rust-lang/crates-io-auth-action@v1

- name: Publish partly-proxy-types
run: cargo publish --package partly-proxy-types
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}

# Sparse-index propagation isn't synchronous on publish ack.
- name: Wait for index propagation
run: sleep 30

- name: Publish partly-proxy-storage-jsonl
run: cargo publish --package partly-proxy-storage-jsonl
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}

- name: Wait for index propagation
run: sleep 30

- name: Publish partly-proxy-storage-sqlite
run: cargo publish --package partly-proxy-storage-sqlite
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}

- name: Wait for index propagation
run: sleep 30

- name: Publish partly-proxy-lib
run: cargo publish --package partly-proxy-lib
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates-io-auth.outputs.token }}

# Publish @partly/proxy-client to npm with provenance.
#
# Uses npm's OIDC Trusted Publishing flow (GA Aug 2025) — configure a
# Trusted Publisher on npmjs.com for `@partly/proxy-client` bound to
# this repo, workflow filename, and the `release` environment. With
# that in place `--provenance` issues a Sigstore attestation linking
# the published tarball to this workflow run; no NPM_TOKEN secret
# required.
publish-npm:
name: Publish to npm
needs: [version-check]
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write
contents: read
defaults:
run:
working-directory: ts-client
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: "20"
registry-url: "https://registry.npmjs.org"
cache: "npm"
cache-dependency-path: ts-client/package-lock.json

- name: Install dependencies
run: npm ci

- name: Publish to npm
run: npm publish --provenance --access public

# Sequenced after every publish job so the release page never
# advertises a version that failed to upload to either registry.
github-release:
name: Create GitHub release
needs: [publish-crates, publish-npm]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
# --generate-notes walks back to the previous release tag.
fetch-depth: 0

- name: Create release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create "${GITHUB_REF_NAME}" \
--title "${GITHUB_REF_NAME}" \
--generate-notes \
--verify-tag
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ members = ["crates/*"]
version = "0.3.0"
edition = "2024"
license = "MIT OR Apache-2.0"
repository = "https://github.com/thepartly/api-proxy"
repository = "https://github.com/thepartly/partly-proxy"
homepage = "https://github.com/thepartly/partly-proxy"
authors = ["Partly <jo@partly.com>"]
rust-version = "1.85"

Expand Down
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# partly-proxy

[![CI](https://github.com/thepartly/partly-proxy/actions/workflows/ci.yml/badge.svg)](https://github.com/thepartly/partly-proxy/actions/workflows/ci.yml)
[![crates.io](https://img.shields.io/crates/v/partly-proxy-lib.svg)](https://crates.io/crates/partly-proxy-lib)
[![docs.rs](https://img.shields.io/docsrs/partly-proxy-lib)](https://docs.rs/partly-proxy-lib)
[![npm](https://img.shields.io/npm/v/@partly/proxy-client.svg)](https://www.npmjs.com/package/@partly/proxy-client)
[![MSRV](https://img.shields.io/badge/MSRV-1.85-blue)](https://github.com/thepartly/partly-proxy/blob/main/Cargo.toml)
![License](https://img.shields.io/crates/l/partly-proxy-lib)

A programmable HTTP/HTTPS proxy library for integration testing. Record real
upstream traffic, replay it deterministically, inject stubbed responses,
intercept and modify requests/responses via reqwest-style middleware,
Expand Down
4 changes: 4 additions & 0 deletions crates/partly-proxy-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ similar_names = "allow"
too_many_lines = "allow"
struct_field_names = "allow"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
# Shared type surface (SnapshotStorage trait, RecordedExchange, ProxyError).
# Re-exported under the original module paths via the shim files in
Expand Down
4 changes: 4 additions & 0 deletions crates/partly-proxy-storage-jsonl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ missing_errors_doc = "allow"
missing_panics_doc = "allow"
must_use_candidate = "allow"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
# Depend on the types crate, NOT on partly-proxy-lib — depending on the
# lib would create a Cargo package-graph cycle since the lib has an
Expand Down
4 changes: 4 additions & 0 deletions crates/partly-proxy-storage-sqlite/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ missing_errors_doc = "allow"
missing_panics_doc = "allow"
must_use_candidate = "allow"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
# Trait + RecordedExchange + ProxyError come from the types crate;
# depending on it here keeps the SQLite backend usable without pulling
Expand Down
4 changes: 4 additions & 0 deletions crates/partly-proxy-types/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ missing_errors_doc = "allow"
missing_panics_doc = "allow"
must_use_candidate = "allow"

[package.metadata.docs.rs]
all-features = true
rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
async-trait = { workspace = true }
base64 = { workspace = true }
Expand Down
Loading