import { Root } from 'mdast'; import { TDoclet, PageKind, TDocletParam, PlaygroundProvider, SlotEntry, Page, NavNode, TJSDocSaltyCollection, Heading, SiteManifest, CollapsibleSidebarSections } from '@clean-jsdoc-theme/utils'; interface ClassMember extends TDoclet { /** Longname of the ancestor this member was inherited from. Absent on own members. */ inheritedFrom?: string; } interface MemberBuckets { instanceMethods: ClassMember[]; staticMethods: ClassMember[]; instanceFields: ClassMember[]; staticFields: ClassMember[]; /** * Getter/setter accessors (`isAccessor` doclets) — TypeDoc surfaces these in a * dedicated "Accessors" section. JSDoc never sets `isAccessor`, so this bucket * is always empty on the JSDoc path (its members stay in the field buckets). */ accessors: ClassMember[]; enums: ClassMember[]; events: ClassMember[]; /** Anything that did not match a bucket above (e.g. typedef nested under a class). */ other: ClassMember[]; } /** * Kind-parametric superset of {@link ClassView}: the same shape plus the page * `kind`. Covers any container (class/interface/mixin/module/namespace). A * {@link ClassView} is just a `ContainerView` with `kind: 'class'`. */ interface ContainerView extends MemberBuckets { /** Canonical container doclet — see {@link getCanonicalDoclet}. */ doclet: TDoclet; /** The page kind this container renders as. */ kind: PageKind; /** Parent longnames in declaration order. Empty if this container extends nothing. */ augments: string[]; /** Constructor params, surfaced for convenience. Empty for non-class kinds. */ constructorParams: TDocletParam[]; /** * Ordered constructor parameter names, for rendering the call signature when * the constructor is undocumented (no `@param` tags, so `constructorParams` is * empty). Recovered from the `meta.code.paramnames` of any doclet sharing the * longname, so `new Foo(a, b)` still shows. Empty for non-class kinds. */ constructorParamNames: string[]; } /** The result of resolving a link target. `external` flags off-site URLs. */ interface ResolvedLink { href: string; external: boolean; } /** * Shared parser for the `@playground` block tag (doclets), the ```` ```js * playground … ```` prose fence, and the `` prose container. All * three use ONE token grammar — the same whitespace-token / `key=value` style as * `embed.ts` — so a single {@link parsePlaygroundSpec} reads every authoring site: * * codepen jsfiddle filename=resize.js highlight=1,4,8 * * Tokens are: bare provider names (`codepen` | `jsfiddle` | `codesandbox`) that * enable those providers; `none`/`off` to opt a block out; `filename=`; and * `highlight=1,4,8` (also `highlight=[1,4,8]`). Values may be single/double * quoted. Unknown tokens are warned-and-ignored; the parser never throws. */ /** The providers a code block can be opened in. */ declare const KNOWN_PROVIDERS: readonly ["codepen", "jsfiddle", "codesandbox"]; /** * The parsed grammar of one `@playground` / fence / container config string. * `providers: null` means "no explicit provider list was given" (a bare * `@playground`) — distinct from an empty list — so callers can fall back to the * site-wide default set. `off` records a `none`/`off` opt-out token. */ interface PlaygroundSpec { /** `none`/`off` token present — opt this block out of the playground dropdown. */ off: boolean; /** Explicit provider list (author order), or `null` when none were named. */ providers: PlaygroundProvider[] | null; /** `filename=` — header label for the code block. */ filename?: string; /** `highlight=…` — sorted, de-duped 1-based line numbers (empty when unset). */ highlight: number[]; } /** The resolved render opts a `` wrapper carries (see {@link resolvePlaygroundOpts}). */ interface PlaygroundOpts { providers: PlaygroundProvider[]; filename?: string; highlight: number[]; } /** * Parse a config string into a {@link PlaygroundSpec}. Never throws — unknown * bare tokens / unknown keys are warned-and-ignored (mirroring `parseEmbedConfig`). */ declare function parsePlaygroundSpec(text: string): PlaygroundSpec; /** * Resolve a {@link PlaygroundSpec} into the concrete {@link PlaygroundOpts} a * `` wrapper carries, or `null` when nothing warrants a wrapper. * * `defaultProviders` fills in the provider list for a bare config (no explicit * providers and not opted out): API examples pass the site-wide default set, * prose fences/containers pass {@link KNOWN_PROVIDERS}. An `off` block keeps an * empty provider list but still wraps when it carries a `filename`/`highlight` * (so opting out of the dropdown doesn't lose the presentation options). */ declare function resolvePlaygroundOpts(spec: PlaygroundSpec, defaultProviders: readonly PlaygroundProvider[]): PlaygroundOpts | null; /** * Translatable API slots — the locale-independent template half of the two-phase * localization build (see `packages/aadesh-bhasha-plan.md`, Phase 2). * * Every translatable doclet prose field (a description, a `@summary`, an * `@example` caption) is funneled through {@link resolveSlotText} as its source * string is read, *before* it's converted to mdast. With no resolver the source * passes through untouched, so the default (no-locale) build is byte-identical. * When a resolver is threaded in, each slot is (a) recorded for extraction via * `collect` and (b) substituted with the active locale's translation via * `translate` — so the very same build pass, re-run with a translating resolver, * is the per-locale "stamp". * * Keys + hashes come from bhasha (`apiSlotKey` / `sourceHash`) so setu and aadesh * agree on identity and staleness. Only prose is a slot: names, type strings, * enum values, and `@example` code stay locale-invariant. */ /** * Build-time resolver threaded through the doclet→mdast conversion. Both hooks * are optional: `collect` records a slot for the extractable template, `translate` * swaps in a locale's text. Omit both (or the whole resolver) for the * byte-identical default build. */ interface SlotResolver { /** Record a slot as its source string is read (template extraction). */ collect?: (entry: SlotEntry) => void; /** * Return the active-locale text for `key`, or `sourceText` when untranslated. * Whatever it returns is fed to the same mdast converter as the source, so a * translation must be authored in the source's format (HTML/Markdown prose). */ translate?: (key: string, sourceText: string) => string; } /** * Resolve one translatable prose field to the string that should be rendered: * collect it (for extraction) and translate it (for stamping). Empty/absent * source, an absent longname, or no resolver all short-circuit to the source * unchanged — so nothing is keyed or substituted when there's nothing to * translate, and the default build is byte-identical. * * @param longname - The owning symbol's longname (the key's namespace). * @param field - Field path within the doclet (e.g. `'description'` or * `['examples', '0', 'caption']`); must be `#`-free (bhasha key invariant). */ declare function resolveSlotText(resolver: SlotResolver | undefined, longname: string | undefined, field: string | readonly string[], sourceText: string | null | undefined): string | null | undefined; /** * Accumulate {@link SlotEntry}s into a deduped, insertion-ordered list — the * `manifest.slots` template. Dedup is by key (the same symbol+field is rendered * once per build, but the collector tolerates repeats); the first-seen source * wins, which is deterministic given setu's stable build order. */ declare class SlotCollector { private readonly byKey; /** A `collect` hook bound to this collector, for a {@link SlotResolver}. */ readonly collect: (entry: SlotEntry) => void; /** The collected slots, in first-seen order. */ list(): SlotEntry[]; } /** * Make a `translate` hook from a flat locale message map (`key → translated * text`). A missing or empty entry falls back to the source string, so a * partially-translated catalog renders the default text for the gaps. */ declare function makeSlotTranslator(messages: Readonly>): NonNullable; type DocletSection = 'summary' | 'modifiers' | 'relations' | 'this' | 'alias' | 'remarks' | 'typeParams' | 'params' | 'properties' | 'returns' | 'yields' | 'throws' | 'type' | 'default' | 'fires' | 'listens' | 'examples' | 'iframes' | 'metadata' | 'deprecation' | 'inherited'; interface DocletBlocksOptions { /** Heading level for sub-section labels ("Parameters", "Returns", …). Default: 4. */ subHeadingLevel?: 4 | 5 | 6; /** Language hint for example code blocks. Default: "js". */ exampleLang?: string; /** * Sections to suppress. Useful when the caller is surfacing them in a * dedicated section elsewhere on the page (e.g. constructor params). */ skip?: readonly DocletSection[]; /** When set, emits a "Source: file:line" link for a doclet that resolves. */ sourceLink?: (doclet: TDoclet) => { href: string; label: string; } | null; /** Resolves a {@link}/@see namepath or URL to an href. Mirrors sourceLink. */ resolveLink?: (target: string) => ResolvedLink | null; /** Resolves a `@tutorial` name to its guide page href + display title. */ resolveTutorial?: (name: string) => { href: string; title: string; } | null; /** * Resolves a doclet's `@playground` tag (+ the site-wide playground config) * into the wrapper opts for its `@example` blocks, or `null` for none. Threaded * like {@link DocletBlocksOptions.sourceLink}; omit for no playground (the * byte-identical default). */ playgroundFor?: (doclet: TDoclet) => PlaygroundOpts | null; /** * Translatable-prose resolver: collects each description/summary/example- * caption slot and (when stamping a locale) substitutes its translation. * Omitted for the byte-identical default build. See {@link SlotResolver}. */ slots?: SlotResolver; /** * Document-model flavor. `'typedoc'` switches member rendering to full * TypeScript signatures (a `ts` code block per member, with type parameters, * parameter types, and return types). `'jsdoc'` (default/omitted) keeps the * name-only heading signature — byte-identical. */ flavor?: 'jsdoc' | 'typedoc'; } /** * Split a JSDoc longname into the parts used for path slugging. The separators * `.`, `#`, `~`, `:` are replaced with whitespace, then the string is split * and empties are dropped. This preserves distinctness — `module:Foo~Bar` and * `Foo.Bar` produce different part arrays even after slugification because * `module` becomes a leading segment in the former. */ declare function splitLongnameForSlug(longname: string): string[]; /** Site-wide playground enablement passed into {@link generateSite}. */ interface PlaygroundSiteConfig { /** Opt every `@example` in (using {@link PlaygroundSiteConfig.providers}). */ enableForAllExamples?: boolean; /** Default provider set + order for a bare `@playground` / `enableForAllExamples`. */ providers?: PlaygroundOpts['providers']; } /** * Build the per-doclet `@playground` resolver from the site-wide config — the * §3.3 resolution table. A doclet's `@playground` tag (parsed via * {@link parsePlaygroundSpec}) wins: an explicit provider list is used as-is, a * bare tag falls back to the default set, and `none`/`off` opts out (but still * wraps for a `filename`/`highlight`). With no tag, `enableForAllExamples` opts * the example in with the default set; otherwise no wrapper. Returns `undefined` * when there is no config at all (feature off → byte-identical output). */ declare function makePlaygroundResolver(config: PlaygroundSiteConfig | undefined): ((doclet: TDoclet) => PlaygroundOpts | null) | undefined; /** * Walk an mdast tree and emit a `Heading` per h{minDepth}..h6 in document order, * with IDs slugified through a per-page registry so duplicates dedupe * consistently with what the renderer will produce. * * h1 handling is adaptive: a lone h1 is the page title and is skipped * (`minDepth` stays 2). But when a page has *two or more* h1s the author is * using h1 as section structure rather than as a title, so they're surfaced * like any other heading (`minDepth` drops to 1) and join the dedup registry. * dwar's slug pass makes the exact same count-then-decide choice, so the * `#id` numbering stays identical on both sides. * * `` JSX nodes (setu's signature headings) are also picked up: * their explicit `id`/`name`/`depth` attributes become the entry directly, and * they do NOT touch the dedup registry — mirroring dwar's slug pass, which skips * them (they carry an explicit id), so the `-1`/`-2` numbering of real markdown * headings stays in sync between the two. Members are never h1, so this branch * is unaffected by the adaptive `minDepth`. */ declare function extractHeadings(tree: Root): Heading[]; /** * Returns the unique longnames of the given `kind` in the collection that have * a documented doclet. Dedupes on longname and skips undocumented doclets. */ declare function enumerateLongnamesByKind(collection: TJSDocSaltyCollection, kind: PageKind): string[]; /** Returns the unique class longnames in the collection that have a canonical doclet. */ declare function enumerateClassLongnames(collection: TJSDocSaltyCollection): string[]; /** Per-render threading options shared by every container/globals page. */ interface RenderOptions { sourceLink?: DocletBlocksOptions['sourceLink']; resolveLink?: DocletBlocksOptions['resolveLink']; resolveTutorial?: DocletBlocksOptions['resolveTutorial']; /** Translatable-prose slot resolver (collect + per-locale translate). */ slots?: DocletBlocksOptions['slots']; /** Per-doclet `@playground` resolver (see {@link makePlaygroundResolver}). */ playgroundFor?: DocletBlocksOptions['playgroundFor']; /** Document-model flavor; `'typedoc'` switches member sections + module index. */ flavor?: 'jsdoc' | 'typedoc'; } /** * Render an already-built {@link ContainerView} into a {@link Page}. This is the * one place a container's mdast is assembled → link tags resolved → serialized, * so the two-pass build in `generateSite` can reuse the view from its dedup pass * (rather than rebuilding it). When `resolveLink` is provided, every `{@link}` / * `@see` reference in the tree is rewritten to a real anchor before `toMdx`; * without a resolver the output is byte-identical to the pre-link-resolution * builder. */ declare function renderContainerPage(view: ContainerView, kind: PageKind, longname: string, slug: string, { sourceLink, resolveLink, resolveTutorial, slots, playgroundFor, flavor }?: RenderOptions): Page; /** * Build a single container page (class/interface/mixin/module/namespace); * returns null if no container view of `kind` can be built for `longname`. * Delegates to {@link renderContainerPage}. The optional `resolveLink` resolves * cross-references; omit it for byte-identical legacy output. */ declare function buildContainerPage(collection: TJSDocSaltyCollection, longname: string, kind: PageKind, sourceLink?: DocletBlocksOptions['sourceLink'], resolveLink?: DocletBlocksOptions['resolveLink']): Page | null; /** * Build a single class page; returns null if the class view cannot be built. * Thin alias over {@link buildContainerPage} with `kind: 'class'`. */ declare function buildClassPage(collection: TJSDocSaltyCollection, longname: string, sourceLink?: DocletBlocksOptions['sourceLink']): Page | null; /** * Build the synthetic "Globals" {@link ContainerView} + its slug, or `null` when * there are no qualifying global-scope symbols. This is the view-building half of * {@link buildGlobalsPage}, split out so `generateSite` can register the globals * page into the link registry during its dedup pass before any body is rendered. */ declare function buildGlobalsView(collection: TJSDocSaltyCollection, flavor?: 'jsdoc' | 'typedoc'): { view: ContainerView; slug: string; } | null; /** * Build the single aggregated "Globals" page: every global-scope symbol that * does not already get its own page (functions, members, constants, enums, * events) rendered as a member section on one synthetic container. Returns * `null` when there are no qualifying globals. Renders through * {@link renderContainerPage}; pass `resolveLink` to resolve cross-references in * the globals' prose. */ declare function buildGlobalsPage(collection: TJSDocSaltyCollection, sourceLink?: DocletBlocksOptions['sourceLink'], resolveLink?: DocletBlocksOptions['resolveLink']): Page | null; /** Section label tutorial/guide nav entries are grouped under. */ declare const TUTORIALS_SECTION = "Tutorials"; /** * Fallback section label for a doc page that carries no `group` (no frontmatter * group, no directory group, no `defaultDocGroup`). Docs that DO carry a group * become their own section under that group's label. */ declare const DOCS_SECTION = "Docs"; /** * Default sidebar section order, used when the consumer supplies no * `sectionOrder`. Includes forward-looking sections (Externals, Events) that * have no pages yet; empty sections are simply skipped. A section absent from * the effective order is omitted from the sidebar entirely. */ declare const DEFAULT_SECTION_ORDER: readonly string[]; /** Built-in `id` for the home menu entry (resolved against the README home page). */ declare const HOME_MENU_ID = "home"; /** Built-in `id`s for the source-files menu entry (`source` preferred, `sourceFile` accepted). */ declare const SOURCE_MENU_IDS: readonly ["source", "sourceFile"]; /** * A single sidebar **menu** entry from the consumer's `menu` config. The menu is * a top region above the API sections (see {@link assembleNav}); each entry is a * built-in link (`home` / `source`) or an external link, and renders with an * icon. * * - `id === 'home'` → the README home page (icon defaults to `house`). * - `id === 'source'` (or `sourceFile`) → the Source Files index (icon defaults * to `code-xml`). * - otherwise → an external link to `link` (or `href`), opening in a new tab. * * `icon` is a prefixed `source:code` string — `simpleicons:` (CDN) or * `lucide:` (bundled set), see {@link NavNode.icon}. When omitted it * defaults by role: home→`lucide:home`, source→`lucide:code-xml`, * external→`lucide:external-link`. * * `target` and `class` are optional link presentation: `target` overrides the * link target (an external entry still defaults to `_blank`), and `class` adds * CSS class(es) to the rendered link. */ interface MenuItem { /** Built-in id (`home` / `source`), or — for an external link — its Simple Icons slug. */ id?: string; /** Display text. Defaults to the built-in label or the link URL. */ title?: string; /** External link URL. */ link?: string; /** External link URL — accepted as an alias for {@link MenuItem.link}. */ href?: string; /** Icon name/slug for the entry. */ icon?: string; /** * Link `target` attribute (e.g. `_blank`, `_self`). Overrides the default — an * external link still defaults to `_blank` when this is omitted. */ target?: string; /** Extra CSS class(es) merged onto the rendered menu link. */ class?: string; } /** Inputs for {@link assembleNav}: the per-source nav pieces + the section order. */ interface AssembleNavOptions { /** API pages, grouped into sections by kind and alphabetized within each. */ apiPages?: readonly Page[]; /** Tutorial nav entries (kept in tree order under "Tutorials"). */ tutorials?: readonly NavNode[]; /** * Doc nav entries (the docs directory). Each is bucketed into a section by its * OWN `group` (a doc with no group falls into {@link DOCS_SECTION}); entries * keep their input order within a section (not alphabetized), like tutorials. * The doc-group section labels render in {@link AssembleNavOptions.docGroups} * order — after the API sections, before Source Files — when those labels are * not already pinned by `sectionOrder`. */ docs?: readonly NavNode[]; /** * Top-level doc-group display order — the doc-group slice of the generalized * sidebar `sectionOrder`. Doc-group section labels listed here render in this * order; doc groups not listed are appended after them in first-seen order. * Folded into the effective section order alongside `sectionOrder` (which * stays the authority for any label it lists). */ docGroups?: readonly string[]; /** Home nav entry — always first, ungrouped, regardless of `sectionOrder`. */ home?: NavNode; /** "Source Files" nav entry — always last, ungrouped, regardless of `sectionOrder`. */ source?: NavNode; /** * Top-level group labels to render, in order — one unified list mixing * `@category` names, doc-group names, and kind labels (e.g. * `["Getting Started", "Core", "Classes", "Globals"]`). For *kind* labels this * acts as BOTH a filter and an ordering (a kind label absent here is dropped). * Category/doc groups it omits are NOT dropped — they render after the listed * labels, alphabetically (doc groups pinned by `docGroups` keep that order). * Defaults to {@link DEFAULT_SECTION_ORDER}. Ignored when * {@link AssembleNavOptions.menu} is set. */ sectionOrder?: readonly string[]; /** * Top-region sidebar menu, in order — rendered above the API sections, with a * divider between. When set, it OWNS the home/source links: the auto Home * (first) and Source Files (last) entries are suppressed and render only if * listed here (`id: 'home'` / `id: 'source'`). External links appear inline. * The API sections below are still ordered by `sectionOrder`. Each entry * carries an icon. See {@link MenuItem}. */ menu?: readonly MenuItem[]; /** * Club related entries within each section into a one-level parent/child tree, * grouping by the path segment before the first `/` in their label (e.g. * `queue`, `queue/Queue`, `queue/types` collapse under a `queue` parent). A * prefix shared by only one entry is left flat (so a lone `strings/format` * keeps its full label). See {@link clubNavTree}. Off by default. */ clubSidebarItems?: boolean; /** * Document-model flavor. `'typedoc'` resolves kind labels with TypeDoc names * (`Type Aliases`) and defaults to {@link TYPEDOC_SECTION_ORDER} when no * `sectionOrder` is given; `'jsdoc'` (default) keeps the JSDoc labels + * {@link DEFAULT_SECTION_ORDER}. */ flavor?: 'jsdoc' | 'typedoc'; } /** * Club a section's entries into a one-level parent/child tree by the path * segment before the first `/` in each label. A prefix shared by ≥2 entries * becomes a non-navigable parent branch whose children are the entries with * their prefix stripped (`queue/Queue` → `Queue`); the entry that IS the bare * prefix (`queue`) becomes an `index` child, sorted first. A prefix with a * single entry is NOT clubbed — it stays flat with its original label (so a lone * `strings/format` is untouched), but its `order` still participates in the * parent-level sort below. * * Order-aware (decisions 4/5): a clubbed parent sorts by the **min `order`** of * its members (so `@order 1` on any member floats the whole parent up), and * children sort by `order` then the `index`-first tiebreak then name (so * `@order` can pull a sibling ahead of the bare-prefix `index` child). With no * `@order`/`order=` anywhere every effective order is `+∞`, so parents fall back * to first-seen order and children to `index`-first-then-alphabetical — i.e. an * unordered section is byte-identical to before. */ declare function clubNavTree(nodes: readonly NavNode[]): NavNode[]; /** * Assemble the final sidebar nav from its parts, honoring `sectionOrder`. * * Every entry carries a full `group` **path** — an `@category` tag (API pages) * or `frontmatter.group` (docs/tutorials), falling back to the kind section * label for untagged API symbols. The path's first segment is the top-level * group (a bold, non-collapsible title); deeper `/`-segments become nested, * collapsible branch nodes ({@link buildGroupTree}). So `@category Core/Parsing` * nests its page under `Core` ▸ `Parsing`. * * Top-level groups render in the effective order: `sectionOrder` labels first, * in that order (a *kind* label it omits is dropped — today's filter behavior); * then category/doc groups it doesn't list, appended alphabetically (doc groups * named in `docGroups` keep that explicit order). Within a deepest group, API * entries sort by `frontmatter.order` then title (a kind-only section stays * purely alphabetical, as before); tutorial/doc entries keep their tree order. * Home (if any) is always emitted first and Source Files (if any) always last; * neither is controlled by `sectionOrder`. Any page kind with no section mapping * is collected under "Other" and appended last (a safety net; in practice empty). * * Each node's `order` mirrors its emission position (section index), so the * monotonic-order invariant holds; the sidebar itself renders in array order. * * Backward compatible: a collection with no `@category`/group and a kind-only * `sectionOrder` produces byte-identical nav to the pre-nesting builder. */ declare function assembleNav(options: AssembleNavOptions): NavNode[]; /** * Nav grouped by page kind in the default section order. Thin wrapper over * {@link assembleNav} kept for callers that only need the API section nav. */ declare function buildNav(pages: readonly Page[]): NavNode[]; /** * `{timestamp}-{hash}` where the hash is a stable digest over slugs + bodies. * The timestamp prefix changes per build; the hash suffix is content-stable. */ declare function computeBuildId(pages: readonly Page[]): string; /** * README + tutorial + docs pages. * * JSDoc surfaces several kinds of free-form prose alongside the API: the project * README (`opts.readme`, already rendered to HTML by JSDoc's markdown plugin), * tutorials (the `--tutorials` directory, resolved into a tree of raw Markdown / * HTML documents), and — new in v5 — a docs directory the bridge walks. All * become ordinary {@link Page}s so they flow through the same MDX → dwar render * path as class pages — same chrome, TOC, heading anchors, and search indexing. * * The README becomes the site home page (slug `''` → `index.html`); tutorials * become guide pages under `tutorials/`, grouped under "Tutorials" in the * nav with their resolved hierarchy flattened in document order. * * Tutorials and docs share one builder ({@link buildDocPages}) fed by the * exported {@link DocInput} shape: the docs front-end reads raw files (frontmatter * still embedded), while the tutorial front-end adapts the existing * {@link TutorialInput} tree via {@link tutorialsToDocInputs}. The adapter path * supplies metadata explicitly and disables frontmatter parsing, so legacy * tutorial output stays byte-identical. */ /** * A tutorial, normalized away from JSDoc's `Tutorial` class so setu doesn't * depend on JSDoc internals. The bridge walks JSDoc's resolver tree and hands * setu this plain shape. */ interface TutorialInput { /** Identifier — the source filename without its extension. */ name: string; /** Display title (from a `.json` config, else the file name). */ title: string; /** Raw source content (Markdown or HTML, per `type`). */ content: string; /** Source format. */ type: 'markdown' | 'html'; /** Child tutorials, in resolved order. */ children?: TutorialInput[]; } /** Sidebar group label for tutorial pages. */ declare const TUTORIALS_GROUP = "Tutorials"; /** * A single doc-page input — the shared shape consumed by {@link buildDocPages}. * The docs front-end (the bridge's directory walk) emits these with frontmatter * still embedded in `content`; the tutorial front-end synthesizes them via * {@link tutorialsToDocInputs} with explicit `group`/`title`/`order` overrides. */ interface DocInput { /** Relative path, POSIX, no extension — drives slug + directory grouping. */ path: string; /** Raw content (frontmatter may still be embedded). */ content: string; type: 'markdown' | 'html'; /** Explicit override (used by the tutorial adapter). */ group?: string; title?: string; order?: number; } /** Options for {@link buildDocPages}. */ interface BuildDocPagesOptions { /** Group label assigned to a doc with no frontmatter/input/directory group. */ defaultDocGroup?: string; /** * Whether to parse + strip a leading YAML frontmatter block from each input's * `content`. The docs front-end wants this (frontmatter drives metadata); the * tutorial adapter sets it `false` so today's tutorial output stays * byte-identical (tutorial content is never frontmatter-stripped). Default * `true`. */ parseFrontmatter?: boolean; } /** * Parse a leading `---\n…\n---` YAML frontmatter block and return the parsed * `data` plus the remaining `body`. Dependency-light: a small hand-rolled parser * for the simple `key: value` (string / number / boolean) cases, which is all * the docs pipeline needs (`title`, `group`, `order`, `slug`, `hidden`, …). * * - No leading block → `{ data: {}, body: raw }`. * - Malformed / unterminated block (no closing `---`) → treated as no * frontmatter: `{ data: {}, body: raw }`. Never throws. * * The block is stripped from the body BEFORE content is converted to mdast, so * it never renders as a thematic break. */ declare function parseFrontmatter(raw: string): { data: Record; body: string; }; /** * Build the home page from the README HTML JSDoc provides in `opts.readme`. * Returns `null` when the README has no renderable content. The page lives at * the site root (slug `''`), so dwar writes it to `index.html`. */ declare function buildReadmePage(readmeHtml: string, pkg?: { name?: string; }, resolveLink?: (target: string) => ResolvedLink | null): Page | null; /** A resolved `@tutorial`/`{@link}` cross-reference: page href + display title. */ interface ResolvedTutorial { href: string; title: string; } /** A resolver: a cross-reference name → its target, or `null` when unknown. */ type CrossRefResolver = (name: string) => ResolvedTutorial | null; /** * Build a `@tutorial ` resolver over the tutorial tree, so a tag links to * the guide page setu generates for it. Walks the same hierarchy * {@link buildTutorialPages} flattens, keying each tutorial by its `name` (the * identifier the tag references). The href and slug share `slugifyPath`, so they * always agree with the emitted page. */ declare function makeTutorialResolver(tutorials: readonly TutorialInput[]): CrossRefResolver; /** * Build a `@tutorial`/`{@link}` resolver over the docs directory, the docs * counterpart of {@link makeTutorialResolver}. Each doc is keyed by its **slug** * — its canonical address (a frontmatter `slug:` override, else the path) — so * `@tutorial guides/advanced` links to that page. Derives the slug/title through * the same {@link deriveDocMeta} the page builder uses, so the resolved href can * never drift from the emitted page. The home page (slug `''`) is not linkable. */ declare function makeDocResolver(docs: readonly DocInput[]): CrossRefResolver; /** * Chain cross-reference resolvers, trying each in order and returning the first * hit (so an earlier resolver wins a name collision). Skips absent resolvers and * returns `undefined` when none are active, matching the optional `resolveTutorial` * the render path threads through. */ declare function composeResolvers(...resolvers: Array): CrossRefResolver | undefined; /** * Build guide/doc pages + flat nav entries from a list of {@link DocInput}. * Shared by the tutorial adapter ({@link tutorialsToDocInputs}) and the docs * front-end. Per input: * * - Parse + strip leading YAML frontmatter from `content` (unless * `opts.parseFrontmatter === false`), so the block never renders as a * thematic break. * - `slug` = `data.slug` ?? slugify the `path` (split on `/`, `slugifyPath` per * segment, join — no prefix). * - `group` = `data.group` ?? `input.group` ?? the directory path derived from * `path` (humanized per segment) ?? `opts.defaultDocGroup`. * - `title` = `data.title` ?? `input.title` ?? humanized basename of `path`. * - `order` = `data.order` ?? `input.order`. * - `kind: 'guide'`; `hidden` honored. A root `index` path → slug `''`, * `kind: 'index'` (the home page). * * A `NavNode` is emitted per page carrying `label`/`slug`/`group`/`order`; nav * is skipped for `hidden` pages and for the home page (whose nav entry is added * elsewhere, matching `buildReadmePage`). */ declare function buildDocPages(docs: readonly DocInput[], opts?: BuildDocPagesOptions, resolveLink?: (target: string) => ResolvedLink | null): { pages: Page[]; nav: NavNode[]; }; /** * Adapt the tutorial tree into {@link DocInput}s for {@link buildDocPages}, * depth-first (parent before its children — JSDoc's resolved order). Each * tutorial gets the path `tutorials/` (so slugify yields exactly today's * `tutorials/`), its title, source type/content, and an incrementing * `order`. * * The sidebar **group** mirrors the tutorial hierarchy (issue #253): a tutorial * that has sub-tutorials opens a nested group named after itself * (`Tutorials/`), with its own page as the first entry; a leaf sits * directly in its parent's group. {@link buildGroupTree} turns these `/`-paths * into nested, collapsible nav branches. A flat tutorial set still yields one * flat "Tutorials" group, and page slugs/frontmatter/bodies are unchanged either * way — only the nav grouping reflects the hierarchy. */ declare function tutorialsToDocInputs(tutorials: readonly TutorialInput[]): DocInput[]; /** * Build guide pages + nav entries from the tutorial tree. The hierarchy drives * the sidebar grouping (issue #253): a parent tutorial becomes a nested, * collapsible group (see {@link tutorialsToDocInputs}); a flat tutorial set * stays a single "Tutorials" group. * * Expressed via the shared {@link buildDocPages} builder. Frontmatter parsing is * disabled so a tutorial whose content begins with `---` keeps its exact output; * page slugs / frontmatter / bodies are unchanged — only the nav grouping now * reflects the hierarchy. */ declare function buildTutorialPages(tutorials: readonly TutorialInput[], resolveLink?: (target: string) => ResolvedLink | null): { pages: Page[]; nav: NavNode[]; }; /** * Source-file viewer pages + the "Source: file:line" link resolver. * * JSDoc records, per doclet, the file + line it was declared in (`meta.path`, * `meta.filename`, `meta.lineno`). When the bridge hands setu the project's * source files, this module turns each into a read-only `kind: 'source'` * {@link Page} (rendered by dwar in an editor island, not compiled as MDX), an * index page listing them all, a nav node, and a `resolve(meta)` function that * maps a doclet's `meta` back to its source page anchor. * * The module is pure: it only transforms the inputs it is given — no fs, no * cwd. Path normalization is defensive (backslashes → `/`) because the inputs * arrive pre-normalized from the bridge in Phase 5. */ /** One source file the bridge wants rendered as a viewer page. */ interface SourceFileInput { /** Absolute path on disk (used to match doclet `meta.path` + `meta.filename`). */ absPath: string; /** Project-relative path (drives the slug, title, and link labels). */ relPath: string; /** Raw file content, rendered verbatim in the editor island. */ content: string; } /** * Detect a Monaco language id from a file path's extension. Returns * `'plaintext'` for unknown or extension-less paths. Uses Monaco ids * (`javascript`/`typescript`), not the bare extension. */ declare function detectLanguage(relPath: string): string; /** A resolved "Source: file:line" link target. */ interface SourceLink { href: string; label: string; } /** Tuning for {@link buildSourceModel}. */ interface SourceModelOptions { /** * When `true`, a `Source: file:line` link points at the doclet's raw * `meta.lineno` — which, for a container documented with a leading JSDoc * block (class/interface/mixin/module/namespace/typedef), is the FIRST line of * the doc comment. The default (`false`) instead lands on the first line of the * actual declaration, skipping past the comment block, so readers see code * rather than a long comment when they follow the link. */ linkToComment?: boolean; } /** * Given a file's content and a doclet's 1-based `lineno`, return the line of the * actual declaration. JSDoc reports `meta.lineno` as the code line for most * symbols (their doclet carries a real AST `range`), but for a container * documented with a leading `/** … *\/` block the documented doclet points at * the comment's opening line (and has no `range`). When `lineno` lands on a line * that opens a block comment, advance past the closing `*\/` to the first * non-blank line — the declaration. Any other line is already code, so it's * returned unchanged. Out-of-range or unterminated input falls back to `lineno`. */ declare function firstCodeLine(content: string, lineno: number): number; /** Result of building source pages: pages, index, nav node, and a resolver. */ interface SourceModel { /** One `kind: 'source'` page per input file. */ pages: Page[]; /** The "Source Files" index page listing every source file. */ indexPage: Page; /** Nav entry pointing at the index page. */ navNode: NavNode; /** * Resolve a doclet's `meta` to its source page anchor. Returns `null` when * there is no `meta`, no matching source file, or insufficient info. */ resolve(meta: TDoclet['meta']): SourceLink | null; } /** * Turn a set of source files into viewer pages, an index page, a nav node, and * a `resolve(meta)` that maps a doclet's declaration site back to its page. */ declare function buildSourceModel(sources: readonly SourceFileInput[], options?: SourceModelOptions): SourceModel; /** Build-side options. */ interface GenerateSiteOptions { /** * Document-model flavor. `'jsdoc'` (default) keeps the JSDoc container/member * model — enums/functions/variables stay members or land on the Globals page, * sidebar kind labels are the JSDoc ones (`Typedefs`, …). `'typedoc'` matches * default TypeDoc: enums, top-level functions, variables, and type aliases each * get a standalone page in their own kind-section, with TypeDoc labels. Only * the TypeDoc bridge passes `'typedoc'`, so JSDoc output is byte-identical. */ flavor?: 'jsdoc' | 'typedoc'; /** Optional package metadata to embed in the manifest. */ pkg?: SiteManifest['pkg']; /** * Project README as HTML (JSDoc renders it from Markdown into `opts.readme`). * Rendered as the site home page (`index.html`). */ readme?: string; /** * Tutorial tree, normalized from JSDoc's `--tutorials` resolver. Rendered as * guide pages under "Tutorials", preserving the resolved order. */ tutorials?: TutorialInput[]; /** * Doc inputs from the bridge's docs-directory walk (already read off disk; * setu does no I/O). Each becomes a prose page at its clean (unprefixed) slug * via {@link buildDocPages}, grouped by its frontmatter/directory group. A root * `index.md` (`path === 'index'`) becomes the home page, overriding the README * home. A doc whose slug would shadow the home or an existing API/source/ * tutorial page is skipped (see the collision handling in `generateSite`). */ docs?: DocInput[]; /** * Top-level doc-group display order — the doc-group slice of the generalized * sidebar `sectionOrder`. Threaded into {@link assembleNav} so the doc-group * sidebar sections render in this order (after the API sections). The * companion sidebar plan generalizes this; here it simply orders the doc * groups consistently with how `sectionOrder` orders the rest. */ docGroups?: string[]; /** * Group label assigned to a doc page that carries no frontmatter/directory * group. Forwarded to {@link buildDocPages}. */ defaultDocGroup?: string; /** * Project source files to render as read-only `kind: 'source'` viewer pages. * When supplied, each class member + the class itself gets a "Source: * file:line" link resolved against these files. */ sources?: SourceFileInput[]; /** * When `true`, `Source: file:line` links point at the doclet's raw comment * line instead of the first line of the declaration. Defaults to `false` (jump * to the code). See {@link SourceModelOptions.linkToComment}. */ sourceLinkToComment?: boolean; /** * Top-level sidebar group order — ONE unified list governing `@category` * names, doc-group names, and kind labels together (e.g. * `["Getting Started", "Core", "Classes", "Globals"]`). For *kind* labels it * acts as both a filter and an ordering — a kind section omitted here is * dropped. Category/doc groups it omits are not dropped; they render after the * listed labels, alphabetically. "Home" (when a README exists) and "Source * Files" (when source pages are emitted) are always present and not controlled * by this. Defaults to `DEFAULT_SECTION_ORDER` when absent or empty. Ignored * when `menu` is set. */ sectionOrder?: string[]; /** * Full sidebar menu, in order. When set, takes precedence over `sectionOrder` * and controls the entire sidebar: Home / Source Files appear only if their * ids (`home` / `sourceFile`) are listed, sections only if named, and external * links render inline. Each entry can carry an icon. See {@link MenuItem}. */ menu?: MenuItem[]; /** * Club related sidebar entries within each section into a one-level * parent/child tree, grouping by the path segment before the first `/` (e.g. * `queue`, `queue/Queue`, `queue/types` collapse under a `queue` parent). A * prefix used by only one entry is left flat. Applies to every section, * tutorials included. Off by default. See {@link clubNavTree}. */ clubSidebarItems?: boolean; /** * Which top-level sidebar sections render as collapse toggles. `undefined` * (default) or `true` → all present sections; `false` → none; `string[]` → * only those exact labels. Resolved against the produced nav into * {@link SiteManifest.collapsibleGroups}. See utils `resolveCollapsibleSections`. */ collapsibleSidebarSections?: CollapsibleSidebarSections; /** * Site-wide code-playground enablement: `enableForAllExamples` opts every * `@example` in, and `providers` is the default provider set + order a bare * `@playground` (or `enableForAllExamples`) falls back to. The per-provider * runtime options are NOT here — they're a dwar/browser concern. When omitted, * `@playground` tags are ignored (feature off → byte-identical output). See * {@link makePlaygroundResolver}. */ playground?: PlaygroundSiteConfig; /** * Translatable-prose slot resolver (localization Phase 2). When omitted, the * build is byte-identical to before but still emits the slot template in * `manifest.slots`. When set with a `translate`, each API description/summary/ * example-caption is substituted for the active locale — this is the per-locale * "stamp". A `collect` hook is added internally regardless, to populate the * template; a caller's own `collect` (if any) also fires. See {@link stampSite}. */ slots?: SlotResolver; } /** * Build a `SiteManifest` from a JSDoc salty collection. This is the boundary * setu→dwar entry point. API pages cover the container kinds in * {@link CONTAINER_KINDS} (module/namespace/class/interface/mixin/typedef) plus * one aggregated "Globals" page for global-scope symbols that don't get their * own page; the README (home page) and tutorials are rendered when supplied via * {@link GenerateSiteOptions}. */ declare function generateSite(collection: unknown, opts?: GenerateSiteOptions): SiteManifest; /** * Stamp a site for one locale: re-run {@link generateSite} with a slot resolver * that substitutes the locale's translations (`messages`, keyed by `apiSlotKey`), * falling back to the source text for any gap. This is the per-locale half of the * two-phase build — the same doclet walk, re-serialized with translated prose. * `messages` carries the `api.*` slot translations for the locale; chrome strings * are handled separately by rang/bhasha at render time. */ declare function stampSite(collection: unknown, messages: Readonly<Record<string, string>>, opts?: GenerateSiteOptions): SiteManifest; /** * Backwards-compatible thin wrapper around `generateSite` that returns each * page body as a string. Kept so the legacy `generateMdx` test/import surface * keeps working until callers are migrated. */ declare function generateMdx(collection: unknown): string[]; export { type AssembleNavOptions, type BuildDocPagesOptions, type CrossRefResolver, DEFAULT_SECTION_ORDER, DOCS_SECTION, type DocInput, type GenerateSiteOptions, HOME_MENU_ID, KNOWN_PROVIDERS, type MenuItem, type PlaygroundOpts, type PlaygroundSiteConfig, type PlaygroundSpec, type ResolvedTutorial, SOURCE_MENU_IDS, SlotCollector, type SlotResolver, type SourceFileInput, type SourceModel, type SourceModelOptions, TUTORIALS_GROUP, TUTORIALS_SECTION, type TutorialInput, assembleNav, buildClassPage, buildContainerPage, buildDocPages, buildGlobalsPage, buildGlobalsView, buildNav, buildReadmePage, buildSourceModel, buildTutorialPages, clubNavTree, composeResolvers, computeBuildId, detectLanguage, enumerateClassLongnames, enumerateLongnamesByKind, extractHeadings, firstCodeLine, generateMdx, generateSite, makeDocResolver, makePlaygroundResolver, makeSlotTranslator, makeTutorialResolver, parseFrontmatter, parsePlaygroundSpec, renderContainerPage, resolvePlaygroundOpts, resolveSlotText, splitLongnameForSlug, stampSite, tutorialsToDocInputs };