@clean-jsdoc-theme/utils
@clean-jsdoc-theme/utils is the shared contract every other package in the pipeline imports. It defines the type boundary between setu and dwar, the slug rules both sides use so nav links and heading anchors always agree, and the pure opts-validation + build-report logic the entry-point bridges run.
It contains no rendering and no I/O — just the interfaces, the Zod schemas, and a handful of small pure functions that the rest of the system is built around.
If you just want to use the theme, you never install this package. It's an internal building block. See the Packages section for the pieces you actually install, and Configuration for options.
Why it's a separate package
The whole pipeline is deliberately one-way — setu turns doclets into a SiteManifest, dwar renders that manifest — and the two halves must never grow a hidden coupling. Pulling the boundary types into their own package gives the project two things:
- One source of truth for the boundary. setu emits a
SiteManifest, dwar consumes the sameSiteManifesttype, and neither imports the other. The shape is defined once here so the build side and the render side can't drift. You can see this in the boundary barrel,site/index.ts, whose own doc comment calls it "the boundary contract between setu (build) and dwar (render)." - Browser-safe, so rang can import it too. The Preact component library (rang) runs in the browser, so the contract it shares has to be node-free. The slug rules, base-path helpers, diagnostics model, and build-report formatter here use only web-platform globals (
URL,TextEncoder,fetch,AbortController) — neverfs,Buffer, ornode:*. The only networked dependency (Google Fonts existence) and the only node-only dependency (gzip sizing) are injected by the caller rather than imported, which is what keeps the package importable in a browser.
The top barrel, src/index.ts, re-exports everything, and package.json exposes a single entry point — so every consumer imports from @clean-jsdoc-theme/utils (there are no subpath exports).
What's inside
The package splits into two surfaces: the site/ contracts and the config/ logic, plus the JSDoc doclet schema.
site/ — the setu ↔ dwar contract
The boundary objects and the rules both sides must share. Verified exports include:
SiteManifest,Page,NavNode,SearchEntry— what setu hands to dwar: the pages, the nav tree, the search index, and abuildIdfor cache busting. The manifest is self-contained on purpose: dwar should never re-read the doclet database. (manifest.ts,page.ts)Frontmatter,PageKind,Heading— per-page metadata, the page kind (class,module,guide,source, …) that drives layout, and the pre-extracted headings the TOC island renders. (page.ts)RenderOptions,RenderResult,OutputFile,RenderError— the input and output ofdwar.render.render()is pure and returns files in memory; the doc comment notes there is intentionally noembedSearchIndexflag. (render.ts)ThemeConfig,ThemeTokens,ThemeColors,ComponentOverrides— the theme contract: color tokens, fonts, Shiki themes, component slot overrides, base path, copy-page and prev/next config, custom CSS/JS. (theme.ts)SiteName,SiteLogoplus the helperssiteNameText()andresolveSiteLogo()— the plain-text-or-logo-set site identity. (site-name.ts)IslandName,IslandPropsMap— the registry of interactive islands (sidebar,toc,cmdk,copy-page, …) and the type-safe prop bag for each, shared between server render and hydration. (islands.ts)slugifyHeading(),slugifyPath(),slugifySourcePath()— the GitHub-style slug rules. Both setu (sidebar / TOC generation) and dwar (rendered anchor IDs) import from here so anchors and links always match; the file calls this out as addressing "Risk R4." (slug-rules.ts)normalizeBasePath(),withBase()— pure, browser-safe, fail-safe helpers for serving the site from a sub-directory. (base-path.ts)
config/ — pure opts validation + build report
The logic the entry-point bridges run before and after a build:
validateThemeOpts()— the orchestrator. Takes a raw opts object, runs thesiteNameandfontsvalidators plus an unknown-key policy into a singleDiagnosticBag, and returns normalized values. Never throws — strict-mode enforcement is the caller's job viadiagnostics.hasErrors(). (validate-opts.ts)DiagnosticBag,Diagnostic,formatDiagnostics()— the reporting spine: a structured, level-tagged (error/warning/info) finding model that's pure and node-free. (diagnostics.ts)createGoogleFontResolver(),FontResolver,FontExistence,FetchLike— the injection pattern that keeps utils browser-safe. The one networked check (does this Google Font exist?) is built around an injectedfetchand is fail-open: anything ambiguous resolves to'unknown'so an offline build never breaks. (google-fonts.ts)formatBuildReport()— the Next.js-style build summary. Byte sizes come fromTextEncoder(neverBuffer); gzip sizing is an injectedgzipSizer, again so utils stays node-free. (report.ts)- The Zod schemas —
THEME_OPT_KEYS,SiteNameSchema,FontsSchema,MenuSchema,CopyPageConfigSchema, and friends — the recognized theme-option surface, expressed as Zod so failures carry a structuredpath+message. (opts-schema.ts)
The doclet schema
doclet-schema.ts (TDoclet and friends) and salty.ts (TJSDocSaltyCollection) carry the JSDoc-side types that setu reads. These are re-exported from the top barrel too, so setu's doclet processing imports them from the same package.
How the injection pattern works
This is the detail that lets a config package stay browser-safe. utils never imports fetch or zlib; the caller passes them in:
validateThemeOpts({ … fontResolver })takes a resolver the bridge builds withcreateGoogleFontResolver(). Without it, font existence checks are skipped gracefully and the build proceeds.formatBuildReport({ … gzipSizer })takes the gzip function. The gzip column appears only when a sizer is supplied; the bridge passes(b) => zlib.gzipSync(b).length.
Both google-fonts.ts and report.ts state this in their module doc comments: the networked / node-only dependency "is never imported, only the optional [resolver/sizer] injected by the caller."
Read the source
The maintainer wants you sent to the code — start here:
- Package directory: packages/utils · packages/utils/src
- Barrels:
src/index.ts·src/site/index.ts(the boundary barrel) ·src/config/index.ts - The contracts:
manifest.ts·page.ts·render.ts·theme.ts·islands.ts·slug-rules.ts - The config logic:
validate-opts.ts·opts-schema.ts·report.ts·google-fonts.ts·diagnostics.ts
Next
- utils Examples — concrete snippets using these types.
- setu Overview — the package that produces the
SiteManifest. - dwar Overview — the package that consumes it.