{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/monorepo-and-releases","markdown_url":"https://test.abhinandan.one/monorepo-and-releases.md","article":{"id":"ae24c291-4e79-4ef3-9217-50ffb5b24747","article_slug":"monorepo-and-releases","parent_article_slug":null,"parent_article_title":null,"kind":"concept","published_at":"2026-08-11T18:55:07.207702+00:00","keywords":["sdks monorepo layout","openapi/primitive-api.yaml","json-schema/email-received-event.schema.json","make node-generate python-generate go-generate","make check","sdk-go/VERSION release"],"meta_description":"Automated releases fire when a PR merges a version bump in sdk-node/package.json, cli-node/package.json, sdk-python/pyproject.toml, or sdk-go/VERSION.","og_image_url":null,"source_file_paths":["docs/architecture.md","RELEASE.md"],"recording_id":null,"replayable":false,"task_name":"Monorepo Structure and Release Process","category":"Core Concepts","summary":null,"description":"Explains how the polyglot sdks repository is laid out, how the shared OpenAPI and JSON Schema contracts flow into each language's generated code, and how each package is released independently.","content_kind":"repo_page","content_markdown":"The `sdks` repository is a single polyglot monorepo: one Node.js SDK, one Python SDK, one Go SDK, and one Node-based CLI, all generated from two shared contracts instead of hand-maintained independently in each language.\n\n## Repository layout\n\nThe repo keeps the two shared contracts at the root and one directory per shipped package:\n\n```text\nsdks/\n  .github/workflows/\n  openapi/\n  json-schema/\n  sdk-go/\n  sdk-node/\n  sdk-python/\n  cli-node/\n  packages/api-core/\n  test-fixtures/\n```\n\n- **`openapi/primitive-api.yaml`**: the hand-written OpenAPI 3.1 spec, the single source of truth for the HTTP API surface consumed by every SDK and the CLI.\n- **`json-schema/email-received-event.schema.json`**: the canonical JSON Schema for the inbound webhook payload (the `email.received` / `EmailReceivedEvent` shape).\n- **`sdk-node/`**, **`sdk-python/`**, **`sdk-go/`**: the three language SDKs.\n- **`cli-node/`**: the `primitive` CLI, built on the Node SDK and `packages/api-core`.\n- **`packages/api-core/`**: the workspace-internal, never-published TypeScript package holding the generated OpenAPI client, the operation manifest, and `PrimitiveApiClient`. See [What is API Core?](api-core-overview).\n- **`test-fixtures/`**: shared behavioral-parity fixtures (schema validation outcomes, signature verification, auth classification, sender trust, forward compatibility) that all three SDKs must pass identically.\n\n## The two shared contracts\n\nEvery SDK is responsible for the same behavior, driven by the same two files:\n\n| Contract | File | Drives |\n|---|---|---|\n| Webhook payload shape | `json-schema/email-received-event.schema.json` | Typed `EmailReceivedEvent` models, runtime schema validation, forward-compatibility handling of unknown event types |\n| HTTP API surface | `openapi/primitive-api.yaml` | Generated typed clients, the operation manifest, request/response types |\n\nFor the webhook side, each SDK independently:\n\n1. verifies the `Primitive-Signature` header\n2. parses the raw request body\n3. validates the payload against the canonical schema\n4. exposes a typed `email.received` event model\n5. preserves forward compatibility for event types the SDK doesn't recognize yet\n\nThe full webhook contract (signature format, event catalog, forward-compatibility guarantees) is explained once, in [Webhook Events Overview](webhook-events); the normalized `ReceivedEmail` object built on top of it is covered in [Inbound and Outbound Email Model](email-model).\n\nFor the API side, `openapi/primitive-api.yaml` is authored as OpenAPI 3.1. It is never edited by generators; instead it is normalized into a build artifact, `primitive-api.codegen.json` (OpenAPI 3.0.3), that the three language-specific code generators actually consume. That normalization step, the per-language generator pipelines, and the api-core package's role are explained in [Architecture: Shared Codegen Pipeline](codegen-architecture) and [OpenAPI Spec Normalization and Codegen Artifacts](openapi-spec-normalization). Running the full regeneration pipeline end to end is covered in [Regenerating SDK Code from the OpenAPI Spec](codegen-workflow).\n\n<Tip>\n\nAuthor and edit `openapi/primitive-api.yaml` directly. Never hand-edit `primitive-api.codegen.json`; it's a generated build artifact regenerated by the api-core scripts.\n\n</Tip>\n\n## SDK layers\n\nEach SDK layers the same generated artifacts on top of the same two contracts, but exposes them through idiomatic language surfaces:\n\n| SDK | Package path | Published as | Generated artifacts |\n|---|---|---|---|\n| Node | `sdk-node/` | `@primitivedotdev/sdk` (root, `webhook`, `api`, `openapi`, `contract`, `parser` subpaths) | AJV standalone validator, TypeScript types, generated API client, OpenAPI document export |\n| Python | `sdk-python/` | `primitivedotdev` (import name `primitive`) | Packaged webhook schema copy, generated webhook models, generated API client under `primitive.api` |\n| Go | `sdk-go/` | `github.com/primitivedotdev/sdks/sdk-go` | Embedded webhook schema source, generated API client under `sdk-go/api` |\n| CLI | `cli-node/` | `primitive` (npm) | Bundles the Node SDK and api-core inline |\n\nNode additionally ships `contract` and `parser` as Node-only subpaths (not exposed in Python or Go).\n\n## Change strategy\n\nWhen changing shared webhook behavior:\n\n1. Update `json-schema/email-received-event.schema.json` if the payload contract itself changes.\n2. Regenerate language-specific artifacts for all three SDKs.\n3. Update `test-fixtures/` when the expected cross-language behavior changes.\n4. Run `make check`.\n5. Review each SDK for language-specific helper implications.\n\nWhen changing the HTTP API contract:\n\n1. Update `openapi/primitive-api.yaml`.\n2. Regenerate the Node, Python, and Go API clients.\n3. Run `make node-check python-check go-check`.\n4. Verify the Node smoke test still exposes `api` and `openapi` subpaths, and still confirms the SDK tarball installs no `primitive` bin (the CLI ships that bin from the separate `cli-node` package).\n\nChanging only one SDK's internal implementation, with no intended change to cross-language public behavior, does not require touching the shared fixture contract.\n\n## Tooling model\n\nThe root `Makefile` is the primary task interface for every workflow; each SDK keeps its own native package tooling (`pnpm`, `uv`, `go`) inside its own directory:\n\n```bash\nmake node-generate python-generate go-generate\nmake check\nmake build\nmake shared-check\n```\n\nDrop to a single SDK's native commands only when iterating locally on that one language before the commit that must include all three regenerated outputs:\n\n```bash\ncd sdk-node && pnpm typecheck && pnpm test\ncd sdk-python && uv sync --dev && uv run pytest && uv run ruff check . && uv run basedpyright\ncd sdk-go && go test ./... && go test -run TestSharedCompatibilityFixtures ./...\n```\n\n`.github/workflows/sdk-checks.yml` runs the same root `make` targets contributors use locally, across five categories: Node SDK, Node CLI, Python SDK, Go SDK, and shared fixture compatibility across all three languages. Using the same targets in CI and locally keeps the documented workflow and the enforced workflow aligned.\n\n## Release process\n\nEach package versions and releases independently, triggered by a version bump landing on `main`:\n\n| Package | Version file | Release trigger |\n|---|---|---|\n| Node SDK (`@primitivedotdev/sdk`) | `sdk-node/package.json` | PR merges with a new version |\n| CLI (`primitive`, mirrored as `primcli` / `@primitivedotdev/cli`) | `cli-node/package.json` | PR merges with a new version |\n| Python SDK (`primitivedotdev`) | `sdk-python/pyproject.toml` | PR merges with a new version |\n| Go SDK (`github.com/primitivedotdev/sdks/sdk-go`) | `sdk-go/VERSION` | PR merges with a new value (starts at `unreleased`, so the first automation PR does not tag a release) |\n\n### Before releasing any package\n\n1. Confirm the working tree is clean.\n2. Update the relevant version metadata in a release PR.\n3. If the webhook or API contract changed, regenerate artifacts for every affected SDK.\n4. Ensure the release PR passes the required `SDK Checks` workflow.\n5. Review the SDK README and changelog notes for public API changes.\n6. Merge the release PR into `main`.\n\n<Steps>\n\n<Step title=\"Node SDK release\">\n\nOpen a PR bumping `sdk-node/package.json` to the target version and merge it. The `Node Release` workflow verifies the version bump, publishes to npm via trusted publishing/OIDC, and tags `sdk-node/vX.Y.Z` plus a GitHub release. Verify with `npm view @primitivedotdev/sdk version`, and confirm the packed tarball exposes the root, `webhook`, `api`, `openapi`, `contract`, and `parser` subpaths, and installs **no** `primitive` bin.\n\n</Step>\n\n<Step title=\"CLI release\">\n\nOpen a PR bumping `cli-node/package.json` and merge it. The `CLI Release` workflow verifies the bump, publishes to npm, and tags `cli-node/vX.Y.Z`. The same workflow mirror-publishes two identical builds under `primcli` and the legacy scoped `@primitivedotdev/cli`, locked to the same version; both are no-ops when that version already exists, so a re-run is safe. Verify with `npm view primitive version`, `npm view primcli version`, and `npm view @primitivedotdev/cli version`.\n\n</Step>\n\n<Step title=\"Python SDK release\">\n\nOpen a PR bumping `sdk-python/pyproject.toml` and merge it. The `Python Release` workflow verifies the bump, publishes to PyPI, and tags `sdk-python/vX.Y.Z`. Verify the release on PyPI.\n\n</Step>\n\n<Step title=\"Go SDK release\">\n\nOpen a PR updating `sdk-go/VERSION` to a semantic version (e.g. `0.1.0`) and merge it. The `Go Release` workflow creates the subdirectory-prefixed `sdk-go/vX.Y.Z` tag plus a GitHub release. Verify the tag resolves through the Go module proxy.\n\n</Step>\n\n</Steps>\n\n<Note>\n\nCoordinate Node SDK and CLI releases when both ship in the same cycle: cut the SDK release first so its npm version is published, then bump the CLI's `@primitivedotdev/sdk` dependency range if needed before shipping the CLI.\n\n</Note>\n\n### Shared contract changes in a release\n\nIf a release includes schema or shared-fixture changes:\n\n1. Update `json-schema/email-received-event.schema.json`.\n2. Regenerate SDK artifacts.\n3. Update `test-fixtures/` if the behavioral contract changed.\n4. Ensure the PR passes `SDK Checks` again before merging.\n\nIf a release includes API spec changes:\n\n1. Update `openapi/primitive-api.yaml`.\n2. Regenerate the Node, Python, and Go API clients.\n3. Ensure the PR passes `SDK Checks` again before merging.\n\nBoth npm packages (`@primitivedotdev/sdk` and `primitive`, plus its mirrors) use npm trusted publishing from GitHub Actions; no npm API tokens are configured. A brand-new mirror package name needs a one-time manual `npm publish` to claim it before trusted publishing can take over.\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"Architecture: Shared Codegen Pipeline\" href=\"codegen-architecture\">\n\nSee how the OpenAPI source of truth flows into per-language generators and why api-core is never published.\n\n</Card>\n\n<Card title=\"Regenerating SDK Code from the OpenAPI Spec\" href=\"codegen-workflow\">\n\nRun the end-to-end codegen pipeline across all three SDK languages in one pass.\n\n</Card>\n\n<Card title=\"OpenAPI Spec Normalization and Codegen Artifacts\" href=\"openapi-spec-normalization\">\n\nLearn how the 3.1 YAML spec becomes the codegen-friendly 3.0.3 JSON artifact.\n\n</Card>\n\n<Card title=\"Webhook Events Overview\" href=\"webhook-events\">\n\nUnderstand the shared webhook contract and forward-compatibility guarantees in full.\n\n</Card>\n\n</CardGroup>","canonical_base_url":"https://test.abhinandan.one","seo_indexing_enabled":true,"last_modified":"2026-08-21T18:22:43.359885+00:00","video_url":null,"voiceover_url":null,"tools_used":[],"demonstrated_by":[],"steps":[],"related_links":[],"intro":null,"prerequisites":[],"verification":[],"troubleshooting":[],"suggest_edit_url":"https://github.com/abhi-browzer/primitive-sdks/edit/main/docs/architecture.md","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Monorepo+Structure+and+Release+Process&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fmonorepo-and-releases","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}