---
title: "Codegen Artifact Generation Scripts"
canonical: "https://test.abhinandan.one/codegen-workflow/api-core-generation-scripts"
markdown_url: "https://test.abhinandan.one/codegen-workflow/api-core-generation-scripts.md"
publisher: "Primitive SDKs"
kind: "reference"
content_type: "reference"
category: "API Core / OpenAPI Generation"
parent: "codegen-workflow"
description: "generate-openapi-artifacts.ts builds the codegen JSON spec, the embedded OpenAPI constant, and the operation manifest; fix-generated-api-imports.ts repairs the hey-api output afterward."
keywords: ["generate-openapi-artifacts.ts", "fix-generated-api-imports.ts", "operationManifest", "primitive-api.codegen.json", "openapiDocument", "pnpm generate:openapi"]
last_modified: "2026-08-11T18:55:03.779797+00:00"
published_at: "2026-08-11T18:55:02.623878+00:00"
source_files:
  - "packages/api-core/scripts/generate-openapi-artifacts.ts"
  - "packages/api-core/scripts/fix-generated-api-imports.ts"
sections:
  - {anchor: "generate-openapi-artifactsts", title: "generate-openapi-artifacts.ts"}
  - {anchor: "inputs-and-outputs", title: "Inputs and outputs"}
  - {anchor: "what-normalization-does", title: "What normalization does"}
  - {anchor: "building-the-operation-manifest", title: "Building the operation manifest"}
  - {anchor: "accordion-how-requestschema-and-responseschema-are-resolved", title: "How requestSchema and responseSchema are resolved"}
  - {anchor: "when-to-re-run-it", title: "When to re-run it"}
  - {anchor: "fix-generated-api-importsts", title: "fix-generated-api-imports.ts"}
  - {anchor: "1-add-js-extensions-to-relative-imports", title: "1. Add `.js` extensions to relative imports"}
  - {anchor: "2-guard-optional-body-content-type-headers", title: "2. Guard optional-body `Content-Type` headers"}
  - {anchor: "3-repair-the-memoryjsonvalue-type", title: "3. Repair the `MemoryJsonValue` type"}
  - {anchor: "when-to-re-run-it-1", title: "When to re-run it"}
  - {anchor: "running-both-together", title: "Running both together"}
---

> Documentation index: https://test.abhinandan.one/llms.txt

# Codegen Artifact Generation Scripts

Reference for the two scripts that turn openapi/primitive-api.yaml into api-core's generated TypeScript artifacts: the normalized codegen spec, the embedded OpenAPI constant, the operation manifest, and the post-generation import fixups.

Two scripts in `packages/api-core/scripts/` turn the hand-written OpenAPI source into the generated TypeScript artifacts that `@primitivedotdev/api-core` exports. Both run as part of `pnpm generate` in `packages/api-core`, and both are wired into the root `make node-generate` target described in [Regenerating SDK Code from the OpenAPI Spec](https://test.abhinandan.one/codegen-workflow.md).

For the overall pipeline shape (why `api-core` exists, how `sdk-node` and `cli-node` bundle it), see [Architecture: Shared Codegen Pipeline](https://test.abhinandan.one/codegen-architecture.md). For the OpenAPI 3.1 → 3.0.3 normalization rules applied here, see [OpenAPI Spec Normalization and Codegen Artifacts](https://test.abhinandan.one/openapi-spec-normalization.md).

## generate-openapi-artifacts.ts

Reads `openapi/primitive-api.yaml` (the authored OpenAPI 3.1 source of truth) and writes three files. Run it directly with:

```bash
cd packages/api-core
pnpm generate:openapi
```

Internally this is `tsx scripts/generate-openapi-artifacts.ts`, declared in `packages/api-core/package.json`.

### Inputs and outputs

| Path | Role |
|---|---|
| `openapi/primitive-api.yaml` | Input. Parsed with the `yaml` package. |
| `openapi/primitive-api.codegen.json` | Output. Normalized OpenAPI 3.0.3 JSON for the code generators. |
| `packages/api-core/src/openapi/openapi.generated.ts` | Output. Exports `openapiDocument`, the raw (non-normalized) spec as a TypeScript constant. |
| `packages/api-core/src/openapi/operations.generated.ts` | Output. Exports `operationManifest`, `PrimitiveOperationManifest`, and `PrimitiveParameterManifest`. |

### What normalization does

Before writing the codegen JSON, the script runs `normalizeForCodegen` over a deep clone of the raw spec:

- **Nullable-type unwrapping**: an OpenAPI 3.1 `type: ["string", "null"]` array becomes `type: "string"` plus `nullable: true`, since @hey-api/openapi-ts's 3.0.3-oriented generator does not consume the 3.1 array form.
- **Content-type remapping for binary bodies**: `message/rfc822` and `application/gzip` request/response content are rekeyed to `application/octet-stream` so the generator treats them as binary payloads.
- The result is stamped `openapi: "3.0.3"` and written to `primitive-api.codegen.json`.

The raw (non-normalized) spec is written unchanged into `openapi.generated.ts` as the `openapiDocument` constant. That constant is what CLI tooling and the [operation manifest](https://test.abhinandan.one/operation-manifest.md) infrastructure serve as the literal OpenAPI document, distinct from the codegen-only JSON.

> **Note:** Never hand-edit `primitive-api.codegen.json`. It's a generated build artifact; edit `openapi/primitive-api.yaml` and re-run the script. See [OpenAPI spec authoring version vs. codegen consumption](https://test.abhinandan.one/openapi-spec-normalization.md).

### Building the operation manifest

For every HTTP method (`delete`, `get`, `head`, `options`, `patch`, `post`, `put`) on every path in the raw spec, the script emits one `PrimitiveOperationManifest` entry when the operation declares an `operationId`:

| Field | Description |
|---|---|
| `command` | kebab-case form of `operationId` (e.g. `sendEmail` → `send-email`). |
| `operationId` / `sdkName` | The original OpenAPI `operationId`. |
| `method` | Uppercased HTTP method. |
| `path` | The OpenAPI path template. |
| `tag` / `tagCommand` | First declared tag, and its kebab-case form. |
| `summary` / `description` | Copied from the operation, or `null`. |
| `pathParams` / `queryParams` | Resolved parameters (merging path-item-level and operation-level parameters, `$ref`s included), split by `in: path` / `in: query`. Each includes `name`, `type`, `required`, `description`, `enum`, `default`, `minimum`, `maximum`. |
| `bodyRequired` | `true` when `requestBody.required` is set. |
| `hasJsonBody` | `true` when `requestBody.content["application/json"].schema` exists. |
| `requestSchema` | The request body JSON Schema with every `$ref` inlined, or `null`. |
| `responseSchema` | The JSON Schema for the `data` property of the 200/201 response envelope, `$ref`s inlined, or `null` if no 200/201 JSON response exists. |
| `binaryResponse` | `true` when any response declares `application/octet-stream`, `application/gzip`, `message/rfc822` content, or a `format: binary` schema. |

The manifest is sorted by `tagCommand`, then `command`.

#### How requestSchema and responseSchema are resolved

Both fields use `inlineSchemaRefs`, a recursive walker that replaces every `$ref` with the resolved schema from `components/schemas` or `components/parameters`, breaking cycles by leaving a bare `{ $ref: "..." }` if the same ref is seen twice on one path.

`responseSchema` specifically unwraps the repo's `allOf: [SuccessEnvelope, { properties: { data: <schema> } }]` idiom (or the `ListEnvelope` equivalent): it looks for an `allOf` member with a `data` property and returns that property's schema directly, so callers get the payload shape rather than the `{ success, data, meta }` wrapper. If no member matches that shape, it falls back to the full inlined response schema rather than returning `null`, on the reasoning that an imperfect schema is more useful to an agent than no schema.

This is what powers `primitive list-operations | jq '.[] | select(.command == "send-email") | .requestSchema'` and the CLI's `describe` command; see [Operation Manifest Reference](https://test.abhinandan.one/operation-manifest.md) and [Direct API Access and Generic Commands](https://test.abhinandan.one/cli-overview/cli-generic-api-access.md).

### When to re-run it

Re-run `generate-openapi-artifacts.ts` (directly, or via `pnpm generate` / `make node-generate`) whenever `openapi/primitive-api.yaml` changes: new operations, changed parameters, changed request/response schemas, or changed tags. Downstream consumers (`operationManifest`, `openapiDocument`, and the codegen JSON that `generate:api` reads next) are all stale until this runs.

## fix-generated-api-imports.ts

`fix-generated-api-imports.ts` post-processes the `@hey-api/openapi-ts` output under `packages/api-core/src/api` to repair three known generator gaps: extensionless relative imports, unconditional `Content-Type` headers on optional-body operations, and a widened `MemoryJsonValue` type. It runs as the second half of `pnpm generate:api`:

```bash
cd packages/api-core
pnpm generate:api
```

That script is `openapi-ts -f openapi-ts.config.ts && tsx scripts/fix-generated-api-imports.ts`, so the fixup always runs immediately after generation, never standalone against stale output.

### 1. Add `.js` extensions to relative imports

`@hey-api/openapi-ts` emits extensionless relative imports (`from "./types"`), which fail under Node's ESM resolution. For every `from "..."` and dynamic `import("...")` specifier starting with `./` or `../`:

- Leaves `.js` / `.json` specifiers untouched.
- If the specifier resolves to a directory containing `index.ts`, rewrites it to `<specifier>/index.js`.
- Otherwise rewrites it to `<specifier>.js`.

### 2. Guard optional-body `Content-Type` headers

`@hey-api/openapi-ts` unconditionally emits:

```ts
headers: {
  'Content-Type': 'application/json',
  ...options.headers
}
```

on every operation with a request body in the spec, even when the body is optional. For the optional-body operations (`testFunction`, `cli_logout`, `start_cli_login`, `search_emails`), this sends the header with no payload when the caller omits the body, which is wrong on the wire. The fixup rewrites the pattern to:

```ts
headers: {
  ...(options.body !== undefined && { 'Content-Type': 'application/json' }),
  ...options.headers
}
```

so the header only appears when a body is actually present. Required-body operations are unaffected, since the type system guarantees `body` is always defined there. This is the same class of bug documented for the Go and Python generators; see [Non-TypeScript Codegen: Go and Python Clients](https://test.abhinandan.one/non-typescript-codegen.md) and [Codegen Troubleshooting](https://test.abhinandan.one/codegen-troubleshooting.md).

### 3. Repair the `MemoryJsonValue` type

`@hey-api/openapi-ts` currently widens the OpenAPI 3.1 `type: "null"` branch of the recursive `MemoryJsonValue` JSON-value schema to `unknown`, which widens the entire generated type alias. The fixup detects `export type MemoryJsonValue = ...` in the generated output and replaces it with the exact type:

```ts
export type MemoryJsonValue = string | number | boolean | Array<MemoryJsonValue> | {
    [key: string]: MemoryJsonValue;
} | null;
```

If the generated file's shape ever changes enough that the replacement pattern no longer matches, the script throws loudly (`Unable to repair generated MemoryJsonValue type in ${file}...`) instead of silently leaving the broken type in place. That failure is the signal that `fixMemoryJsonValueType` needs updating before the next publish. See [Memory Value Validation Helper](https://test.abhinandan.one/memory-json-value-helper.md) for the runtime counterpart, `isMemoryJsonValue`.

### When to re-run it

Never run it standalone. It only makes sense immediately after `openapi-ts` regenerates `packages/api-core/src/api`, which is why `generate:api` chains both steps. If `pnpm generate:api` (or `make node-generate`) fails partway or you regenerate with a different `openapi-ts` version and start seeing raw `unknown` types on `MemoryJsonValue` or missing `.js` extensions in generated imports, re-run `pnpm generate:api` from `packages/api-core` rather than invoking the fixup script alone against stale generator output.

## Running both together

```bash
cd packages/api-core
pnpm generate
```

`pnpm generate` runs `generate:openapi` then `generate:api` in sequence, which is exactly what `make node-generate` invokes from the repo root. Always commit the regenerated files alongside the source spec/schema change, per [Regenerating SDK Code from the OpenAPI Spec](https://test.abhinandan.one/codegen-workflow.md).
