Skip to content

The presets

This guide shows you the shared configs a workspace extends for TypeScript, oxlint, oxfmt and TypeDoc, what each one sets, and where each goes.

Compile with the TypeScript configs

@wtf/project-typescript has four configs. Each is a JSON file that tsc reads as it is:

SubpathWhat it sets
./baseESNext on NodeNext, strict, with source maps, declarations and @wtf/source
./strict./base plus noUncheckedIndexedAccess and exactOptionalPropertyTypes, with no ambient types: use it for a library
./testWhat tests add on top of the project’s own config: no emit, no references, Node’s types, and JSON
./bundlerWhat code compiled by a bundler adds: Bundler resolution, React’s JSX and the DOM

./base sets customConditions: ["@wtf/source"], so a package in the workspace resolves to the other packages’ TypeScript source, not to a stale build. SOURCE_CONDITION of @wtf/project-resolve/condition names the same condition for code, as the Vite and Vitest configs use it. verbatimModuleSyntax and erasableSyntaxOnly keep the source runnable by Node, which only strips the types. tsc keeps the comments in what it emits, so a published .d.ts carries its JSDoc.

./test and ./bundler extend nothing, so list each one after the config it adds to:

{
"extends": ["./tsconfig.json", "@wtf/project-typescript/test"],
"compilerOptions": {
"rootDir": ".",
"tsBuildInfoFile": "node_modules/.tmp/tsconfig.test.tsbuildinfo"
},
"include": ["src", "test"]
}

A test config references no project and emits nothing. It reads each workspace package from its source through @wtf/source, so pnpm run typecheck (tsc -b tsconfig.test.json) builds no package and writes no dist. The build checks each package with that package’s own config. A test config names its tsBuildInfoFile in node_modules/.tmp; otherwise the build info would land in the dist folder that outDir names.

Lint and format with oxlint and oxfmt

@wtf/project-oxlint/config turns on the typescript, unicorn and import plugins. Correctness rules are errors, suspicious and perf rules are warnings, and import/no-cycle, eqeqeq and typescript/no-explicit-any are errors. Run oxlint with --deny-warnings, so a warning fails the lint just as an error does.

@wtf/project-oxfmt/config formats at 100 columns, with single quotes, no semicolons and no trailing commas. It leaves the build, the lockfile, the changelog and the generated reference alone.

You can add rules of your own on top of the preset:

import preset from '@wtf/project-oxlint/config'
import { defineConfig } from 'oxlint'
export default defineConfig({ extends: [preset], rules: { 'no-console': 'error' } })

Build the reference with TypeDoc

typedocConfig() of @wtf/project-typedoc/config is the config for a docs package, packages/docs. It writes the reference of the workspace’s packages as markdown in api/, with an llms.txt built from the package’s llms-template.md. Each source links to its file in your repository, and any warning fails the build.

import { typedocConfig } from '@wtf/project-typedoc/config'
export default typedocConfig({
docs: import.meta.url,
repository: 'https://gitlab.com/wetryfun/project/project',
entryPoints: ['../core', '../cli']
})

The revision each source links to is the first of these that applies:

  1. --gitRevision <ref>, passed to typedoc;
  2. the commit of HEAD;
  3. in a repo with no commit yet, its branch; outside git, main.

./plugins holds the plugins the preset gives TypeDoc: cleanGeneratedMarkdown, preventMediaFiles and linkTrackedSources. Add your own in plugins, each a function of TypeDoc’s application or a module by its name: they run after the preset’s, and before typedoc-plugin-llms-txt and typedoc-plugin-markdown. TypeDoc and its two plugins are peers.

import { typedocConfig } from '@wtf/project-typedoc/config'
import type { TypedocPlugin } from '@wtf/project-typedoc/plugins'
/** A plugin of your own, which TypeDoc calls with its application, once. */
const notice: TypedocPlugin = (app) => {
app.renderer.on('endRender', () => console.log('The reference is written.'))
}
export default typedocConfig({
docs: import.meta.url,
repository: 'https://gitlab.com/wetryfun/project/project',
entryPoints: ['../core'],
plugins: [notice]
})