Skip to content

Commit 5ec528e

Browse files
committed
feat:format tracing & auto pypi
1 parent ba9c0f0 commit 5ec528e

File tree

11 files changed

+651
-54
lines changed

11 files changed

+651
-54
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ python -m pytest -v tests/...
3131
- [ ] I ran the relevant tests locally.
3232
- [ ] I ran lint or formatting checks for touched code.
3333
- [ ] I updated docs for user-facing behavior changes.
34+
- [ ] I updated `CHANGELOG.md` for user-facing API, model, semantic, or installation changes.
35+
- [ ] This PR does not need a changelog entry because it is docs/tests/CI/internal-only, or it is explicitly labeled `skip-changelog`.
3436
- [ ] I included links and plain-text snippets instead of screenshots.
3537

3638
## Reviewer Notes

.github/workflows/changelog.yml

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: changelog
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- synchronize
8+
- reopened
9+
- ready_for_review
10+
- edited
11+
- labeled
12+
- unlabeled
13+
14+
permissions:
15+
contents: read
16+
pull-requests: read
17+
18+
jobs:
19+
require-changelog:
20+
runs-on: ubuntu-latest
21+
steps:
22+
- name: Enforce changelog policy
23+
uses: actions/github-script@v7
24+
with:
25+
script: |
26+
const pr = context.payload.pull_request;
27+
if (!pr) {
28+
core.info("No pull request payload found.");
29+
return;
30+
}
31+
32+
if (pr.draft) {
33+
core.notice("Draft PR: changelog enforcement will run again when the PR is marked ready for review.");
34+
return;
35+
}
36+
37+
const labels = new Set((pr.labels || []).map((label) => label.name));
38+
if (labels.has("skip-changelog")) {
39+
core.notice("Skipping changelog check because the PR has the skip-changelog label.");
40+
return;
41+
}
42+
43+
const files = await github.paginate(github.rest.pulls.listFiles, {
44+
owner: context.repo.owner,
45+
repo: context.repo.repo,
46+
pull_number: pr.number,
47+
per_page: 100,
48+
});
49+
50+
const changedFiles = files.map((file) => file.filename);
51+
if (changedFiles.includes("CHANGELOG.md")) {
52+
core.notice("CHANGELOG.md was updated in this PR.");
53+
return;
54+
}
55+
56+
const exemptFiles = new Set([
57+
"README.md",
58+
"CHANGELOG.md",
59+
"LICENSE",
60+
"mkdocs.yml",
61+
"uv.lock",
62+
".gitignore",
63+
".pre-commit-config.yaml",
64+
]);
65+
const exemptPrefixes = [
66+
".github/",
67+
"docs/",
68+
"tests/",
69+
];
70+
71+
const requiresChangelog = changedFiles.filter((file) => {
72+
if (exemptFiles.has(file)) {
73+
return false;
74+
}
75+
if (exemptPrefixes.some((prefix) => file.startsWith(prefix))) {
76+
return false;
77+
}
78+
return true;
79+
});
80+
81+
if (requiresChangelog.length === 0) {
82+
core.notice("Only docs/tests/CI/internal files changed, so no changelog entry is required.");
83+
return;
84+
}
85+
86+
const details = requiresChangelog.slice(0, 20).map((file) => `- ${file}`).join("\n");
87+
core.setFailed(
88+
[
89+
"This PR changes user-facing or package-relevant files but does not update CHANGELOG.md.",
90+
"Add a changelog entry, or apply the skip-changelog label if the change is intentionally internal.",
91+
"",
92+
"Files that triggered this check:",
93+
details,
94+
].join("\n"),
95+
);

.github/workflows/release.yml

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
name: release
2+
3+
"on":
4+
push:
5+
tags:
6+
- "v*"
7+
workflow_dispatch:
8+
9+
jobs:
10+
build:
11+
name: Build distributions
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
15+
steps:
16+
- uses: actions/checkout@v4
17+
with:
18+
persist-credentials: false
19+
20+
- name: Set up Python
21+
uses: actions/setup-python@v5
22+
with:
23+
python-version: "3.12"
24+
25+
- name: Verify tag matches package version
26+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
27+
env:
28+
TAG_NAME: ${{ github.ref_name }}
29+
run: |
30+
package_version=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' src/rs_embed/_version.py)
31+
tag_version="${TAG_NAME#v}"
32+
33+
if [ -z "$package_version" ]; then
34+
echo "Unable to determine package version from src/rs_embed/_version.py" >&2
35+
exit 1
36+
fi
37+
38+
if [ "$package_version" != "$tag_version" ]; then
39+
echo "Tag version ${tag_version} does not match package version ${package_version}" >&2
40+
exit 1
41+
fi
42+
43+
- name: Extract release notes from CHANGELOG.md
44+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
45+
env:
46+
TAG_NAME: ${{ github.ref_name }}
47+
run: |
48+
version="${TAG_NAME#v}"
49+
awk -v version="$version" '
50+
$0 ~ "^## \\[" version "\\]" { capture = 1; next }
51+
capture && $0 ~ "^## \\[" { exit }
52+
capture { print }
53+
' CHANGELOG.md > RELEASE_NOTES.md
54+
55+
if ! grep -q '[^[:space:]]' RELEASE_NOTES.md; then
56+
echo "No changelog entry found for ${version} in CHANGELOG.md" >&2
57+
exit 1
58+
fi
59+
60+
- name: Install build tooling
61+
run: python -m pip install --upgrade build twine
62+
63+
- name: Build wheel and sdist
64+
run: python -m build
65+
66+
- name: Check distribution metadata
67+
run: python -m twine check --strict dist/*
68+
69+
- name: Store distribution packages
70+
uses: actions/upload-artifact@v4
71+
with:
72+
name: python-package-distributions
73+
path: dist/
74+
75+
- name: Store release notes
76+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
77+
uses: actions/upload-artifact@v4
78+
with:
79+
name: github-release-notes
80+
path: RELEASE_NOTES.md
81+
82+
publish-to-testpypi:
83+
name: Publish package to TestPyPI
84+
if: github.event_name == 'workflow_dispatch'
85+
needs: build
86+
runs-on: ubuntu-latest
87+
environment:
88+
name: testpypi
89+
url: https://test.pypi.org/p/rs-embed
90+
permissions:
91+
id-token: write
92+
steps:
93+
- name: Download distributions
94+
uses: actions/download-artifact@v4
95+
with:
96+
name: python-package-distributions
97+
path: dist/
98+
99+
- name: Publish distribution to TestPyPI
100+
uses: pypa/gh-action-pypi-publish@release/v1
101+
with:
102+
repository-url: https://test.pypi.org/legacy/
103+
104+
smoke-test-testpypi:
105+
name: Install package from TestPyPI
106+
if: github.event_name == 'workflow_dispatch'
107+
needs: publish-to-testpypi
108+
runs-on: ubuntu-latest
109+
permissions:
110+
contents: read
111+
steps:
112+
- uses: actions/checkout@v4
113+
114+
- name: Set up Python
115+
uses: actions/setup-python@v5
116+
with:
117+
python-version: "3.12"
118+
119+
- name: Upgrade pip
120+
run: python -m pip install --upgrade pip
121+
122+
- name: Install published package from TestPyPI
123+
run: |
124+
version=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' src/rs_embed/_version.py)
125+
126+
if [ -z "$version" ]; then
127+
echo "Unable to determine package version from src/rs_embed/_version.py" >&2
128+
exit 1
129+
fi
130+
131+
for attempt in 1 2 3 4 5; do
132+
if python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ "rs-embed==${version}"; then
133+
exit 0
134+
fi
135+
136+
if [ "$attempt" -eq 5 ]; then
137+
echo "TestPyPI install failed for rs-embed==${version}" >&2
138+
exit 1
139+
fi
140+
141+
sleep 30
142+
done
143+
144+
- name: Verify installed version
145+
run: |
146+
version=$(sed -n 's/^__version__ = "\(.*\)"$/\1/p' src/rs_embed/_version.py)
147+
installed=$(python -c "import importlib.metadata as md; print(md.version('rs-embed'))")
148+
149+
if [ "$installed" != "$version" ]; then
150+
echo "Installed version ${installed} does not match expected ${version}" >&2
151+
exit 1
152+
fi
153+
154+
- name: Verify package import
155+
run: |
156+
python -c "import rs_embed; print(rs_embed.__version__)"
157+
158+
- name: Verify CLI entry point
159+
run: |
160+
rs-embed --help
161+
162+
publish-to-pypi:
163+
name: Publish package to PyPI
164+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
165+
needs: build
166+
runs-on: ubuntu-latest
167+
environment:
168+
name: pypi
169+
url: https://pypi.org/p/rs-embed
170+
permissions:
171+
id-token: write
172+
steps:
173+
- name: Download distributions
174+
uses: actions/download-artifact@v4
175+
with:
176+
name: python-package-distributions
177+
path: dist/
178+
179+
- name: Publish distribution to PyPI
180+
uses: pypa/gh-action-pypi-publish@release/v1
181+
182+
github-release:
183+
name: Publish GitHub Release
184+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
185+
needs: publish-to-pypi
186+
runs-on: ubuntu-latest
187+
permissions:
188+
contents: write
189+
steps:
190+
- name: Download release notes
191+
uses: actions/download-artifact@v4
192+
with:
193+
name: github-release-notes
194+
path: .
195+
196+
- name: Publish GitHub Release
197+
uses: softprops/action-gh-release@v2
198+
with:
199+
body_path: RELEASE_NOTES.md
200+
generate_release_notes: false

CHANGELOG.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# Changelog
2+
3+
All notable changes to `rs-embed` will be documented in this file.
4+
5+
The project keeps this changelog as the canonical release record. GitHub Releases should summarize the same versioned changes rather than introducing a second source of truth.
6+
7+
The format is based on Keep a Changelog, and the project follows Semantic Versioning with extra care around model and embedding semantics.
8+
9+
## [Unreleased]
10+
11+
### Added
12+
- Automated pull request changelog enforcement with a `skip-changelog` escape hatch for docs, tests, CI, and other internal-only changes.
13+
- Tag-driven GitHub Release publishing that uses the matching `CHANGELOG.md` section as the release notes.
14+
- Trusted Publishing release automation for PyPI and TestPyPI, including a manual TestPyPI dry run and install smoke test.
15+
16+
### Changed
17+
- The contribution and release workflow now treats `CHANGELOG.md` as the canonical source for user-visible release notes.
18+
- The tag-triggered release flow now validates `src/rs_embed/_version.py`, publishes to PyPI, and only then creates the GitHub Release.
19+
- The tag-triggered release flow now validates the matching `CHANGELOG.md` entry before publishing to PyPI, so a missing release-notes section fails early instead of after package upload.
20+
21+
### Deprecated
22+
23+
### Removed
24+
25+
### Fixed
26+
- The TestPyPI smoke test now verifies package importability and the `rs-embed` CLI entry point, not just installability and version metadata.
27+
28+
## [0.1.0] - 2026-03-31
29+
30+
### Added
31+
- Initial public alpha release of `rs-embed`.
32+
- Unified ROI to embedding API centered on `get_embedding(...)`, `get_embeddings_batch(...)`, `export_batch(...)`, and `inspect_provider_patch(...)`.
33+
- Support for precomputed embedding products including `tessera`, `gse`, and `copernicus`.
34+
- Support for on-the-fly model adapters including `satmae`, `satmaepp`, `satmaepp_s2_10b`, `prithvi`, `scalemae`, `remoteclip`, `dofa`, `satvision`, `anysat`, `galileo`, `wildsat`, `fomo`, `terramind`, `terrafm`, `thor`, and `agrifm`.
35+
- Documentation site covering quickstart, model behavior, API contracts, and extension guidance.
36+
37+
[Unreleased]: https://github.com/cybergis/rs-embed/compare/v0.1.0...HEAD
38+
[0.1.0]: https://github.com/cybergis/rs-embed/releases/tag/v0.1.0

README.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616

1717

18-
[Docs](https://cybergis.github.io/rs-embed/) · [Learn](https://cybergis.github.io/rs-embed/quickstart/) · [Guide](https://cybergis.github.io/rs-embed/workflows/) · [StartNow](https://github.com/cybergis/rs-embed/blob/main/examples/playground.ipynb) · [UseCase](https://github.com/cybergis/rs-embed/blob/main/examples/demo.ipynb) · [Paper](https://arxiv.org/abs/2602.23678)
18+
[Docs](https://cybergis.github.io/rs-embed/) · [Releases](https://github.com/cybergis/rs-embed/releases) · [Changelog](https://github.com/cybergis/rs-embed/blob/main/CHANGELOG.md) · [StartNow](https://github.com/cybergis/rs-embed/blob/main/examples/playground.ipynb) · [UseCase](https://github.com/cybergis/rs-embed/blob/main/examples/demo.ipynb) · [Paper](https://arxiv.org/abs/2602.23678)
1919

2020
</div>
2121

@@ -132,6 +132,10 @@ Resolution here means the default provider/source fetch resolution used by the a
132132

133133
📚 [Full documentation](https://cybergis.github.io/rs-embed/)
134134

135+
🧾 [Release policy and versioning](https://cybergis.github.io/rs-embed/releases/)
136+
137+
📌 [Project changelog](https://github.com/cybergis/rs-embed/blob/main/CHANGELOG.md)
138+
135139
🪄 [Get Started: Try `rs-embed` Now](https://github.com/cybergis/rs-embed/blob/main/examples/playground.ipynb)
136140

137141
🪀 [Use case: Maize yield mapping Illinois](https://github.com/cybergis/rs-embed/blob/main/examples/demo.ipynb)

docs/contributing.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ This repository includes GitHub issue forms for bug reports and feature requests
2828

2929
The repository also includes a pull request template. Please use it to summarize the change, link the related issue or reference material, and show how you verified the result. If the PR changes model behavior, it is especially helpful to mention the input contract, defaults, and any behavior that remains intentionally unsupported.
3030

31+
If a pull request changes public API, model defaults, output semantics, or user-facing installation behavior, please update `CHANGELOG.md` as part of the same change so the release record stays current.
32+
33+
Pull requests that touch user-facing or package-relevant files now get an automated changelog check in GitHub Actions. If a PR is intentionally docs-only, tests-only, CI-only, or otherwise internal, maintainers can apply the `skip-changelog` label to bypass that check.
34+
3135
## Tests
3236

3337
`rs-embed` uses GitHub Actions for continuous integration, and it helps a lot if pull requests keep those checks green. If you change Python behavior, please add or update tests so the new behavior is explicit and easy to review. The repository already contains many focused tests, so the easiest starting point is usually to find a nearby test and mirror its style rather than inventing a new pattern.

0 commit comments

Comments
 (0)