sveld 0.36.10 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,9 +9,9 @@ The goal is to get third-party Svelte libraries working with the Svelte Language
9
9
 
10
10
  [Carbon Components Svelte](https://github.com/carbon-design-system/carbon-components-svelte) uses this library to auto-generate component types and API metadata.
11
11
 
12
- `sveld` uses the Svelte 5 compiler to parse `.svelte` files. That single parse path powers docgen and TypeScript output for Svelte 3, Svelte 4, and Svelte 5 without runes (`export let`, `<slot>`, `$$restProps`, …). It also covers Svelte 5 Runes (`$props()`, `$bindable()`, `{@render ...}`, callback props such as `onclick`, …).
12
+ `sveld` parses `.svelte` files with its own template parser (`src/template-parse/`), kept in parity with `svelte/compiler`'s parser by a differential test suite. That single parse path powers docgen and TypeScript output for Svelte 3, Svelte 4, and Svelte 5 without runes (`export let`, `<slot>`, `$$restProps`, …). It also covers Svelte 5 Runes (`$props()`, `$bindable()`, `{@render ...}`, callback props such as `onclick`, …).
13
13
 
14
- For `lang="ts"` components, `sveld` keeps source-level prop type annotations when it can, instead of forcing JSDoc. That covers legacy `export let` props, typed `$props()` destructuring, typed whole-object `$props()` captures, local `interface`/`type` declarations, and imported type references in emitted `.d.ts` files.
14
+ For `lang="ts"` components, `sveld` keeps source-level prop type annotations when it can, instead of forcing JSDoc. That covers legacy `export let` props, typed `$props()` destructuring (whole-object and per-prop), local `interface`/`type`/`enum` declarations, and TypeScript signatures on accessor exports (`export function`). Any type a prop annotation or accessor signature depends on — whether imported with `import type` or as a plain value import used only in a type position — is re-emitted as an `import type` at the top of the generated `.d.ts`, and local `interface`/`type`/`enum` declarations it depends on are copied alongside it. Everything stays textual: no semantic expansion, and `satisfies`/`as` are treated alike. A `const enum` is widened to a literal union of its member values instead of being re-declared, since `const enum` isn't supported by common bundlers under `isolatedModules`.
15
15
 
16
16
  By default, generated `.d.ts` files extend `SvelteComponentTyped` from `svelte`, so TypeScript and the Svelte Language Server work whether consumers use Svelte 3, Svelte 4, or Svelte 5. Set `typesOptions.format: "component"` to instead emit the Svelte 5 `Component` type; see [`typesOptions.format`](#typesoptionsformat).
17
17
 
@@ -125,6 +125,9 @@ export default class Button extends SvelteComponentTyped<
125
125
  - [Persistent parse cache (`cache`)](#persistent-parse-cache-cache)
126
126
  - [Compile-checked `@example` blocks (`checkExamples`)](#compile-checked-example-blocks-checkexamples)
127
127
  - [Type inference diagnostics](#type-inference-diagnostics)
128
+ - [Diagnostic codes](#diagnostic-codes)
129
+ - [Severity and `--strict=errors`](#severity-and---stricterrors)
130
+ - [Ignoring diagnostics](#ignoring-diagnostics)
128
131
  - [Requirements](#requirements)
129
132
  - [Usage](#usage)
130
133
  - [Installation](#installation)
@@ -132,6 +135,7 @@ export default class Button extends SvelteComponentTyped<
132
135
  - [CLI](#cli)
133
136
  - [Exit codes](#exit-codes)
134
137
  - [CI: API-drift checks (`--check`)](#ci-api-drift-checks---check)
138
+ - [CI: strictness profiles (`--strict=ci`/`--strict=local`)](#ci-strictness-profiles---strictci---strictlocal)
135
139
  - [Node.js](#nodejs)
136
140
  - [Browser](#browser)
137
141
  - [Config File](#config-file)
@@ -141,18 +145,30 @@ export default class Button extends SvelteComponentTyped<
141
145
  - [JSON Output](#json-output)
142
146
  - [Custom Elements Manifest](#custom-elements-manifest)
143
147
  - [Consuming the manifest](#consuming-the-manifest)
148
+ - [llms.txt Output](#llmstxt-output)
149
+ - [Custom Writers](#custom-writers)
150
+ - [The `OutputWriter` contract](#the-outputwriter-contract)
151
+ - [Registering a writer](#registering-a-writer)
152
+ - [Running it via the plugin](#running-it-via-the-plugin)
153
+ - [Worked example: a `components.txt` name-list writer](#worked-example-a-componentstxt-name-list-writer)
144
154
  - [API Reference](#api-reference)
145
155
  - [reactive](#reactive)
146
156
  - [binding](#binding)
147
157
  - [@type](#type)
148
158
  - [@default](#default)
149
159
  - [@typedef](#typedef)
160
+ - [@property](#property)
150
161
  - [@callback](#callback)
151
162
  - [@slot / @snippet](#slot--snippet)
152
163
  - [Extra JSDoc tags before `@slot`](#extra-jsdoc-tags-before-slot)
153
164
  - [Svelte 5 Snippet Compatibility](#svelte-5-snippet-compatibility)
154
165
  - [@event](#event)
166
+ - [@ignore / @internal](#ignore--internal)
155
167
  - [@deprecated](#deprecated)
168
+ - [@since](#since)
169
+ - [@see](#see)
170
+ - [@link](#link)
171
+ - [@example](#example)
156
172
  - [Context API](#context-api)
157
173
  - [@restProps](#restprops)
158
174
  - [@extendProps](#extendprops)
@@ -166,7 +182,7 @@ export default class Button extends SvelteComponentTyped<
166
182
 
167
183
  ## Approach
168
184
 
169
- `sveld` uses the Svelte compiler to statically analyze exported components and emit docs for consumers.
185
+ `sveld` statically analyzes exported components and emits docs for consumers. Template parsing runs through sveld's own parser rather than `svelte/compiler`; `svelte/compiler` is imported only for its types, and `svelte/package.json` is read for the installed Svelte version. A differential test (`tests/svelte-template-parse-shim.test.ts`) parses every fixture `.svelte` file with both parsers and asserts the resulting ASTs match, and a weekly `svelte-canary` workflow re-runs that comparison against `svelte@latest` ahead of the lockfile pin.
170
186
 
171
187
  It extracts:
172
188
 
@@ -282,7 +298,7 @@ Without `resolveTypes`, JSON lists no props. With it, each field shows up with `
282
298
  }
283
299
  ```
284
300
 
285
- **Performance.** Off by default. This is the only path that loads TypeScript. It needs `typescript` and a `tsconfig.json`, runs slower than the AST-only pipeline, and gets slower as your types grow. Use it only when you need expanded JSON. `.d.ts` output is unchanged.
301
+ **Performance.** Off by default. This is one of the two paths that load TypeScript. It needs `typescript` 7+ and a `tsconfig.json` (see [Requirements](#requirements)); if either is missing, `resolveTypes` fails the run instead of silently producing empty props. It also runs slower than the AST-only pipeline and gets slower as your types grow. Use it only when you need expanded JSON. `.d.ts` output is unchanged.
286
302
 
287
303
  ### Persistent parse cache (`cache`)
288
304
 
@@ -298,7 +314,7 @@ If a component [`@extendProps`](#extendprops) / [`@extends`](#extendprops) anoth
298
314
 
299
315
  ### Compile-checked `@example` blocks (`checkExamples`)
300
316
 
301
- `@example` blocks are just text. Rename a prop and the sample code can sit there broken for months. Set `checkExamples: true` to run plain TS/JS `@example` blocks through the TypeScript program. Broken examples show up as `example-compile-error` diagnostics.
317
+ `@example` blocks are just text. Rename a prop and the sample code can sit there broken for months. Set `checkExamples: true` to check them: plain TS/JS bodies run through the TypeScript program, and `svelte`/`html` bodies run through sveld's own template parser. Broken examples show up as `example-compile-error` (TS/JS) or `example-syntax-error` (markup) diagnostics.
302
318
 
303
319
  ```ts
304
320
  await sveld({ json: true, checkExamples: true });
@@ -329,15 +345,23 @@ If `formatValue` is later renamed and the example is never updated, `checkExampl
329
345
  - Line 1: Cannot find name 'formatValue'.
330
346
  ```
331
347
 
332
- Plain TS/JS only. Examples fenced as `svelte` or `html`, or bare markup like `<Button />`, are skipped. Checking those needs `svelte2tsx` or similar, and sveld stays AST-only.
348
+ A `svelte`/`html`-fenced example is syntax-checked, not type-checked: sveld parses the markup and discards the AST, so it catches malformed markup (a mismatched closing tag, an unterminated attribute) but not a prop that doesn't exist or a type error inside an expression. Those still need `svelte-check` in the consumer's own tests. Bare unfenced markup (`<Button />` with no code fence) is skipped either way.
333
349
 
334
- The check is narrow on purpose. It catches renamed or removed symbols and wrong argument counts. It is not full type checking and never pulls in types sveld cannot see.
350
+ ```
351
+ @example blocks that failed to parse (1):
352
+ ./Component.svelte
353
+ - Line 1: sveld: invalid closing tag </span>.
354
+ ```
355
+
356
+ The TS/JS check is narrow on purpose too. It catches renamed or removed symbols and wrong argument counts. Neither path is full type checking, and neither pulls in types sveld cannot see.
335
357
 
336
- Needs `typescript` and a `tsconfig.json`, same as `resolveTypes`. Use `--strict` (or the `strict` option) to fail CI when an example breaks.
358
+ The TS/JS path needs `typescript` 7+ and a `tsconfig.json`, same as `resolveTypes` (see [Requirements](#requirements)); missing either fails the run rather than silently skipping every example. The markup path needs neither: pass `checkExamples: "syntax"` to run only it, so a project with only `svelte`/`html` examples (or no `tsconfig.json`) never loads TypeScript. Use `--strict` (or the `strict` option) to fail CI when an example breaks.
337
359
 
338
360
  ### Type inference diagnostics
339
361
 
340
- `sveld` collects unresolved-type diagnostics on every run: props that fall back to `any`, context values typed as `any`, `@event` tags with no dispatch or callback, `$props()`/`{@render}` syntax sveld can't model, and (when `checkExamples` is enabled) `example-compile-error`. They are always returned from the programmatic `sveld()` API in `SveldResult.diagnostics`. Each diagnostic carries an optional `source` range (the same `{ start: { line, column }, end: { line, column } }` shape as JSON output source ranges) whenever the parser holds a stable position for it.
362
+ `sveld` collects unresolved-type diagnostics on every run: props that fall back to `any`, context values typed as `any`, `@event` tags with no dispatch or callback, `$props()`/`{@render}` syntax sveld can't model, and (when `checkExamples` is enabled) `example-compile-error`/`example-syntax-error`. They are always returned from the programmatic `sveld()` API in `SveldResult.diagnostics`. Each diagnostic carries an optional `source` range (the same `{ start: { line, column }, end: { line, column } }` shape as JSON output source ranges) whenever the parser holds a stable position for it.
363
+
364
+ A prop with no type annotation, no `@type` JSDoc, and no initializer has nothing to infer a type or a default from: its JSON `type` stays absent (`"typeSource": "unknown"`), it triggers a `prop-unknown-type` diagnostic, and the emitted `.d.ts` types it as `any` with no `@default` line (rather than the literal type `undefined`).
341
365
 
342
366
  With `reportDiagnostics` or `strict`, the grouped summary looks like this:
343
367
 
@@ -346,28 +370,32 @@ sveld: 5 unresolved types found.
346
370
 
347
371
  Props without inferred types (1):
348
372
  ./icons/Add.svelte
349
- - Prop "title" type could not be inferred; falling back to "any". (./icons/Add.svelte:4:2)
373
+ - Prop "title" type could not be inferred; falling back to "any". (./icons/Add.svelte:4:2) [sveld/prop-unknown-type]
350
374
 
351
375
  Context values typed as `any` (1):
352
376
  ./ThemeProvider.svelte
353
- - Context "theme" variable "themeStore" has no type annotation; defaulted to "any". (./ThemeProvider.svelte:8:6)
377
+ - Context "theme" variable "themeStore" has no type annotation; defaulted to "any". (./ThemeProvider.svelte:8:6) [sveld/context-any-type]
354
378
 
355
379
  @event tags with no dispatch or callback (2):
356
380
  ./Modal.svelte
357
- - @event "open" has no matching dispatch or callback prop. (./Modal.svelte:3:5)
358
- - @event "close" has no matching dispatch or callback prop. (./Modal.svelte:4:5)
381
+ - @event "open" has no matching dispatch or callback prop. (./Modal.svelte:3:5) [sveld/event-no-source]
382
+ - @event "close" has no matching dispatch or callback prop. (./Modal.svelte:4:5) [sveld/event-no-source]
359
383
 
360
384
  Component syntax sveld skipped (1):
361
385
  ./Tabs.svelte
362
- - {@render tabs(getTabProps())} argument is not a plain object literal; the render call was not mapped to slot metadata. (./Tabs.svelte:6:4)
386
+ - {@render tabs(getTabProps())} argument is not a plain object literal; the render call was not mapped to slot metadata. (./Tabs.svelte:6:4) [sveld/syntax-skipped]
363
387
  ```
364
388
 
365
- When `checkExamples` is also enabled, `@example` compile failures appear as a fifth group:
389
+ When `checkExamples` is also enabled, `@example` failures appear as additional groups: TS/JS failures under `example-compile-error`, `svelte`/`html` failures under `example-syntax-error`.
366
390
 
367
391
  ```
368
392
  @example blocks that failed to compile (1):
369
393
  ./Component.svelte
370
- - Line 1: Cannot find name 'formatValue'.
394
+ - Line 1: Cannot find name 'formatValue'. [sveld/example-compile-error]
395
+
396
+ @example blocks that failed to parse (1):
397
+ ./Component.svelte
398
+ - Line 1: sveld: invalid closing tag </span>. [sveld/example-syntax-error]
371
399
  ```
372
400
 
373
401
  By default, nothing is printed. Opt in when you are working on types or want CI output:
@@ -391,11 +419,94 @@ npx sveld --json --strict
391
419
 
392
420
  `--check` is separate: it diffs `COMPONENT_API.json` for API drift and semver classification, not inference warnings.
393
421
 
422
+ #### Diagnostic codes
423
+
424
+ Every diagnostic carries a stable, namespaced `code` (`"sveld/<kind>"`) alongside the older `kind`, so CI config and `diagnostics.ignore` matchers (below) have something that won't shift if the human-readable `message` text changes:
425
+
426
+ | Code | Severity | Fix |
427
+ | --- | --- | --- |
428
+ | `sveld/prop-unknown-type` | `warning` | Add a native TypeScript annotation, a `@type` JSDoc tag, or an initializer sveld can infer a type from. |
429
+ | `sveld/context-any-type` | `warning` | Annotate the `setContext` value's declaration with `@type` or a native TypeScript type. |
430
+ | `sveld/slot-missing-type` | `warning` | Add the required `{Type}` annotation to the `@slot`/`@snippet` tag (e.g. `@slot {{}} name`); until then it falls back to `Record<string, never>`. |
431
+ | `sveld/event-no-source` | `warning` | Dispatch the event (`createEventDispatcher`/`dispatch`), forward it (`on:name`), or add a matching `on<Name>` callback prop; otherwise remove the stale `@event` tag. |
432
+ | `sveld/example-compile-error` | `error` | Fix the `@example` TS/JS code block so it type-checks, or remove the broken example. |
433
+ | `sveld/example-syntax-error` | `error` | Fix the `@example` `svelte`/`html` markup so it parses, or remove the broken example. |
434
+ | `sveld/syntax-skipped` | `error` | Rewrite the flagged syntax in a form sveld can model (see the diagnostic's `message` for what was skipped). |
435
+ | `sveld/rest-props-unresolved` | `warning` | Spread `$$restProps` onto a plain element (or `svelte:element`) instead of a component, or add an `@restProps` tag to type it manually. |
436
+ | `sveld/context-duplicate-key` | `warning` | Remove the duplicate `setContext` call, or give it a distinct key; only the first call's shape is used. |
437
+ | `sveld/spread-unresolved` | `warning` | Spread a local object literal or a variable with a resolvable type instead; otherwise the spread widens the generated type to `Record<string, any>`. |
438
+ | `sveld/export-unresolved` | `warning` | Export a local declaration directly instead of re-exporting an import or a binding from another file; sveld only resolves exports of a local declaration. |
439
+ | `sveld/extend-props-target-missing` | `error` | Point `@extends`/`@extendProps` at a file that exists, and (for a bundled `.svelte` target) name its generated `<Name>Props` interface exactly. |
440
+ | `sveld/extend-props-duplicate` | `warning` | Remove the extra `@extends`/`@extendProps` tag; only the last one is used. |
441
+ | `sveld/extend-props-override` | `warning` | Rename the own prop, or accept that it intentionally overrides the `@extends` target's prop of the same name. |
442
+ | `sveld/jsdoc-unknown-tag` | `warning` | Fix the tag name if it's a typo (e.g. `@depreacted` → `@deprecated`); otherwise no action needed, the tag still passes through unchanged. Only surfaced under `--strict`/`--report-diagnostics`. |
443
+ | `sveld/typedef-duplicate` | `warning` | Rename one of the `@typedef`/`@callback` declarations; only the later one is kept. |
444
+ | `sveld/property-duplicate` | `warning` | Remove the duplicate `@property`; only the later one is kept. |
445
+ | `sveld/generics-conflict` | `warning` | Rename one of the `@generics`/`@template` declarations to a distinct generic name. |
446
+ | `sveld/jsdoc-tag-dropped` | `warning` | Move the tag next to a `@slot`/`@snippet`/`@event`/`@typedef`/`@callback` tag in the same comment block so it has something to attach to. |
447
+ | `sveld/internal-typedef-referenced` | `error` | Remove `@internal`/`@ignore` from the referenced typedef, or stop referencing it from public type text (inline the shape, or make the referencing item `@internal` too). |
448
+
449
+ #### Severity and `--strict=errors`
450
+
451
+ Each diagnostic's `severity` is `"error"` (`example-compile-error`, `example-syntax-error`, `syntax-skipped`, `extend-props-target-missing`, `internal-typedef-referenced` — sveld emitted broken or unmodeled output) or `"warning"` (`prop-unknown-type`, `context-any-type`, `slot-missing-type`, `event-no-source`, `rest-props-unresolved`, `context-duplicate-key`, `spread-unresolved`, `export-unresolved`, `extend-props-duplicate`, `extend-props-override`, `jsdoc-unknown-tag`, `typedef-duplicate`, `property-duplicate`, `generics-conflict`, `jsdoc-tag-dropped` — a type fell back to `any`). Plain `strict: true` / `--strict` fails on both, unchanged from before. Pass `strict: "errors"` (or `--strict=errors`) to fail CI only on `error`-severity diagnostics, letting `any`-fallback warnings through:
452
+
453
+ ```sh
454
+ npx sveld --json --strict=errors
455
+ ```
456
+
457
+ ```ts
458
+ await sveld({ json: true, strict: "errors" });
459
+ ```
460
+
461
+ #### Ignoring diagnostics
462
+
463
+ Two ways to suppress a diagnostic without disabling `strict` for the whole run. Either way, the diagnostic still appears in `SveldResult.diagnostics` (with `ignored: true`) and is still counted in the text summary (`sveld: 2 unresolved types found (1 ignored).`), but never fails `--strict` / `--strict=errors`.
464
+
465
+ **Config matchers** (`diagnostics.ignore`, an array of `{ code?, component?, name? }`): every field you set on a matcher must match for it to apply; an omitted field matches anything. `component` is a glob (`*` within a path segment, `**` across segments):
466
+
467
+ ```ts
468
+ // sveld.config.js
469
+ export default defineConfig({
470
+ diagnostics: {
471
+ ignore: [
472
+ // Every prop-unknown-type diagnostic under legacy/.
473
+ { code: "sveld/prop-unknown-type", component: "./legacy/**" },
474
+ // One named symbol, anywhere.
475
+ { name: "internalOnly" },
476
+ ],
477
+ },
478
+ });
479
+ ```
480
+
481
+ **Inline `@sveld-ignore <code>`**, on the same JSDoc comment as the prop, `@event` tag, or context variable it applies to:
482
+
483
+ ```svelte
484
+ <script>
485
+ /**
486
+ * @sveld-ignore sveld/prop-unknown-type
487
+ */
488
+ export let value;
489
+ </script>
490
+ ```
491
+
492
+ ```svelte
493
+ <script>
494
+ /**
495
+ * @event {CustomEvent<null>} legacyEvent
496
+ * @sveld-ignore sveld/event-no-source
497
+ */
498
+ export let label;
499
+ </script>
500
+ ```
501
+
502
+ A bare `@sveld-ignore` (no code) suppresses every diagnostic for that symbol.
503
+
394
504
  ## Requirements
395
505
 
396
- - Node 22+. CI tests against Node 22 on Linux, Windows, and macOS; earlier LTS versions are not verified.
506
+ - Node 22+ if your config is `sveld.config.js` or `sveld.config.mjs` — `sveld` declares no `engines.node` floor. A `sveld.config.ts` file loads via a raw `import()`, with no transpile step, so it only works if the runtime strips TypeScript itself: on Node this needs unflagged type stripping (Node 22.18 / 23.6+; on older Node 22 patches it's behind `--experimental-strip-types`, so use a `.js`/`.mjs` config instead), which is why Node 24 — the first current major where type stripping is always on — is the only reason to require it. Bun (see `.bun-version`) loads `.ts` configs without Node at all. CI runs on Bun and does not pin a Node version; the release workflow pins Node 24 for `actions/setup-node` on the npm publish job only, not a requirement for consumers.
397
507
  - `sveld` is ESM-only. `require("sveld")` does not work — use `import` or dynamic `import()`.
398
- - `sveld` bundles its own Svelte 5 compiler to parse `.svelte` files. Parsing does not depend on the Svelte version installed in your project, so Svelte 3 and Svelte 4 codebases parse the same way Svelte 5 codebases do — there is no compiler version to match up.
508
+ - `sveld` bundles its own template parser to parse `.svelte` files, kept in parity with `svelte/compiler` (see [Approach](#approach)). Parsing does not depend on the Svelte version installed in your project, so Svelte 3 and Svelte 4 codebases parse the same way Svelte 5 codebases do — there is no compiler version to match up.
509
+ - [`resolveTypes`](#opt-in-semantic-resolution-resolvetypes) and [`checkExamples`](#compile-checked-example-blocks-checkexamples) are optional and need `typescript` 7 or later (which provides `typescript/unstable/async`) plus a `tsconfig.json`. Everything else, including `.d.ts` generation, is AST-only and never loads TypeScript. If either is enabled and TypeScript can't be started (missing, too old, or no `tsconfig.json`), the run fails loudly: `sveld()` throws and the CLI exits `2` naming the requirement, rather than silently skipping the check.
399
510
 
400
511
  ## Usage
401
512
 
@@ -434,6 +545,15 @@ export default defineConfig({
434
545
 
435
546
  Since Vite uses Rollup for production builds, the same plugin works in Rollup configs.
436
547
 
548
+ Unlike the CLI and the programmatic `sveld()` API, the plugin ignores `sveld.config.*` by default: pass `config: true` to load it and merge it with the options given here (these take precedence over the same key in the file), or a string to point at a specific config file.
549
+
550
+ ```ts
551
+ sveld({
552
+ config: true,
553
+ json: true, // wins over `json` set in sveld.config.*
554
+ });
555
+ ```
556
+
437
557
  By default, `sveld` uses the `"svelte"` field from your `package.json` to determine the entry point. You can override this by specifying an explicit `entry` option:
438
558
 
439
559
  ```js
@@ -472,7 +592,7 @@ npx sveld --json --markdown
472
592
 
473
593
  If no entry point can be resolved (no `package.json#svelte` field and no `--entry`), the CLI exits `1` and prints the reason to `stderr`. If `src/index.js` happens to exist relative to your working directory, sveld falls back to it and prints a one-line note asking you to set `package.json#svelte` (or `--entry`) instead of relying on the fallback.
474
594
 
475
- Flags are kebab-case: `--entry`, `--glob`, `--types`, `--json`, `--markdown`, `--fail-fast`, `--dry-run`, `--cache`, `--resolve-types`, `--check-examples`, `--report-diagnostics`, `--strict`, `--check`, `--types-format`, `--quiet`, `--stdout`, `--format`. The camelCase spellings `--resolveTypes` and `--checkExamples` still work as deprecated aliases for compatibility with existing scripts. `--entry`, `--cache`, `--check`, and `--types-format` take their value either as `--flag=value` or as a separate `--flag value` argument (`sveld --entry src/index.js` and `sveld --entry=src/index.js` are equivalent); if the next argument starts with `--` it's not consumed as a value, so `--cache` and `--check` fall back to their default location and `--entry` and `--types-format` report a usage error naming the flag. Boolean flags (`--json`, `--glob`, `--strict`, and the like) never consume a following argument. An unrecognized flag (e.g. `--markdwon`) prints `Unknown flag: --markdwon` to `stderr`, exits `1`, and skips generation; when a close match exists it appends a suggestion, e.g. `Unknown flag: --markdwon Did you mean --markdown?`. sveld takes no positional arguments, so any non-flag argument errors the same way.
595
+ Flags are kebab-case: `--entry`, `--glob`, `--types`, `--json`, `--markdown`, `--custom-elements`, `--llms`, `--fail-fast`, `--dry-run`, `--cache`, `--resolve-types`, `--check-examples`, `--report-diagnostics`, `--strict`, `--check`, `--check-level`, `--types-format`, `--quiet`, `--stdout`, `--format`. The camelCase spellings `--resolveTypes` and `--checkExamples` still work as deprecated aliases for compatibility with existing scripts. `--entry`, `--cache`, `--check`, and `--types-format` take their value either as `--flag=value` or as a separate `--flag value` argument (`sveld --entry src/index.js` and `sveld --entry=src/index.js` are equivalent); if the next argument starts with `--` it's not consumed as a value, so `--cache` and `--check` fall back to their default location and `--entry` and `--types-format` report a usage error naming the flag. Boolean flags (`--json`, `--glob`, `--strict`, and the like) never consume a following argument. An unrecognized flag (e.g. `--markdwon`) prints `Unknown flag: --markdwon` to `stderr`, exits `1`, and skips generation; when a close match exists it appends a suggestion, e.g. `Unknown flag: --markdwon Did you mean --markdown?`. sveld takes no positional arguments, so any non-flag argument errors the same way.
476
596
 
477
597
  Writer progress lines (`created "..."` / `unchanged "..."`) print to `stderr`, keeping `stdout` reserved for machine-readable data. Pass `--quiet` (or `quiet: true` in `sveld.config.*`) to suppress them; it does not suppress error messages, the diagnostics summary (`--report-diagnostics` / `--strict`), or the `--check` report.
478
598
 
@@ -482,16 +602,24 @@ Pass `--stdout` alongside exactly one of `--json`, `--markdown`, or `--custom-el
482
602
 
483
603
  `--stdout=ndjson` (only valid with `--json`) prints one minified JSON object per exported component per line instead of the single combined document, in the same order as the combined document's `components` array, e.g. `sveld --json --stdout=ndjson | jq -c 'select(.props | length > 0)'`. Lines are only written after the run completes (component records are only final after cross-component resolution), so this is a line-oriented serialization format, not incremental streaming. Bare `--stdout` (or `--stdout=json`) keeps the single-document behavior; any other value is a usage error.
484
604
 
485
- `--format=json` switches the `--check` report and the `--report-diagnostics` / `--strict` diagnostics summary from prose to JSON, so scripts and agents don't have to regex the text output, e.g. `sveld --json --check --format=json | jq '.bump'`. Channels are unchanged: the check report prints to `stdout` and the diagnostics summary to `stderr`, same as the text format. The default remains `--format=text`; an unrecognized value (e.g. `--format=yaml`) is a usage error that prints to `stderr` and exits `1` without generating anything.
605
+ `--format=json` switches the `--check` report and the `--report-diagnostics` / `--strict` diagnostics summary from prose to JSON, so scripts and agents don't have to regex the text output, e.g. `sveld --json --check --format=json | jq '.bump'`. Channels are unchanged: the check report prints to `stdout` and the diagnostics summary to `stderr`, same as the text format. Each envelope (`{ kind: "check-report", schemaVersion: 1, ... }` / `{ kind: "diagnostics", schemaVersion: 1, diagnostics: [...] }`) carries its own `schemaVersion`, bumped independently of `COMPONENT_API.json`'s if either shape ever changes incompatibly. The default remains `--format=text`; an unrecognized value (e.g. `--format=yaml`) is a usage error that prints to `stderr` and exits `1` without generating anything.
606
+
607
+ `--format=github` prints [GitHub Actions workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions) instead: `::warning`/`::error` for each diagnostic (by `severity`) and `::error` for each `--check` change that meets `--check-level` (`major` by default), annotating the offending line directly in the PR's "Files changed" tab. Ignored diagnostics and changes below `--check-level` produce no annotation. When the `GITHUB_STEP_SUMMARY` env var is set (GitHub Actions sets it automatically), sveld also appends a Markdown table mirroring the same rows to that file, so the job summary shows them even for someone not reviewing the diff. A minimal workflow step:
608
+
609
+ ```yaml
610
+ - run: npx sveld --json --check --report-diagnostics --strict=errors --format=github
611
+ ```
486
612
 
487
613
  Run `npx sveld --help` for the full flag list with descriptions, or `npx sveld --version` to print the installed version.
488
614
 
615
+ Pass `--glob` with a directory as `--entry` (no barrel file) to document every `.svelte` file under it directly, with no re-export needed: each component's sanitized filename becomes its module name in JSON, Markdown, and the generated `index.d.ts`, in addition to the per-component `.d.ts` every `--glob` run already produces. This is the same set `mergeGlobbedComponents` discovers when `--glob` is combined with a file entry; a directory entry just has no barrel to layer it onto.
616
+
489
617
  ### Exit codes
490
618
 
491
619
  | Code | Meaning |
492
620
  |------|---------|
493
621
  | `0` | Success |
494
- | `1` | Usage or configuration error (unknown flag, bad flag value, unresolvable entry) |
622
+ | `1` | Usage or configuration error (unknown flag, bad flag value, unresolvable entry, unresolved re-export or path alias) |
495
623
  | `2` | Generation failure (a component failed to parse under `--fail-fast`, or an unrecoverable pipeline error) |
496
624
  | `3` | Breaking API change detected by `--check` |
497
625
  | `4` | Diagnostics present under `--strict` |
@@ -518,16 +646,68 @@ Suggested semver bump: major.
518
646
  [BREAKING] prop "href" removed
519
647
  ```
520
648
 
521
- Removed props, events, or slots, and props that become required, are breaking (`major`). New optional props, new events, and widened union types are additive (`minor`). Changes to generics, `@restProps`, `@extends`, or context shapes are not classified further. If any of those changed, `--check` calls it breaking.
649
+ | Change | Bump |
650
+ | --- | --- |
651
+ | Component added | `minor` |
652
+ | Component removed | `major` |
653
+ | Prop/export added (optional) | `minor` |
654
+ | Prop/export added (required) | `major` |
655
+ | Prop/export removed | `major` |
656
+ | Prop/export became required | `major` |
657
+ | Prop/export became optional | `minor` |
658
+ | Prop/export type widened (union gained a member) | `minor` |
659
+ | Prop/export type narrowed (union lost a member) | `major` |
660
+ | Prop/export type changed (anything else) | `major` |
661
+ | Function-typed prop/export gained a trailing optional param | `minor` |
662
+ | Function-typed prop/export lost a param, or return type changed | `major` |
663
+ | Prop gained a writable binding (`bind:`-able) | `minor` |
664
+ | Prop lost a writable binding | `major` |
665
+ | Prop/export default value changed (type unchanged) | `patch` |
666
+ | `@deprecated` added | `minor` |
667
+ | `@deprecated` removed | `patch` |
668
+ | `constant`/`reactive` flag flipped | `minor` |
669
+ | Event/slot added | `minor` |
670
+ | Event/slot removed | `major` |
671
+ | Event detail / slot props type widened | `minor` |
672
+ | Event detail / slot props type narrowed or otherwise changed | `major` |
673
+ | `generics`, `@restProps`, `@extends`, or context shape changed | `major` (not classified further) |
674
+ | Description-only change | not reported |
522
675
 
523
676
  `--check` does not write the snapshot. Run `sveld --json` (or `sveld --json --check`) and commit the file when you want to update it. If there is no snapshot yet, `--check` prints a notice and exits `0`.
524
677
 
525
678
  Use `--check=<path>` to diff against a snapshot at a custom location (defaults to `jsonOptions.outFile`, or `COMPONENT_API.json`).
526
679
 
680
+ By default `--check` only fails the run (exit `3`) on a `major` bump; `minor` and `patch` changes are still reported but don't fail CI. Pass `--check-level=minor` or `--check-level=patch` to fail the run at a lower threshold, e.g. to gate a package that promises no additive changes without a minor release.
681
+
682
+ If the committed snapshot's `schemaVersion` doesn't match the version `sveld` currently emits, `--check` reports a `kind: "schema"` entry instead of diffing (a version mismatch isn't a semver bump) and exits `1` (usage error): regenerate the snapshot with `sveld --json`.
683
+
527
684
  The CLI exits non-zero on any fatal error (an unreadable entry, a config file that throws, etc.), not just on `--strict`/`--check` findings, so it's safe to use either flag as a CI gate. See [Exit codes](#exit-codes) for how these are differentiated.
528
685
 
529
686
  Pass `--format=json` for a machine-readable report on `stdout` instead of the prose above, e.g. `sveld --check --format=json | jq '.bump'` or `sveld --check --format=json | jq '.changes[] | select(.bump == "major")'`.
530
687
 
688
+ ### CI: strictness profiles (`--strict=ci`/`--strict=local`)
689
+
690
+ CI and local runs usually want a different bundle of `strict`, `reportDiagnostics`, `check`, and `checkExamples` flags, previously assembled by hand. `--strict=ci`/`strict: "ci"` and `--strict=local`/`strict: "local"` are shorthands for two common bundles. Bare `--strict`/`strict: true` (and `--strict=errors`) are unchanged.
691
+
692
+ `--strict=ci` expands to `{ strict: true, reportDiagnostics: true, check: true, checkExamples: true }` — the full gate for a CI job:
693
+
694
+ ```sh
695
+ npx sveld --json --strict=ci
696
+ ```
697
+
698
+ `--strict=local` expands to `{ reportDiagnostics: true }` — diagnostics printed for a developer to see, without failing the command or requiring a committed `COMPONENT_API.json` snapshot:
699
+
700
+ ```sh
701
+ npx sveld --json --strict=local
702
+ ```
703
+
704
+ The profile's keys are applied first, then any other key set alongside `strict` overrides it, so you can opt back out of one piece:
705
+
706
+ ```ts
707
+ // Everything --strict=ci implies, except the TypeScript-checked half of checkExamples.
708
+ await sveld({ json: true, strict: "ci", checkExamples: "syntax" });
709
+ ```
710
+
531
711
  ### Node.js
532
712
 
533
713
  You can also call `sveld` from Node.js. See [Requirements](#requirements) for supported Node versions and the ESM-only constraint.
@@ -560,19 +740,20 @@ const { diagnostics } = await sveld({
560
740
  });
561
741
  ```
562
742
 
563
- `diagnostics` is always populated; printing is opt-in via `reportDiagnostics` or `strict` (see [Type inference diagnostics](#type-inference-diagnostics)).
743
+ `diagnostics` is always populated; printing is opt-in via `reportDiagnostics` or `strict` (see [Type inference diagnostics](#type-inference-diagnostics)). `errors` is always populated too: components that failed to parse, whether or not `failFast` is set.
564
744
 
565
- Pass `check: true` (or `check: "<path>"` for a custom snapshot location) to diff against a committed `COMPONENT_API.json`, the same way `--check` does on the CLI. The result lands on `SveldResult.check`; sveld does not print it or touch `process.exitCode` for you, so inspect and act on it yourself:
745
+ Pass `check: true` (or `check: "<path>"` for a custom snapshot location) to diff against a committed `COMPONENT_API.json`, the same way `--check` does on the CLI. The result lands on `SveldResult.check`. `sveld()` never touches `process.exitCode` itself; it returns a suggested `exitCode` (`0`, `3` for a breaking `check` result, or `4` for `strict` diagnostics — the same mapping the CLI uses, `3` winning over `4`) so you can assign it yourself:
566
746
 
567
747
  ```js
568
748
  import { formatCheckReport } from "sveld";
569
749
 
570
- const { check } = await sveld({ json: true, check: true });
750
+ const { check, exitCode } = await sveld({ json: true, check: true });
571
751
 
572
752
  if (check) {
573
753
  console.log(formatCheckReport(check));
574
- if (check.bump === "major") process.exitCode = 1;
575
754
  }
755
+
756
+ process.exitCode = exitCode;
576
757
  ```
577
758
 
578
759
  See [CI: API-drift checks (`--check`)](#ci-api-drift-checks---check) for how changes are classified.
@@ -594,6 +775,23 @@ sveld({
594
775
  });
595
776
  ```
596
777
 
778
+ #### `jsonOptions.source`
779
+
780
+ Every prop, slot, event, typedef, and context in `COMPONENT_API.json` carries a `source` position range (plus `componentCommentSource` on the component itself) when the parser has a stable AST position for it. For a large component library this is a substantial share of the file — roughly a quarter of `COMPONENT_API.json` for a 150+ component library — and many consumers (docs sites, LLM context) never read it.
781
+
782
+ Set `jsonOptions.source` to `false` to omit every `source`/`componentCommentSource` range and shrink the file:
783
+
784
+ ```js
785
+ sveld({
786
+ json: true,
787
+ jsonOptions: {
788
+ source: false,
789
+ },
790
+ });
791
+ ```
792
+
793
+ `source: true` is the default; every other field is unaffected. `EntryExport.source` (the declaring module's relative path, from `documentExports`) is a different field with a string value and is never stripped.
794
+
597
795
  ### Browser
598
796
 
599
797
  `sveld/browser` is a Node-free subpath export for running `sveld` client-side — e.g. an in-browser Svelte playground or REPL that parses whatever `.svelte` source the user typed and renders docs for it live. It bundles with Vite, esbuild, webpack, or Rollup without a `node:fs`/`node:path` polyfill.
@@ -637,11 +835,11 @@ const cem = buildCustomElementsManifest(components, {
637
835
 
638
836
  `ComponentParser` is stateful but reusable across parses — call `parseSvelteComponent` again on the same instance for the next component instead of constructing a new one each time.
639
837
 
640
- See [`playground/`](playground) in this repo for a working example: it parses Svelte source typed into an editor and renders JSON, Markdown, TypeScript, and Custom Elements Manifest tabs, all client-side.
838
+ See [`playground/`](playground) in this repo for a working example: it parses Svelte source typed into an editor and renders JSON, Markdown, TypeScript, and Custom Elements Manifest tabs, all client-side. Deployed at [sveld.onrender.com](https://sveld.onrender.com).
641
839
 
642
840
  ### Config File
643
841
 
644
- Put a `sveld.config.js`, `sveld.config.mjs`, or `sveld.config.ts` at your project root to set defaults for the CLI and the programmatic `sveld()` API.
842
+ Put a `sveld.config.js`, `sveld.config.mjs`, or `sveld.config.ts` at your project root to set defaults for the CLI and the programmatic `sveld()` API. The Vite/Rollup plugin ignores it unless you opt in with the [`config`](#vite) option.
645
843
 
646
844
  Import `defineConfig` from `sveld` for typed options. Config files must use ESM syntax (`export default`).
647
845
 
@@ -673,6 +871,19 @@ export default {
673
871
  };
674
872
  ```
675
873
 
874
+ Merging is one level deep for object-valued options (`typesOptions`, `jsonOptions`, `markdownOptions`, `customElementsOptions`, `additionalWriters`): setting one nested key at the CLI or in `sveld()` doesn't drop sibling keys set in the config file. Arrays and functions (e.g. `markdownOptions.onAppend`) are replaced outright, never merged.
875
+
876
+ ```js
877
+ // sveld.config.js
878
+ export default {
879
+ typesOptions: { outDir: "dist", preamble: "// license" },
880
+ };
881
+ ```
882
+
883
+ `npx sveld --types-format=component` keeps `outDir` and `preamble` from the file and adds `format: "component"`, rather than replacing `typesOptions` entirely.
884
+
885
+ An unrecognized option key (top-level, or inside a `*Options` object) is not an error: it prints a `console.warn` naming the key, with a "did you mean" suggestion when a known key is close enough.
886
+
676
887
  A bad config (syntax error, throws at load time, or no default-export object) fails with an error that names the file.
677
888
 
678
889
  ### Publishing to NPM
@@ -708,22 +919,41 @@ The `svelte` condition lets bundlers that understand it (Vite, Rollup, webpack v
708
919
  - **`glob`** (boolean, optional): Enable glob mode to analyze all `*.svelte` files.
709
920
  - **`documentExports`** (boolean, optional): Include consts, functions, and types from the entry barrel in JSON (`exports`) and Markdown ("Exports"). Off by default. See [Documenting Entry Exports](#documenting-entry-exports).
710
921
  - **`types`** (boolean, optional, default: `true`): Generate TypeScript definitions.
711
- - **`typesOptions`** (object, optional): Options for TypeScript definition generation, including `outDir`, `preamble`, and `format`.
922
+ - **`typesOptions`** (object, optional): Options for TypeScript definition generation.
923
+ - **`outDir`** (string, optional, default: `"types"`): Output directory for generated `.d.ts` files, relative to the project root.
924
+ - **`preamble`** (string, optional, default: `""`): Raw text prepended to the top of the generated `index.d.ts` barrel file, before the `export * from "./..."` lines. Useful for license headers or lint-disable comments. See [`typesOptions.preamble`](#typesoptionspreamble) below.
712
925
  - **`format`** (`"class"` | `"component"`, optional, default: `"class"`): `.d.ts` output shape. `"class"` extends `SvelteComponentTyped`; `"component"` emits the Svelte 5 `Component` type. Also available as `--types-format`. See [`.d.ts` output format](#dts-output-format-typesoptionsformat).
713
926
  - **`json`** (boolean, optional): Generate component documentation in JSON format.
714
927
  - **`jsonOptions`** (object, optional): Options for JSON output.
928
+ - **`outFile`** (string, optional, default: `"COMPONENT_API.json"`): Path (relative to the project root) for the single combined JSON document. Ignored when `outDir` is set.
929
+ - **`outDir`** (string, optional): Emit one JSON file per component (`<ComponentName>.api.json`) into this directory instead of a single combined file. See [`jsonOptions.outDir`](#jsonoptionsoutdir).
930
+ - **`source`** (boolean, optional, default: `true`): Set to `false` to omit every `source`/`componentCommentSource` position range from the output. See [`jsonOptions.source`](#jsonoptionssource).
715
931
  - **`markdown`** (boolean, optional): Generate component documentation in Markdown format.
716
932
  - **`markdownOptions`** (object, optional): Options for Markdown output.
933
+ - **`outFile`** (string, optional, default: `"COMPONENT_INDEX.md"`): Path (relative to the project root) for the single combined Markdown document. Ignored when `outDir` is set.
934
+ - **`outDir`** (string, optional): Emit one `<ModuleName>.md` file per component into this directory, plus an index `README.md` linking to each, instead of a single combined file. See [`markdownOptions.outDir`](#markdownoptionsoutdir).
935
+ - **`write`** (boolean, optional, default: `true`): Set to `false` to skip writing to disk — the rendered combined document is still returned, or (with `outDir` set) no files are written at all.
936
+ - **`onAppend`** (function, optional): Callback invoked every time a heading, quote, paragraph, divider, or raw block is appended to the document. Lets you inject extra content, e.g. a summary line under the title. See [`markdownOptions.onAppend`](#markdownoptionsonappend) below.
717
937
  - **`customElements`** (boolean, optional): Generate a [Custom Elements Manifest](#custom-elements-manifest) (`custom-elements.json`). Also available as the `--custom-elements` CLI flag.
718
- - **`customElementsOptions`** (object, optional): Options for Custom Elements Manifest output, including `outFile`.
719
- - **`watch`** (boolean, optional, default: `false`): Regenerate output incrementally when `.svelte` source changes during `vite dev` / `vite build --watch`. Only the changed component and the components that depend on it via [`@extendProps`](#extendprops) / `@extends` are re-parsed, rather than rebuilding every component. Without this option, the plugin only runs during `vite build`.
938
+ - **`customElementsOptions`** (object, optional): Options for Custom Elements Manifest output.
939
+ - **`outFile`** (string, optional, default: `"custom-elements.json"`): Path (relative to the project root) for the generated manifest file.
940
+ - **`llms`** (boolean, optional): Generate an [`llms.txt` / `llms-full.txt`](#llmstxt-output) pair. Also available as the `--llms` CLI flag.
941
+ - **`llmsOptions`** (object, optional): Options for `llms.txt` / `llms-full.txt` output.
942
+ - **`outDir`** (string, optional): Directory (relative to the project root) both files are written into. Defaults to the project root.
943
+ - **`linkBase`** (string, optional, default: `""`): Prefixed to each component's link in `llms.txt`.
944
+ - **`title`** (string, optional, default: the `"name"` field from `package.json`): The `# <title>` heading both files start with.
945
+ - **`summary`** (string, optional, default: the `"description"` field from `package.json`): The `> <summary>` blockquote under the title.
946
+ - **`config`** (boolean | string, optional, default: `false`): Load `sveld.config.{js,mjs,ts}` and merge it with these options; these options win when a key is set in both. `true` resolves the config from the Vite project root (or `process.cwd()` outside Vite); a string is an explicit path to the config file. See [Config File](#config-file).
947
+ - **`watch`** (boolean, optional, default: `false`): Regenerate output incrementally when relevant source changes during `vite dev` / `vite build --watch`. A reparse is triggered by: editing a component; editing the entry barrel itself, which adds/removes the corresponding component; or editing a non-`.svelte` file a component depends on via [`@extendProps`](#extendprops) / `@extends` or a typedef `import("./x")` reference. Only the affected components are re-parsed, rather than rebuilding every component. Overlapping regenerations are queued, never run concurrently. Without this option, the plugin only runs during `vite build`.
720
948
  - **`failFast`** (boolean, optional, default: `false`): Abort the entire run when a single component fails to parse. By default, parse failures are collected as diagnostics (and reported to `stderr`) so the remaining components still emit their output. Also available as the `--fail-fast` CLI flag.
721
949
  - **`resolveTypes`** (boolean, optional, default: `false`): Load the TypeScript program to expand opaque imported whole-object `$props()` types into JSON. Also available as `--resolve-types` (`--resolveTypes` remains as a deprecated alias). See [Opt-in semantic resolution](#opt-in-semantic-resolution-resolvetypes).
722
950
  - **`cache`** (boolean | string, optional, default: `true`): Write parsed component output to disk and skip re-parsing unchanged files on later runs. On by default, writing to `node_modules/.cache/sveld/parse-cache.json`; a string sets a custom path; pass `false` to disable. Also available as `--cache` / `--cache=<path>` / `--cache=false`. See [Persistent parse cache](#persistent-parse-cache-cache).
723
- - **`checkExamples`** (boolean, optional, default: `false`): Run plain TS/JS `@example` blocks through the TypeScript program. Broken ones get an `example-compile-error` diagnostic. Also available as `--check-examples` (`--checkExamples` remains as a deprecated alias). See [Compile-checked `@example` blocks](#compile-checked-example-blocks-checkexamples).
951
+ - **`checkExamples`** (`boolean | "syntax"`, optional, default: `false`): `true` runs plain TS/JS `@example` blocks through the TypeScript program (`example-compile-error` diagnostics) and `svelte`/`html` blocks through sveld's own template parser (`example-syntax-error` diagnostics). `"syntax"` runs only the markup path, so `typescript` is never loaded. Also available as `--check-examples` / `--check-examples=syntax` (`--checkExamples` remains as a deprecated alias). See [Compile-checked `@example` blocks](#compile-checked-example-blocks-checkexamples).
724
952
  - **`reportDiagnostics`** (boolean, optional, default: `false`): Print unresolved-type diagnostics to stderr (CLI) or `console.warn` (programmatic API). Also available as `--report-diagnostics`. See [Type inference diagnostics](#type-inference-diagnostics).
725
- - **`strict`** (boolean, optional, default: `false`): Exit with code `4` when diagnostics exist. Implies `reportDiagnostics`. Also available as `--strict`. See [Type inference diagnostics](#type-inference-diagnostics).
953
+ - **`strict`** (`boolean | "errors" | "ci" | "local"`, optional, default: `false`): Exit with code `4` when diagnostics exist. Implies `reportDiagnostics`. `"errors"` fails only on `severity: "error"` diagnostics, letting `warning` ones through. `"ci"` and `"local"` are strictness profiles that expand into other options before this object's own keys are applied. Also available as `--strict` / `--strict=errors` / `--strict=ci` / `--strict=local`. See [Type inference diagnostics](#type-inference-diagnostics) and [CI: strictness profiles](#ci-strictness-profiles---strictci---strictlocal).
954
+ - **`diagnostics.ignore`** (`Array<{ code?: string; component?: string; name?: string }>`, optional): Marks matching diagnostics `ignored` — they're still reported and counted, but never fail `strict`. `component` is a glob; an omitted field on a matcher matches anything. No CLI flag; config-file or `sveld()` only. See [Ignoring diagnostics](#ignoring-diagnostics).
726
955
  - **`check`** (boolean | string, optional, default: `false`): Diff the parsed component API against a committed snapshot and assign a semver bump to each change. `true` uses the `json` writer's `outFile` (or `COMPONENT_API.json`); a string sets a custom snapshot path. Also available as `--check` / `--check=<path>`. On the CLI this exits `3` on a breaking change; from `sveld()` it's returned on `SveldResult.check` for you to act on. See [CI: API-drift checks (`--check`)](#ci-api-drift-checks---check).
956
+ - **`checkLevel`** (`"major" | "minor" | "patch"`, optional, default: `"major"`): Minimum bump `--check` fails the CLI run on. Also available as `--check-level`. See [CI: API-drift checks (`--check`)](#ci-api-drift-checks---check).
727
957
  - **`quiet`** (boolean, optional, default: `false`): Suppress writer progress logs (`created "..."` / `unchanged "..."`), which print to `stderr` by default. Does not suppress error messages, the diagnostics summary, or the `--check` report. Also available as `--quiet`.
728
958
  - **`dryRun`** (boolean, optional, default: `false`): Resolve the entry, load config, and parse components through the real pipeline, then print `would write "<path>"` to `stdout` for each output file instead of writing it, including the parse cache. Diagnostics, `strict`, and `check` behave as in a real run. CLI-only via `--dry-run`; the Vite plugin does not expose this option.
729
959
 
@@ -738,6 +968,74 @@ sveld({
738
968
  })
739
969
  ```
740
970
 
971
+ #### `typesOptions.preamble`
972
+
973
+ Use `typesOptions.preamble` to prepend raw text to the generated `types/index.d.ts` barrel file — for example, a license header that should ship with every published `.d.ts` file.
974
+
975
+ ```js
976
+ sveld({
977
+ types: true,
978
+ typesOptions: {
979
+ preamble: "// Copyright (c) 2026 Acme Inc. All rights reserved.\n\n",
980
+ },
981
+ });
982
+ ```
983
+
984
+ ```ts
985
+ // types/index.d.ts
986
+ // Copyright (c) 2026 Acme Inc. All rights reserved.
987
+
988
+ export { default as Button } from "./Button.svelte";
989
+ ```
990
+
991
+ `preamble` only affects the barrel file (`index.d.ts`); per-component `.d.ts` files are untouched.
992
+
993
+ #### `markdownOptions.onAppend`
994
+
995
+ `markdownOptions.onAppend` fires on every heading, quote, paragraph, divider, and raw block written to the Markdown document, and receives the block's `type`, the in-progress `WriterMarkdown` document, and the full component map. Use it to inject extra content, e.g. a summary line under the `h1` title.
996
+
997
+ ```js
998
+ import pkg from "./package.json" with { type: "json" };
999
+
1000
+ sveld({
1001
+ markdown: true,
1002
+ markdownOptions: {
1003
+ onAppend: (type, document, components) => {
1004
+ if (type === "h1") {
1005
+ document.append(
1006
+ "quote",
1007
+ `${components.size} components exported from ${pkg.name}@${pkg.version}.`,
1008
+ );
1009
+ }
1010
+ },
1011
+ },
1012
+ });
1013
+ ```
1014
+
1015
+ ```md
1016
+ # Component Index
1017
+
1018
+ > 1 components exported from sveld-scratch@1.0.0.
1019
+ ```
1020
+
1021
+ #### `markdownOptions.outDir`
1022
+
1023
+ With `markdown: true`, `sveld` writes a single `COMPONENT_INDEX.md` at the project root, documenting every component.
1024
+
1025
+ Use `markdownOptions.outDir` to split that into one `<ModuleName>.md` file per component, plus an index `README.md` that links to each one (and holds the Exports section when `documentExports` is on — per-component sections live entirely in their own file):
1026
+
1027
+ ```js
1028
+ sveld({
1029
+ markdown: true,
1030
+ markdownOptions: {
1031
+ // e.g. "docs/Button.md", "docs/README.md"
1032
+ outDir: "docs",
1033
+ },
1034
+ });
1035
+ ```
1036
+
1037
+ `onAppend` still fires for both the index and every per-component file. `outFile` is ignored once `outDir` is set.
1038
+
741
1039
  ## Documenting Entry Exports
742
1040
 
743
1041
  Most entry barrels re-export more than `.svelte` components. Set `documentExports: true` to add consts, functions, and types to the JSON and Markdown output.
@@ -762,7 +1060,11 @@ export type { Theme } from "./types";
762
1060
 
763
1061
  From that barrel, `sveld` documents `VERSION`, `clamp`, and `Theme`. `Button` still goes through the component path. Type text is copied from source, not resolved with `tsc`, same as the rest of the tool.
764
1062
 
765
- JSON adds `exports` and `totalExports`. Markdown adds an "Exports" section. Each item has `name`, `kind`, type text, optional JSDoc `description`, and `source`.
1063
+ Nested barrels are followed too: `export { X } from "./dir"`, where `./dir/index.js` itself re-exports `.svelte` files, resolves `X` to the underlying component without needing `--glob`.
1064
+
1065
+ JSON adds `exports` and `totalExports`. Markdown adds an "Exports" section. Each item has `name`, `kind`, type text, optional JSDoc `description`, `source`, and — same as props — optional `@deprecated` and pass-through `tags`. A deprecated export's name is struck through in the Markdown table, same as a deprecated prop. See [`@deprecated`](#deprecated).
1066
+
1067
+ An overloaded function (repeated `export function f(...)` signatures, or an import re-exported through a barrel) always documents the implementation signature, i.e. the last declaration. An `enum` export's `type` is the literal union of its members' values (`"A" | "B"` for a string enum, `0 | 1` for a numeric one), falling back to the bare enum name when a member's value can't be determined (e.g. a computed initializer). When two different modules export the same name (most commonly via `export * from "./a"; export * from "./b"`), `sveld` keeps whichever was declared first and prints a warning; it does not silently pick one or emit both.
766
1068
 
767
1069
  ## JSON Output
768
1070
 
@@ -771,6 +1073,14 @@ component API. For stable output, generated `events` arrays are emitted in deter
771
1073
 
772
1074
  The JSON Schema lives on GitHub ([path to file](https://github.com/carbon-design-system/sveld/blob/main/schema/component-api.schema.json), [raw URL](https://raw.githubusercontent.com/carbon-design-system/sveld/main/schema/component-api.schema.json)). Use it to validate generated `COMPONENT_API.json` files. Optional fields may be missing when the parser has no stable source for that metadata.
773
1075
 
1076
+ `sveld` also ships the schema as a package subpath, so you don't need network access to validate at build time:
1077
+
1078
+ ```ts
1079
+ import schema from "sveld/schema/component-api.schema.json" with { type: "json" };
1080
+ ```
1081
+
1082
+ `require.resolve("sveld/schema/component-api.schema.json")` works too. The `$id` in the schema still points at the `main` branch on GitHub for tooling that dereferences it by URL, but that URL always reflects the latest release; the copy packaged with your installed `sveld` version is the authoritative one for the output it produced.
1083
+
774
1084
  ```ts
775
1085
  interface ComponentApiJson {
776
1086
  schemaVersion: 1;
@@ -792,6 +1102,8 @@ interface EntryExport {
792
1102
  type?: string;
793
1103
  value?: string;
794
1104
  description?: string;
1105
+ deprecated?: string | true;
1106
+ tags?: Array<{ name: string; body: string }>;
795
1107
  source?: string;
796
1108
  isTypeOnly: boolean;
797
1109
  }
@@ -906,15 +1218,62 @@ sveld({
906
1218
 
907
1219
  Each exported component becomes one `javascript-module` with a class declaration:
908
1220
 
909
- - **Members** — every prop becomes a `ClassField` (`name`, `type.text`, `default`, `description`, `deprecated`).
910
- - **Attributes** — derived conservatively from props: only props with a bare primitive type (`string`, `number`, or `boolean`) become attributes, using the same default name Svelte's custom-element runtime uses (`prop.toLowerCase()`, unless the tool's own `attribute` override applies). Props whose lowercased name collides with another prop are dropped from `attributes` on both sides, since which one wins at runtime is ambiguous. Complex types (arrays, objects, unions, custom types) never become attributes.
1221
+ - **Members** — every `export let`/`const` prop becomes a `ClassField` (`name`, `type.text`, `default`, `description`, `deprecated`; `readonly: true` for an `export const`). An accessor prop (`export function`) becomes a `ClassMethod` (`name`, `static: false`, `parameters`, `return.type.text`) instead — see [Accessor methods](#accessor-methods) below.
1222
+ - **Attributes** — every prop becomes an attribute (Svelte's custom-element runtime observes one for every prop by default), excluding `export function` accessors. The attribute name is `prop.toLowerCase()` by default, or the `customElement.props.<name>.attribute` config when set — see [Object-form `customElement` config](#object-form-customelement-config) below. Props that collide on the same attribute name keep the first (in declaration order); the rest are skipped with a console warning, since which one wins at runtime is ambiguous.
911
1223
  - **Events** — dispatched events (`createEventDispatcher()`, and `$host().dispatchEvent(...)` from inside a custom element) become `{ name, type: { text: "CustomEvent<...>" } }`. Forwarded (`on:click`) events are left out, since they aren't dispatched by the component's own class.
912
1224
  - **Slots** — named and default slots, with descriptions. The default slot's `name` is `""`, matching the CEM convention.
1225
+ - **`cssParts`/`cssProperties`** — from `@csspart`/`@cssprop` JSDoc tags — see [CSS parts and custom properties](#css-parts-and-custom-properties) below.
913
1226
 
914
1227
  When a component sets `<svelte:options customElement="x-foo" />` (or the object form, `<svelte:options customElement={{ tag: "x-foo" }} />`), its declaration gets `tagName: "x-foo"` and `customElement: true`, and the module's `exports` include a `custom-element-definition` export alongside the plain `js` export.
915
1228
 
916
1229
  Components without `customElement` still emit a plain class declaration (no `tagName`/`customElement`) — useful for documenting the class shape even before it's compiled as a custom element, but the manifest is most useful for `customElement`-compiled builds, where downstream tooling can resolve `tagName`, `attributes`, and `events` for actual custom-element usage.
917
1230
 
1231
+ ### Object-form `customElement` config
1232
+
1233
+ The object form of `<svelte:options customElement={{ ... }} />` is read in full, not just `tag`:
1234
+
1235
+ ```svelte
1236
+ <svelte:options
1237
+ customElement={{
1238
+ tag: "x-widget",
1239
+ shadow: "none",
1240
+ props: {
1241
+ variant: { attribute: "data-variant" },
1242
+ active: { reflect: true },
1243
+ tags: { type: "Array" }
1244
+ },
1245
+ extend: (customElementConstructor) => customElementConstructor
1246
+ }}
1247
+ />
1248
+ ```
1249
+
1250
+ | Config | Effect on the manifest |
1251
+ | --- | --- |
1252
+ | `props.<name>.attribute` | Overrides that prop's attribute name (default: `name.toLowerCase()`). `attribute: false` omits the prop's attribute entirely. |
1253
+ | `props.<name>.reflect` | Adds `reflects: true` to that prop's attribute. |
1254
+ | `props.<name>.type` | `"Array"`/`"Object"` appends a note to the attribute's description that the value is JSON-serialized (matching Svelte's runtime `JSON.stringify`/`JSON.parse` for those types); the attribute's `type.text` is still the prop's own TS type, not this config value. |
1255
+ | `shadow`, `extend` | Parsed and available on the raw `ParsedComponent.customElement` (Node API), but don't affect the manifest. |
1256
+
1257
+ This full config (`tag`, `shadow`, `props`, `extend`) is also on `ParsedComponent.customElement` in the JSON output (`COMPONENT_API.json`), alongside the existing `customElementTag` shorthand.
1258
+
1259
+ ### Accessor methods
1260
+
1261
+ An `export function` prop (a Svelte accessor, e.g. `export function focus() { ... }`) becomes a `ClassMethod`, not a `ClassField`, and is excluded from `attributes` (accessors aren't part of Svelte's props definition). `parameters`/`return` come from `@param`/`@returns` JSDoc when present, otherwise from splitting the function's TypeScript signature text (e.g. `(id: string) => boolean`).
1262
+
1263
+ ### CSS parts and custom properties
1264
+
1265
+ Document shadow-DOM styling hooks with `@csspart`/`@cssprop` (alias `@cssproperty`) tags in the component's own JSDoc comment (the same comment block `@slot` tags go in):
1266
+
1267
+ ```js
1268
+ /**
1269
+ * @csspart header - Styles the header region.
1270
+ * @cssprop {Color} [--card-background=white] - Background color of the card.
1271
+ * @cssprop --card-border-color - Border color of the card.
1272
+ */
1273
+ ```
1274
+
1275
+ `@cssprop`'s `{type}` and `[--name=default]` are both optional, following the [Custom Elements Manifest analyzer](https://custom-elements-manifest.open-wc.org/analyzer/getting-started/#css-custom-properties) grammar. These populate `cssParts`/`cssProperties` on the class declaration, and `ParsedComponent.cssParts`/`cssProperties` in the JSON output; the Markdown writer renders them as two extra tables when present.
1276
+
918
1277
  ### Consuming the manifest
919
1278
 
920
1279
  Most tools discover `custom-elements.json` through a `customElements` field in `package.json`, pointing at the generated file:
@@ -931,6 +1290,196 @@ With that in place:
931
1290
  - [Storybook](https://storybook.js.org/docs/api/doc-blocks/doc-block-argtypes#extracting-argtypes) for web components reads the manifest to auto-generate `argTypes` (controls, docs tables) for `customElement`-compiled components, once you point it at the file (e.g. `setCustomElementsManifest` from `@storybook/web-components`, or `customElements: "custom-elements.json"` in `.storybook/main.js`).
932
1291
  - Any other tool built against the [Custom Elements Manifest spec](https://github.com/webcomponents/custom-elements-manifest) (API viewers, doc generators, linters) can read the file directly without sveld-specific integration.
933
1292
 
1293
+ ## llms.txt Output
1294
+
1295
+ Set `llms: true` to emit an [`llms.txt`](https://llmstxt.org) / `llms-full.txt` pair: `llms.txt` is an index of every exported component (one link plus a one-line summary each), and `llms-full.txt` is the flattened full reference (every component's Props, Bindings, Events, Slots/Snippets, Typedefs, and Module exports, as terse Markdown tables). Props and Events Description columns include `@since`/`@example` tags, same as `COMPONENT_INDEX.md`.
1296
+
1297
+ ```diff
1298
+ sveld({
1299
+ + llms: true,
1300
+ })
1301
+ ```
1302
+
1303
+ - **`llms`** (boolean, optional): Generate `llms.txt` and `llms-full.txt`. Also available as the `--llms` CLI flag.
1304
+ - **`llmsOptions`** (object, optional):
1305
+ - **`outDir`** (string, optional): Directory (relative to the project root) both files are written into. Defaults to the project root.
1306
+ - **`linkBase`** (string, optional, default: `""`): Prefixed to each component's link in `llms.txt`, e.g. `[Button](<linkBase>/Button)`. Set this to your published docs site's base path.
1307
+ - **`title`** (string, optional, default: the `"name"` field from `package.json`): The `# <title>` heading both files start with.
1308
+ - **`summary`** (string, optional, default: the `"description"` field from `package.json`): The `> <summary>` blockquote under the title. Omitted when neither is set.
1309
+
1310
+ Each component's one-line summary in `llms.txt` is the first sentence of its [`@component` comment](#component-comments), falling back to `"Component"`. Set `documentExports: true` to also list entry-barrel exports (consts, functions, types) in `llms.txt` under an `## Exports` heading.
1311
+
1312
+ ## Custom Writers
1313
+
1314
+ `json`, `markdown`, `types`, and `custom-elements` are all built on the same
1315
+ extensibility point: a small writer registry that third parties can use to
1316
+ add new output formats without a core PR. This is a stable, public part of
1317
+ sveld's API — the four built-in writers register through it in
1318
+ `src/writer/built-in-writers.ts`, there is no separate "internal" path.
1319
+
1320
+ ### The `OutputWriter` contract
1321
+
1322
+ ```ts
1323
+ interface OutputWriter<TOptions = unknown> {
1324
+ name: string;
1325
+ /** Which component set this writer expects. @default "exported" */
1326
+ componentSet?: "exported" | "all";
1327
+ write(components: ComponentDocs, options: TOptions): Promise<unknown> | unknown;
1328
+ }
1329
+ ```
1330
+
1331
+ - **`name`** is how the plugin's `additionalWriters` option (and anything else
1332
+ that calls `getWriter`) looks the writer up. Pick something that won't
1333
+ collide with `types` / `json` / `markdown` / `custom-elements` or another
1334
+ third-party writer.
1335
+ - **`componentSet`** picks which map `write` receives:
1336
+ - `"exported"` (the default) — components reachable from the entry barrel,
1337
+ keyed by `moduleName`. This is what `json` / `markdown` /
1338
+ `custom-elements` use.
1339
+ - `"all"` — every `--glob`-discovered `.svelte` file, keyed by resolved
1340
+ `filePath` instead of `moduleName` (two files in different directories
1341
+ can share a basename). This is what `types` uses.
1342
+ - **`write`** receives a `ComponentDocs` (`Map<string, ComponentDocApi>`) —
1343
+ the same `ComponentDocApi` shape documented in [JSON Output](#json-output).
1344
+ Don't iterate the raw map yourself; call `buildComponentApiDocument(components, { entryExports })`
1345
+ (also exported from `sveld`) to get the sorted, `diagnostics`-stripped,
1346
+ schema-versioned document the built-in writers build from. It's memoized
1347
+ per `components` map, so calling it more than once in one run is free.
1348
+ - **`options`** always carries `dryRun` alongside whatever fields the writer
1349
+ itself defines. See [The `dryRun` contract](#the-dryrun-contract) below.
1350
+
1351
+ ### Registering a writer
1352
+
1353
+ ```ts
1354
+ import { registerWriter } from "sveld";
1355
+
1356
+ registerWriter({
1357
+ name: "my-format",
1358
+ write(components, options) {
1359
+ // ...
1360
+ },
1361
+ });
1362
+ ```
1363
+
1364
+ `registerWriter` is a side effect — call it once, before the plugin or CLI
1365
+ runs, e.g. at the top of `vite.config.ts` or `sveld.config.ts`, or in a file
1366
+ either of those imports.
1367
+
1368
+ Registering a `name` that's already taken — a built-in writer's name, or
1369
+ another `registerWriter` call — throws instead of silently overwriting it.
1370
+ Pass `{ replace: true }` as a second argument when overwriting is intentional
1371
+ (e.g. re-registering a writer module during development):
1372
+
1373
+ ```ts
1374
+ registerWriter(
1375
+ {
1376
+ name: "my-format",
1377
+ write(components, options) {
1378
+ // ...
1379
+ },
1380
+ },
1381
+ { replace: true },
1382
+ );
1383
+ ```
1384
+
1385
+ ### The `dryRun` contract
1386
+
1387
+ Every writer — built-in or `additionalWriters` — receives `dryRun: true` in
1388
+ its `options` when the run is `sveld --dry-run` (or `{ dryRun: true }` from
1389
+ the programmatic API). A writer must check it and skip touching disk; sveld
1390
+ does not do this for you:
1391
+
1392
+ ```ts
1393
+ import { writeFileSync } from "node:fs";
1394
+
1395
+ registerWriter({
1396
+ name: "my-format",
1397
+ write(components, options: { outFile: string; dryRun?: boolean }) {
1398
+ if (options.dryRun) {
1399
+ console.log(`would write "${options.outFile}"`);
1400
+ return;
1401
+ }
1402
+ writeFileSync(options.outFile, "...");
1403
+ },
1404
+ });
1405
+ ```
1406
+
1407
+ A writer that throws — synchronously or from a rejected promise — has its
1408
+ error re-thrown as `sveld: writer "<name>" failed: <message>`, with the
1409
+ original error attached as `cause`, so a broken third-party writer never
1410
+ fails silently or without saying which one.
1411
+
1412
+ ### Running it via the plugin
1413
+
1414
+ ```ts
1415
+ // vite.config.ts
1416
+ import sveld from "sveld";
1417
+ import "./writers/my-format"; // calls registerWriter as a side effect
1418
+
1419
+ export default {
1420
+ plugins: [
1421
+ sveld({
1422
+ additionalWriters: {
1423
+ "my-format": { outFile: "MY_FORMAT.txt" },
1424
+ },
1425
+ }),
1426
+ ],
1427
+ };
1428
+ ```
1429
+
1430
+ `additionalWriters` is keyed by the writer's registered `name`; the value is
1431
+ whatever options object that writer's `write` expects. An unknown name logs a
1432
+ warning and is skipped rather than failing the build. Registered writers run
1433
+ alongside whichever built-in outputs (`types` / `json` / `markdown` /
1434
+ `customElements`) are also enabled, and the same option is available from the
1435
+ programmatic Node API and from `sveld.config.ts` (`additionalWriters` lives on
1436
+ the options shared by all three entry points).
1437
+
1438
+ ### Worked example: a `components.txt` name-list writer
1439
+
1440
+ Looking for an `llms.txt` writer specifically? Sveld ships one — see [llms.txt Output](#llmstxt-output). This example is a much smaller custom writer, just to show the pattern: a plain-text list of exported component names, needing nothing beyond `ComponentApiDocument`.
1441
+
1442
+ ```ts
1443
+ // writers/components-txt-writer.ts
1444
+ import { writeFileSync } from "node:fs";
1445
+ import { join } from "node:path";
1446
+ import { buildComponentApiDocument, registerWriter } from "sveld";
1447
+
1448
+ interface ComponentsTxtWriterOptions {
1449
+ outFile?: string;
1450
+ }
1451
+
1452
+ registerWriter<ComponentsTxtWriterOptions>({
1453
+ name: "components-txt",
1454
+ componentSet: "exported",
1455
+ write(components, options = {}) {
1456
+ const document = buildComponentApiDocument(components);
1457
+ const rendered = document.components.map((component) => component.moduleName).join("\n");
1458
+
1459
+ writeFileSync(join(process.cwd(), options.outFile ?? "components.txt"), rendered);
1460
+ },
1461
+ });
1462
+ ```
1463
+
1464
+ ```ts
1465
+ // vite.config.ts
1466
+ import sveld from "sveld";
1467
+ import "./writers/components-txt-writer";
1468
+
1469
+ export default {
1470
+ plugins: [
1471
+ sveld({
1472
+ additionalWriters: {
1473
+ "components-txt": { outFile: "components.txt" },
1474
+ },
1475
+ }),
1476
+ ],
1477
+ };
1478
+ ```
1479
+
1480
+ Running a build now produces a `components.txt` alongside the usual output,
1481
+ listing one exported component name per line.
1482
+
934
1483
  ## API Reference
935
1484
 
936
1485
  ### `reactive`
@@ -1299,7 +1848,7 @@ The `@typedef` tag defines a shared type used multiple times in a component. All
1299
1848
 
1300
1849
  #### Using `@property` for complex typedefs
1301
1850
 
1302
- For complex object types, use `@property` to document individual fields. That gives per-property tooltips in the IDE.
1851
+ For complex object types, use `@property` to document individual fields. That gives per-property tooltips in the IDE. See the [`@property`](#property) reference for the tag's valid contexts.
1303
1852
 
1304
1853
  **Signature:**
1305
1854
 
@@ -1696,6 +2245,35 @@ function render(value: unknown, props: ComponentProps) {
1696
2245
  }
1697
2246
  ```
1698
2247
 
2248
+ ### `@property`
2249
+
2250
+ `@property` documents one field of an object. `sveld` only reads it in two places: directly inside a `@typedef {object}` block, and directly inside an `@event` block (after an explicit `@type {object}`, or with no `@type` at all — `sveld` builds the detail type from the collected `@property` entries). It has no effect anywhere else.
2251
+
2252
+ **Valid contexts:**
2253
+
2254
+ | Context | Behavior |
2255
+ | :- | :- |
2256
+ | After `@typedef {object} Name`, in the same comment block | Builds `Name`'s fields. See [Using `@property` for complex typedefs](#using-property-for-complex-typedefs). |
2257
+ | After `@event eventname`, in the same comment block | Builds the event's detail type. See [Using `@property` for complex event details](#using-property-for-complex-event-details). |
2258
+ | A plain prop with no `@typedef`/`@type {object}` | Not structured. Folded into the prop's description as literal text (`@property name - description`). |
2259
+ | Before a `@slot` / `@snippet` line | Dropped entirely — no structured output, and unlike other unrecognized tags in that position, it is not folded into the slot's description either. |
2260
+ | Inside `@callback` | Not read. Use `@param` for callback parameters instead. |
2261
+
2262
+ **Signature:**
2263
+
2264
+ ```js
2265
+ /**
2266
+ * @typedef {object} TypeName
2267
+ * @property {Type} propertyName - Property description
2268
+ *
2269
+ * @event eventname
2270
+ * @type {object}
2271
+ * @property {Type} propertyName - Property description
2272
+ */
2273
+ ```
2274
+
2275
+ Both forms support the same modifiers as typedef properties elsewhere in this doc: optional properties (`[name]`), default values (`[name=value]`), and nested/discriminated-union shapes. See the linked sections above for full signatures and worked examples with generated output.
2276
+
1699
2277
  ### `@callback`
1700
2278
 
1701
2279
  The `@callback` tag defines a function type with `@param` and `@returns`, following the [TypeScript JSDoc `@callback` spec](https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#callback). Like `@typedef`, callbacks are exported from the generated `.d.ts`.
@@ -1798,7 +2376,7 @@ Descriptions are optional for every slot, including the default slot. Put prose
1798
2376
  */
1799
2377
  ```
1800
2378
 
1801
- Omit the `slot-name` to type the default slot.
2379
+ Omit the `slot-name` to type the default slot. `{Type}` itself is required; omitting it falls back to `Record<string, never>` and raises a [`sveld/slot-missing-type`](#diagnostic-codes) warning.
1802
2380
 
1803
2381
  ```js
1804
2382
  /**
@@ -2081,9 +2659,30 @@ export default class Component extends SvelteComponentTyped<
2081
2659
  > {}
2082
2660
  ```
2083
2661
 
2662
+ #### Detail inference without `@event`
2663
+
2664
+ Without an `@event` tag or a typed dispatcher, `sveld` infers a dispatched event's detail type from the `dispatch()` call site itself:
2665
+
2666
+ - A scalar literal argument narrows to its literal type: `dispatch("count", 5)` types the detail as `5`, not `number`. Use `@event` or a typed dispatcher (below) to widen it.
2667
+ - An object or array literal argument infers a structural type per field/element: `dispatch("save", { id })` types the detail as `{ id: string }` (resolving the `id` variable's own type), and `dispatch("items", [1, 2])` types it as `number[]`. Fields or elements sveld can't resolve fall back to `any` individually, not for the whole detail.
2668
+
2669
+ #### Typed dispatchers
2670
+
2671
+ `createEventDispatcher<T>()`'s generic argument (`lang="ts"`, or the JSDoc `/** @type {import('svelte').EventDispatcher<T>} */` cast form) works like an `@event` block for every member of `T`, including ones never actually dispatched in the file:
2672
+
2673
+ ```svelte
2674
+ <script lang="ts">
2675
+ import { createEventDispatcher } from "svelte";
2676
+
2677
+ const dispatch = createEventDispatcher<{ save: { id: string }; cancel: null }>();
2678
+ </script>
2679
+ ```
2680
+
2681
+ `T` may also be a reference to a local `type`/`interface`. An `@event` tag for the same name still overrides the generic's detail type.
2682
+
2084
2683
  #### Using `@property` for complex event details
2085
2684
 
2086
- For events with complex object payloads, use `@property` to document individual fields. The main comment becomes the event description.
2685
+ For events with complex object payloads, use `@property` to document individual fields. The main comment becomes the event description. See the [`@property`](#property) reference for the tag's valid contexts.
2087
2686
 
2088
2687
  This is the idiomatic way to describe each field of an event detail. An inline object literal such as `@event {{ items: string[]; added: string[] }} change` types the payload but cannot carry per-field descriptions, since a nested block comment would terminate the host JSDoc. Declare the detail with `@type {object}` and `@property` instead to document every field.
2089
2688
 
@@ -2311,9 +2910,80 @@ export default class Component extends SvelteComponentTyped<
2311
2910
 
2312
2911
  Any free-text prose after the tags is attached to the event description, not to a property doc.
2313
2912
 
2913
+ ### `@ignore` / `@internal`
2914
+
2915
+ `@ignore` and `@internal` are equivalent aliases: either one excludes a prop, event, slot, typedef, module export, entry export, or context from every output — JSON, Markdown, `.d.ts`, and the Custom Elements Manifest. Use them for implementation details that would otherwise leak into the public API docs.
2916
+
2917
+ The tag's position mirrors [`@deprecated`](#deprecated): before `@slot`/`@snippet`/`@typedef`/`@callback`, alongside the description, and after the `@event` line.
2918
+
2919
+ ```svelte
2920
+ <script>
2921
+ /** The visible label. */
2922
+ export let label = "";
2923
+
2924
+ /**
2925
+ * Implementation detail; not part of the public API.
2926
+ * @internal
2927
+ */
2928
+ export let debugId = "";
2929
+
2930
+ /**
2931
+ * Fired when the value changes.
2932
+ * @event {{ value: string }} change
2933
+ */
2934
+
2935
+ /**
2936
+ * Fired for internal diagnostics only.
2937
+ * @event {{ reason: string }} debug
2938
+ * @internal
2939
+ */
2940
+
2941
+ /**
2942
+ * @internal
2943
+ * @slot {{}} debug-panel
2944
+ */
2945
+ </script>
2946
+ ```
2947
+
2948
+ `debugId`, the `debug` event, and the `debug-panel` slot never appear in `COMPONENT_INDEX.md`, `COMPONENT_API.json`, the generated `.d.ts`, or the Custom Elements Manifest — as if they were never declared. Internally, the parser still records them (with an `internal: true` flag) on the raw parsed component; only `buildComponentApiDocument` — the shared step every writer runs through — filters them out, so a custom writer built on the raw parse result can still see them if it chooses to.
2949
+
2950
+ For a context (`setContext(key, value)`), tag the JSDoc on the *value* variable, the same place its type annotation and description already live:
2951
+
2952
+ ```svelte
2953
+ <script>
2954
+ /**
2955
+ * @type {{ token: string }}
2956
+ * @internal
2957
+ */
2958
+ let authContext = { token: "" };
2959
+
2960
+ setContext("auth", authContext);
2961
+ </script>
2962
+ ```
2963
+
2964
+ A whole inline object literal passed directly to `setContext` (no intermediate variable) has no JSDoc position of its own, so `@internal` isn't supported there. An individual property *inside* one can be marked `@internal`, though, as long as its value is an identifier carrying its own JSDoc:
2965
+
2966
+ ```svelte
2967
+ <script>
2968
+ /**
2969
+ * @type {string}
2970
+ * @internal
2971
+ */
2972
+ let debugToken = "";
2973
+
2974
+ let publicUser = { name: "" };
2975
+
2976
+ setContext("session", { user: publicUser, debug: debugToken });
2977
+ </script>
2978
+ ```
2979
+
2980
+ Only the `debug` property is excluded from `session`'s generated shape; `user` and the context itself are unaffected.
2981
+
2982
+ Because an internal member never appears in output, adding `@internal` to a previously-public prop, event, slot, or typedef is a breaking change for `--check` purposes (it disappears from the generated `.d.ts` just like an outright removal). Removing an already-`@internal` member, by contrast, is not breaking — it was never part of the committed public snapshot to begin with.
2983
+
2314
2984
  ### `@deprecated`
2315
2985
 
2316
- Add `@deprecated` to a prop, event, slot, or exported accessor. An optional message after the tag can explain why or name a replacement.
2986
+ Add `@deprecated` to a prop, event, slot, entry export, or exported accessor. An optional message after the tag can explain why or name a replacement.
2317
2987
 
2318
2988
  ```svelte
2319
2989
  <script>
@@ -2342,7 +3012,7 @@ Add `@deprecated` to a prop, event, slot, or exported accessor. An optional mess
2342
3012
  </script>
2343
3013
  ```
2344
3014
 
2345
- For slots, put `@deprecated` before the `@slot` / `@snippet` line, alongside the description and any other [extra tags](#extra-jsdoc-tags-before-slot). For events, put it after the `@event` line.
3015
+ For slots, put `@deprecated` before the `@slot` / `@snippet` line, alongside the description and any other [extra tags](#extra-jsdoc-tags-before-slot). For events, put it after the `@event` line. For entry exports (see [Documenting Entry Exports](#documenting-entry-exports)), put it in the JSDoc directly above the `const`/`function`/`class`/`type`/`interface` declaration, same as a prop.
2346
3016
 
2347
3017
  Generated `.d.ts` files include an `@deprecated` JSDoc line so editors strike the symbol through. JSON adds a `deprecated` field (the message string, or `true` when the tag has no message). Markdown strikes through the name and adds a **Deprecated** badge with the message when present.
2348
3018
 
@@ -2358,6 +3028,184 @@ label?: string;
2358
3028
  { "name": "label", "deprecated": "Use the `text` prop instead." }
2359
3029
  ```
2360
3030
 
3031
+ ### `@since`
3032
+
3033
+ `@since` records the version a prop, event, or slot was introduced. `sveld` does not validate or parse the version text — whatever follows the tag is copied through as-is.
3034
+
3035
+ **Valid contexts:**
3036
+
3037
+ - **Prop / module export** — captured as a structured tag: JSON adds a `tags: [{ "name": "since", "body": "..." }]` array on the prop (kept separate from `description`), the generated `.d.ts` emits `@since ...` as its own JSDoc line above `@default`, and the Markdown table's Description column appends `@since ...` after the description, same as slots.
3038
+ - **`@event`** — same structured behavior as props, but only when `@since` is placed **after** the `@event` line in the same comment block (matching the [`@deprecated`](#deprecated) rule for events). Placed before `@event`, it is silently dropped.
3039
+ - **`@slot` / `@snippet`** — placed before the `@slot`/`@snippet` line, alongside the description. Fully surfaced in JSON, `.d.ts`, *and* the Markdown table. See [extra tags before `@slot`](#extra-jsdoc-tags-before-slot).
3040
+ - **`@typedef`** — **not supported.** A `@since` before `@typedef` produces no output anywhere; it is silently dropped.
3041
+ - **`@component` comments** — passed through verbatim as part of the raw HTML comment text. See [@component comments](#component-comments).
3042
+
3043
+ **Example:**
3044
+
3045
+ ```svelte
3046
+ <script>
3047
+ /**
3048
+ * A width prop.
3049
+ * @since 1.2.0
3050
+ * @type {number}
3051
+ */
3052
+ let { width = 0 } = $props();
3053
+ </script>
3054
+ ```
3055
+
3056
+ Output (`.d.ts`):
3057
+
3058
+ ```ts
3059
+ export type ScratchProps = {
3060
+ /**
3061
+ * A width prop.
3062
+ * @since 1.2.0
3063
+ * @default 0
3064
+ */
3065
+ width?: number;
3066
+ };
3067
+ ```
3068
+
3069
+ Output (JSON, relevant slice):
3070
+
3071
+ ```json
3072
+ { "name": "width", "description": "A width prop.", "tags": [{ "name": "since", "body": "1.2.0" }] }
3073
+ ```
3074
+
3075
+ Output (Markdown props table Description column):
3076
+
3077
+ ```
3078
+ A width prop.<br />@since 1.2.0
3079
+ ```
3080
+
3081
+ ### `@see`
3082
+
3083
+ `@see` adds a reference link or citation. Like `@since`, `sveld` does not resolve or validate the target — it is free text.
3084
+
3085
+ **Valid contexts:**
3086
+
3087
+ - **Prop / module export** — `@see` is *not* one of the small set of tags `sveld` structures specially, so it is folded verbatim into the prop's `description` as an extra line (`@see ...`) rather than getting its own `tags` entry. Because it lives in `description`, it *does* show up everywhere `description` is used: JSON, `.d.ts`, and the Markdown table.
3088
+ - **`@event`** — **not supported in either position** (before or after `@event`). A `@see` tag near an `@event` block is silently dropped in both JSON and `.d.ts`.
3089
+ - **`@slot` / `@snippet`** — placed before `@slot`/`@snippet`, alongside the description: fully supported in JSON, `.d.ts`, and Markdown, same as `@since`. See [extra tags before `@slot`](#extra-jsdoc-tags-before-slot).
3090
+ - **`@typedef`** — **not supported.** Dropped silently, same as `@since`.
3091
+ - **`@component` comments** — passed through verbatim, same as `@since`.
3092
+
3093
+ **Example:**
3094
+
3095
+ ```svelte
3096
+ <script>
3097
+ /**
3098
+ * A width prop.
3099
+ * @see https://example.com/width-docs
3100
+ * @type {number}
3101
+ */
3102
+ let { width = 0 } = $props();
3103
+ </script>
3104
+ ```
3105
+
3106
+ Output (`.d.ts`):
3107
+
3108
+ ```ts
3109
+ export type ScratchProps = {
3110
+ /**
3111
+ * A width prop.
3112
+ * @see https://example.com/width-docs
3113
+ * @default 0
3114
+ */
3115
+ width?: number;
3116
+ };
3117
+ ```
3118
+
3119
+ Output (Markdown props table Description column):
3120
+
3121
+ ```
3122
+ A width prop.<br />@see https://example.com/width-docs
3123
+ ```
3124
+
3125
+ ### `@link`
3126
+
3127
+ `{@link target}` (optionally `{@link target|display text}`) is an inline JSDoc tag used inside prose, not a block-level tag like the others on this page. `sveld`'s comment parser only treats a *line* as starting a new tag when it begins with `@` — `{@link ...}` always starts with `{`, so it's never intercepted. It is always literal text.
3128
+
3129
+ **Valid contexts:** anywhere free-form description text is read — prop and module export descriptions, event descriptions, slot descriptions, entry export descriptions, typedef descriptions, `@component` HTML comments, and `@example` bodies. `sveld` never resolves or validates the target. JSON `description` and `.d.ts` JSDoc always keep the tag verbatim. **Markdown is the one exception:** in a prop, event, slot, or entry export's Description table cell, `{@link target|text}` / `{@link target}` is rewritten to a Markdown link, `[text](target)` / `[target](target)`. Typedef descriptions (rendered as a `.d.ts`-style code block) and `@component` comments keep the tag literal in Markdown too, since neither goes through the table-cell renderer.
3130
+
3131
+ **Example:**
3132
+
3133
+ ```svelte
3134
+ <script>
3135
+ /**
3136
+ * The element's width in pixels. See {@link https://example.com/width|width docs}.
3137
+ * @type {number}
3138
+ */
3139
+ let { width = 0 } = $props();
3140
+ </script>
3141
+ ```
3142
+
3143
+ Output (`.d.ts`):
3144
+
3145
+ ```ts
3146
+ export type ScratchProps = {
3147
+ /**
3148
+ * The element's width in pixels. See {@link https://example.com/width|width docs}.
3149
+ * @default 0
3150
+ */
3151
+ width?: number;
3152
+ };
3153
+ ```
3154
+
3155
+ The same literal string appears unchanged in JSON `description`. The Markdown table's Description column instead shows: `The element's width in pixels. See [width docs](https://example.com/width).`
3156
+
3157
+ ### `@example`
3158
+
3159
+ `@example` has two related uses: as a plain JSDoc tag whose body is copied into generated output, and — with `checkExamples: true` — as input to sveld's compile-checker for plain TS/JS example code. This section covers the tag itself; see [Compile-checked `@example` blocks (`checkExamples`)](#compile-checked-example-blocks-checkexamples) for the type-checking feature.
3160
+
3161
+ `@example` shares its underlying mechanism with `@since` (both are in the small set of tags `sveld` treats as structured "IDE passthrough" tags), so the valid contexts are the same:
3162
+
3163
+ - **Prop / module export (including functions)** — structured `tags` entry in JSON, its own `@example` block in `.d.ts`, and appended to the Markdown table's Description column, same as `@since`.
3164
+ - **`@event`** — only when placed **after** the `@event` line in the same block, same rule as `@since`.
3165
+ - **`@slot` / `@snippet`** — placed before `@slot`/`@snippet`: fully supported in JSON, `.d.ts`, and Markdown. See [extra tags before `@slot`](#extra-jsdoc-tags-before-slot).
3166
+ - **`@typedef`** — **not supported**, dropped silently, same as `@since`.
3167
+ - **`@component` comments** — passed through verbatim; this is already how the [`@component` comments](#component-comments) example on this page uses `@example`.
3168
+
3169
+ **Example:**
3170
+
3171
+ ```svelte
3172
+ <script>
3173
+ /**
3174
+ * Formats a value.
3175
+ * @param {string} value
3176
+ * @returns {string}
3177
+ * @example
3178
+ * ```js
3179
+ * formatValue("ok");
3180
+ * ```
3181
+ */
3182
+ export function formatValue(value) {
3183
+ return value;
3184
+ }
3185
+ </script>
3186
+ ```
3187
+
3188
+ Output (`.d.ts`):
3189
+
3190
+ ```ts
3191
+ /**
3192
+ * Formats a value.
3193
+ * @example
3194
+ * ```js
3195
+ * formatValue("ok");
3196
+ * ```
3197
+ */
3198
+ formatValue: (value: string) => string;
3199
+ ```
3200
+
3201
+ `@param`/`@returns` are consumed into the function's type signature rather than kept as separate JSDoc lines.
3202
+
3203
+ Output (Markdown props table Description column, newlines rendered as `<br />`):
3204
+
3205
+ ````
3206
+ Formats a value.<br />@example ```js<br /> formatValue("ok");<br /> ```
3207
+ ````
3208
+
2361
3209
  ### Context API
2362
3210
 
2363
3211
  `sveld` generates TypeScript definitions for Svelte's `setContext`/`getContext` by extracting types from JSDoc on context values.
@@ -3006,6 +3854,8 @@ export default class Component extends SvelteComponentTyped<
3006
3854
 
3007
3855
  When only `@param` tags are present without `@returns`, the return type defaults to `any`. When only `@returns` is present without `@param`, the function signature is `() => returnType`.
3008
3856
 
3857
+ The Markdown writer marks the same exports (a `const`, or a real function declaration) with Kind `accessor` in the Props table, instead of their raw `const`/`function` kind, matching the `.d.ts` output above.
3858
+
3009
3859
  ## Troubleshooting
3010
3860
 
3011
3861
  **A prop came out `any`.** Enable [`reportDiagnostics`](#type-inference-diagnostics) (or `strict` to fail CI) to see which props sveld couldn't infer, then tighten them with `@type` or a native TypeScript annotation.
@@ -3014,6 +3864,10 @@ When only `@param` tags are present without `@returns`, the return type defaults
3014
3864
 
3015
3865
  **Output differs in CI.** Commit `COMPONENT_API.json` and run [`--check`](#ci-api-drift-checks---check) in CI so API drift fails the build instead of silently diverging.
3016
3866
 
3867
+ **`sveld: cannot resolve "..." from ...`.** An `export *` or a named re-export points at a module or path alias that doesn't resolve to a file on disk. Fix the specifier, or check the tsconfig/jsconfig `paths` entry it's supposed to match. This exits `1`; see [Exit codes](#exit-codes).
3868
+
3869
+ **A path alias (`$lib`, `@components`, ...) isn't picked up.** sveld only reads aliases from `compilerOptions.paths` in the nearest `tsconfig.json` or `jsconfig.json`, found by walking up from the file doing the importing. Vite/SvelteKit alias config (`vite.config.*`, `svelte.config.*`) is not read directly — add the same aliases to `paths` so both tools agree. When a pattern has more than one mapping (`"$lib/*": ["./src/lib/*", "./lib/*"]`), sveld tries them in order and uses the first one that exists on disk, falling back to the first mapping if none do; among multiple matching patterns, the one with the longest non-wildcard prefix wins, regardless of declaration order (matching `tsc`).
3870
+
3017
3871
  ## Contributing
3018
3872
 
3019
3873
  See [contributing guidelines](CONTRIBUTING.md).