Skip to content

Port C# SDK generator from nextlove-sdk-generator#301

Merged
razor-x merged 5 commits into
mainfrom
claude/csharp-codegen-port-k5johp
Jul 22, 2026
Merged

Port C# SDK generator from nextlove-sdk-generator#301
razor-x merged 5 commits into
mainfrom
claude/csharp-codegen-port-k5johp

Conversation

@razor-x

@razor-x razor-x commented Jul 21, 2026

Copy link
Copy Markdown
Member

This PR ports the C# SDK code generation off @seamapi/nextlove-sdk-generator onto the Metalsmith + Handlebars + @seamapi/blueprint architecture used by seamapi/javascript-http and the python SDK (seamapi/python#581), making this repository self-contained. The generated output under output/csharp/ stays byte-identical after csharpier.

Summary

Code generation now lives in codegen/, built on @seamapi/smith (Metalsmith): a plugin builds a data model from the OpenAPI spec, and logic-free Handlebars templates serialize it to C#. TEMPORARY-bannered files are reserved for the raw-OpenAPI parsing helpers only; all serialization is durable templates + a data model.

Key changes

  • Metalsmith pipeline (codegen/smith.ts, codegen/lib/csharp.ts): iterates the OpenAPI routes and schemas, builds the model, and emits each file through a Handlebars layout. Only schema-derived files are generated — src/Seam/Api/*.cs and the resource src/Seam/Model/*.cs.
  • Data model + builder (durable): codegen/lib/class-model.ts (plain interfaces) and codegen/lib/build-model.ts (the traversal that decides what classes, enums, unions, properties, and routes exist — their names, order, types, and nullability). Preserves every output quirk: the x-fern-sdk-group-name reversal for class names, the is_void logic, DataContract naming, member order, inline enums, oneOf/JsonSubtypes discriminated unions (top-level and nested), DeviceProperties force-nullable, the errors/warnings message override, reserved-keyword remapping, and Object vs object.
  • Handlebars templates (codegen/layouts/): model.hbs, api.hbs, and partials model-class, enum-def, data-member, oneof-union, route-methods, tostring. Partials are loaded via getHandlebarsPartials; a few pure helpers (csParams, csNamedArgs, eq) precompute comma-joined lists. csharpier owns indentation, brace style, and line wrapping.
  • Raw-OpenAPI parsing helpers (codegen/lib/openapi/*, schema-modifications.ts, types.ts): verbatim ports kept as frozen output-parity workarounds, each with a // TEMPORARY: banner and a TODO to delete once generation can be driven by @seamapi/blueprint. These are the only TEMPORARY files — grep -rn TEMPORARY codegen/ lists exactly this set.
  • Static package source: the runtime src/Seam/Client/*, the static Model helpers (AsbtractModelSchema.cs, SafeStringEnumConverter.cs), the .sln, README, icon, and test project are normal committed files under output/csharp/, not codegen assets — the generator never touches them (mirroring how the python codegen only writes seam/routes/).
  • Config: tsconfig.json/eslint.config.ts/.prettierignore adopt the @seamapi/smith setup; generate/lint/typecheck/format scripts wired; Node bumped to 24 (CI tests on 22, since @seamapi/smith requires Node ≥20.9). @seamapi/types stays pinned at 1.691.0 and change-case at ^4 — the generated output is tied to both.

Notes

  • @seamapi/blueprint is installed to satisfy the smith peer dependency but is not wired into the pipeline: the port iterates the raw OpenAPI spec for output parity, and blueprint 0.55.0 does not parse the pinned @seamapi/types. TODOs mark the migration path.
  • The generator does not clean its output directory; the previous generator overwrote without deleting, so the committed output still contains stale files for schemas removed from the pinned types. Cleaning would change the output and is deferred (see the TODO in smith.ts).

🤖 Generated with Claude Code

https://claude.ai/code/session_018oCdmrhyAEX8e9sAFRXZAF

claude and others added 4 commits July 21, 2026 21:17
Replace @seamapi/nextlove-sdk-generator with a Metalsmith + Handlebars
pipeline (via @seamapi/smith), mirroring the javascript-http and python
codegen architecture. The nextlove C# generator (its AST codegen
framework, dataclass serializer, and OpenAPI helpers) is ported verbatim
under codegen/lib as TEMPORARY output-parity workarounds, so the
generated output stays byte-identical after csharpier. A thin, durable
Metalsmith plugin (codegen/lib/csharp.ts) drives the port and emits each
generated file through a passthrough layout.

- codegen/smith.ts: Metalsmith entry point
- codegen/lib/csharp.ts: plugin wrapping the ported generator
- codegen/lib/generate-csharp-sdk.ts, codegen.ts, dataclass.ts,
  schema-modifications.ts, endpoint-rules.ts, types.ts, openapi/*:
  verbatim TEMPORARY ports (grep TEMPORARY to skip in review)
- Keep @seamapi/types pinned at 1.691.0 and change-case at ^4; the
  generated output is tied to both
- Adopt @seamapi/smith eslint/tsconfig config; add eslint.config.ts,
  tsconfig.json, .prettierignore; wire lint/typecheck/format scripts
- Bump Node to 24 (.nvmrc, setup action) and CI test matrix to 22
  (@seamapi/smith requires Node >=20.9)

The generator does not clean the output directory: the previous
generator overwrote without deleting, so the committed output still
contains stale files for schemas removed from the pinned types. Cleaning
would change the output and is deferred (see TODO in smith.ts).

@seamapi/blueprint is installed to satisfy the smith peer dependency but
is not wired into the pipeline: the port iterates the raw OpenAPI spec
for parity, and blueprint 0.55.0 does not parse the pinned types.

Verified: generated output is byte-identical after csharpier 0.29.2;
npm run lint, typecheck, and format all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018oCdmrhyAEX8e9sAFRXZAF
The C# test suite pointed SeamClient at https://<random>.fakeseamconnect.seam.vc,
a hosted fake Seam Connect service that was returning 500 on /devices/list and
failing every network-dependent test. Remove those tests along with the
SeamConnectTest fixture that constructed the networked client, and keep the four
pure-deserialization tests, which exercise the generated model, enum, and
discriminated-union code with no network dependency.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018oCdmrhyAEX8e9sAFRXZAF
Replace the verbatim port of the nextlove C# AST serializer with the
Handlebars-template + context architecture used by the python and
javascript-http codegen. Serialization now lives in .hbs templates driven by
a durable data model and model builder; TEMPORARY banners are reserved for the
raw-OpenAPI parsing helpers only, matching the python migration.

- Delete the verbatim AST framework (codegen.ts), the serializer
  (dataclass.ts), and the main loop (generate-csharp-sdk.ts).
- Add durable class-model.ts (data interfaces) and build-model.ts (the
  traversal that produces the data model, preserving every output quirk:
  group-name reversal, is_void logic, DataContract naming, member order,
  inline enums, oneOf/JsonSubtypes unions, DeviceProperties forceNullable,
  errors/warnings message override, reserved keywords, Object vs object).
- Add .hbs templates: model.hbs, api.hbs and partials (model-class, enum-def,
  data-member, oneof-union, route-methods, tostring); load partials via
  getHandlebarsPartials; add csParams/csNamedArgs/eq helpers.
- Stop vendoring static .cs into codegen: the Client/* runtime, the two static
  Model helpers, and the .sln are normal committed package source under
  output/csharp/, not codegen assets (remove templates/fs/** and
  populate-fs.ts). The generator now emits only schema-derived Api/*.cs and
  Model/*.cs, mirroring how the python codegen only writes seam/routes/.
- Remove dead endpoint-rules.ts.

Generated output verified byte-identical after csharpier 0.29.2; npm run lint,
typecheck, and format pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018oCdmrhyAEX8e9sAFRXZAF
@razor-x
razor-x marked this pull request as ready for review July 21, 2026 23:10
Add greppable TODO comments at each spot in the durable codegen files that
consults the raw OpenAPI spec or reproduces a previous-generator quirk for
output parity, so `grep -rn TODO codegen/` surfaces every deferred item:

- build-model.ts: type resolution (int/float, unions, inline enums), the
  reserved-keyword remap, the DeviceProperties force-nullable special case, the
  errors/warnings `message` override heuristic, the discriminated-union quirks
  (abstract message, reversed KnownSubType order, Unrecognized fallback), and
  the object-response never-nullable quirk.
- csharp.ts: the x-fern-sdk-group-name reversal for class names and the
  void-endpoint filter.

Comments only; generated output stays byte-identical.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018oCdmrhyAEX8e9sAFRXZAF
@razor-x
razor-x merged commit 811a859 into main Jul 22, 2026
11 checks passed
@razor-x
razor-x deleted the claude/csharp-codegen-port-k5johp branch July 22, 2026 14:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants