Skip to content

feat(plugin-ts,client): add a comments option - #747

Closed
stijnvanhulle wants to merge 11 commits into
mainfrom
claude/output-size-reduction-nsjj5o
Closed

feat(plugin-ts,client): add a comments option#747
stijnvanhulle wants to merge 11 commits into
mainfrom
claude/output-size-reduction-nsjj5o

Conversation

@stijnvanhulle

@stijnvanhulle stijnvanhulle commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

🎯 Changes

Generated JSDoc is 35.1% of everything Kubb writes on a large spec — 1,015,442 of 2,894,451 bytes on the OpenAI spec, where types.ts alone is 64% comments. Most of it is spec prose repeating what the type signature already says, and there was no way to turn it down: plugin-ts had no option for comments at all.

This adds comments: 'full' | 'brief' | 'none':

  • 'full' (default) emits every description in full. Nothing about the current output changes.
  • 'brief' keeps the opening sentence of each @description. Every other tag stays, so each type remains documented.
  • 'none' emits no JSDoc. The generated-by banner is unaffected.

Not a breaking change

An earlier revision of this PR defaulted to 'brief'. It no longer does. Every generated .ts file under examples/ is byte-identical to main — the only diff left in that tree is an unrelated validateStatus fix that arrived with the upstream merge. Shrinking output is opt-in.

Naming

comments is already this codebase's own word for the construct — buildJSDoc(comments) in internals/utils, buildOperationComments() in internals/shared. It also matches TypeScript's own removeComments (both the tsconfig flag and ts.createPrinter, which we print through) and hey-api's comments: false, so people arriving from either look for the word they already know.

Sentence detection

Two rules, each added because the simpler version mangled real output on the OpenAI spec.

Cutting at the first period produced 43 mangled descriptions, including The role of the message (e.g. — content lost, bracket left open. The truncation skips abbreviations (e.g., i.e., etc., …) and refuses to cut where that would strand an open bracket. Verified against the real spec: 0 abbreviation cuts across 6,878 descriptions.

A flat 120-character cap then hard-cut 206 descriptions, 28 of them mid-link or mid-code-span ([here](https://... reads as broken markup on hover). A whole sentence is now kept up to 150 characters, and only a description that never finishes a sentence is cut — at the last word before 120, backing off further when that would leave a markdown link or code span half written. On the real spec that is 115 hard cuts, 0 broken, for 1,821 more bytes.

Scope

The client plugins share a single options type in internals/client, so the option landed there once and reaches plugin-axios and plugin-fetch together — covering operation JSDoc in clients.ts as well as the types.

Measured

Generated from the OpenAI spec (281 operations) with the locally built packages:

comments Output Saving
'full' (default) 2,894,495 B baseline
'brief' 2,697,091 B −197,404 (6.8%)
'none' 1,867,737 B −1,026,758 (35.5%)

On the +3.98% bundle size

The size bot reports +12.2 kB across the published plugins, ~880 B into each package that reaches buildOperationComments and ~940 B into plugin-ts. That is applyCommentLevel, its abbreviation list, and the markup back-off being bundled in.

The cost is paid once per published package; the saving is paid per generated project that opts in — 197 KB at 'brief', 1.03 MB at 'none' on a large spec.

Four of those packages — plugin-mcp, plugin-react-query, plugin-swr, plugin-vue-query — bundle the code but have no comments option of their own yet, so they always call it at 'full'. Extending the option to them is the obvious follow-up.

🛠 Build fix included in this PR

The first pass at this broke pnpm build: six packages (plugin-axios, plugin-fetch, plugin-mcp, and the three query plugins — exactly those reaching buildOperationComments) never finished their dts build, and CI's runner was reaped rather than timing out.

Cause: a cross-package type import into an internals package that gets bundled. Both @internals/shared and @internals/client did import type { CommentLevel } from '@internals/utils', and all six affected plugins bundle those packages. That edge leaves an import their .d.ts cannot resolve and inflates dts generation enormously.

Fix: each internals package that exposes the level declares it locally, and @internals/shared bundles @internals/* like its two sibling internals packages already do. CommentLevel therefore appears in three packages on purpose — both non-utils copies carry a comment saying why, since collapsing them reintroduces the failure. Both halves were confirmed load-bearing by reverting each independently.

Measured on a 4-core box, full pnpm build --force:

Result
main 152s ✅
this branch, before the fix >480s, never finished ❌
this branch, after 208s

The +56s over main is the cost of threading comments through six more packages.

Unrelated observation while debugging: pnpm build runs at turbo's default concurrency of 10 while the repo's generate script caps at 5. Main builds fine, so it is not what broke this PR, but on a 4-core runner it is worth a look separately.

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm run test.

pnpm test 1259 passed · pnpm typecheck 15/15 · pnpm lint clean · pnpm build 148s. New coverage in codegen.test.ts, utils.test.ts, operation.test.ts, plus generator snapshots for all three levels.

🚀 Release Impact

  • This change affects published code, and I have generated a changeset.
  • This change is for the docs (no release).

Minor on @kubb/plugin-ts, @kubb/plugin-axios, @kubb/plugin-fetch. Docs in kubb-labs/docs#174.

🤖 Generated with Claude Code

https://claude.ai/code/session_019QYdRKpk7zCy1XVbydevXf

Generated JSDoc is a third of what Kubb writes on a large spec, and most
of it is spec prose that repeats what the type signature already says.
There was no way to turn it down: plugin-ts had no option for comments at
all.

Add `comments: 'full' | 'brief' | 'none'`, defaulting to 'brief', which
keeps the opening sentence of each description and caps it at 120
characters. Every other tag stays, so each type remains documented.
`comments: 'full'` restores the previous output exactly.

The truncation does not treat an abbreviation such as `e.g.` as the end
of a sentence, and will not cut where that would strand an open bracket.
Without those guards the OpenAI spec produced 43 descriptions cut to
fragments like "The role of the message (e.g.".

The client plugins share one options type in internals/client, so the
option lands there once and reaches plugin-axios and plugin-fetch
together, covering the operation JSDoc as well as the types.

Measured on the OpenAI spec (281 operations, 2.76 MB of output):
'brief' trims 199,225 bytes (6.9%) and 'none' trims 1,026,758 (35.5%).
At 'full' every generated file is byte-identical to before this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QYdRKpk7zCy1XVbydevXf
@codesandbox

codesandbox Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@changeset-bot

changeset-bot Bot commented Aug 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a9e621b

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
@kubb/plugin-ts Minor
@kubb/plugin-axios Minor
@kubb/plugin-fetch Minor
tests-3.0.x Patch
e2e Patch
performance Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Aug 11, 2026
claude added 4 commits August 11, 2026 11:20
The Build job on ea3c7f7 was killed by a GitHub runner shutdown, not by
anything in the diff — every package compiled before the signal landed.
That cancellation skipped Tests, Typecheck, Compressed size and the rest
of the pipeline, so this empty commit re-runs it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QYdRKpk7zCy1XVbydevXf
Drops JSDoc from the private sentence helpers, whose names already say
what they do, and keeps a comment only where the reason is not obvious:
why the abbreviation list exists at all. Also folds the bracket-balance
loop into a single expression.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QYdRKpk7zCy1XVbydevXf
`@internals/shared` builds with `neverBundle: [/@internals/]`, so a type
imported from `@internals/utils` survives into its `.d.ts` as
`import { CommentLevel } from "@internals/utils"`. Most plugins depend on
`@internals/shared` without depending on `@internals/utils`, so that
import does not resolve for them.

Spell the union out on `BuildOperationCommentsOptions` instead. The
runtime import of `applyCommentLevel` stays, matching how this package
already uses `camelCase` and `toFilePath` from the same place.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QYdRKpk7zCy1XVbydevXf
`@internals/shared` was the only internals package keeping `@internals/*`
external. Every plugin that consumes it bundles `@internals/*`, so those
plugins inherited shared's unresolved `@internals/utils` import, which
they cannot resolve from their own node_modules.

Nothing reachable from those plugin entries pulled a `@internals/utils`
symbol through shared before, so the mismatch stayed hidden. Calling
`applyCommentLevel` inside `buildOperationComments` created the first
such edge, and the six plugins that reach it hung during their dts build
instead of failing with a resolution error.

`@internals/client` and `@internals/utils` already use this setting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QYdRKpk7zCy1XVbydevXf
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

e18e dependency analysis

No dependency warnings found.

claude added 2 commits August 11, 2026 17:14
`@internals/client` bundles `@internals/*`, so importing the level type
from `@internals/utils` puts a cross-package type edge in the graph that
every client and query plugin then pulls in. Declare it here instead and
point the components and the SDK builder at the local type.

This mirrors the same change already made in `@internals/shared`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QYdRKpk7zCy1XVbydevXf
@pkg-pr-new

pkg-pr-new Bot commented Aug 11, 2026

Copy link
Copy Markdown
More templates

@kubb/plugin-axios

npm i https://pkg.pr.new/@kubb/plugin-axios@747

@kubb/plugin-cypress

npm i https://pkg.pr.new/@kubb/plugin-cypress@747

@kubb/plugin-faker

npm i https://pkg.pr.new/@kubb/plugin-faker@747

@kubb/plugin-fetch

npm i https://pkg.pr.new/@kubb/plugin-fetch@747

@kubb/plugin-mcp

npm i https://pkg.pr.new/@kubb/plugin-mcp@747

@kubb/plugin-msw

npm i https://pkg.pr.new/@kubb/plugin-msw@747

@kubb/plugin-react-query

npm i https://pkg.pr.new/@kubb/plugin-react-query@747

@kubb/plugin-redoc

npm i https://pkg.pr.new/@kubb/plugin-redoc@747

@kubb/plugin-swr

npm i https://pkg.pr.new/@kubb/plugin-swr@747

@kubb/plugin-ts

npm i https://pkg.pr.new/@kubb/plugin-ts@747

@kubb/plugin-vue-query

npm i https://pkg.pr.new/@kubb/plugin-vue-query@747

@kubb/plugin-zod

npm i https://pkg.pr.new/@kubb/plugin-zod@747

commit: a9e621b

`CommentLevel` now exists in `@internals/utils`, `@internals/shared` and
`@internals/client`, which reads like duplication worth collapsing. It is
not: both internals packages are bundled into the client and query
plugins, and importing the type across packages leaves an import their
`.d.ts` cannot resolve and sends their dts build time through the roof.

Records that on both copies, and aligns the wording in utils with the
rest.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QYdRKpk7zCy1XVbydevXf
@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

Size Change: +12.2 kB (+3.98%)

Total Size: 318 kB

📦 View Changed
Filename Size Change
packages/plugin-axios/dist/index.cjs 15.6 kB +835 B (+5.66%) 🔍
packages/plugin-axios/dist/index.js 15.1 kB +851 B (+5.98%) 🔍
packages/plugin-cypress/dist/index.cjs 5.7 kB -2 B (-0.04%)
packages/plugin-fetch/dist/index.cjs 15.6 kB +835 B (+5.66%) 🔍
packages/plugin-fetch/dist/index.js 15.1 kB +851 B (+5.97%) 🔍
packages/plugin-mcp/dist/index.cjs 10.9 kB +878 B (+8.75%) 🔍
packages/plugin-mcp/dist/index.js 10.4 kB +878 B (+9.21%) 🔍
packages/plugin-msw/dist/index.cjs 5.81 kB -4 B (-0.07%)
packages/plugin-msw/dist/index.js 5.72 kB -1 B (-0.02%)
packages/plugin-react-query/dist/index.cjs 18.5 kB +901 B (+5.11%) 🔍
packages/plugin-react-query/dist/index.js 18 kB +880 B (+5.15%) 🔍
packages/plugin-swr/dist/index.cjs 12.8 kB +884 B (+7.44%) 🔍
packages/plugin-swr/dist/index.js 12.2 kB +880 B (+7.75%) 🔍
packages/plugin-ts/dist/index.cjs 24.5 kB +935 B (+3.96%)
packages/plugin-ts/dist/index.js 23.9 kB +941 B (+4.11%)
packages/plugin-vue-query/dist/index.cjs 15.6 kB +862 B (+5.84%) 🔍
packages/plugin-vue-query/dist/index.js 15.1 kB +863 B (+6.07%) 🔍
packages/plugin-zod/dist/index.cjs 18.6 kB -41 B (-0.22%)
packages/plugin-zod/dist/index.js 18.5 kB -46 B (-0.25%)
ℹ️ View Unchanged
Filename Size
packages/plugin-axios/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-cypress/dist/index.js 5.62 kB
packages/plugin-cypress/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-faker/dist/index.cjs 14.5 kB
packages/plugin-faker/dist/index.js 14.4 kB
packages/plugin-faker/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-fetch/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-mcp/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-msw/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-react-query/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-redoc/dist/index.cjs 2.22 kB
packages/plugin-redoc/dist/index.js 1.75 kB
packages/plugin-redoc/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-swr/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-ts/dist/rolldown-runtime-CNktS9qV.js 291 B
packages/plugin-vue-query/dist/rolldown-runtime-C0LytTxp.js 168 B
packages/plugin-zod/dist/rolldown-runtime-C0LytTxp.js 168 B

compressed-size-action

claude added 2 commits August 11, 2026 18:02
Swaps the em-dash option bullets for the verb form the neighbouring
`syntaxType` entry already uses, drops a clause semicolon from the axios
and fetch pages, and rewrites the sentence that managed to say
"description" twice about the OpenAPI description of the OpenAI API.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QYdRKpk7zCy1XVbydevXf
The brief level cut every description at 120 characters, which on the OpenAI
spec meant 206 hard cuts and 28 of them landing inside a markdown link or a
code span. `[here](https://...` reads as broken markup on hover.

A sentence is now kept whole up to 150 characters, and a cut that would leave a
link or code span half written backs off to before it opened. That drops the
hard cuts to 115 and the broken ones to zero, for 2 KB more across 6,878
descriptions.

Also regenerates the examples that were still carrying full descriptions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QYdRKpk7zCy1XVbydevXf
@dosubot dosubot Bot added size:M This PR changes 30-99 lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Aug 11, 2026
Making brief the default meant every existing user's generated files changed
on upgrade, for a saving most of them never asked for. The option is worth
having; changing what people get without asking is not.

comments now defaults to 'full', so generated output is byte-identical to main
for every example in the repo. Anyone who wants the bytes back opts in with
'brief' or 'none'.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019QYdRKpk7zCy1XVbydevXf
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. and removed size:M This PR changes 30-99 lines, ignoring generated files. labels Aug 11, 2026
@stijnvanhulle stijnvanhulle changed the title feat(plugin-ts,client): add a comments option, defaulting to brief feat(plugin-ts,client): add a comments option Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants