ossplate 0.1.9__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. ossplate-0.1.9/.gitignore +9 -0
  2. ossplate-0.1.9/PKG-INFO +44 -0
  3. ossplate-0.1.9/README.md +30 -0
  4. ossplate-0.1.9/hatch_build.py +97 -0
  5. ossplate-0.1.9/ossplate/bin/linux-x64/ossplate +0 -0
  6. ossplate-0.1.9/pyproject.toml +40 -0
  7. ossplate-0.1.9/src/ossplate/__init__.py +3 -0
  8. ossplate-0.1.9/src/ossplate/bin/darwin-arm64/ossplate +0 -0
  9. ossplate-0.1.9/src/ossplate/bin/darwin-x64/ossplate +3 -0
  10. ossplate-0.1.9/src/ossplate/bin/linux-x64/ossplate +0 -0
  11. ossplate-0.1.9/src/ossplate/bin/win32-x64/ossplate.exe +3 -0
  12. ossplate-0.1.9/src/ossplate/cli.py +55 -0
  13. ossplate-0.1.9/src/ossplate/scaffold/.github/workflows/ci.yml +111 -0
  14. ossplate-0.1.9/src/ossplate/scaffold/.github/workflows/publish-npm.yml +72 -0
  15. ossplate-0.1.9/src/ossplate/scaffold/.github/workflows/publish.yml +173 -0
  16. ossplate-0.1.9/src/ossplate/scaffold/.github/workflows/release.yml +117 -0
  17. ossplate-0.1.9/src/ossplate/scaffold/.gitignore +9 -0
  18. ossplate-0.1.9/src/ossplate/scaffold/.pre-commit-config.yaml +9 -0
  19. ossplate-0.1.9/src/ossplate/scaffold/CONTRIBUTING.md +53 -0
  20. ossplate-0.1.9/src/ossplate/scaffold/LICENSE +9 -0
  21. ossplate-0.1.9/src/ossplate/scaffold/README.md +81 -0
  22. ossplate-0.1.9/src/ossplate/scaffold/assets/illustrations/chestplate.svg +6 -0
  23. ossplate-0.1.9/src/ossplate/scaffold/core-rs/Cargo.lock +338 -0
  24. ossplate-0.1.9/src/ossplate/scaffold/core-rs/Cargo.toml +28 -0
  25. ossplate-0.1.9/src/ossplate/scaffold/core-rs/src/main.rs +1730 -0
  26. ossplate-0.1.9/src/ossplate/scaffold/docs/README.md +16 -0
  27. ossplate-0.1.9/src/ossplate/scaffold/docs/adrs/0001-rust-core-thin-wrappers.md +21 -0
  28. ossplate-0.1.9/src/ossplate/scaffold/docs/adrs/0002-sync-owns-bounded-identity.md +31 -0
  29. ossplate-0.1.9/src/ossplate/scaffold/docs/adrs/0003-curated-scaffold-payload.md +21 -0
  30. ossplate-0.1.9/src/ossplate/scaffold/docs/architecture.md +89 -0
  31. ossplate-0.1.9/src/ossplate/scaffold/docs/customizing-the-template.md +121 -0
  32. ossplate-0.1.9/src/ossplate/scaffold/docs/releases.md +91 -0
  33. ossplate-0.1.9/src/ossplate/scaffold/docs/testing.md +76 -0
  34. ossplate-0.1.9/src/ossplate/scaffold/ossplate.toml +15 -0
  35. ossplate-0.1.9/src/ossplate/scaffold/scripts/bump-version.mjs +53 -0
  36. ossplate-0.1.9/src/ossplate/scaffold/scripts/release-plan.mjs +94 -0
  37. ossplate-0.1.9/src/ossplate/scaffold/scripts/verify.sh +36 -0
  38. ossplate-0.1.9/src/ossplate/scaffold/wrapper-js/README.md +30 -0
  39. ossplate-0.1.9/src/ossplate/scaffold/wrapper-js/bin/ossplate.js +5 -0
  40. ossplate-0.1.9/src/ossplate/scaffold/wrapper-js/package-lock.json +51 -0
  41. ossplate-0.1.9/src/ossplate/scaffold/wrapper-js/package.json +43 -0
  42. ossplate-0.1.9/src/ossplate/scaffold/wrapper-js/src/index.ts +69 -0
  43. ossplate-0.1.9/src/ossplate/scaffold/wrapper-js/tsconfig.json +14 -0
  44. ossplate-0.1.9/src/ossplate/scaffold/wrapper-py/README.md +30 -0
  45. ossplate-0.1.9/src/ossplate/scaffold/wrapper-py/hatch_build.py +97 -0
  46. ossplate-0.1.9/src/ossplate/scaffold/wrapper-py/pyproject.toml +40 -0
  47. ossplate-0.1.9/src/ossplate/scaffold/wrapper-py/src/ossplate/__init__.py +3 -0
  48. ossplate-0.1.9/src/ossplate/scaffold/wrapper-py/src/ossplate/cli.py +55 -0
  49. ossplate-0.1.9/tests/fixtures/ossplate-stub.sh +18 -0
  50. ossplate-0.1.9/tests/test_cli.py +237 -0
@@ -0,0 +1,9 @@
1
+ .DS_Store
2
+ .venv/
3
+ __pycache__/
4
+ *.pyc
5
+ .tmp-*/
6
+ dist/
7
+ build/
8
+ node_modules/
9
+ target/
@@ -0,0 +1,44 @@
1
+ Metadata-Version: 2.4
2
+ Name: ossplate
3
+ Version: 0.1.9
4
+ Summary: Build one project, ship it everywhere.
5
+ Project-URL: Homepage, https://github.com/stefdevscore/ossplate
6
+ Project-URL: Repository, https://github.com/stefdevscore/ossplate
7
+ Author-email: Stef <stefdevscore@github.com>
8
+ License: Unlicense
9
+ Classifier: License :: Public Domain
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Programming Language :: Python :: 3
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+
15
+ # Ossplate
16
+
17
+ <p align="center">
18
+ <img src="https://raw.githubusercontent.com/stefdevscore/ossplate/main/assets/illustrations/chestplate.svg" alt="Ossplate armor" width="320">
19
+ </p>
20
+
21
+ `ossplate` helps you start and maintain a project that ships the same CLI through Rust, npm, and PyPI.
22
+
23
+ Use it to:
24
+
25
+ - create a new scaffolded project
26
+ - initialize an existing directory
27
+ - validate project identity and metadata
28
+ - keep owned files in sync
29
+
30
+ Common commands:
31
+
32
+ ```bash
33
+ ossplate version
34
+ ossplate create <target>
35
+ ossplate init --path <dir>
36
+ ossplate validate
37
+ ossplate sync --check
38
+ ```
39
+
40
+ Learn more:
41
+
42
+ - [Main documentation](../docs/README.md)
43
+ - [Testing guide](../docs/testing.md)
44
+ - [Architecture](../docs/architecture.md)
@@ -0,0 +1,30 @@
1
+ # Ossplate
2
+
3
+ <p align="center">
4
+ <img src="https://raw.githubusercontent.com/stefdevscore/ossplate/main/assets/illustrations/chestplate.svg" alt="Ossplate armor" width="320">
5
+ </p>
6
+
7
+ `ossplate` helps you start and maintain a project that ships the same CLI through Rust, npm, and PyPI.
8
+
9
+ Use it to:
10
+
11
+ - create a new scaffolded project
12
+ - initialize an existing directory
13
+ - validate project identity and metadata
14
+ - keep owned files in sync
15
+
16
+ Common commands:
17
+
18
+ ```bash
19
+ ossplate version
20
+ ossplate create <target>
21
+ ossplate init --path <dir>
22
+ ossplate validate
23
+ ossplate sync --check
24
+ ```
25
+
26
+ Learn more:
27
+
28
+ - [Main documentation](../docs/README.md)
29
+ - [Testing guide](../docs/testing.md)
30
+ - [Architecture](../docs/architecture.md)
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import platform
5
+ import subprocess
6
+ import sysconfig
7
+ from pathlib import Path
8
+
9
+ try:
10
+ from hatchling.builders.hooks.plugin.interface import BuildHookInterface
11
+ except ModuleNotFoundError: # pragma: no cover - exercised in local unit tests without hatchling
12
+ class BuildHookInterface: # type: ignore[override]
13
+ def __init__(self, *args, **kwargs) -> None:
14
+ pass
15
+
16
+ BUILD_TARGET_ENV = "OSSPLATE_PY_TARGET"
17
+ TARGETS = {
18
+ "darwin-arm64": "ossplate",
19
+ "darwin-x64": "ossplate",
20
+ "linux-x64": "ossplate",
21
+ "win32-x64": "ossplate.exe",
22
+ }
23
+ HOST_TARGETS = {
24
+ ("Darwin", "arm64"): "darwin-arm64",
25
+ ("Darwin", "x86_64"): "darwin-x64",
26
+ ("Linux", "x86_64"): "linux-x64",
27
+ ("Windows", "AMD64"): "win32-x64",
28
+ ("Windows", "x86_64"): "win32-x64",
29
+ }
30
+
31
+
32
+ class CustomBuildHook(BuildHookInterface):
33
+ def initialize(self, version: str, build_data: dict) -> None:
34
+ repo_root = Path(self.root).resolve().parent
35
+ script = repo_root / "scripts" / "stage-distribution-assets.mjs"
36
+ try:
37
+ subprocess.run(["node", str(script)], cwd=repo_root, check=True)
38
+ except FileNotFoundError as error:
39
+ raise RuntimeError("node is required to stage distribution assets for wrapper-py builds") from error
40
+
41
+ target = resolve_build_target()
42
+ binary_name = TARGETS[target]
43
+ binary_source = repo_root / "wrapper-js" / "bin" / target / binary_name
44
+ if not binary_source.exists():
45
+ raise RuntimeError(
46
+ f"required ossplate binary for target {target} is missing at {binary_source}"
47
+ )
48
+
49
+ build_data["pure_python"] = False
50
+ build_data["tag"] = f"py3-none-{platform_tag_for_target(target)}"
51
+ force_include = build_data.setdefault("force_include", {})
52
+ force_include[str(binary_source)] = f"ossplate/bin/{target}/{binary_name}"
53
+
54
+
55
+ def resolve_build_target() -> str:
56
+ target = os.environ.get(BUILD_TARGET_ENV)
57
+ if target:
58
+ if target not in TARGETS:
59
+ raise RuntimeError(f"unsupported {BUILD_TARGET_ENV} value: {target}")
60
+ return target
61
+
62
+ host = HOST_TARGETS.get((platform.system(), platform.machine()))
63
+ if host is None:
64
+ raise RuntimeError(
65
+ f"unsupported host platform for wrapper-py wheel build: {platform.system()}/{platform.machine()}"
66
+ )
67
+ return host
68
+
69
+
70
+ def platform_tag_for_target(target: str) -> str:
71
+ if target == "linux-x64":
72
+ return linux_platform_tag()
73
+ if target == "darwin-arm64":
74
+ return macos_platform_tag("arm64")
75
+ if target == "darwin-x64":
76
+ return macos_platform_tag("x86_64")
77
+ if target == "win32-x64":
78
+ return "win_amd64"
79
+ raise RuntimeError(f"unsupported target for wheel tag generation: {target}")
80
+
81
+
82
+ def linux_platform_tag() -> str:
83
+ libc_name, libc_version = platform.libc_ver()
84
+ if libc_name != "glibc" or not libc_version:
85
+ raise RuntimeError("linux wheel builds require glibc to derive a manylinux platform tag")
86
+
87
+ major, minor, *_rest = libc_version.split(".")
88
+ return f"manylinux_{major}_{minor}_x86_64"
89
+
90
+
91
+ def macos_platform_tag(arch: str) -> str:
92
+ tag = sysconfig.get_platform().replace("-", "_").replace(".", "_")
93
+ if "universal2" in tag:
94
+ return tag.replace("universal2", arch)
95
+ if tag.endswith("_x86_64") or tag.endswith("_arm64"):
96
+ return tag.rsplit("_", 1)[0] + f"_{arch}"
97
+ return f"{tag}_{arch}"
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "ossplate"
7
+ version = "0.1.9"
8
+ description = "Build one project, ship it everywhere."
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "Unlicense" }
12
+ authors = [
13
+ { name = "Stef", email = "stefdevscore@github.com" }
14
+ ]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "License :: Public Domain",
18
+ "Operating System :: OS Independent"
19
+ ]
20
+ dependencies = []
21
+
22
+ [project.urls]
23
+ Homepage = "https://github.com/stefdevscore/ossplate"
24
+ Repository = "https://github.com/stefdevscore/ossplate"
25
+
26
+ [project.scripts]
27
+ ossplate = "ossplate.cli:main"
28
+
29
+ [tool.hatch.build.targets.wheel]
30
+ packages = ["src/ossplate"]
31
+ artifacts = [
32
+ "src/ossplate/scaffold/**",
33
+ ]
34
+ exclude = [
35
+ "src/ossplate/bin/**",
36
+ ]
37
+ pure_python = false
38
+
39
+ [tool.hatch.build.hooks.custom]
40
+ path = "hatch_build.py"
@@ -0,0 +1,3 @@
1
+ from .cli import cli, get_binary_path, get_packaged_binary_path, main
2
+
3
+ __all__ = ["cli", "get_binary_path", "get_packaged_binary_path", "main"]
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ printf '{"status":"packaged-stub"}\n'
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ printf '{"status":"packaged-stub"}\n'
@@ -0,0 +1,55 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import platform
5
+ import subprocess
6
+ import sys
7
+ from importlib import resources
8
+ from pathlib import Path
9
+
10
+ ENV_OVERRIDE = "OSSPLATE_BINARY"
11
+ TEMPLATE_ROOT_ENV = "OSSPLATE_TEMPLATE_ROOT"
12
+ TARGETS = {
13
+ ("Darwin", "arm64"): ("darwin-arm64", "ossplate"),
14
+ ("Darwin", "x86_64"): ("darwin-x64", "ossplate"),
15
+ ("Linux", "x86_64"): ("linux-x64", "ossplate"),
16
+ ("Windows", "AMD64"): ("win32-x64", "ossplate.exe"),
17
+ }
18
+
19
+
20
+ def get_packaged_binary_path(base_dir: Path | None = None) -> str:
21
+ base_dir = base_dir or Path(resources.files("ossplate"))
22
+ env_override = os.environ.get(ENV_OVERRIDE)
23
+ if env_override:
24
+ return env_override
25
+
26
+ system = platform.system()
27
+ machine = platform.machine()
28
+ target = TARGETS.get((system, machine))
29
+ if target is None:
30
+ raise RuntimeError(f"Unsupported platform/arch: {system}/{machine}")
31
+
32
+ folder, executable = target
33
+ binary_path = base_dir / "bin" / folder / executable
34
+ if not binary_path.exists():
35
+ raise RuntimeError(f"Bundled ossplate binary not found at {binary_path}")
36
+ return str(binary_path)
37
+
38
+
39
+ def get_binary_path() -> str:
40
+ return get_packaged_binary_path()
41
+
42
+
43
+ def cli(args: tuple[str, ...]) -> int:
44
+ env = os.environ.copy()
45
+ env.setdefault(TEMPLATE_ROOT_ENV, str(Path(resources.files("ossplate")) / "scaffold"))
46
+ result = subprocess.run([get_binary_path(), *args], check=False, env=env)
47
+ return result.returncode
48
+
49
+
50
+ def main() -> None:
51
+ raise SystemExit(cli(tuple(sys.argv[1:])))
52
+
53
+
54
+ if __name__ == "__main__":
55
+ main()
@@ -0,0 +1,111 @@
1
+ # ossplate:workflow-name:start
2
+ name: Ossplate CI
3
+ # ossplate:workflow-name:end
4
+
5
+ on:
6
+ push:
7
+ branches:
8
+ - main
9
+ pull_request:
10
+
11
+ jobs:
12
+ template-readiness:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v5
16
+ - uses: dtolnay/rust-toolchain@stable
17
+ - uses: actions/setup-node@v5
18
+ with:
19
+ node-version: '24'
20
+ - run: cargo run --quiet --manifest-path core-rs/Cargo.toml -- validate
21
+ - run: cargo run --quiet --manifest-path core-rs/Cargo.toml -- sync --check
22
+ - run: node --test ./scripts/validate-template-readiness.test.mjs
23
+
24
+ rust:
25
+ needs: template-readiness
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v5
29
+ - uses: dtolnay/rust-toolchain@stable
30
+ - run: cargo fmt --check
31
+ working-directory: ./core-rs
32
+ - run: cargo clippy -- -D warnings
33
+ working-directory: ./core-rs
34
+ - run: cargo test
35
+ working-directory: ./core-rs
36
+
37
+ js:
38
+ needs: template-readiness
39
+ runs-on: ubuntu-latest
40
+ steps:
41
+ - uses: actions/checkout@v5
42
+ - uses: dtolnay/rust-toolchain@stable
43
+ - uses: actions/setup-node@v5
44
+ with:
45
+ node-version: '24'
46
+ cache: npm
47
+ cache-dependency-path: ./wrapper-js/package-lock.json
48
+ - run: npm ci
49
+ working-directory: ./wrapper-js
50
+ - run: npm run build
51
+ working-directory: ./wrapper-js
52
+ - run: npm test
53
+ working-directory: ./wrapper-js
54
+ - run: npm pack --dry-run
55
+ working-directory: ./wrapper-js
56
+
57
+ python:
58
+ needs: template-readiness
59
+ runs-on: ubuntu-latest
60
+ steps:
61
+ - uses: actions/checkout@v5
62
+ - uses: dtolnay/rust-toolchain@stable
63
+ - uses: actions/setup-node@v5
64
+ with:
65
+ node-version: '24'
66
+ - uses: actions/setup-python@v6
67
+ with:
68
+ python-version: '3.11'
69
+ - name: Test and build
70
+ run: |
71
+ python -m pip install --upgrade pip
72
+ pip install -e .
73
+ pip install build
74
+ python -m unittest discover -s tests -p 'test_*.py'
75
+ working-directory: ./wrapper-py
76
+
77
+ python-wheel:
78
+ needs: template-readiness
79
+ strategy:
80
+ fail-fast: false
81
+ matrix:
82
+ include:
83
+ - runner: ubuntu-latest
84
+ target: linux-x64
85
+ - runner: macos-14
86
+ target: darwin-arm64
87
+ - runner: macos-15-intel
88
+ target: darwin-x64
89
+ - runner: windows-latest
90
+ target: win32-x64
91
+ runs-on: ${{ matrix.runner }}
92
+ steps:
93
+ - uses: actions/checkout@v5
94
+ - uses: dtolnay/rust-toolchain@stable
95
+ - uses: actions/setup-node@v5
96
+ with:
97
+ node-version: '24'
98
+ - uses: actions/setup-python@v6
99
+ with:
100
+ python-version: '3.11'
101
+ - name: Build core binary
102
+ run: cargo build --manifest-path core-rs/Cargo.toml
103
+ - name: Validate wheel artifact
104
+ env:
105
+ OSSPLATE_PY_TARGET: ${{ matrix.target }}
106
+ run: |
107
+ python -m pip install --upgrade pip
108
+ pip install -e .
109
+ pip install build
110
+ python -m unittest discover -s tests -p 'test_*.py'
111
+ working-directory: ./wrapper-py
@@ -0,0 +1,72 @@
1
+ # ossplate:workflow-name:start
2
+ name: Ossplate publish-npm
3
+ # ossplate:workflow-name:end
4
+
5
+ on:
6
+ release:
7
+ types: [published]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ publish-npm:
12
+ if: >
13
+ github.event_name == 'workflow_dispatch' ||
14
+ github.event_name == 'release'
15
+ runs-on: ubuntu-latest
16
+ permissions:
17
+ contents: read
18
+ id-token: write
19
+ steps:
20
+ - uses: actions/checkout@v5
21
+ - uses: actions/setup-node@v5
22
+ with:
23
+ node-version: '24'
24
+ registry-url: 'https://registry.npmjs.org'
25
+ cache: npm
26
+ cache-dependency-path: ./wrapper-js/package-lock.json
27
+ - name: Detect published version
28
+ id: published
29
+ working-directory: ./wrapper-js
30
+ run: |
31
+ NAME=$(node -p "require('./package.json').name")
32
+ VERSION=$(node -p "require('./package.json').version")
33
+ echo "name=$NAME" >> "$GITHUB_OUTPUT"
34
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
35
+ if npm view "$NAME@$VERSION" version >/dev/null 2>&1; then
36
+ echo "already=true" >> "$GITHUB_OUTPUT"
37
+ echo "::notice title=npm::${NAME}@${VERSION} is already published; skipping."
38
+ else
39
+ echo "already=false" >> "$GITHUB_OUTPUT"
40
+ fi
41
+ - if: steps.published.outputs.already != 'true'
42
+ run: npm ci
43
+ working-directory: ./wrapper-js
44
+ - name: Publish to npm
45
+ if: steps.published.outputs.already != 'true'
46
+ working-directory: ./wrapper-js
47
+ env:
48
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
49
+ run: |
50
+ logfile=$(mktemp)
51
+ cleanup() {
52
+ rm -f "$logfile"
53
+ }
54
+ trap cleanup EXIT
55
+ set +e
56
+ npm publish --access public --provenance 2>&1 | tee "$logfile"
57
+ status=${PIPESTATUS[0]}
58
+ set -e
59
+ if [ "$status" -eq 0 ]; then
60
+ echo "::notice title=npm::published with OIDC trusted publishing."
61
+ exit 0
62
+ fi
63
+ if [ -z "${NODE_AUTH_TOKEN:-}" ]; then
64
+ echo "::error title=npm::OIDC publish failed and NPM_TOKEN is not configured."
65
+ exit "$status"
66
+ fi
67
+ if grep -qiE "already exists|cannot publish over|previously published" "$logfile"; then
68
+ echo "::notice title=npm::package version already exists; skipping."
69
+ exit 0
70
+ fi
71
+ echo "::warning title=npm::OIDC publish failed; retrying with NPM_TOKEN fallback."
72
+ npm publish --access public
@@ -0,0 +1,173 @@
1
+ # ossplate:workflow-name:start
2
+ name: Ossplate publishing
3
+ # ossplate:workflow-name:end
4
+
5
+ on:
6
+ release:
7
+ types: [published]
8
+ workflow_dispatch:
9
+
10
+ jobs:
11
+ build-pypi-wheel:
12
+ if: >
13
+ github.event_name == 'workflow_dispatch' ||
14
+ github.event_name == 'release'
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ include:
19
+ - runner: ubuntu-latest
20
+ target: linux-x64
21
+ - runner: macos-14
22
+ target: darwin-arm64
23
+ - runner: macos-15-intel
24
+ target: darwin-x64
25
+ - runner: windows-latest
26
+ target: win32-x64
27
+ runs-on: ${{ matrix.runner }}
28
+ permissions:
29
+ contents: read
30
+ steps:
31
+ - uses: actions/checkout@v5
32
+ - uses: dtolnay/rust-toolchain@stable
33
+ - uses: actions/setup-node@v5
34
+ with:
35
+ node-version: '24'
36
+ - uses: actions/setup-python@v6
37
+ with:
38
+ python-version: '3.11'
39
+ - name: Build core binary
40
+ run: cargo build --manifest-path core-rs/Cargo.toml
41
+ - name: Build wheel
42
+ env:
43
+ OSSPLATE_PY_TARGET: ${{ matrix.target }}
44
+ run: |
45
+ python -m pip install --upgrade pip
46
+ pip install build
47
+ python -m build --wheel --outdir dist/${{ matrix.target }}
48
+ working-directory: ./wrapper-py
49
+ - uses: actions/upload-artifact@v4
50
+ with:
51
+ name: pypi-wheel-${{ matrix.target }}
52
+ path: ./wrapper-py/dist/${{ matrix.target }}/*.whl
53
+
54
+ build-pypi-sdist:
55
+ if: >
56
+ github.event_name == 'workflow_dispatch' ||
57
+ github.event_name == 'release'
58
+ runs-on: ubuntu-latest
59
+ permissions:
60
+ contents: read
61
+ steps:
62
+ - uses: actions/checkout@v5
63
+ - uses: actions/setup-python@v6
64
+ with:
65
+ python-version: '3.11'
66
+ - name: Build sdist
67
+ run: |
68
+ python -m pip install --upgrade pip
69
+ pip install build
70
+ python -m build --sdist --outdir dist/sdist
71
+ working-directory: ./wrapper-py
72
+ - uses: actions/upload-artifact@v4
73
+ with:
74
+ name: pypi-sdist
75
+ path: ./wrapper-py/dist/sdist/*.tar.gz
76
+
77
+ publish-pypi:
78
+ if: >
79
+ github.event_name == 'workflow_dispatch' ||
80
+ github.event_name == 'release'
81
+ needs:
82
+ - build-pypi-wheel
83
+ - build-pypi-sdist
84
+ runs-on: ubuntu-latest
85
+ permissions:
86
+ contents: read
87
+ id-token: write
88
+ steps:
89
+ - uses: actions/download-artifact@v4
90
+ with:
91
+ pattern: pypi-*
92
+ path: ./dist
93
+ merge-multiple: true
94
+ - name: Publish to PyPI
95
+ uses: pypa/gh-action-pypi-publish@release/v1
96
+ with:
97
+ packages-dir: ./dist/
98
+ skip-existing: true
99
+ attestations: false
100
+
101
+ publish-cargo:
102
+ if: >
103
+ github.event_name == 'workflow_dispatch' ||
104
+ github.event_name == 'release'
105
+ runs-on: ubuntu-latest
106
+ permissions:
107
+ contents: read
108
+ id-token: write
109
+ steps:
110
+ - uses: actions/checkout@v5
111
+ - uses: dtolnay/rust-toolchain@stable
112
+ - name: Authenticate with crates.io via OIDC
113
+ id: auth
114
+ continue-on-error: true
115
+ uses: rust-lang/crates-io-auth-action@v1
116
+ - name: Detect published crate version
117
+ id: version
118
+ working-directory: ./core-rs
119
+ run: |
120
+ CRATE_NAME=$(python3 - <<'PY'
121
+ import tomllib
122
+ with open("Cargo.toml", "rb") as f:
123
+ data = tomllib.load(f)
124
+ print(data["package"]["name"])
125
+ PY
126
+ )
127
+ CRATE_VERSION=$(python3 - <<'PY'
128
+ import tomllib
129
+ with open("Cargo.toml", "rb") as f:
130
+ data = tomllib.load(f)
131
+ print(data["package"]["version"])
132
+ PY
133
+ )
134
+ echo "name=$CRATE_NAME" >> "$GITHUB_OUTPUT"
135
+ echo "version=$CRATE_VERSION" >> "$GITHUB_OUTPUT"
136
+ if curl -fsSL "https://crates.io/api/v1/crates/${CRATE_NAME}/${CRATE_VERSION}" >/dev/null 2>&1; then
137
+ echo "already=true" >> "$GITHUB_OUTPUT"
138
+ echo "::notice title=cargo::${CRATE_NAME} ${CRATE_VERSION} is already published; skipping."
139
+ else
140
+ echo "already=false" >> "$GITHUB_OUTPUT"
141
+ fi
142
+ - name: Publish to crates.io
143
+ if: steps.version.outputs.already != 'true'
144
+ working-directory: ./core-rs
145
+ env:
146
+ CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token || secrets.CARGO_TOKEN }}
147
+ run: |
148
+ logfile=$(mktemp)
149
+ cleanup() {
150
+ rm -f "$logfile"
151
+ }
152
+ trap cleanup EXIT
153
+ if [ -n "${{ steps.auth.outputs.token }}" ]; then
154
+ echo "::notice title=cargo::using crates.io OIDC token from trusted publishing."
155
+ else
156
+ echo "::notice title=cargo::OIDC token unavailable; falling back to CARGO_TOKEN secret."
157
+ fi
158
+ set +e
159
+ cargo publish 2>&1 | tee "$logfile"
160
+ status=${PIPESTATUS[0]}
161
+ set -e
162
+ if [ "$status" -eq 0 ]; then
163
+ exit 0
164
+ fi
165
+ if grep -q "status 429 Too Many Requests" "$logfile"; then
166
+ echo "::warning title=cargo::crates.io rate limit hit. Treating this publish attempt as non-blocking."
167
+ exit 0
168
+ fi
169
+ if grep -qi "already exists on crates.io index" "$logfile"; then
170
+ echo "::notice title=cargo::crate version already exists; skipping."
171
+ exit 0
172
+ fi
173
+ exit "$status"