From 8cb3b7282b62129d23e6e5bd58bc7060f8ae7a8b Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Fri, 4 Sep 2026 04:41:44 +0200 Subject: [PATCH] Publish the non-Rust packages and make the licensing uniform Rust consumers pin a CultLib revision through cargo git dependencies, which is how Ghostlight, Epiphany, CodexConnector and Odin already consume the core stack. npm and pip cannot take a package from a subdirectory of a git repo at all, which is why the TypeScript and Python consumers vendored forks instead: there was nothing else to depend on. This gives them a versioned artifact. Tagging cultcache-ts-v* or cultcache-py-v* builds, tests and publishes. The tag version must match the manifest version or the workflow refuses rather than shipping a mislabelled artifact. PyPI uses trusted publishing, so no long-lived token is stored; npm needs an NPM_TOKEN secret. cultcache-ts is now @gamecult/cultcache-ts. The scope is not cosmetic: it makes the package unambiguously org-owned on a public registry, and it is what lets CultLib hold the name rather than racing for an unscoped one. CultLib's own cultmesh-ts and cultnet-ts and their imports move with it; all three packages typecheck and cultcache-ts passes 12 of 13 tests, the exception being the cross-runtime store test that shells out to Python, which cannot run on this machine because its Python has no msgpack. Licensing was inconsistent with the repo. cultcache-py declared itself "Private GameCult infrastructure" and cultcache-rs and cultmesh-rs declared nothing, inside a repo whose LICENSE is MIT and whose every other package says MIT. All three now say MIT. That was load-bearing here: a package that calls itself private is not one to push to a public registry. Co-Authored-By: Claude Opus 5 --- .github/workflows/publish-packages.yml | 114 ++++++++++++++++++ package-lock.json | 15 +-- packages/cultcache-py/pyproject.toml | 3 +- packages/cultcache-rs/Cargo.toml | 1 + packages/cultcache-ts/package.json | 2 +- packages/cultmesh-rs/Cargo.toml | 1 + packages/cultmesh-ts/package.json | 2 +- packages/cultmesh-ts/src/index.ts | 2 +- packages/cultmesh-ts/test/cultmesh.test.ts | 2 +- packages/cultnet-ts/package.json | 2 +- .../cultnet-ts/src/json-schema-contract.ts | 2 +- packages/cultnet-ts/src/replication.ts | 2 +- packages/cultnet-ts/test/cultnet.test.ts | 2 +- .../test/interop/cultnet-interop-peer.ts | 2 +- 14 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 .github/workflows/publish-packages.yml diff --git a/.github/workflows/publish-packages.yml b/.github/workflows/publish-packages.yml new file mode 100644 index 00000000..f92868aa --- /dev/null +++ b/.github/workflows/publish-packages.yml @@ -0,0 +1,114 @@ +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`. The tag's +# version must match the version in the package manifest; the workflow refuses +# the publish otherwise rather than shipping a mislabelled artifact. + +on: + push: + tags: + - "cultcache-ts-v*" + - "cultcache-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 + +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: + - name: Check out CultLib + uses: actions/checkout@v4 + + - name: Set up Node + uses: actions/setup-node@v4 + with: + node-version: 24.x + registry-url: https://registry.npmjs.org + + - name: Install workspace dependencies + run: npm ci + + - name: Verify the tag matches the manifest version + if: startsWith(github.ref, 'refs/tags/cultcache-ts-v') + run: | + manifest="$(node -p "require('./packages/cultcache-ts/package.json').version")" + tagged="${GITHUB_REF_NAME#cultcache-ts-v}" + if [ "$manifest" != "$tagged" ]; then + echo "tag $GITHUB_REF_NAME does not match manifest version $manifest" >&2 + exit 1 + fi + + - name: Build and test + working-directory: packages/cultcache-ts + run: npm test + + # Scoped packages default to restricted even on public npm, and this is + # MIT like the rest of the repo, so say public explicitly. + - name: Publish + if: startsWith(github.ref, 'refs/tags/cultcache-ts-v') + working-directory: packages/cultcache-ts + run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + cultcache-py: + name: cultcache-py to PyPI + if: startsWith(github.ref, 'refs/tags/cultcache-py-v') || inputs.package == 'cultcache-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: Check out CultLib + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + + - name: Verify the tag matches the manifest version + if: startsWith(github.ref, 'refs/tags/cultcache-py-v') + run: | + manifest="$(python -c "import tomllib,pathlib;print(tomllib.loads(pathlib.Path('packages/cultcache-py/pyproject.toml').read_text())['project']['version'])")" + tagged="${GITHUB_REF_NAME#cultcache-py-v}" + if [ "$manifest" != "$tagged" ]; then + echo "tag $GITHUB_REF_NAME does not match manifest version $manifest" >&2 + exit 1 + fi + + - name: Build sdist and wheel + run: | + python -m pip install --upgrade build + python -m build packages/cultcache-py + + - name: Publish + if: startsWith(github.ref, 'refs/tags/cultcache-py-v') + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: packages/cultcache-py/dist diff --git a/package-lock.json b/package-lock.json index b105fb5c..391f8dfe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -478,6 +478,10 @@ "node": ">=18" } }, + "node_modules/@gamecult/cultcache-ts": { + "resolved": "packages/cultcache-ts", + "link": true + }, "node_modules/@jsdevtools/ono": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", @@ -541,10 +545,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/cultcache-ts": { - "resolved": "packages/cultcache-ts", - "link": true - }, "node_modules/cultmesh-browser": { "resolved": "packages/cultmesh-browser", "link": true @@ -831,6 +831,7 @@ } }, "packages/cultcache-ts": { + "name": "@gamecult/cultcache-ts", "version": "0.13.5", "license": "MIT", "dependencies": { @@ -864,8 +865,8 @@ "version": "0.1.1", "license": "MIT", "dependencies": { + "@gamecult/cultcache-ts": "^0.13.5", "@msgpack/msgpack": "^3.1.2", - "cultcache-ts": "^0.13.5", "cultnet-ts": "^0.2.0" }, "devDependencies": { @@ -881,9 +882,9 @@ "version": "0.2.0", "license": "MIT", "dependencies": { + "@gamecult/cultcache-ts": "^0.13.5", "@msgpack/msgpack": "^3.1.3", - "ajv": "^8.17.1", - "cultcache-ts": "^0.13.5" + "ajv": "^8.17.1" }, "devDependencies": { "@types/node": "^24.0.0", diff --git a/packages/cultcache-py/pyproject.toml b/packages/cultcache-py/pyproject.toml index 93ebf849..9f515ac2 100644 --- a/packages/cultcache-py/pyproject.toml +++ b/packages/cultcache-py/pyproject.toml @@ -8,9 +8,10 @@ version = "0.1.0" description = "Python CultCache, CultNet, and CultMesh runtime package for GameCult typed state." readme = "README.md" requires-python = ">=3.10" -license = { text = "Private GameCult infrastructure" } +license = "MIT" authors = [{ name = "GameCult" }] classifiers = [ + "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", diff --git a/packages/cultcache-rs/Cargo.toml b/packages/cultcache-rs/Cargo.toml index e6ec5e59..6ca593bc 100644 --- a/packages/cultcache-rs/Cargo.toml +++ b/packages/cultcache-rs/Cargo.toml @@ -2,6 +2,7 @@ name = "cultcache-rs" version = "0.1.0" edition = "2024" +license = "MIT" publish = false [workspace] diff --git a/packages/cultcache-ts/package.json b/packages/cultcache-ts/package.json index b9df4300..93db0237 100644 --- a/packages/cultcache-ts/package.json +++ b/packages/cultcache-ts/package.json @@ -1,5 +1,5 @@ { - "name": "cultcache-ts", + "name": "@gamecult/cultcache-ts", "version": "0.13.5", "description": "CultCache-style typed persistence for TypeScript with schema-backed polymorphic MessagePack storage.", "main": "dist/index.js", diff --git a/packages/cultmesh-rs/Cargo.toml b/packages/cultmesh-rs/Cargo.toml index 9a719d32..820050c3 100644 --- a/packages/cultmesh-rs/Cargo.toml +++ b/packages/cultmesh-rs/Cargo.toml @@ -2,6 +2,7 @@ name = "cultmesh-rs" version = "0.1.0" edition = "2024" +license = "MIT" publish = false [lib] diff --git a/packages/cultmesh-ts/package.json b/packages/cultmesh-ts/package.json index 58a3c159..eff7fec4 100644 --- a/packages/cultmesh-ts/package.json +++ b/packages/cultmesh-ts/package.json @@ -43,7 +43,7 @@ }, "dependencies": { "@msgpack/msgpack": "^3.1.2", - "cultcache-ts": "^0.13.5", + "@gamecult/cultcache-ts": "^0.13.5", "cultnet-ts": "^0.2.0" }, "devDependencies": { diff --git a/packages/cultmesh-ts/src/index.ts b/packages/cultmesh-ts/src/index.ts index 2ed6e4b7..5cf26466 100644 --- a/packages/cultmesh-ts/src/index.ts +++ b/packages/cultmesh-ts/src/index.ts @@ -8,7 +8,7 @@ import { type CacheBackingStore, type CultCacheEnvelope, type CultCacheDocumentValue, -} from "cultcache-ts"; +} from "@gamecult/cultcache-ts"; import { CultNetDocumentRegistry, CultNetRudpSession, diff --git a/packages/cultmesh-ts/test/cultmesh.test.ts b/packages/cultmesh-ts/test/cultmesh.test.ts index 07ac0559..16d51e1e 100644 --- a/packages/cultmesh-ts/test/cultmesh.test.ts +++ b/packages/cultmesh-ts/test/cultmesh.test.ts @@ -5,7 +5,7 @@ import test from "node:test"; import assert from "node:assert/strict"; import { encode } from "@msgpack/msgpack"; import { z } from "zod"; -import { defineDocumentType } from "cultcache-ts"; +import { defineDocumentType } from "@gamecult/cultcache-ts"; import { CultNetDocumentRegistry, CultNetPeer, diff --git a/packages/cultnet-ts/package.json b/packages/cultnet-ts/package.json index df129a42..5f87eccc 100644 --- a/packages/cultnet-ts/package.json +++ b/packages/cultnet-ts/package.json @@ -53,7 +53,7 @@ "dependencies": { "@msgpack/msgpack": "^3.1.3", "ajv": "^8.17.1", - "cultcache-ts": "^0.13.5" + "@gamecult/cultcache-ts": "^0.13.5" }, "devDependencies": { "@types/node": "^24.0.0", diff --git a/packages/cultnet-ts/src/json-schema-contract.ts b/packages/cultnet-ts/src/json-schema-contract.ts index b97a6762..4d8fb85d 100644 --- a/packages/cultnet-ts/src/json-schema-contract.ts +++ b/packages/cultnet-ts/src/json-schema-contract.ts @@ -1,5 +1,5 @@ import Ajv2020, { type ErrorObject, type ValidateFunction } from "ajv/dist/2020"; -import type { CultCacheSchema } from "cultcache-ts"; +import type { CultCacheSchema } from "@gamecult/cultcache-ts"; export interface JsonSchemaContract extends CultCacheSchema { readonly schemaId: string; diff --git a/packages/cultnet-ts/src/replication.ts b/packages/cultnet-ts/src/replication.ts index d5706bea..77dab70f 100644 --- a/packages/cultnet-ts/src/replication.ts +++ b/packages/cultnet-ts/src/replication.ts @@ -6,7 +6,7 @@ import type { CultCacheDocumentFormatter, CultCacheDocumentValue, CultCacheEnvelope, -} from "cultcache-ts"; +} from "@gamecult/cultcache-ts"; import { type CultNetDocumentDeleteMessage, diff --git a/packages/cultnet-ts/test/cultnet.test.ts b/packages/cultnet-ts/test/cultnet.test.ts index 7ffbafb9..f0c3c3c3 100644 --- a/packages/cultnet-ts/test/cultnet.test.ts +++ b/packages/cultnet-ts/test/cultnet.test.ts @@ -12,7 +12,7 @@ import { CultCache, SingleFileMessagePackBackingStore, defineDocumentType, -} from "cultcache-ts"; +} from "@gamecult/cultcache-ts"; import { CultNetClientSecurityOptions, diff --git a/packages/cultnet-ts/test/interop/cultnet-interop-peer.ts b/packages/cultnet-ts/test/interop/cultnet-interop-peer.ts index 0af43301..57b7fa37 100644 --- a/packages/cultnet-ts/test/interop/cultnet-interop-peer.ts +++ b/packages/cultnet-ts/test/interop/cultnet-interop-peer.ts @@ -6,7 +6,7 @@ import process from "node:process"; import { setTimeout as delay } from "node:timers/promises"; import { decode, encode } from "@msgpack/msgpack"; -import { CultCache, SingleFileMessagePackBackingStore, defineDocumentType, type AnyCultCacheDocumentDefinition } from "cultcache-ts"; +import { CultCache, SingleFileMessagePackBackingStore, defineDocumentType, type AnyCultCacheDocumentDefinition } from "@gamecult/cultcache-ts"; import { CultNetDocumentRegistry, CultNetPeer,