name: Publish packages # Non-Rust consumers cannot take a package from a subdirectory of a git repo the # way cargo can, which is why they ended up vendoring forks instead. This gives # them a versioned artifact to depend on so CultLib stays the only source. # # Rust needs nothing here: cargo git dependencies pin a CultLib revision # directly, which is already how Ghostlight, Epiphany, CodexConnector and Odin # consume the core stack. # # Tag a release to publish, for example `cultcache-ts-v0.13.6` or # `cultnet-py-v0.1.0`. The tag's version must match the version in the package # manifest; scripts/release-after-suite.mjs refuses the release otherwise rather # than shipping a mislabelled artifact. on: push: tags: - "cultcache-ts-v*" - "cultcache-py-v*" - "cultnet-py-v*" - "cultmesh-py-v*" workflow_dispatch: inputs: package: description: Package to build and test (publishes nothing on manual runs) required: true type: choice options: - cultcache-ts - cultcache-py - cultnet-py - cultmesh-py permissions: contents: read jobs: cultcache-ts: name: "@gamecult/cultcache-ts to npm" if: startsWith(github.ref, 'refs/tags/cultcache-ts-v') || inputs.package == 'cultcache-ts' runs-on: ubuntu-latest timeout-minutes: 20 steps: # fetch-depth: 0 so the semver check in the release step can see every previously # published tag, not just the one that triggered this run. - name: Check out CultLib uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Node uses: actions/setup-node@v4 with: node-version: 24.x registry-url: https://registry.npmjs.org # The release-checks suite, which Publish runs, needs .NET for the API gate. - name: Set up .NET uses: actions/setup-dotnet@v4 with: dotnet-version: 10.0.x - name: Install workspace dependencies run: npm ci # Manual runs publish nothing, so they build and test here. A tag run's # Publish runs these tests itself. - name: Build and test if: ${{ !startsWith(github.ref, 'refs/tags/') }} working-directory: packages/cultcache-ts run: npm test # One process checks that the tag is the manifest version, runs the # release-checks suite, the semver policy check (docs/semver-policy.md) # and npm test, and only then runs npm publish --access public. Only # npm publish receives the token. It starts without NODE_OPTIONS, so no # preloaded module runs inside it. - name: Publish if: startsWith(github.ref, 'refs/tags/cultcache-ts-v') run: env -u NODE_OPTIONS node scripts/release-after-suite.mjs npm-publish packages/cultcache-ts "$GITHUB_REF" env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} # The three Python packages layer cultcache-py < cultnet-py < cultmesh-py, the # same way the Rust crates and npm packages do. One job shape serves all # three; the matrix entry names the package and the tag prefix that releases it. python: name: ${{ matrix.package }} to PyPI if: startsWith(github.ref, 'refs/tags/cultcache-py-v') || startsWith(github.ref, 'refs/tags/cultnet-py-v') || startsWith(github.ref, 'refs/tags/cultmesh-py-v') || endsWith(inputs.package, '-py') strategy: fail-fast: false matrix: package: [cultcache-py, cultnet-py, cultmesh-py] runs-on: ubuntu-latest timeout-minutes: 20 environment: pypi permissions: contents: read # PyPI trusted publishing exchanges this for a short-lived token, so no # long-lived API token is stored in the repository. id-token: write steps: - name: Skip packages this run does not name id: selected run: | if [ "${GITHUB_REF_NAME#${{ matrix.package }}-v}" != "$GITHUB_REF_NAME" ] || [ "${{ inputs.package }}" = "${{ matrix.package }}" ]; then echo "run=true" >> "$GITHUB_OUTPUT" else echo "run=false" >> "$GITHUB_OUTPUT" fi # fetch-depth: 0 so the semver check in the release step can see every previously # published tag, not just the one that triggered this run. - name: Check out CultLib if: steps.selected.outputs.run == 'true' uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python if: steps.selected.outputs.run == 'true' uses: actions/setup-python@v5 with: python-version: "3.12" - name: Set up Node if: steps.selected.outputs.run == 'true' uses: actions/setup-node@v4 with: node-version: 24.x - name: Set up .NET if: steps.selected.outputs.run == 'true' uses: actions/setup-dotnet@v4 with: dotnet-version: 10.0.x # Lower layers install from source so a release of one package is tested # against the sibling revision it was cut with, not whatever PyPI has. - name: Install if: steps.selected.outputs.run == 'true' run: python -m pip install --upgrade build -e packages/cultcache-py -e packages/cultnet-py -e packages/cultmesh-py # One process checks, on a tag, that the tag is the manifest version, runs # the release-checks suite, on a tag the semver policy check # (docs/semver-policy.md), and the package's unit tests, and only then # builds the sdist and wheel. Publish uploads only what this step built, # so it has nothing to upload unless every check passed. It starts without # NODE_OPTIONS, so no preloaded module runs inside it. - name: Build sdist and wheel if: steps.selected.outputs.run == 'true' run: env -u NODE_OPTIONS node scripts/release-after-suite.mjs python-build packages/${{ matrix.package }} "$GITHUB_REF" - name: Publish if: steps.selected.outputs.run == 'true' && startsWith(github.ref, 'refs/tags/') uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: packages/${{ matrix.package }}/dist