@kagal/build-tsdoc
v0.3.0
Published
Build-hook adapter for @microsoft/api-extractor
Maintainers
Readme
@kagal/build-tsdoc
Build-hook adapter for @microsoft/api-extractor.
Slim wrapper with dist/<entryName>.* defaults, a
stub-aware skip, and per-bundler hook factories for
unbuild and obuild.
The root entry runs the extraction; a dependency-light
/utils subpath holds helpers for working with the
emitted manifests without pulling in api-extractor.
Usage
Each factory returns a map keyed by the bundler's own
hook names, so the result spreads straight into
hooks. For unbuild:
import { defineBuildConfig } from 'unbuild';
import { newUnbuildHooks } from '@kagal/build-tsdoc';
export default defineBuildConfig({
entries: [{ input: 'src/index', name: 'index' }],
declaration: true,
hooks: { ...newUnbuildHooks() },
});obuild's end hook does not carry entries, and only
its entries hook sees them bundler-resolved (absolute
paths, effective stub flags), so newOBuildHooks()
returns a pair that captures from one and extracts
from the other:
import { defineBuildConfig } from 'obuild/config';
import { newOBuildHooks } from '@kagal/build-tsdoc';
export default defineBuildConfig({
entries: [{ type: 'bundle', input: ['./src/index.ts'] }],
hooks: { ...newOBuildHooks() },
});Both obuild hooks must be wired — end throws
HooksNotWiredError when entries never fired.
To combine extraction with other post-build steps, keep the map in a variable and call its hooks from your own wrappers:
import { copyFileSync } from 'node:fs';
import { defineBuildConfig } from 'obuild/config';
import { newOBuildHooks } from '@kagal/build-tsdoc';
const tsdoc = newOBuildHooks();
export default defineBuildConfig({
entries: [{ type: 'bundle', input: ['./src/index.ts'] }],
hooks: {
entries: tsdoc.entries,
end(context) {
tsdoc.end(context);
copyFileSync('dist/index.d.mts', 'dist/index.d.ts');
},
},
});unbuild entries carry their bundler-resolved name;
obuild entry names derive from each input basename.
Stub builds are skipped via unbuild's options.stub
or obuild's per-entry stub; per-entry outDir is
honoured. Entries missing that data — e.g.
hand-written name lists — are rejected with
InvalidBuildEntryError: only the bundler's own
entries carry the data extraction detects from.
Each entry writes
<projectFolder>/<outDir>/<entryName>.api.json in
@microsoft/api-extractor-model's wire format. Read it
back with loadPackage from @kagal/build-tsdoc/utils,
or ApiPackage.loadFromJsonFile() directly. For finer
control, call
extractEntryManifest({ projectFolder, entryName })
yourself per entry — the hooks are a loop over it.
A runnable example — a TypeScript 6.x consumer wired
through the unbuild hooks — lives in
examples/playground-ts6.
Defaults
Paths derive from projectFolder, outDir, and
entryName:
| Option | Default |
| --- | --- |
| outDir | dist, resolved against <projectFolder> |
| entryFile | <outDir>/<entryName>.d.mts |
| outputPath | <outDir>/<entryName>.api.json |
| tsconfigPath | <projectFolder>/tsconfig.json |
| packageFullPath | <projectFolder>/package.json |
Override any of them individually for non-standard layouts.
newlineKind selects the manifest's line endings
('os' | 'crlf' | 'lf', default 'os'). The default
follows the host, so the file matches whatever the
consuming repo normalises to; pin 'lf' or 'crlf'
to override. An omitted or unexpected value falls back
to the host default.
Behaviour
- Returns
undefinedwhenentryFileis missing — stub builds emit only the JS bundle, no declarations, so the call is safe to make unconditionally. - Runtime dependencies are passed to api-extractor as
bundledPackages: a symbol re-exported from a dependency is part of the package contract, so it is documented as a member of the package itself. Dependencies the entry never references are a no-op. - Analysis uses the consumer's installed
typescript, not api-extractor's bundled compiler: a package built on a newer TypeScript is parsed by the engine that emitted its declarations, so no version-mismatch notice is printed. A no-op when the consumer ships no TypeScript or it already matches the bundled version. - Throws when api-extractor reports any error. Warnings
surface in the returned
warningCount. - The bundler hooks reject duplicate entry names
with
DuplicateEntryNameError, entries missing their bundler-resolved data withInvalidBuildEntryError, and contexts that match no supported bundler shape withUnrecognisedBuildContextError. The single-entryextractEntryManifesthas no list-level checks — callers iterating directly own any collision logic.
@kagal/build-tsdoc/utils
Dependency-light helpers for working with manifests, importable without pulling in api-extractor — both reading a manifest back and writing one out.
loadPackage reads a *.api.json manifest into an
ApiPackage, the inverse of extractEntryManifest. It
depends on @microsoft/api-extractor-model alone, so a
renderer or SSR consumer can load a manifest without the
build tooling:
import { loadPackage } from '@kagal/build-tsdoc/utils';
const pkg = loadPackage('dist/index.api.json');It is file-based by necessity: api-extractor-model only rehydrates a package from a path, not an in-memory object.
serialiseJSON formats a value as JSON the way
api-extractor writes manifests — 2-space indent,
trailing newline, and configurable line endings:
import { serialiseJSON } from '@kagal/build-tsdoc/utils';
const json = serialiseJSON(value); // host endings
const lf = serialiseJSON(value, 'lf'); // forced LFThe endings follow the same NewlineKind
('os' | 'crlf' | 'lf', default host) as the manifest
writer; resolveNewlineKind exposes that resolution on
its own. The subpath exports:
loadPackage(file)— read a*.api.jsonmanifest into anApiPackageserialiseJSON(value, newlineKind?)— JSON text in api-extractor's manifest formatresolveNewlineKind(kind?)— resolve aNewlineKindto the host's concrete'crlf' | 'lf'NewlineKind— line-ending policy ('os' | 'crlf' | 'lf')ConcreteNewlineKind— aNewlineKindwith'os'resolved ('crlf' | 'lf'); the typeresolveNewlineKindreturns
Exports
The lists below cover the root entry; the
@kagal/build-tsdoc/utils subpath is documented above.
Functions
extractEntryManifest— extract one entry's.api.jsonnewUnbuildHooks,newOBuildHooks— per-bundler hook-map factoriesasUnbuildContext,asOBuildContext— narrow anunknowncontext to the matching bundler shape
Types
ExtractEntryOptions,ExtractEntryResult— the helper's options and resultNewlineKind— manifest line-ending policy ('os' | 'crlf' | 'lf')UnbuildHooks,UnbuildBuildHookContext,UnbuildBuildHookEntry— unbuild shapesOBuildHooks,OBuildBuildHookContext,OBuildBuildHookEntry— obuild shapes
Errors
DuplicateEntryNameError,HooksNotWiredError,InvalidBuildEntryError,UnrecognisedBuildContextError
Constant
VERSION— package version
