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
5 changes: 5 additions & 0 deletions .changeset/publish-python-on-pypi.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@operatorstack/yield": patch
---

Publish the Python SDK and its six version-matched runtimes on PyPI through the supervised stable release.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Publish Yield to npm
name: Publish Yield packages

on:
push:
Expand All @@ -15,10 +15,9 @@ on:

permissions:
contents: read
id-token: write

concurrency:
group: npm-${{ github.event_name == 'push' && 'canary' || 'stable' }}
group: packages-${{ github.event_name == 'push' && 'canary' || 'stable' }}
cancel-in-progress: false

jobs:
Expand Down Expand Up @@ -77,11 +76,10 @@ jobs:
echo "dist_tag=$dist_tag" >> "$GITHUB_OUTPUT"
echo "source_sha=$source_sha" >> "$GITHUB_OUTPUT"

publish:
build:
needs: resolve
if: needs.resolve.outputs.publish == 'true'
runs-on: ubuntu-latest
environment: ${{ needs.resolve.outputs.channel == 'stable' && 'npm-production' || 'npm-canary' }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
Expand All @@ -95,14 +93,15 @@ jobs:
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: "24"
registry-url: https://registry.npmjs.org
package-manager-cache: false
- name: Pin npm trusted-publishing client
run: npm install --global npm@12.0.2
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.12"
- name: Verify source
run: |
go test ./...
node --test packaging/*.test.mjs sdk/typescript/bin/*.test.mjs
python -m unittest discover -s sdk/python -p 'test_*.py'
- name: Build immutable runtimes
env:
VERSION: ${{ needs.resolve.outputs.version }}
Expand All @@ -120,28 +119,64 @@ jobs:
-o "dist/bin/yskill-${goos}-${goarch}${suffix}" ./cmd/yskill
done
node packaging/assemble.mjs --version "$VERSION" --binaries dist/bin --output dist/packages
- name: Build Python wheels
env:
VERSION: ${{ needs.resolve.outputs.version }}
shell: bash
run: |
set -euo pipefail
python -m pip install --disable-pip-version-check build==1.3.0 setuptools==80.9.0 wheel==0.45.1
mkdir -p dist/pypi
for directory in dist/packages/python/*; do
python -m build --wheel --no-isolation --outdir "$GITHUB_WORKSPACE/dist/pypi" "$directory"
done
node packaging/pypi-release.mjs inspect --version "$VERSION" --dist dist/pypi
- name: Inspect npm tarballs
shell: bash
run: |
set -euo pipefail
for directory in dist/packages/npm/*; do
(cd "$directory" && npm pack --dry-run)
done
- name: Assemble immutable release unit
run: |
mkdir -p dist/release-unit
cp dist/packages/SHA256SUMS.json dist/release-unit/
cp -R dist/packages/npm dist/release-unit/npm
cp -R dist/pypi dist/release-unit/pypi
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: npm-${{ needs.resolve.outputs.version }}-release-unit
path: |
dist/packages/SHA256SUMS.json
dist/packages/npm/
name: packages-${{ needs.resolve.outputs.version }}-${{ needs.resolve.outputs.source_sha }}
path: dist/release-unit/
if-no-files-found: error

npm:
needs: [resolve, build]
runs-on: ubuntu-latest
environment: ${{ needs.resolve.outputs.channel == 'stable' && 'npm-production' || 'npm-canary' }}
permissions:
contents: read
id-token: write
steps:
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
name: packages-${{ needs.resolve.outputs.version }}-${{ needs.resolve.outputs.source_sha }}
path: dist/release-unit
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: "24"
registry-url: https://registry.npmjs.org
package-manager-cache: false
- name: Pin npm trusted-publishing client
run: npm install --global npm@12.0.2
- name: Publish platform runtimes
env:
DIST_TAG: ${{ needs.resolve.outputs.dist_tag }}
VERSION: ${{ needs.resolve.outputs.version }}
shell: bash
run: |
set -euo pipefail
for directory in dist/packages/npm/darwin-* dist/packages/npm/linux-* dist/packages/npm/windows-*; do
for directory in dist/release-unit/npm/darwin-* dist/release-unit/npm/linux-* dist/release-unit/npm/windows-*; do
package="$(node -p "require('./${directory}/package.json').name")"
if npm view "${package}@${VERSION}" version >/dev/null 2>&1; then
echo "${package}@${VERSION} already exists"
Expand All @@ -159,19 +194,67 @@ jobs:
if npm view "@operatorstack/yield@${VERSION}" version >/dev/null 2>&1; then
echo "@operatorstack/yield@${VERSION} already exists"
else
(cd dist/packages/npm/yield && npm publish --tag "$DIST_TAG")
(cd dist/release-unit/npm/yield && npm publish --tag "$DIST_TAG")
fi
- name: Verify complete public release unit
- name: Verify complete npm release unit
env:
VERSION: ${{ needs.resolve.outputs.version }}
shell: bash
run: |
set -euo pipefail
for directory in dist/packages/npm/*; do
for directory in dist/release-unit/npm/*; do
package="$(node -p "require('./${directory}/package.json').name")"
for attempt in {1..12}; do
if [[ "$(npm view "${package}@${VERSION}" version 2>/dev/null || true)" == "$VERSION" ]]; then break; fi
test "$attempt" -lt 12
sleep 10
done
done

pypi:
needs: [resolve, build]
if: needs.resolve.outputs.channel == 'stable'
runs-on: ubuntu-latest
environment: pypi-production
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
persist-credentials: false
ref: ${{ needs.resolve.outputs.source_sha }}
- uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5
with:
name: packages-${{ needs.resolve.outputs.version }}-${{ needs.resolve.outputs.source_sha }}
path: dist/release-unit
- uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
node-version: "24"
package-manager-cache: false
- id: prepare
name: Refuse PyPI drift and select missing wheels
env:
VERSION: ${{ needs.resolve.outputs.version }}
run: >-
node packaging/pypi-release.mjs prepare
--version "$VERSION"
--dist dist/release-unit/pypi
--upload dist/pypi-upload
--output "$GITHUB_OUTPUT"
- name: Publish Python wheels with trusted publishing
if: steps.prepare.outputs.publish == 'true'
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
with:
packages-dir: dist/pypi-upload
attestations: true
verbose: true
- name: Verify complete PyPI release unit
env:
VERSION: ${{ needs.resolve.outputs.version }}
run: >-
node packaging/pypi-release.mjs verify
--version "$VERSION"
--dist dist/release-unit/pypi
--attempts 12
--delay-ms 10000
17 changes: 13 additions & 4 deletions .github/workflows/release-finalize.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Finalize Yield release

on:
workflow_run:
workflows: ["Publish Yield to npm"]
workflows: ["Publish Yield packages"]
types: [completed]
workflow_dispatch:
inputs:
Expand Down Expand Up @@ -85,15 +85,24 @@ jobs:
run: |
set -euo pipefail
version="${TAG#v}"
conclusion="$(gh api "/repos/${GITHUB_REPOSITORY}/actions/workflows/npm-publish.yml/runs?head_sha=${SOURCE_SHA}&status=completed&per_page=30" \
--jq '[.workflow_runs[] | select(.event == "workflow_run" or .event == "workflow_dispatch")][0].conclusion // "missing"')"
test "$conclusion" = success
run="$(gh api "/repos/${GITHUB_REPOSITORY}/actions/workflows/package-publish.yml/runs?head_sha=${SOURCE_SHA}&status=completed&per_page=30" \
--jq '[.workflow_runs[] | select(.event == "workflow_run" or .event == "workflow_dispatch")][0] // {}')"
test "$(jq -r '.conclusion // "missing"' <<< "$run")" = success
run_id="$(jq -r '.id // empty' <<< "$run")"
test -n "$run_id"
gh run download "$run_id" --repo "$GITHUB_REPOSITORY" \
--name "packages-${version}-${SOURCE_SHA}" --dir "$RUNNER_TEMP/release-unit"
for package in \
@operatorstack/yield \
@operatorstack/yield-darwin-amd64 @operatorstack/yield-darwin-arm64 \
@operatorstack/yield-linux-amd64 @operatorstack/yield-linux-arm64 \
@operatorstack/yield-windows-amd64 @operatorstack/yield-windows-arm64; do
test "$(npm view "${package}@${version}" version)" = "$version"
done
node packaging/pypi-release.mjs verify \
--version "$version" \
--dist "$RUNNER_TEMP/release-unit/pypi" \
--attempts 3 \
--delay-ms 10000
test "$(git rev-list -n 1 "$TAG")" = "$SOURCE_SHA"
gh release edit "$TAG" --repo "$GITHUB_REPOSITORY" --draft=false
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<p align="center">
<a href="https://www.npmjs.com/package/@operatorstack/yield"><img alt="npm version" src="https://img.shields.io/npm/v/@operatorstack/yield?style=flat-square" /></a>
<a href="https://pypi.org/project/yieldskill/"><img alt="PyPI version" src="https://img.shields.io/pypi/v/yieldskill?style=flat-square" /></a>
<a href="https://github.com/operatorstack/yield/actions/workflows/verify.yml"><img alt="Build status" src="https://img.shields.io/github/actions/workflow/status/operatorstack/yield/verify.yml?branch=main&amp;style=flat-square&amp;label=build" /></a>
<a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/npm/l/@operatorstack/yield?style=flat-square" /></a>
</p>
Expand All @@ -22,6 +23,7 @@
<a href="https://yield.operatorstack.systems/">Website</a> ·
<a href="https://yield.operatorstack.systems/docs/">Documentation</a> ·
<a href="https://www.npmjs.com/package/@operatorstack/yield">npm</a> ·
<a href="https://pypi.org/project/yieldskill/">PyPI</a> ·
<a href="https://github.com/operatorstack/yield">GitHub</a>
</p>

Expand Down
4 changes: 2 additions & 2 deletions evals/results/latest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"schema_version": 2,
"methodology_version": "1.1",
"generated_at": "2026-08-07T11:49:51.097Z",
"source_digest": "d75c9a27b4782ff37a16c76b472327ecb6c8ab1b014dcf8e01626870340a82ac",
"generated_at": "2026-08-07T20:19:15.755Z",
"source_digest": "1695983e8279e9580deefe615b29814c8cc2777ce043cc73dd47caf992d7f269",
"status": "passed",
"workflow_conformance": {
"passed": 40,
Expand Down
12 changes: 11 additions & 1 deletion packaging/assemble.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ test("accepts stable and exact Yield canary versions", () => {
assert.equal(isPackageVersion("v1.2.3"), false);
});

test("assembles one public npm package and six matching runtimes", async (t) => {
test("assembles one public npm package and six matching npm and Python runtimes", async (t) => {
const root = await mkdtemp(join(tmpdir(), "yield-assemble-"));
t.after(() => rm(root, { recursive: true, force: true }));

Expand Down Expand Up @@ -62,5 +62,15 @@ test("assembles one public npm package and six matching runtimes", async (t) =>
assert.deepEqual(runtime.cpu, [target.nodeCpu]);
assert.equal(runtime.publishConfig.provenance, true);
assert.match(await readFile(join(output, `npm/${target.id}/LICENSE`), "utf8"), /MIT License/);

const pythonRoot = join(output, `python/${target.id}`);
assert.match(await readFile(join(pythonRoot, "pyproject.toml"), "utf8"), /version = "1\.2\.3"/);
assert.match(await readFile(join(pythonRoot, "setup.py"), "utf8"), new RegExp(target.pythonTag));
assert.match(await readFile(join(pythonRoot, "LICENSE"), "utf8"), /MIT License/);
const pythonRuntime = target.goos === "windows" ? "yskill.exe" : "yskill";
assert.equal(
await readFile(join(pythonRoot, "yieldskill/_runtime", pythonRuntime), "utf8"),
`runtime:${target.id}`,
);
}
});
Loading