{"schema_version":"1.0","publisher":"Primitive SDKs","canonical_url":"https://test.abhinandan.one/codegen-workflow","markdown_url":"https://test.abhinandan.one/codegen-workflow.md","article":{"id":"16c6629f-9641-4f68-a080-78f80e739b0b","article_slug":"codegen-workflow","parent_article_slug":null,"parent_article_title":null,"kind":"quickstart","published_at":"2026-08-11T18:55:03.307499+00:00","keywords":["make node-generate python-generate go-generate","primitive-api.codegen.json","generate-openapi-artifacts.ts","fix-generated-api-imports.ts","generate_api_client.py","openapi-python-client"],"meta_description":"Run make node-generate python-generate go-generate to regenerate all three SDK clients from openapi/primitive-api.yaml in one pass.","og_image_url":null,"source_file_paths":["packages/api-core/package.json","packages/api-core/scripts/generate-openapi-artifacts.ts","packages/api-core/scripts/fix-generated-api-imports.ts","sdk-python/scripts/generate_api_client.py"],"recording_id":null,"replayable":false,"task_name":"Regenerating SDK Code from the OpenAPI Spec","category":"API Core / OpenAPI Generation","summary":null,"description":"Run the root Makefile codegen targets to turn openapi/primitive-api.yaml into typed clients for the Node, Python, and Go SDKs in one pass.","content_kind":"repo_page","content_markdown":"Every typed method in the Node, Python, and Go SDKs is generated from one file: `openapi/primitive-api.yaml`. Change that spec (or `json-schema/email-received-event.schema.json`), regenerate, and all three SDKs pick up the change with matching types.\n\n<Note>\n\nPrerequisites: a clone of the `sdks` monorepo, Node.js with `pnpm` installed (for the TypeScript codegen scripts), and each SDK's own toolchain (`uv` for Python, Go `>=1.25` for Go) available on `PATH`.\n\n</Note>\n\n## What gets regenerated\n\nThe pipeline runs in two stages, both driven from `openapi/primitive-api.yaml`:\n\n1. **Normalize the spec.** `packages/api-core`'s `generate-openapi-artifacts.ts` reads the hand-written OpenAPI 3.1 YAML and writes `openapi/primitive-api.codegen.json`, a codegen-friendly OpenAPI 3.0.3 JSON document. This is the file every language generator actually consumes. Full detail on this step lives on [OpenAPI Spec Normalization and Codegen Artifacts](openapi-spec-normalization).\n2. **Generate per-language clients.** Each SDK points its own generator at `primitive-api.codegen.json` and produces a typed client in its own idiom (a `fetch`-based TypeScript client for Node, an `openapi-python-client` package for Python, an `ogen` client for Go). See [Non-TypeScript Codegen: Go and Python Clients](non-typescript-codegen) for the Go/Python specifics.\n\nYou never hand-edit `primitive-api.codegen.json`. It's a build artifact; edit `openapi/primitive-api.yaml` and regenerate.\n\n<Steps>\n\n<Step title=\"Edit the OpenAPI spec\">\n\nMake your change in `openapi/primitive-api.yaml`. Author it as OpenAPI 3.1, that's the single source of truth for every downstream client.\n\n```yaml\n# openapi/primitive-api.yaml\npaths:\n  /v1/widgets:\n    post:\n      operationId: createWidget\n      summary: Create a widget\n      # ...\n```\n\nIf the change is to the inbound webhook shape instead, edit `json-schema/email-received-event.schema.json`, see [Webhook Schema Codegen](webhook-schema-codegen) for that half of the pipeline.\n\n</Step>\n\n<Step title=\"Regenerate every SDK from the repo root\">\n\nRun the root `Makefile` targets. This is the recommended path for any change that must ship in a single commit:\n\n```bash\ncd sdks\nmake node-generate python-generate go-generate\n```\n\nEach target wraps that language's native generate script, so it's equivalent to (but easier to keep in sync than) running them individually.\n\n<Tip>\n\nIterating on a single language locally before your final commit? Run that language's native script directly instead of all three, for example `pnpm --dir sdk-node generate`. Just make sure the final commit that changes the OpenAPI spec regenerates **all three** languages before you open a PR, reviewers and CI expect the generated output and the spec to move together.\n\n</Tip>\n\n</Step>\n\n<Step title=\"Verify the regenerated output\">\n\nConfirm the codegen artifact updated and each SDK's generated client compiles/typechecks:\n\n```bash\ngit status openapi/primitive-api.codegen.json\nmake node-check python-check go-check\n```\n\nExpect `openapi/primitive-api.codegen.json` to show as modified (or untouched, if your spec edit didn't change the normalized shape), and each `*-check` target to pass. A failing `node-check` after a spec edit usually means a new required field broke an existing hand-written caller, fix the call site, not the generated code.\n\n</Step>\n\n<Step title=\"Commit the spec change and the generated files together\">\n\n```bash\ngit add openapi/primitive-api.yaml openapi/primitive-api.codegen.json \\\n  packages/api-core/src/openapi/ sdk-node/src/api/ \\\n  sdk-python/src/primitive/api/ sdk-go/api/\ngit commit -m \"Add POST /v1/widgets and regenerate SDK clients\"\n```\n\nCommit the spec edit and every regenerated file in the same commit (or the same PR) so the repo never has an SDK whose generated code disagrees with the spec it was built from.\n\n</Step>\n\n</Steps>\n\n## What each generator does under the hood\n\nEach language runs its own generator against `openapi/primitive-api.codegen.json`, then applies language-specific fixups to the output.\n\n<Tabs>\n\n<Tab title=\"Node / TypeScript\">\n\n`packages/api-core` owns the TypeScript generation. Its `generate` script runs both stages:\n\n```json\n// packages/api-core/package.json\n{\n  \"scripts\": {\n    \"generate:openapi\": \"tsx scripts/generate-openapi-artifacts.ts\",\n    \"generate:api\": \"openapi-ts -f openapi-ts.config.ts && tsx scripts/fix-generated-api-imports.ts\",\n    \"generate\": \"pnpm generate:openapi && pnpm generate:api\"\n  }\n}\n```\n\n`generate-openapi-artifacts.ts` reads `openapi/primitive-api.yaml`, writes the normalized `primitive-api.codegen.json`, and also emits two TypeScript files consumed elsewhere in the repo: `src/openapi/openapi.generated.ts` (the raw OpenAPI document as a JS constant, for tooling like `primitive describe`) and `src/openapi/operations.generated.ts` (the [operation manifest](operation-manifest), one entry per operation with its command name, method, path, parameters, and inlined request/response JSON Schemas).\n\n`generate:api` then runs `@hey-api/openapi-ts` against the normalized spec to produce the actual fetch client, followed by `fix-generated-api-imports.ts`, a post-processing pass that:\n\n- adds `.js` extensions to every relative import (required for the package's ESM output)\n- guards the `Content-Type: application/json` header on optional-body operations so it's only sent when a body is actually present\n- repairs the generated `MemoryJsonValue` type, which `@hey-api/openapi-ts` currently widens to `unknown` on the OpenAPI 3.1 `type: \"null\"` branch\n\nFull detail on that fixup pass lives on [Generated TypeScript Client Fixups](typescript-client-fixups). Both `sdk-node` and `cli-node` bundle `@primitivedotdev/api-core`'s output inline; it is never published as its own package. See [What is API Core?](api-core-overview).\n\n</Tab>\n\n<Tab title=\"Python\">\n\n`sdk-python/scripts/generate_api_client.py` drives `openapi-python-client` against the same `primitive-api.codegen.json`:\n\n```python\n# sdk-python/scripts/generate_api_client.py\nsubprocess.run(\n    [\n        \"openapi-python-client\", \"generate\",\n        \"--meta\", \"none\",\n        \"--config\", str(CONFIG_PATH),\n        \"--path\", str(SPEC_PATH),   # openapi/primitive-api.codegen.json\n        \"--output-path\", str(output_path),\n    ],\n    check=True,\n    cwd=SDK_ROOT,\n)\n```\n\nIt generates into a temp directory, then copies `api/`, `client.py`, `errors.py`, `models/`, and `types.py` into `sdk-python/src/primitive/api/`, replacing whatever was there.\n\nThree post-processing passes run afterward:\n\n- `dedupe_imports` removes duplicate module-level `from X import ...` lines that `openapi-python-client` 0.28.3 occasionally emits.\n- `guard_optional_body_content_type` re-indents the `Content-Type` header assignment so it only fires when an optional body is actually sent.\n- `use_bytes_for_file_responses` fixes binary-response handling to read `response.content` instead of `response.text`.\n\nRun it directly with:\n\n```bash\ncd sdk-python\nuv sync --dev\nuv run python scripts/generate_schema_module.py\nuv run python scripts/generate_models.py\nuv run python scripts/generate_api_client.py\n```\n\nOr via the root Makefile target, which wraps all three scripts: `make python-generate`.\n\n</Tab>\n\n<Tab title=\"Go\">\n\nThe Go SDK generates its `sdk-go/api` package with `ogen` from the normalized codegen spec. Run it with:\n\n```bash\nmake go-generate\n```\n\nSee [Non-TypeScript Codegen: Go and Python Clients](non-typescript-codegen) for the Go-specific generator config and fixups.\n\n</Tab>\n\n</Tabs>\n\n## Verify the full pipeline\n\nVerify a regeneration by running each language's check target (typecheck, lint, and build, not just tests) before you commit:\n\n```bash\nmake node-check\nmake python-check\nmake go-check\n```\n\nOr run everything the CI runs in one shot:\n\n```bash\nmake check\n```\n\n<Warning>\n\nNever hand-edit anything under `packages/api-core/src/openapi/`, `sdk-node/src/api/`, `sdk-python/src/primitive/api/`, or `sdk-go/api/`. Every file in those trees is regenerated on the next `make *-generate` run and any manual edit is silently overwritten. If the generated output is wrong, fix the generator script or the OpenAPI spec, not the output.\n\n</Warning>\n\n## Next steps\n\n<CardGroup cols={2}>\n\n<Card title=\"OpenAPI Spec Normalization and Codegen Artifacts\" href=\"openapi-spec-normalization\">\n\nSee exactly how the 3.1 YAML becomes the 3.0.3 codegen JSON and what the operation manifest contains.\n\n</Card>\n\n<Card title=\"Architecture: Shared Codegen Pipeline\" href=\"codegen-architecture\">\n\nUnderstand why api-core is never published and how sdk-node/cli-node bundle it inline.\n\n</Card>\n\n<Card title=\"Codegen Troubleshooting\" href=\"codegen-troubleshooting\">\n\nDiagnose schema drift, stale generated imports, and Content-Type mismatches after a regeneration.\n\n</Card>\n\n<Card title=\"Non-TypeScript Codegen: Go and Python Clients\" href=\"non-typescript-codegen\">\n\nDig into the Go ogen client and the Python openapi-python-client generator and their fixups.\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/packages/api-core/package.json","raise_issue_url":"https://github.com/abhi-browzer/primitive-sdks/issues/new?title=Docs+feedback%3A+Regenerating+SDK+Code+from+the+OpenAPI+Spec&body=Page%3A+https%3A%2F%2Ftest.abhinandan.one%2Fcodegen-workflow","page_feedback_enabled":true,"verified_ref":null,"verified_at":"2026-08-11T18:38:45.205849+00:00"}}