├── .changeset ├── README.md └── config.json ├── .github └── workflows │ ├── autofix.yml │ ├── integration.yml │ └── release.yml ├── .gitignore ├── .node-version ├── .prettierignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── docs ├── README.md ├── astro.config.ts ├── package.json ├── public │ └── favicon.svg ├── src │ ├── assets │ │ └── showcase │ │ │ ├── bunshi.org.png │ │ │ ├── chord.vercel.app.png │ │ │ ├── dgmjs.dev.png │ │ │ ├── discord-components.js.org.png │ │ │ ├── docs.seyfert.dev.png │ │ │ ├── docs.shepherdpro.com.png │ │ │ ├── echo-d.net.png │ │ │ ├── eddienubes.github.io-sagetest.png │ │ │ ├── fevol.github.io-obsidian-typings.png │ │ │ ├── lilybird.didas.dev.png │ │ │ └── vinoth.info-react-sketch-canvas.png │ ├── content.config.ts │ ├── content │ │ └── docs │ │ │ ├── configuration.md │ │ │ ├── getting-started.mdx │ │ │ ├── guides │ │ │ ├── frontmatter.mdx │ │ │ └── multiple-instances.md │ │ │ ├── index.mdx │ │ │ └── resources │ │ │ ├── showcase.mdx │ │ │ └── starlight.mdx │ ├── env.d.ts │ └── styles │ │ └── custom.css └── tsconfig.json ├── eslint.config.mjs ├── example ├── README.md ├── astro.config.ts ├── astro.multiple-entrypoints.config.ts ├── astro.multiple-plugins.config.ts ├── astro.packages-entrypoints.config.ts ├── package.json ├── public │ └── favicon.svg ├── src │ ├── assets │ │ └── houston.webp │ ├── content.config.ts │ ├── content │ │ └── docs │ │ │ ├── .gitignore │ │ │ ├── guides │ │ │ └── example.md │ │ │ └── index.mdx │ ├── env.d.ts │ ├── plugins │ │ └── frontmatter.js │ └── styles │ │ └── custom.css └── tsconfig.json ├── fixtures ├── basics │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Bar.ts │ │ ├── Baz.ts │ │ ├── Foo.ts │ │ ├── functions.ts │ │ ├── index.ts │ │ ├── module.ts │ │ ├── noDocs.ts │ │ ├── noExports.ts │ │ ├── shared.ts │ │ └── types.ts │ └── tsconfig.json └── packages │ ├── package.json │ ├── packages │ ├── bar │ │ ├── package.json │ │ ├── src │ │ │ ├── functions.ts │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── typedoc.json │ └── foo │ │ ├── package.json │ │ ├── src │ │ ├── functions.ts │ │ └── index.ts │ │ ├── tsconfig.json │ │ └── typedoc.json │ ├── tsconfig.base.json │ └── tsconfig.json ├── package.json ├── packages └── starlight-typedoc │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── eslint.config.mjs │ ├── index.ts │ ├── libs │ ├── logger.ts │ ├── markdown.ts │ ├── starlight.ts │ ├── theme.ts │ └── typedoc.ts │ ├── package.json │ ├── playwright.config.ts │ ├── tests │ ├── e2e │ │ ├── basics │ │ │ ├── asides.test.ts │ │ │ ├── content.test.ts │ │ │ ├── pagination.test.ts │ │ │ ├── sidebar.test.ts │ │ │ └── slug.test.ts │ │ ├── fixtures │ │ │ └── DocPage.ts │ │ ├── packages │ │ │ ├── content.test.ts │ │ │ └── sidebar.test.ts │ │ ├── plugins │ │ │ └── sidebar.test.ts │ │ └── test.ts │ └── unit │ │ ├── sidebar.test.ts │ │ └── typedoc.test.ts │ └── vitest.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { "repo": "HiDeoo/starlight-typedoc" } 6 | ], 7 | "commit": false, 8 | "access": "public", 9 | "baseBranch": "main", 10 | "updateInternalDependencies": "patch", 11 | "ignore": ["starlight-typedoc-docs", "starlight-typedoc-example", "@starlight-typedoc/fixtures-*"] 12 | } 13 | -------------------------------------------------------------------------------- /.github/workflows/autofix.yml: -------------------------------------------------------------------------------- 1 | name: autofix.ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_call: 11 | 12 | permissions: 13 | contents: read 14 | 15 | concurrency: 16 | cancel-in-progress: true 17 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request_target' && github.head_ref || github.ref }} 18 | 19 | jobs: 20 | autofix: 21 | name: Format code 22 | runs-on: ubuntu-latest 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | 27 | - name: Install pnpm 28 | uses: pnpm/action-setup@v4 29 | with: 30 | version: 8.6.1 31 | 32 | - name: Install Node.js 33 | uses: actions/setup-node@v4 34 | with: 35 | cache: pnpm 36 | node-version: 18 37 | 38 | - name: Install dependencies 39 | run: pnpm install 40 | 41 | - name: Format code 42 | run: pnpm format 43 | 44 | - name: Run autofix 45 | uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c 46 | with: 47 | fail-fast: false 48 | -------------------------------------------------------------------------------- /.github/workflows/integration.yml: -------------------------------------------------------------------------------- 1 | name: integration 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_call: 11 | 12 | concurrency: 13 | cancel-in-progress: true 14 | group: ${{ github.workflow }}-${{ github.event_name == 'pull_request_target' && github.head_ref || github.ref }} 15 | 16 | jobs: 17 | lint_test: 18 | name: Lint & Test 19 | runs-on: ubuntu-latest 20 | steps: 21 | - name: Checkout 22 | uses: actions/checkout@v4 23 | 24 | - name: Install pnpm 25 | uses: pnpm/action-setup@v4 26 | with: 27 | version: 8.6.1 28 | 29 | - name: Install Node.js 30 | uses: actions/setup-node@v4 31 | with: 32 | cache: pnpm 33 | node-version: 18 34 | 35 | - name: Install dependencies 36 | run: pnpm install 37 | 38 | - name: Generates docs TypeScript types 39 | run: pnpm astro sync 40 | working-directory: docs 41 | 42 | - name: Generates example TypeScript types 43 | run: pnpm astro sync 44 | working-directory: example 45 | 46 | - name: Lint 47 | run: pnpm lint 48 | 49 | - name: Test 50 | run: pnpm test 51 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | changeset: 10 | name: Changeset 11 | if: ${{ github.repository_owner == 'hideoo' }} 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: write 15 | id-token: write 16 | pull-requests: write 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Install pnpm 24 | uses: pnpm/action-setup@v4 25 | with: 26 | version: 8.6.1 27 | 28 | - name: Install Node.js 29 | uses: actions/setup-node@v4 30 | with: 31 | cache: pnpm 32 | node-version: 18 33 | 34 | - name: Install dependencies 35 | run: pnpm install 36 | 37 | - name: Create Release Pull Request or Publish 38 | uses: changesets/action@v1 39 | with: 40 | version: pnpm run version 41 | publish: pnpm changeset publish 42 | commit: 'ci: release' 43 | title: 'ci: release' 44 | env: 45 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .astro 2 | .DS_Store 3 | .eslintcache 4 | .idea 5 | .next 6 | .turbo 7 | .vercel 8 | .vscode/* 9 | !.vscode/extensions.json 10 | !.vscode/launch.json 11 | !.vscode/settings.json 12 | !.vscode/tasks.json 13 | .vscode-test 14 | .vscode-test-web 15 | *.local 16 | *.log 17 | *.pem 18 | *.tsbuildinfo 19 | build 20 | coverage 21 | dist 22 | dist-multiple-entrypoints 23 | dist-ssr 24 | lerna-debug.log* 25 | logs 26 | next-env.d.ts 27 | node_modules 28 | npm-debug.log* 29 | out 30 | pnpm-debug.log* 31 | releases 32 | test-results 33 | yarn-debug.log* 34 | yarn-error.log* 35 | -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- 1 | v18.17.1 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .astro 2 | .changeset 3 | .github/blocks 4 | .next 5 | .vercel 6 | .vscode-test 7 | .vscode-test-web 8 | build 9 | coverage 10 | dist 11 | dist-ssr 12 | out 13 | pnpm-lock.yaml 14 | example/src/content/docs/api* 15 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.useFlatConfig": true, 3 | "eslint.validate": [ 4 | "javascript", 5 | "javascriptreact", 6 | "typescript", 7 | "typescriptreact", 8 | "html", 9 | "vue", 10 | "markdown", 11 | "astro" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023-present, HiDeoo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | packages/starlight-typedoc/README.md -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 |
2 |

starlight-typedoc 📚

3 |

Starlight plugin to generate documentation from TypeScript using TypeDoc.

4 |
5 | 6 |
7 | 8 | Integration Status 9 | 10 | 11 | License 12 | 13 |
14 |
15 | 16 | ## Docs 17 | 18 | Run the docs locally using [pnpm](https://pnpm.io): 19 | 20 | ```shell 21 | pnpm run dev 22 | ``` 23 | 24 | ## License 25 | 26 | Licensed under the MIT License, Copyright © HiDeoo. 27 | 28 | See [LICENSE](https://github.com/HiDeoo/starlight-typedoc/blob/main/LICENSE) for more information. 29 | -------------------------------------------------------------------------------- /docs/astro.config.ts: -------------------------------------------------------------------------------- 1 | import starlight from '@astrojs/starlight' 2 | import { defineConfig } from 'astro/config' 3 | 4 | export default defineConfig({ 5 | integrations: [ 6 | starlight({ 7 | customCss: ['./src/styles/custom.css'], 8 | editLink: { 9 | baseUrl: 'https://github.com/HiDeoo/starlight-typedoc/edit/main/docs/', 10 | }, 11 | sidebar: [ 12 | { 13 | label: 'Start Here', 14 | items: [ 15 | { label: 'Getting Started', link: '/getting-started/' }, 16 | { label: 'Configuration', link: '/configuration/' }, 17 | ], 18 | }, 19 | { 20 | label: 'Guides', 21 | items: [ 22 | { label: 'Multiple Instances', link: '/guides/multiple-instances/' }, 23 | { label: 'Frontmatter', link: '/guides/frontmatter/' }, 24 | ], 25 | }, 26 | { 27 | label: 'Resources', 28 | items: [ 29 | { label: 'Showcase', link: '/resources/showcase/' }, 30 | { label: 'Plugins and Tools', link: '/resources/starlight/' }, 31 | ], 32 | }, 33 | { label: 'Demo', link: 'https://starlight-typedoc-example.vercel.app/api/functions/dothingc/' }, 34 | ], 35 | social: { 36 | blueSky: 'https://bsky.app/profile/hideoo.dev', 37 | github: 'https://github.com/HiDeoo/starlight-typedoc', 38 | }, 39 | title: 'Starlight TypeDoc', 40 | }), 41 | ], 42 | }) 43 | -------------------------------------------------------------------------------- /docs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starlight-typedoc-docs", 3 | "version": "0.17.0", 4 | "license": "MIT", 5 | "description": "Starlight plugin to generate documentation from TypeScript using TypeDoc.", 6 | "author": "HiDeoo (https://hideoo.dev)", 7 | "type": "module", 8 | "scripts": { 9 | "dev": "astro dev", 10 | "build": "astro build", 11 | "preview": "astro preview", 12 | "astro": "astro", 13 | "lint": "eslint . --cache --max-warnings=0" 14 | }, 15 | "dependencies": { 16 | "@astrojs/starlight": "^0.32.0", 17 | "@hideoo/starlight-plugins-docs-components": "^0.4.0", 18 | "astro": "^5.3.0", 19 | "sharp": "^0.33.5" 20 | }, 21 | "engines": { 22 | "node": ">=18.17.1" 23 | }, 24 | "packageManager": "pnpm@8.6.3", 25 | "private": true, 26 | "sideEffects": false, 27 | "keywords": [ 28 | "starlight", 29 | "plugin", 30 | "typedoc", 31 | "typescript", 32 | "documentation", 33 | "astro" 34 | ], 35 | "homepage": "https://github.com/HiDeoo/starlight-typedoc", 36 | "repository": { 37 | "type": "git", 38 | "url": "https://github.com/HiDeoo/starlight-typedoc.git", 39 | "directory": "docs" 40 | }, 41 | "bugs": "https://github.com/HiDeoo/starlight-typedoc/issues" 42 | } 43 | -------------------------------------------------------------------------------- /docs/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 📚 2 | -------------------------------------------------------------------------------- /docs/src/assets/showcase/bunshi.org.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/docs/src/assets/showcase/bunshi.org.png -------------------------------------------------------------------------------- /docs/src/assets/showcase/chord.vercel.app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/docs/src/assets/showcase/chord.vercel.app.png -------------------------------------------------------------------------------- /docs/src/assets/showcase/dgmjs.dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/docs/src/assets/showcase/dgmjs.dev.png -------------------------------------------------------------------------------- /docs/src/assets/showcase/discord-components.js.org.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/docs/src/assets/showcase/discord-components.js.org.png -------------------------------------------------------------------------------- /docs/src/assets/showcase/docs.seyfert.dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/docs/src/assets/showcase/docs.seyfert.dev.png -------------------------------------------------------------------------------- /docs/src/assets/showcase/docs.shepherdpro.com.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/docs/src/assets/showcase/docs.shepherdpro.com.png -------------------------------------------------------------------------------- /docs/src/assets/showcase/echo-d.net.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/docs/src/assets/showcase/echo-d.net.png -------------------------------------------------------------------------------- /docs/src/assets/showcase/eddienubes.github.io-sagetest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/docs/src/assets/showcase/eddienubes.github.io-sagetest.png -------------------------------------------------------------------------------- /docs/src/assets/showcase/fevol.github.io-obsidian-typings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/docs/src/assets/showcase/fevol.github.io-obsidian-typings.png -------------------------------------------------------------------------------- /docs/src/assets/showcase/lilybird.didas.dev.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/docs/src/assets/showcase/lilybird.didas.dev.png -------------------------------------------------------------------------------- /docs/src/assets/showcase/vinoth.info-react-sketch-canvas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/docs/src/assets/showcase/vinoth.info-react-sketch-canvas.png -------------------------------------------------------------------------------- /docs/src/content.config.ts: -------------------------------------------------------------------------------- 1 | import { docsLoader } from '@astrojs/starlight/loaders' 2 | import { docsSchema } from '@astrojs/starlight/schema' 3 | import { defineCollection } from 'astro:content' 4 | 5 | export const collections = { 6 | docs: defineCollection({ loader: docsLoader(), schema: docsSchema() }), 7 | } 8 | -------------------------------------------------------------------------------- /docs/src/content/docs/configuration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Configuration 3 | description: An overview of all the configuration options supported by the Starlight TypeDoc plugin. 4 | --- 5 | 6 | The Starlight TypeDoc plugin can be configured inside the `astro.config.mjs` configuration file of your project: 7 | 8 | ```js {11} 9 | // astro.config.mjs 10 | import starlight from '@astrojs/starlight' 11 | import { defineConfig } from 'astro/config' 12 | import starlightTypeDoc, { typeDocSidebarGroup } from 'starlight-typedoc' 13 | 14 | export default defineConfig({ 15 | integrations: [ 16 | starlight({ 17 | plugins: [ 18 | starlightTypeDoc({ 19 | // Configuration options go here. 20 | }), 21 | ], 22 | title: 'My Docs', 23 | }), 24 | ], 25 | }) 26 | ``` 27 | 28 | ## Configuration options 29 | 30 | You can pass the following options to the Starlight TypeDoc plugin. 31 | 32 | ### `entryPoints` (required) 33 | 34 | **Type:** `string[]` 35 | 36 | The path(s) to the entry point(s) to document. 37 | 38 | ### `tsconfig` (required) 39 | 40 | **Type:** `string` 41 | 42 | The path to the `tsconfig.json` file to use for the documentation generation. 43 | 44 | ### `output` 45 | 46 | **Type:** `string` 47 | **Default:** `'api'` 48 | 49 | The output directory containing the generated documentation markdown files relative to the `src/content/docs/` directory. 50 | 51 | ### `pagination` 52 | 53 | **Type:** `boolean` 54 | **Default:** `false` 55 | 56 | Whether the footer should include previous and next page links for the generated documentation. 57 | 58 | ### `sidebar` 59 | 60 | **Type:** [`StarlightTypeDocSidebarOptions`](#sidebar-configuration) 61 | 62 | The generated documentation [sidebar configuration](#sidebar-configuration). 63 | 64 | ### `typeDoc` 65 | 66 | **Type:** `TypeDocConfig` 67 | 68 | Additional [TypeDoc](https://typedoc.org/options) or [typedoc-plugin-markdown](https://github.com/tgreyuk/typedoc-plugin-markdown/blob/next/packages/typedoc-plugin-markdown/docs/usage/options.md) configuration to override the [default settings](https://github.com/HiDeoo/starlight-typedoc/blob/main/packages/starlight-typedoc/libs/typedoc.ts#L21-L28) used by the plugin. 69 | 70 | :::note 71 | When using TypeDoc [`packages`](https://typedoc.org/options/input/#packages) entry point strategy, all entry points should be directories that may contain their own TypeDoc configuration. 72 | As documented in the [TypeDoc documentation](https://typedoc.org/options/input/#packages), the root configuration provided by this plugin will not be copied or merged with the entry point configuration. 73 | ::: 74 | 75 | ### `watch` 76 | 77 | **Type:** `boolean` 78 | **Default:** `false` 79 | 80 | Whether to watch the entry point(s) for changes and regenerate the documentation when needed. 81 | 82 | ### `errorOnEmptyDocumentation` 83 | 84 | **Type:** `boolean` 85 | **Default:** `true` 86 | 87 | Whether the plugin should error when no TypeDoc documentation is generated. 88 | 89 | By default, the plugin will error when no TypeDoc documentation is generated. 90 | Setting this option to `false` will prevent the plugin from erroring in this case. 91 | This can be useful when generating documentation for multiple entry points and only some of them contain documented code at a given time. 92 | 93 | ## Sidebar configuration 94 | 95 | The sidebar configuration is an object with the following properties: 96 | 97 | ### `collapsed` 98 | 99 | **Type:** `boolean` 100 | **Default:** `false` 101 | 102 | Wheter the generated documentation sidebar group should be collapsed by default. 103 | Note that nested sidebar groups are always collapsed. 104 | 105 | ### `label` 106 | 107 | **Type:** `string` 108 | **Default:** `'API'` 109 | 110 | The generated documentation sidebar group label. 111 | -------------------------------------------------------------------------------- /docs/src/content/docs/getting-started.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Getting Started 3 | description: Learn how to generate documentation from TypeScript code using the Starlight TypeDoc plugin. 4 | --- 5 | 6 | A [Starlight](https://starlight.astro.build) plugin using [TypeDoc](https://typedoc.org) and [typedoc-plugin-markdown](https://github.com/tgreyuk/typedoc-plugin-markdown) to generate documentation from TypeScript code. 7 | 8 | Check out the [demo](https://starlight-typedoc-example.vercel.app) for a preview of the generated documentation. 9 | 10 | ## Installation 11 | 12 | import { Steps } from '@astrojs/starlight/components' 13 | import { PackageManagers } from '@hideoo/starlight-plugins-docs-components' 14 | 15 | 16 | 17 | 1. Starlight TypeDoc is a Starlight [plugin](https://starlight.astro.build/reference/plugins/). Install it and its peer dependencies using your favorite package manager: 18 | 19 | 20 | 21 | 2. Configure the plugin in your Starlight [configuration](https://starlight.astro.build/reference/configuration/#plugins) in the `astro.config.mjs` file. 22 | 23 | ```diff lang="js" 24 | // astro.config.mjs 25 | import starlight from '@astrojs/starlight' 26 | import { defineConfig } from 'astro/config' 27 | +import starlightTypeDoc, { typeDocSidebarGroup } from 'starlight-typedoc' 28 | 29 | export default defineConfig({ 30 | integrations: [ 31 | starlight({ 32 | + plugins: [ 33 | + // Generate the documentation. 34 | + starlightTypeDoc({ 35 | + entryPoints: ['../path/to/entry-point.ts'], 36 | + tsconfig: '../path/to/tsconfig.json', 37 | + }), 38 | + ], 39 | sidebar: [ 40 | { 41 | label: 'Guides', 42 | items: [{ label: 'Example Guide', link: '/guides/example/' }], 43 | }, 44 | + // Add the generated sidebar group to the sidebar. 45 | + typeDocSidebarGroup, 46 | ], 47 | title: 'My Docs', 48 | }), 49 | ], 50 | }) 51 | ``` 52 | 53 | 3. [Start the development server](https://starlight.astro.build/getting-started/#start-the-development-server) to preview the generated documentation. 54 | 55 | 56 | 57 | The Starlight TypeDoc plugin behavior can be tweaked using various [configuration options](/configuration). 58 | -------------------------------------------------------------------------------- /docs/src/content/docs/guides/frontmatter.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Frontmatter 3 | description: Learn how to customize the frontmatter of pages generated by the Starlight TypeDoc plugin. 4 | --- 5 | 6 | By default, the Starlight TypeDoc plugin generates documentation pages which include a [frontmatter](https://starlight.astro.build/reference/frontmatter/) with various fields already set like the [title](https://starlight.astro.build/reference/frontmatter/#title-required) of the page. 7 | 8 | ## Customize the frontmatter 9 | 10 | Using the [`typedoc-plugin-frontmatter`](https://typedoc-plugin-markdown.org/plugins/frontmatter) plugin, you can customize the frontmatter of the generated documentation pages using a custom TypeDoc plugin. 11 | 12 | import { Steps } from '@astrojs/starlight/components' 13 | import { PackageManagers } from '@hideoo/starlight-plugins-docs-components' 14 | 15 | 16 | 17 | 1. Install the `typedoc-plugin-frontmatter` plugin using your favorite package manager: 18 | 19 | 20 | 21 | 2. Create a custom TypeDoc plugin to [customize](https://typedoc-plugin-markdown.org/plugins/frontmatter/customizing) the frontmatter of the generated documentation pages. 22 | 23 | The following example customizes the frontmatter of the `variables/something.md` page to include a sidebar [`badge`](https://starlight.astro.build/reference/frontmatter/#badge) with the text "New": 24 | 25 | ```diff lang="js" 26 | // src/plugins/frontmatter.js 27 | import { MarkdownPageEvent } from 'typedoc-plugin-markdown' 28 | 29 | /** @param {import('typedoc-plugin-markdown').MarkdownApplication} app */ 30 | export function load(app) { 31 | app.renderer.on( 32 | MarkdownPageEvent.BEGIN, 33 | /** @param {MarkdownPageEvent} page */ 34 | (page) => { 35 | // Customize the frontmatter for a specific page. 36 | if (page.url === 'variables/something.md') { 37 | // Update the frontmatter of the generated page. 38 | page.frontmatter = { 39 | // Include a sidebar badge with the text 'New'. 40 | sidebar: { 41 | badge: { 42 | text: 'New', 43 | }, 44 | }, 45 | ...page.frontmatter, 46 | } 47 | } 48 | }, 49 | ) 50 | } 51 | 52 | ``` 53 | 54 | 3. Edit the Starlight TypeDoc plugin configuration in your `astro.config.mjs` file to include the `typedoc-plugin-frontmatter` plugin and the custom one you created in the previous step using the [`typeDoc`](/configuration/#typedoc) option: 55 | 56 | ```diff lang="js" 57 | // astro.config.mjs 58 | import starlight from '@astrojs/starlight' 59 | import { defineConfig } from 'astro/config' 60 | import starlightTypeDoc from 'starlight-typedoc' 61 | 62 | export default defineConfig({ 63 | integrations: [ 64 | starlight({ 65 | plugins: [ 66 | starlightTypeDoc({ 67 | entryPoints: ['../path/to/entry-point.ts'], 68 | tsconfig: '../path/to/tsconfig.json', 69 | + // Add plugins to customize the frontmatter. 70 | + typeDoc: { 71 | + plugin: [ 72 | + 'typedoc-plugin-frontmatter', 73 | + './src/plugins/frontmatter.js' 74 | + ], 75 | + }, 76 | }), 77 | ], 78 | title: 'My Docs', 79 | }), 80 | ], 81 | }) 82 | ``` 83 | 84 | 85 | 86 | To learn more about the `typedoc-plugin-frontmatter` plugin, check out the [documentation](https://typedoc-plugin-markdown.org/plugins/frontmatter/customizing). 87 | -------------------------------------------------------------------------------- /docs/src/content/docs/guides/multiple-instances.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Multiple Instances 3 | description: Learn how to use multiple instances of the Starlight TypeDoc plugin. 4 | --- 5 | 6 | By default, the Starlight TypeDoc plugin generates documentation for one or multiple [entry points](/configuration/#entrypoints-required) using the same plugin or TypeDoc configuration. 7 | 8 | Sometimes, you may want to generate documentation for different entry points using different configurations. 9 | To achieve this, the Starlight TypeDoc plugin exposes a `createStarlightTypeDocPlugin()` function that allows you to create multiple instances of the plugin. 10 | 11 | ## `createStarlightTypeDocPlugin()` 12 | 13 | Calling the `createStarlightTypeDocPlugin()` function returns an array with exactly two values: 14 | 15 | 1. A new Starlight TypeDoc plugin instance that you can add to your Starlight configuration. 16 | 1. A reference to the generated sidebar group for that instance that you can add to your sidebar. 17 | 18 | The following example creates two Starlight TypeDoc plugin instances for two different entry points: one for a public API and another for an admin API. 19 | The associated sidebar groups are then added to the sidebar: 20 | 21 | ```js {6-7} 22 | // astro.config.mjs 23 | import starlight from '@astrojs/starlight' 24 | import { defineConfig } from 'astro/config' 25 | import { createStarlightTypeDocPlugin } from 'starlight-typedoc' 26 | 27 | const [publicStarlightTypeDoc, publicTypeDocSidebarGroup] = createStarlightTypeDocPlugin() 28 | const [adminStarlightTypeDoc, adminTypeDocSidebarGroup] = createStarlightTypeDocPlugin() 29 | 30 | export default defineConfig({ 31 | integrations: [ 32 | starlight({ 33 | plugins: [ 34 | // Generate the documentation for the public API. 35 | publicStarlightTypeDoc({ 36 | entryPoints: ['../path/to/public/entry-point.ts'], 37 | output: 'api-public', 38 | tsconfig: '../path/to/public/tsconfig.json', 39 | }), 40 | // Generate the documentation for the admin API. 41 | adminStarlightTypeDoc({ 42 | entryPoints: ['../path/to/admin/entry-point.ts'], 43 | output: 'api-admin', 44 | tsconfig: '../path/to/admin/tsconfig.json', 45 | }), 46 | ], 47 | sidebar: [ 48 | { 49 | label: 'User Guide', 50 | // Add the generated public sidebar group to the sidebar. 51 | items: [publicTypeDocSidebarGroup], 52 | }, 53 | { 54 | label: 'Admin Guide', 55 | // Add the generated admin sidebar group to the sidebar. 56 | items: [adminTypeDocSidebarGroup], 57 | }, 58 | ], 59 | title: 'My Docs', 60 | }), 61 | ], 62 | }) 63 | ``` 64 | -------------------------------------------------------------------------------- /docs/src/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Starlight TypeDoc 3 | description: Starlight plugin to generate documentation from TypeScript using TypeDoc. 4 | head: 5 | - tag: title 6 | content: Starlight TypeDoc 7 | template: splash 8 | editUrl: false 9 | lastUpdated: false 10 | hero: 11 | tagline: Starlight plugin to generate documentation from TypeScript using TypeDoc. 12 | image: 13 | html: '📚' 14 | actions: 15 | - text: Getting Started 16 | link: /getting-started/ 17 | icon: rocket 18 | - text: Demo 19 | link: https://starlight-typedoc-example.vercel.app/api/functions/dothingc/ 20 | icon: external 21 | variant: minimal 22 | --- 23 | 24 | import { Card, CardGrid } from '@astrojs/starlight/components' 25 | 26 | ## Next steps 27 | 28 | 29 | 30 | Check the [getting started guide](/getting-started/) for installation instructions. 31 | 32 | 33 | Edit your config in `astro.config.mjs`. 34 | 35 | 36 | Generate documentation from your TypeScript code. 37 | 38 | 39 | Learn more in [the Starlight TypeDoc Docs](/getting-started/). 40 | 41 | 42 | -------------------------------------------------------------------------------- /docs/src/content/docs/resources/showcase.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Showcase 3 | description: Discover Starlight project using the Starlight TypeDoc plugin. 4 | --- 5 | 6 | import { ShowcaseIntro, Showcase } from '@hideoo/starlight-plugins-docs-components' 7 | 8 | 12 | 13 | ## Sites 14 | 15 | Starlight TypeDoc is already being used in production. These are some of the sites around the web: 16 | 17 | 76 | 77 | See all the [public project repos using Starlight TypeDoc on GitHub](https://github.com/hideoo/starlight-typedoc/network/dependents). 78 | -------------------------------------------------------------------------------- /docs/src/content/docs/resources/starlight.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Starlight Plugins and Tools 3 | description: Discover other Starlight plugins, components and tools developed by HiDeoo. 4 | --- 5 | 6 | import { ResourcesIntro, Resources } from '@hideoo/starlight-plugins-docs-components' 7 | 8 | 9 | 10 | ## Plugins 11 | 12 | 13 | 14 | ## Components 15 | 16 | 17 | 18 | ## Tools 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /docs/src/styles/custom.css: -------------------------------------------------------------------------------- 1 | .hero-html { 2 | --size: 10rem; 3 | 4 | font-size: var(--size); 5 | justify-content: center; 6 | line-height: var(--size); 7 | } 8 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import hideoo from '@hideoo/eslint-config' 2 | 3 | export default hideoo() 4 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 |
2 |

starlight-typedoc 📚

3 |

Starlight plugin to generate documentation from TypeScript using TypeDoc.

4 |
5 | 6 |
7 | 8 | Integration Status 9 | 10 | 11 | License 12 | 13 |
14 |
15 | 16 | ## Example 17 | 18 | Run the example locally using [pnpm](https://pnpm.io): 19 | 20 | ```shell 21 | pnpm run dev 22 | ``` 23 | 24 | ## License 25 | 26 | Licensed under the MIT License, Copyright © HiDeoo. 27 | 28 | See [LICENSE](https://github.com/HiDeoo/starlight-typedoc/blob/main/LICENSE) for more information. 29 | -------------------------------------------------------------------------------- /example/astro.config.ts: -------------------------------------------------------------------------------- 1 | import starlight from '@astrojs/starlight' 2 | import { defineConfig } from 'astro/config' 3 | import starlightTypeDoc, { typeDocSidebarGroup } from 'starlight-typedoc' 4 | 5 | export default defineConfig({ 6 | integrations: [ 7 | starlight({ 8 | customCss: ['./src/styles/custom.css'], 9 | editLink: { 10 | baseUrl: 'https://github.com/HiDeoo/starlight-typedoc/edit/main/example/', 11 | }, 12 | plugins: [ 13 | starlightTypeDoc({ 14 | entryPoints: ['../fixtures/basics/src/index.ts'], 15 | tsconfig: '../fixtures/basics/tsconfig.json', 16 | sidebar: { 17 | label: 'API (auto-generated)', 18 | }, 19 | typeDoc: { 20 | plugin: ['typedoc-plugin-mdn-links', 'typedoc-plugin-frontmatter', './src/plugins/frontmatter.js'], 21 | }, 22 | }), 23 | ], 24 | sidebar: [ 25 | { 26 | label: 'Guides', 27 | items: ['guides/example'], 28 | }, 29 | typeDocSidebarGroup, 30 | ], 31 | social: { 32 | blueSky: 'https://bsky.app/profile/hideoo.dev', 33 | github: 'https://github.com/HiDeoo/starlight-typedoc', 34 | }, 35 | title: 'Starlight TypeDoc Example', 36 | }), 37 | ], 38 | }) 39 | -------------------------------------------------------------------------------- /example/astro.multiple-entrypoints.config.ts: -------------------------------------------------------------------------------- 1 | import starlight from '@astrojs/starlight' 2 | import { defineConfig } from 'astro/config' 3 | import starlightTypeDoc, { typeDocSidebarGroup } from 'starlight-typedoc' 4 | 5 | export default defineConfig({ 6 | base: '/multiple-entrypoints/', 7 | integrations: [ 8 | starlight({ 9 | plugins: [ 10 | starlightTypeDoc({ 11 | entryPoints: ['../fixtures/basics/src/Bar.ts', '../fixtures/basics/src/Foo.ts'], 12 | output: 'api-multiple-entrypoints', 13 | pagination: true, 14 | sidebar: { 15 | collapsed: true, 16 | }, 17 | tsconfig: '../fixtures/basics/tsconfig.json', 18 | // @ts-expect-error - Fake the `readme` option not being set to ensure that frontmatter titles are escaped properly. 19 | // @see https://github.com/HiDeoo/starlight-typedoc/pull/7 20 | typeDoc: { 21 | readme: undefined, 22 | }, 23 | }), 24 | ], 25 | sidebar: [ 26 | { 27 | label: 'Guides', 28 | items: [{ label: 'Example Guide', link: '/guides/example/' }], 29 | }, 30 | typeDocSidebarGroup, 31 | ], 32 | title: 'Starlight TypeDoc Multiple Entry Points Example', 33 | }), 34 | ], 35 | outDir: './dist-multiple-entrypoints', 36 | server: { 37 | port: 4322, 38 | }, 39 | }) 40 | -------------------------------------------------------------------------------- /example/astro.multiple-plugins.config.ts: -------------------------------------------------------------------------------- 1 | import starlight from '@astrojs/starlight' 2 | import { defineConfig } from 'astro/config' 3 | import { createStarlightTypeDocPlugin } from 'starlight-typedoc' 4 | 5 | const [barStarlightTypeDoc, barTypeDocSidebarGroup] = createStarlightTypeDocPlugin() 6 | const [fooStarlightTypeDoc, fooTypeDocSidebarGroup] = createStarlightTypeDocPlugin() 7 | 8 | export default defineConfig({ 9 | base: '/multiple-plugins/', 10 | integrations: [ 11 | starlight({ 12 | plugins: [ 13 | barStarlightTypeDoc({ 14 | entryPoints: ['../fixtures/basics/src/Bar.ts'], 15 | output: 'api-multiple-plugins-bar', 16 | tsconfig: '../fixtures/basics/tsconfig.json', 17 | sidebar: { 18 | label: 'Bar API', 19 | }, 20 | }), 21 | fooStarlightTypeDoc({ 22 | entryPoints: ['../fixtures/basics/src/Foo.ts'], 23 | output: 'api-multiple-plugins-foo', 24 | tsconfig: '../fixtures/basics/tsconfig.json', 25 | sidebar: { 26 | label: 'Foo API', 27 | }, 28 | }), 29 | ], 30 | sidebar: [ 31 | { 32 | label: 'Bar Content', 33 | items: [{ ...barTypeDocSidebarGroup, badge: 'generated' }], 34 | }, 35 | { 36 | label: 'Foo Content', 37 | items: [fooTypeDocSidebarGroup], 38 | }, 39 | ], 40 | title: 'Starlight TypeDoc Multiple Plugins Example', 41 | }), 42 | ], 43 | }) 44 | -------------------------------------------------------------------------------- /example/astro.packages-entrypoints.config.ts: -------------------------------------------------------------------------------- 1 | import starlight from '@astrojs/starlight' 2 | import { defineConfig } from 'astro/config' 3 | import starlightTypeDoc, { typeDocSidebarGroup } from 'starlight-typedoc' 4 | 5 | export default defineConfig({ 6 | base: '/packages-entrypoints/', 7 | integrations: [ 8 | starlight({ 9 | plugins: [ 10 | starlightTypeDoc({ 11 | entryPoints: ['../fixtures/packages/packages/*'], 12 | output: 'api-packages-entrypoints', 13 | tsconfig: '../fixtures/packages/tsconfig.json', 14 | typeDoc: { 15 | entryPointStrategy: 'packages', 16 | }, 17 | }), 18 | ], 19 | sidebar: [ 20 | { 21 | label: 'Guides', 22 | items: [{ label: 'Example Guide', link: '/guides/example/' }], 23 | }, 24 | typeDocSidebarGroup, 25 | ], 26 | title: 'Starlight TypeDoc Packages Entry Points Example', 27 | }), 28 | ], 29 | }) 30 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starlight-typedoc-example", 3 | "version": "0.17.0", 4 | "license": "MIT", 5 | "description": "Starlight plugin to generate documentation from TypeScript using TypeDoc.", 6 | "author": "HiDeoo (https://hideoo.dev)", 7 | "type": "module", 8 | "scripts": { 9 | "dev": "astro dev", 10 | "dev:single-entrypoints": "astro dev --config astro.config.ts", 11 | "dev:multiple-entrypoints": "astro dev --config astro.multiple-entrypoints.config.ts", 12 | "dev:packages-entrypoints": "pnpm -C ../fixtures/packages run build && astro dev --config astro.packages-entrypoints.config.ts", 13 | "dev:multiple-plugins": "astro dev --config astro.multiple-plugins.config.ts", 14 | "build": "astro build", 15 | "build:single-entrypoints": "astro build --config astro.config.ts", 16 | "build:multiple-entrypoints": "astro build --config astro.multiple-entrypoints.config.ts", 17 | "build:packages-entrypoints": "pnpm -C ../fixtures/packages run build && astro build --config astro.packages-entrypoints.config.ts", 18 | "build:multiple-plugins": "astro build --config astro.multiple-plugins.config.ts", 19 | "preview": "astro preview", 20 | "preview:single-entrypoints": "astro preview --config astro.config.ts", 21 | "preview:multiple-entrypoints": "astro preview --config astro.multiple-entrypoints.config.ts", 22 | "preview:packages-entrypoints": "astro preview --config astro.packages-entrypoints.config.ts", 23 | "preview:multiple-plugins": "astro preview --config astro.multiple-plugins.config.ts", 24 | "astro": "astro", 25 | "lint": "eslint . --cache --max-warnings=0" 26 | }, 27 | "dependencies": { 28 | "@astrojs/starlight": "^0.32.0", 29 | "astro": "^5.3.0", 30 | "sharp": "^0.33.5", 31 | "starlight-typedoc": "workspace:*", 32 | "typedoc": "^0.28.1", 33 | "typedoc-plugin-frontmatter": "^1.3.0", 34 | "typedoc-plugin-markdown": "^4.6.0", 35 | "typedoc-plugin-mdn-links": "^5.0.1" 36 | }, 37 | "engines": { 38 | "node": ">=18.17.1" 39 | }, 40 | "packageManager": "pnpm@8.6.1", 41 | "private": true, 42 | "sideEffects": false, 43 | "keywords": [ 44 | "starlight", 45 | "plugin", 46 | "typedoc", 47 | "typescript", 48 | "documentation", 49 | "astro" 50 | ], 51 | "homepage": "https://github.com/HiDeoo/starlight-typedoc", 52 | "repository": { 53 | "type": "git", 54 | "url": "https://github.com/HiDeoo/starlight-typedoc.git", 55 | "directory": "example" 56 | }, 57 | "bugs": "https://github.com/HiDeoo/starlight-typedoc/issues" 58 | } 59 | -------------------------------------------------------------------------------- /example/public/favicon.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /example/src/assets/houston.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HiDeoo/starlight-typedoc/e990682b951cecd249a051afc332a503d9dd2b8e/example/src/assets/houston.webp -------------------------------------------------------------------------------- /example/src/content.config.ts: -------------------------------------------------------------------------------- 1 | import { docsLoader } from '@astrojs/starlight/loaders' 2 | import { docsSchema } from '@astrojs/starlight/schema' 3 | import { defineCollection, z } from 'astro:content' 4 | 5 | export const collections = { 6 | docs: defineCollection({ 7 | loader: docsLoader(), 8 | schema: docsSchema({ 9 | extend: z.object({ 10 | banner: z 11 | .object({ 12 | content: z.string(), 13 | }) 14 | .default({ 15 | content: 16 | 'This is a demo of the Starlight TypeDoc plugin — Back to the documentation.', 17 | }), 18 | }), 19 | }), 20 | }), 21 | } 22 | -------------------------------------------------------------------------------- /example/src/content/docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Autogenerated by https://github.com/HiDeoo/starlight-typedoc 2 | api/ 3 | api-multiple-entrypoints/ 4 | api-packages-entrypoints/ 5 | api-multiple-plugins-bar/ 6 | api-multiple-plugins-foo/ 7 | -------------------------------------------------------------------------------- /example/src/content/docs/guides/example.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Example Guide 3 | description: A guide in my new Starlight docs site. 4 | --- 5 | 6 | Guides lead a user through a specific task they want to accomplish, often with a sequence of steps. 7 | Writing a good guide requires thinking about what your users are trying to do. 8 | 9 | ## Further reading 10 | 11 | - Read [about how-to guides](https://diataxis.fr/how-to-guides/) in the Diátaxis framework 12 | -------------------------------------------------------------------------------- /example/src/content/docs/index.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Starlight TypeDoc Example 3 | description: Starlight plugin to generate documentation from TypeScript using TypeDoc. 4 | head: 5 | - tag: title 6 | content: Starlight TypeDoc Example 7 | template: splash 8 | editUrl: false 9 | lastUpdated: false 10 | hero: 11 | tagline: Starlight plugin to generate documentation from TypeScript using TypeDoc. 12 | image: 13 | html: '📚' 14 | actions: 15 | - text: Check Example 16 | link: /api/functions/dothingc/ 17 | icon: rocket 18 | - text: Documentation 19 | link: https://starlight-typedoc.vercel.app/ 20 | icon: external 21 | variant: minimal 22 | --- 23 | 24 | import { Card, CardGrid } from '@astrojs/starlight/components' 25 | 26 | ## Next steps 27 | 28 | 29 | 30 | Browse through the example pages to see how the plugin works. 31 | 32 | 33 | Check the [getting started](https://starlight-typedoc.vercel.app/getting-started/) guide for installation 34 | instructions. 35 | 36 | 37 | -------------------------------------------------------------------------------- /example/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /example/src/plugins/frontmatter.js: -------------------------------------------------------------------------------- 1 | import { MarkdownPageEvent } from 'typedoc-plugin-markdown' 2 | 3 | /** @param {import('typedoc-plugin-markdown').MarkdownApplication} app */ 4 | export function load(app) { 5 | app.renderer.on( 6 | MarkdownPageEvent.BEGIN, 7 | /** @param {MarkdownPageEvent} page */ 8 | (page) => { 9 | if (page.url === 'variables/anObject.md') { 10 | page.frontmatter = { 11 | sidebar: { 12 | badge: { 13 | text: 'New', 14 | }, 15 | }, 16 | ...page.frontmatter, 17 | } 18 | } 19 | }, 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /example/src/styles/custom.css: -------------------------------------------------------------------------------- 1 | .hero-html { 2 | --size: 10rem; 3 | 4 | font-size: var(--size); 5 | justify-content: center; 6 | line-height: var(--size); 7 | } 8 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json" 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/basics/README.md: -------------------------------------------------------------------------------- 1 |
2 |

starlight-typedoc 📚

3 |

Starlight plugin to generate documentation from TypeScript using TypeDoc.

4 |
5 | 6 |
7 | 8 | Integration Status 9 | 10 | 11 | License 12 | 13 |
14 |
15 | 16 | ## Fixtures 17 | 18 | This folder contains fixtures used for the example and tests. 19 | 20 | ## License 21 | 22 | Licensed under the MIT License, Copyright © HiDeoo. 23 | 24 | See [LICENSE](https://github.com/HiDeoo/starlight-typedoc/blob/main/LICENSE) for more information. 25 | -------------------------------------------------------------------------------- /fixtures/basics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@starlight-typedoc/fixtures-basics", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "description": "Starlight plugin to generate documentation from TypeScript using TypeDoc.", 6 | "author": "HiDeoo (https://hideoo.dev)", 7 | "type": "module", 8 | "dependencies": { 9 | "typescript": "^5.7.2" 10 | }, 11 | "engines": { 12 | "node": ">=18.17.1" 13 | }, 14 | "packageManager": "pnpm@8.6.1", 15 | "private": true, 16 | "sideEffects": false, 17 | "keywords": [ 18 | "starlight", 19 | "plugin", 20 | "typedoc", 21 | "typescript", 22 | "documentation", 23 | "astro" 24 | ], 25 | "homepage": "https://github.com/HiDeoo/starlight-typedoc", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/HiDeoo/starlight-typedoc.git", 29 | "directory": "fixtures/basics" 30 | }, 31 | "bugs": "https://github.com/HiDeoo/starlight-typedoc/issues" 32 | } 33 | -------------------------------------------------------------------------------- /fixtures/basics/src/Bar.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a Bar class. 3 | */ 4 | export class Bar { 5 | /** 6 | * The bar property. 7 | * @default 'bar' 8 | */ 9 | readonly bar = 'bar' 10 | 11 | /** 12 | * This does something. 13 | * @alpha 14 | */ 15 | doSomething(element: HTMLElement) { 16 | element 17 | 18 | this.#doSomethingElse() 19 | 20 | return 'something' 21 | } 22 | 23 | #doSomethingElse() { 24 | return 'something else' 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /fixtures/basics/src/Baz.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a Baz class. 3 | * 4 | * @group MyCustomGroup 5 | */ 6 | export class Baz { 7 | /** 8 | * The baz property. 9 | * @default 'baz' 10 | */ 11 | readonly baz = 'baz' 12 | } 13 | -------------------------------------------------------------------------------- /fixtures/basics/src/Foo.ts: -------------------------------------------------------------------------------- 1 | import { Bar } from './Bar' 2 | import type { Thing } from './types' 3 | 4 | /** 5 | * This is a Foo class. 6 | */ 7 | export class Foo extends Bar { 8 | /** 9 | * The foo property. 10 | * @default 'foo' 11 | */ 12 | readonly foo = 'foo' 13 | 14 | /** 15 | * Parse a thing. 16 | * @see {@link Thing} 17 | */ 18 | parseThing(thing: Thing) { 19 | return thing 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /fixtures/basics/src/functions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A function that does a thing. 3 | */ 4 | export function doThingA() { 5 | return 'thingA' 6 | } 7 | 8 | /** 9 | * A function that does another thing. 10 | * @deprecated 11 | */ 12 | export function doThingB() { 13 | return 'thingB' 14 | } 15 | 16 | /** 17 | * A function that does another thing. 18 | * @deprecated Use the new {@link doThingFaster} function instead. 19 | */ 20 | export function doThingC() { 21 | return 'thingC' 22 | } 23 | 24 | /** 25 | * A function that does another thing but faster. 26 | * 27 | * This is a faster alternative to {@link doThingB}. 28 | */ 29 | export function doThingFaster() { 30 | return 'thingB' 31 | } 32 | 33 | /** 34 | * A function that print dollars. 35 | */ 36 | export function $() { 37 | return '$' 38 | } 39 | -------------------------------------------------------------------------------- /fixtures/basics/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Bar' 2 | export * from './Foo' 3 | export * from './Baz' 4 | export * from './functions' 5 | export * from './shared' 6 | export * from './types' 7 | -------------------------------------------------------------------------------- /fixtures/basics/src/module.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This is a module 3 | * 4 | * @module 5 | */ 6 | 7 | export * as bar from './Bar' 8 | export * as foo from './Foo' 9 | export * as functions from './functions' 10 | export * as shared from './shared' 11 | export * as types from './types' 12 | -------------------------------------------------------------------------------- /fixtures/basics/src/noDocs.ts: -------------------------------------------------------------------------------- 1 | export const UNDOCUMENTED = 'UNDOCUMENTED' 2 | -------------------------------------------------------------------------------- /fixtures/basics/src/noExports.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /fixtures/basics/src/shared.ts: -------------------------------------------------------------------------------- 1 | export { doThingA as doThingARef } from './functions' 2 | 3 | /** 4 | * This is a string variable. 5 | */ 6 | export const aString = 'the_string_value' 7 | 8 | // eslint-disable-next-line import/no-mutable-exports 9 | export let anUndefinedString: string 10 | 11 | /** 12 | * This is an object variable. 13 | */ 14 | export const anObject = { 15 | /** 16 | * This is foo. 17 | */ 18 | foo: 'Foo', 19 | bar: { 20 | /** 21 | * This is baz. 22 | * @beta 23 | */ 24 | baz: ['Baz'], 25 | }, 26 | } 27 | 28 | export const anObjectAsConst = { 29 | foo: 'Foo', 30 | bar: { 31 | baz: ['Baz'], 32 | }, 33 | } as const 34 | 35 | export enum ANumericEnum { 36 | Foo, 37 | Bar, 38 | } 39 | 40 | export enum AStringEnum { 41 | Foo = 'foo', 42 | Bar = 'bar', 43 | } 44 | -------------------------------------------------------------------------------- /fixtures/basics/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Thing { 2 | /** 3 | * This is foo. 4 | */ 5 | foo: string 6 | /** 7 | * This is bar. 8 | * @experimental 9 | */ 10 | bar: number 11 | } 12 | 13 | export type Things = Thing[] 14 | -------------------------------------------------------------------------------- /fixtures/basics/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@hideoo/tsconfig" 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/packages/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@starlight-typedoc/fixtures-packages", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "description": "Starlight plugin to generate documentation from TypeScript using TypeDoc.", 6 | "author": "HiDeoo (https://hideoo.dev)", 7 | "type": "module", 8 | "scripts": { 9 | "build": "tsc --build" 10 | }, 11 | "dependencies": { 12 | "typescript": "^5.7.2" 13 | }, 14 | "engines": { 15 | "node": ">=18.17.1" 16 | }, 17 | "packageManager": "pnpm@8.6.1", 18 | "private": true, 19 | "sideEffects": false, 20 | "keywords": [ 21 | "starlight", 22 | "plugin", 23 | "typedoc", 24 | "typescript", 25 | "documentation", 26 | "astro" 27 | ], 28 | "homepage": "https://github.com/HiDeoo/starlight-typedoc", 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/HiDeoo/starlight-typedoc.git", 32 | "directory": "fixtures/packages" 33 | }, 34 | "bugs": "https://github.com/HiDeoo/starlight-typedoc/issues" 35 | } 36 | -------------------------------------------------------------------------------- /fixtures/packages/packages/bar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bar", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "description": "Starlight plugin to generate documentation from TypeScript using TypeDoc.", 6 | "author": "HiDeoo (https://hideoo.dev)", 7 | "type": "module", 8 | "packageManager": "pnpm@8.6.1", 9 | "private": true, 10 | "sideEffects": false, 11 | "keywords": [ 12 | "starlight", 13 | "plugin", 14 | "typedoc", 15 | "typescript", 16 | "documentation", 17 | "astro" 18 | ], 19 | "homepage": "https://github.com/HiDeoo/starlight-typedoc", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/HiDeoo/starlight-typedoc.git" 23 | }, 24 | "bugs": "https://github.com/HiDeoo/starlight-typedoc/issues" 25 | } 26 | -------------------------------------------------------------------------------- /fixtures/packages/packages/bar/src/functions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A function that does a bar thing. 3 | */ 4 | export function doBar() { 5 | return 'doBar' 6 | } 7 | 8 | /** 9 | * A function that does another bar thing but better. 10 | * 11 | * This is a better alternative to {@link doBar}. 12 | */ 13 | export function doBarBetter(options: DoBarBetterOptions) { 14 | return options.name 15 | } 16 | 17 | /** 18 | * Options for {@link doBarBetter} 19 | */ 20 | export interface DoBarBetterOptions { 21 | name: string 22 | number: number 23 | } 24 | -------------------------------------------------------------------------------- /fixtures/packages/packages/bar/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './functions.js' 2 | -------------------------------------------------------------------------------- /fixtures/packages/packages/bar/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /fixtures/packages/packages/bar/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/index.ts"] 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/packages/packages/foo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foo", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "description": "Starlight plugin to generate documentation from TypeScript using TypeDoc.", 6 | "author": "HiDeoo (https://hideoo.dev)", 7 | "type": "module", 8 | "packageManager": "pnpm@8.6.1", 9 | "private": true, 10 | "sideEffects": false, 11 | "keywords": [ 12 | "starlight", 13 | "plugin", 14 | "typedoc", 15 | "typescript", 16 | "documentation", 17 | "astro" 18 | ], 19 | "homepage": "https://github.com/HiDeoo/starlight-typedoc", 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/HiDeoo/starlight-typedoc.git" 23 | }, 24 | "bugs": "https://github.com/HiDeoo/starlight-typedoc/issues" 25 | } 26 | -------------------------------------------------------------------------------- /fixtures/packages/packages/foo/src/functions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A function that does a foo thing. 3 | * @deprecated Use the new {@link doFooFaster} function instead. 4 | */ 5 | export function doFoo() { 6 | return 'doFoo' 7 | } 8 | 9 | /** 10 | * A function that does another foo thing but faster. 11 | * 12 | * This is a faster alternative to {@link doFoo}. 13 | */ 14 | export function doFooFaster() { 15 | return 'doFoo' 16 | } 17 | -------------------------------------------------------------------------------- /fixtures/packages/packages/foo/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './functions.js' 2 | -------------------------------------------------------------------------------- /fixtures/packages/packages/foo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "rootDir": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["src"] 8 | } 9 | -------------------------------------------------------------------------------- /fixtures/packages/packages/foo/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "entryPoints": ["src/index.ts"] 3 | } 4 | -------------------------------------------------------------------------------- /fixtures/packages/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "declaration": true, 5 | "declarationMap": true, 6 | "module": "NodeNext", 7 | "strict": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /fixtures/packages/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./packages/bar" 6 | }, 7 | { 8 | "path": "./packages/foo" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starlight-typedoc-monorepo", 3 | "version": "0.17.0", 4 | "license": "MIT", 5 | "description": "Starlight plugin to generate documentation from TypeScript using TypeDoc.", 6 | "author": "HiDeoo (https://hideoo.dev)", 7 | "type": "module", 8 | "scripts": { 9 | "test": "pnpm --stream -r test", 10 | "lint": "astro check --noSync && pnpm -r lint", 11 | "format": "prettier -w --cache --ignore-unknown .", 12 | "version": "pnpm changeset version && pnpm i --no-frozen-lockfile" 13 | }, 14 | "devDependencies": { 15 | "@astrojs/check": "^0.9.4", 16 | "@changesets/changelog-github": "^0.5.0", 17 | "@changesets/cli": "^2.27.10", 18 | "@hideoo/eslint-config": "^4.0.0", 19 | "@hideoo/prettier-config": "^2.0.0", 20 | "@hideoo/tsconfig": "^2.0.1", 21 | "astro": "^5.3.0", 22 | "eslint": "^9.17.0", 23 | "prettier": "^3.4.2", 24 | "typescript": "^5.7.2" 25 | }, 26 | "engines": { 27 | "node": ">=18.17.1" 28 | }, 29 | "packageManager": "pnpm@8.6.1", 30 | "pnpm": { 31 | "peerDependencyRules": { 32 | "allowedVersions": { 33 | "typescript": "5.7.2" 34 | } 35 | } 36 | }, 37 | "private": true, 38 | "sideEffects": false, 39 | "keywords": [ 40 | "starlight", 41 | "plugin", 42 | "typedoc", 43 | "typescript", 44 | "documentation", 45 | "astro" 46 | ], 47 | "homepage": "https://github.com/HiDeoo/starlight-typedoc", 48 | "repository": { 49 | "type": "git", 50 | "url": "https://github.com/HiDeoo/starlight-typedoc.git" 51 | }, 52 | "bugs": "https://github.com/HiDeoo/starlight-typedoc/issues", 53 | "prettier": "@hideoo/prettier-config" 54 | } 55 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/.npmignore: -------------------------------------------------------------------------------- 1 | .eslintcache 2 | eslint.config.mjs 3 | playwright.config.ts 4 | tests 5 | vitest.config.ts 6 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # starlight-typedoc 2 | 3 | ## 0.21.3 4 | 5 | ### Patch Changes 6 | 7 | - [#85](https://github.com/HiDeoo/starlight-typedoc/pull/85) [`51d3e37`](https://github.com/HiDeoo/starlight-typedoc/commit/51d3e370a5fff417a2c104175c0ee17d4048eb66) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes a sidebar generation issue when using the [plugin `watch` configuration option](https://starlight-typedoc.vercel.app/configuration/#watch). 8 | 9 | ## 0.21.2 10 | 11 | ### Patch Changes 12 | 13 | - [#83](https://github.com/HiDeoo/starlight-typedoc/pull/83) [`2fc1bf2`](https://github.com/HiDeoo/starlight-typedoc/commit/2fc1bf2499d226ca624e18ae235134f314b18d47) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes a regression introduced in [version `0.21.0`](https://github.com/HiDeoo/starlight-typedoc/releases/tag/starlight-typedoc%400.21.0) that prevented the development server from starting when using the [plugin `watch` configuration option](https://starlight-typedoc.vercel.app/configuration/#watch). 14 | 15 | ## 0.21.1 16 | 17 | ### Patch Changes 18 | 19 | - [#80](https://github.com/HiDeoo/starlight-typedoc/pull/80) [`e447787`](https://github.com/HiDeoo/starlight-typedoc/commit/e4477874721b8c8482375c35587aebabb3fa8d17) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Fixes a potential page and link generation issue with some declaration reference names such as a function named `$`. 20 | 21 | ## 0.21.0 22 | 23 | ### Minor Changes 24 | 25 | - [#77](https://github.com/HiDeoo/starlight-typedoc/pull/77) [`751021f`](https://github.com/HiDeoo/starlight-typedoc/commit/751021f9e1029600266cc8c3bea8232e385bbbc3) Thanks [@HiDeoo](https://github.com/HiDeoo)! - ⚠️ **BREAKING CHANGE:** The minimum supported version of `typedoc` is now `0.28.0`. 26 | 27 | ⚠️ **BREAKING CHANGE:** The minimum supported version of `typedoc-plugin-markdown` is now `4.6.0`. 28 | 29 | ## 0.20.0 30 | 31 | ### Minor Changes 32 | 33 | - [#74](https://github.com/HiDeoo/starlight-typedoc/pull/74) [`2765549`](https://github.com/HiDeoo/starlight-typedoc/commit/276554979760b992d204ce25106c51611f289749) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Adds a new [`errorOnEmptyDocumentation`](https://starlight-typedoc.vercel.app/configuration/#erroronemptydocumentation) option, defaulting to `true`, to control whether the plugin should error when no TypeDoc documentation is generated. 34 | 35 | Setting this option to `false` will prevent the plugin from erroring in this case which can be useful when generating documentation for multiple entry points and only some of them contain documented code at a given time. 36 | 37 | The current behavior remains unchanged, and the plugin will error when no TypeDoc documentation is generated if the option is not explicitly set to `false`. 38 | 39 | ## 0.19.0 40 | 41 | ### Minor Changes 42 | 43 | - [#70](https://github.com/HiDeoo/starlight-typedoc/pull/70) [`8ffcff1`](https://github.com/HiDeoo/starlight-typedoc/commit/8ffcff196052e58913135db766a102d7c3a4fb94) Thanks [@HiDeoo](https://github.com/HiDeoo)! - ⚠️ **BREAKING CHANGE:** The minimum supported version of Starlight is now version `0.32.0`. 44 | 45 | Please use the `@astrojs/upgrade` command to upgrade your project: 46 | 47 | ```sh 48 | npx @astrojs/upgrade 49 | ``` 50 | 51 | ## 0.18.0 52 | 53 | ### Minor Changes 54 | 55 | - [#66](https://github.com/HiDeoo/starlight-typedoc/pull/66) [`c4014bc`](https://github.com/HiDeoo/starlight-typedoc/commit/c4014bc2669e2072c2a452367641f11cc621214b) Thanks [@HiDeoo](https://github.com/HiDeoo)! - Adds support for Astro v5, drops support for Astro v4. 56 | 57 | ⚠️ **BREAKING CHANGE:** The minimum supported version of Starlight is now `0.30.0`. 58 | 59 | Please follow the [upgrade guide](https://github.com/withastro/starlight/releases/tag/%40astrojs/starlight%400.30.0) to update your project. 60 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/README.md: -------------------------------------------------------------------------------- 1 |
2 |

starlight-typedoc 📚

3 |

Starlight plugin to generate documentation from TypeScript using TypeDoc.

4 |

5 | 6 | Screenshot of starlight-typedoc 7 | 8 |

9 |
10 | 11 |
12 | 13 | Integration Status 14 | 15 | 16 | License 17 | 18 |
19 |
20 | 21 | ## Getting Started 22 | 23 | Want to get started immediately? Check out the [getting started guide](https://starlight-typedoc.vercel.app/getting-started/) or check out the [demo](https://starlight-typedoc-example.vercel.app) to see the plugin in action. 24 | 25 | ## Features 26 | 27 | A [Starlight](https://starlight.astro.build) plugin using [TypeDoc](https://typedoc.org) and [typedoc-plugin-markdown](https://github.com/tgreyuk/typedoc-plugin-markdown) to generate documentation from TypeScript code. 28 | 29 | ## License 30 | 31 | Licensed under the MIT License, Copyright © HiDeoo. 32 | 33 | See [LICENSE](https://github.com/HiDeoo/starlight-typedoc/blob/main/LICENSE) for more information. 34 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import hideoo from '@hideoo/eslint-config' 2 | 3 | export default hideoo({ 4 | ignores: ['eslint.config.mjs'], 5 | languageOptions: { 6 | parserOptions: { 7 | project: ['../../tsconfig.json'], 8 | }, 9 | }, 10 | }) 11 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/index.ts: -------------------------------------------------------------------------------- 1 | import { randomBytes } from 'node:crypto' 2 | 3 | import type { StarlightPlugin } from '@astrojs/starlight/types' 4 | import type { TypeDocOptions } from 'typedoc' 5 | 6 | import { 7 | getSidebarFromReflections, 8 | getSidebarGroupPlaceholder, 9 | getSidebarWithoutReflections, 10 | type SidebarGroup, 11 | } from './libs/starlight' 12 | import { generateTypeDoc, NoReflectionsError, type TypeDocConfig } from './libs/typedoc' 13 | 14 | export const typeDocSidebarGroup = getSidebarGroupPlaceholder() 15 | 16 | export default function starlightTypeDocPlugin(options: StarlightTypeDocOptions): StarlightPlugin { 17 | return makeStarlightTypeDocPlugin(typeDocSidebarGroup)(options) 18 | } 19 | 20 | export function createStarlightTypeDocPlugin(): [plugin: typeof starlightTypeDocPlugin, sidebarGroup: SidebarGroup] { 21 | const sidebarGroup = getSidebarGroupPlaceholder(Symbol(randomBytes(24).toString('base64url'))) 22 | 23 | return [makeStarlightTypeDocPlugin(sidebarGroup), sidebarGroup] 24 | } 25 | 26 | function makeStarlightTypeDocPlugin(sidebarGroup: SidebarGroup): (options: StarlightTypeDocOptions) => StarlightPlugin { 27 | return function starlightTypeDocPlugin(options: StarlightTypeDocOptions) { 28 | return { 29 | name: 'starlight-typedoc-plugin', 30 | hooks: { 31 | async 'config:setup'({ astroConfig, command, config, logger, updateConfig }) { 32 | if (command === 'preview') return 33 | 34 | try { 35 | const { definitions, outputDirectory, reflections } = await generateTypeDoc(options, astroConfig, logger) 36 | 37 | updateConfig({ 38 | sidebar: getSidebarFromReflections( 39 | config.sidebar, 40 | sidebarGroup, 41 | options.sidebar, 42 | reflections, 43 | definitions, 44 | outputDirectory, 45 | ), 46 | }) 47 | } catch (error) { 48 | if (options.errorOnEmptyDocumentation === false && error instanceof NoReflectionsError) { 49 | logger.warn('No documentation generated but ignoring as `errorOnEmptyDocumentation` is disabled.') 50 | updateConfig({ sidebar: getSidebarWithoutReflections(config.sidebar, sidebarGroup) }) 51 | return 52 | } 53 | 54 | throw error 55 | } 56 | }, 57 | }, 58 | } 59 | } 60 | } 61 | 62 | export interface StarlightTypeDocOptions { 63 | /** 64 | * The path(s) to the entry point(s) to document. 65 | */ 66 | entryPoints: NonNullable 67 | /** 68 | * Whether the plugin should error when no TypeDoc documentation is generated. 69 | * @default true 70 | */ 71 | errorOnEmptyDocumentation?: boolean 72 | /** 73 | * The output directory containing the generated documentation markdown files relative to the `src/content/docs/` 74 | * directory. 75 | * @default 'api' 76 | */ 77 | output?: string 78 | /** 79 | * The sidebar configuration for the generated documentation. 80 | */ 81 | sidebar?: StarlightTypeDocSidebarOptions 82 | /** 83 | * Whether the footer should include previous and next page links for the generated documentation. 84 | * @default false 85 | */ 86 | pagination?: boolean 87 | /** 88 | * The path to the `tsconfig.json` file to use for the documentation generation. 89 | */ 90 | tsconfig: NonNullable 91 | /** 92 | * Additional TypeDoc configuration. 93 | * @see https://typedoc.org/options 94 | */ 95 | typeDoc?: TypeDocConfig 96 | /** 97 | * Whether to watch the entry point(s) for changes and regenerate the documentation when needed. 98 | * @default false 99 | */ 100 | watch?: boolean 101 | } 102 | 103 | export interface StarlightTypeDocSidebarOptions { 104 | /** 105 | * Wheter the generated documentation sidebar group should be collapsed by default. 106 | * Note that nested sidebar groups are always collapsed. 107 | * @default false 108 | */ 109 | collapsed?: boolean 110 | /** 111 | * The generated documentation sidebar group label. 112 | * @default 'API' 113 | */ 114 | label?: string 115 | } 116 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/libs/logger.ts: -------------------------------------------------------------------------------- 1 | import type { AstroIntegrationLogger } from 'astro' 2 | import { LogLevel, Logger } from 'typedoc' 3 | 4 | export class StarlightTypeDocLogger extends Logger { 5 | #logger: AstroIntegrationLogger 6 | 7 | constructor(logger: AstroIntegrationLogger) { 8 | super() 9 | 10 | this.#logger = logger 11 | } 12 | 13 | override log(message: string, level: LogLevel): void { 14 | super.log(message, level) 15 | 16 | if (level < this.level) { 17 | return 18 | } 19 | 20 | switch (level) { 21 | case LogLevel.Error: { 22 | this.#logger.error(message) 23 | break 24 | } 25 | case LogLevel.Warn: { 26 | this.#logger.warn(message) 27 | break 28 | } 29 | case LogLevel.Verbose: { 30 | this.#logger.debug(message) 31 | break 32 | } 33 | default: { 34 | this.#logger.info(message) 35 | break 36 | } 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/libs/markdown.ts: -------------------------------------------------------------------------------- 1 | export function addFrontmatter(content: string, frontmatter: Record) { 2 | const entries = Object.entries(frontmatter).map(([key, value]) => `${key}: ${value}`) 3 | 4 | if (entries.length === 0) { 5 | return content 6 | } 7 | 8 | return `---\n${entries.join('\n')}\n---\n\n${content}` 9 | } 10 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/libs/starlight.ts: -------------------------------------------------------------------------------- 1 | import path from 'node:path' 2 | 3 | import type { HookParameters } from '@astrojs/starlight/types' 4 | import { slug } from 'github-slugger' 5 | import { 6 | type DeclarationReflection, 7 | type ProjectReflection, 8 | ReflectionKind, 9 | ReferenceReflection, 10 | type ReflectionGroup, 11 | } from 'typedoc' 12 | 13 | import type { StarlightTypeDocSidebarOptions } from '..' 14 | 15 | import type { TypeDocDefinitions } from './typedoc' 16 | 17 | const externalLinkRegex = /^(http|ftp)s?:\/\// 18 | 19 | const sidebarDefaultOptions = { 20 | collapsed: false, 21 | label: 'API', 22 | } satisfies StarlightTypeDocSidebarOptions 23 | 24 | const starlightTypeDocSidebarGroupLabel = Symbol('StarlightTypeDocSidebarGroupLabel') 25 | 26 | export function getSidebarGroupPlaceholder(label = starlightTypeDocSidebarGroupLabel): SidebarGroup { 27 | return { 28 | items: [], 29 | label: label.toString(), 30 | } 31 | } 32 | 33 | export function getSidebarFromReflections( 34 | sidebar: StarlightUserConfigSidebar, 35 | sidebarGroupPlaceholder: SidebarGroup, 36 | options: StarlightTypeDocSidebarOptions = {}, 37 | reflections: ProjectReflection | DeclarationReflection, 38 | definitions: TypeDocDefinitions, 39 | baseOutputDirectory: string, 40 | ): StarlightUserConfigSidebar { 41 | if (!sidebar || sidebar.length === 0) { 42 | return sidebar 43 | } 44 | 45 | const sidebarGroup = getSidebarGroupFromReflections( 46 | options, 47 | reflections, 48 | definitions, 49 | baseOutputDirectory, 50 | baseOutputDirectory, 51 | ) 52 | 53 | function replaceSidebarGroupPlaceholder(group: SidebarManualGroup): SidebarGroup { 54 | if (group.label === sidebarGroupPlaceholder.label) { 55 | return group.badge ? { ...sidebarGroup, badge: group.badge } : sidebarGroup 56 | } 57 | 58 | if (isSidebarManualGroup(group)) { 59 | return { 60 | ...group, 61 | items: group.items.map((item) => { 62 | return isSidebarManualGroup(item) ? replaceSidebarGroupPlaceholder(item) : item 63 | }), 64 | } 65 | } 66 | 67 | return group 68 | } 69 | 70 | return sidebar.map((item) => { 71 | return isSidebarManualGroup(item) ? replaceSidebarGroupPlaceholder(item) : item 72 | }) 73 | } 74 | 75 | export function getSidebarWithoutReflections( 76 | sidebar: StarlightUserConfigSidebar, 77 | sidebarGroupPlaceholder: SidebarGroup, 78 | ): StarlightUserConfigSidebar { 79 | if (!sidebar || sidebar.length === 0) { 80 | return sidebar 81 | } 82 | 83 | function removeSidebarGroupPlaceholder( 84 | entries: NonNullable, 85 | ): NonNullable { 86 | const sidebarWithoutPlaceholder: StarlightUserConfigSidebar = [] 87 | 88 | for (const item of entries) { 89 | if (isSidebarManualGroup(item)) { 90 | if (item.label === sidebarGroupPlaceholder.label) continue 91 | 92 | sidebarWithoutPlaceholder.push({ 93 | ...item, 94 | items: removeSidebarGroupPlaceholder(item.items), 95 | }) 96 | continue 97 | } 98 | 99 | sidebarWithoutPlaceholder.push(item) 100 | } 101 | 102 | return sidebarWithoutPlaceholder 103 | } 104 | 105 | return removeSidebarGroupPlaceholder(sidebar) 106 | } 107 | 108 | function getSidebarGroupFromPackageReflections( 109 | options: StarlightTypeDocSidebarOptions, 110 | reflections: ProjectReflection | DeclarationReflection, 111 | definitions: TypeDocDefinitions, 112 | baseOutputDirectory: string, 113 | ): SidebarGroup { 114 | const groups = (reflections.children ?? []).map((child) => { 115 | const url = definitions[child.id] 116 | 117 | if (!url) { 118 | return undefined 119 | } 120 | 121 | const parsedPath = path.parse(url) 122 | 123 | return getSidebarGroupFromReflections( 124 | options, 125 | child, 126 | definitions, 127 | baseOutputDirectory, 128 | `${baseOutputDirectory}/${parsedPath.dir}`, 129 | child.name, 130 | ) 131 | }) 132 | 133 | return { 134 | label: options.label ?? sidebarDefaultOptions.label, 135 | collapsed: options.collapsed ?? sidebarDefaultOptions.collapsed, 136 | items: groups.filter((item): item is SidebarGroup => item !== undefined), 137 | } 138 | } 139 | 140 | function getSidebarGroupFromReflections( 141 | options: StarlightTypeDocSidebarOptions, 142 | reflections: ProjectReflection | DeclarationReflection, 143 | definitions: TypeDocDefinitions, 144 | baseOutputDirectory: string, 145 | outputDirectory: string, 146 | label?: string, 147 | ): SidebarGroup { 148 | if ((!reflections.groups || reflections.groups.length === 0) && reflections.children) { 149 | return getSidebarGroupFromPackageReflections(options, reflections, definitions, outputDirectory) 150 | } 151 | 152 | const groups = reflections.groups ?? [] 153 | 154 | return { 155 | label: label ?? options.label ?? sidebarDefaultOptions.label, 156 | collapsed: options.collapsed ?? sidebarDefaultOptions.collapsed, 157 | items: groups 158 | .flatMap((group) => { 159 | if (group.title === 'Modules') { 160 | return group.children.map((child) => { 161 | const url = definitions[child.id] 162 | 163 | if (!url || child.variant === 'document') { 164 | return undefined 165 | } 166 | 167 | const parsedPath = path.parse(url) 168 | const isParentKindModule = child.parent?.kind === ReflectionKind.Module 169 | 170 | return getSidebarGroupFromReflections( 171 | { collapsed: true, label: child.name }, 172 | child, 173 | definitions, 174 | baseOutputDirectory, 175 | `${outputDirectory}/${isParentKindModule ? parsedPath.dir.split('/').slice(1).join('/') : parsedPath.dir}`, 176 | ) 177 | }) 178 | } 179 | 180 | if (isReferenceReflectionGroup(group)) { 181 | return getReferencesSidebarGroup(group, definitions, baseOutputDirectory) 182 | } 183 | 184 | const directory = `${outputDirectory}/${slug(group.title.toLowerCase())}` 185 | 186 | // The groups generated using the `@group` tag do not have an associated directory on disk. 187 | const isGroupWithDirectory = group.children.some((child) => { 188 | return path.posix 189 | .join(baseOutputDirectory, definitions[child.id]?.replace('\\', '/') ?? '') 190 | .startsWith(directory) 191 | }) 192 | 193 | if (!isGroupWithDirectory) { 194 | return undefined 195 | } 196 | 197 | return { 198 | collapsed: true, 199 | label: group.title, 200 | autogenerate: { 201 | collapsed: true, 202 | directory, 203 | }, 204 | } 205 | }) 206 | .filter((item): item is SidebarGroup => item !== undefined), 207 | } 208 | } 209 | 210 | function getReferencesSidebarGroup( 211 | group: ReflectionGroup, 212 | definitions: TypeDocDefinitions, 213 | baseOutputDirectory: string, 214 | ): SidebarManualGroup | undefined { 215 | const referenceItems: LinkItem[] = group.children 216 | .map((child) => { 217 | const reference = child as ReferenceReflection 218 | let target = reference.tryGetTargetReflectionDeep() 219 | 220 | if (!target) { 221 | return undefined 222 | } 223 | 224 | if (target.kindOf(ReflectionKind.TypeLiteral) && target.parent) { 225 | target = target.parent 226 | } 227 | 228 | const url = definitions[target.id] 229 | 230 | if (!url) { 231 | return undefined 232 | } 233 | 234 | return { 235 | label: reference.name, 236 | link: getRelativeURL(url, getStarlightTypeDocOutputDirectory(baseOutputDirectory)), 237 | } 238 | }) 239 | .filter((item): item is LinkItem => item !== undefined) 240 | 241 | if (referenceItems.length === 0) { 242 | return undefined 243 | } 244 | 245 | return { 246 | label: group.title, 247 | items: referenceItems, 248 | } 249 | } 250 | 251 | export function getAsideMarkdown(type: AsideType, title: string, content: string) { 252 | return `:::${type}[${title}] 253 | ${content} 254 | :::` 255 | } 256 | 257 | export function getRelativeURL(url: string, baseUrl: string, pageUrl?: string): string { 258 | if (externalLinkRegex.test(url)) { 259 | return url 260 | } 261 | 262 | const currentDirname = path.dirname(pageUrl ?? '') 263 | const urlDirname = path.dirname(url) 264 | const relativeUrl = 265 | currentDirname === urlDirname ? url : path.posix.join(currentDirname, path.posix.relative(currentDirname, url)) 266 | 267 | const filePath = path.parse(relativeUrl) 268 | const [, anchor] = filePath.base.split('#') 269 | const segments = filePath.dir 270 | .split(/[/\\]/) 271 | .map((segment) => slug(segment)) 272 | .filter((segment) => segment !== '') 273 | 274 | let constructedUrl = typeof baseUrl === 'string' ? baseUrl : '' 275 | constructedUrl += segments.length > 0 ? `${segments.join('/')}/` : '' 276 | const fileNameSlug = slug(filePath.name) 277 | constructedUrl += fileNameSlug || filePath.name 278 | constructedUrl += '/' 279 | constructedUrl += anchor && anchor.length > 0 ? `#${anchor}` : '' 280 | 281 | return constructedUrl 282 | } 283 | 284 | export function getStarlightTypeDocOutputDirectory(outputDirectory: string, base = '') { 285 | return path.posix.join(base, `/${outputDirectory}${outputDirectory.endsWith('/') ? '' : '/'}`) 286 | } 287 | 288 | function isSidebarManualGroup(item: NonNullable[number]): item is SidebarManualGroup { 289 | return typeof item === 'object' && 'items' in item 290 | } 291 | 292 | function isReferenceReflectionGroup(group: ReflectionGroup) { 293 | return group.children.every((child) => child instanceof ReferenceReflection) 294 | } 295 | 296 | export type SidebarGroup = 297 | | SidebarManualGroup 298 | | { 299 | autogenerate: { 300 | collapsed?: boolean 301 | directory: string 302 | } 303 | collapsed?: boolean 304 | label: string 305 | } 306 | 307 | interface SidebarManualGroup { 308 | collapsed?: boolean 309 | items: (LinkItem | SidebarGroup)[] 310 | label: string 311 | badge?: 312 | | string 313 | | { 314 | text: string 315 | variant: 'note' | 'danger' | 'success' | 'caution' | 'tip' | 'default' 316 | } 317 | | undefined 318 | } 319 | 320 | interface LinkItem { 321 | label: string 322 | link: string 323 | } 324 | 325 | type AsideType = 'caution' | 'danger' | 'note' | 'tip' 326 | 327 | type StarlightUserConfigSidebar = HookParameters<'config:setup'>['config']['sidebar'] 328 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/libs/theme.ts: -------------------------------------------------------------------------------- 1 | import { Reflection, type Comment, type CommentTag, type Options, type CommentDisplayPart } from 'typedoc' 2 | import { MarkdownTheme, MarkdownThemeContext, type MarkdownPageEvent } from 'typedoc-plugin-markdown' 3 | 4 | import { getAsideMarkdown, getRelativeURL } from './starlight' 5 | 6 | const customBlockTagTypes = ['@deprecated'] as const 7 | const customModifiersTagTypes = ['@alpha', '@beta', '@experimental'] as const 8 | 9 | export class StarlightTypeDocTheme extends MarkdownTheme { 10 | override getRenderContext(event: MarkdownPageEvent): StarlightTypeDocThemeRenderContext { 11 | return new StarlightTypeDocThemeRenderContext(this, event, this.application.options) 12 | } 13 | } 14 | 15 | class StarlightTypeDocThemeRenderContext extends MarkdownThemeContext { 16 | constructor(theme: MarkdownTheme, event: MarkdownPageEvent, options: Options) { 17 | super(theme, event, options) 18 | 19 | const superPartials = this.partials 20 | 21 | this.partials = { 22 | ...superPartials, 23 | comment: (comment, options) => { 24 | const filteredComment = { ...comment } as Comment 25 | filteredComment.blockTags = [] 26 | filteredComment.modifierTags = new Set<`@${string}`>() 27 | 28 | const customTags: CustomTag[] = [] 29 | 30 | for (const blockTag of comment.blockTags) { 31 | if (this.#isCustomBlockCommentTagType(blockTag.tag)) { 32 | customTags.push({ blockTag, type: blockTag.tag }) 33 | } else { 34 | blockTag.content = blockTag.content.map((part) => this.#parseCommentDisplayPart(part)) 35 | filteredComment.blockTags.push(blockTag) 36 | } 37 | } 38 | 39 | for (const modifierTag of comment.modifierTags) { 40 | if (this.#isCustomModifierCommentTagType(modifierTag)) { 41 | customTags.push({ type: modifierTag }) 42 | } else { 43 | filteredComment.modifierTags.add(modifierTag) 44 | } 45 | } 46 | 47 | filteredComment.summary = comment.summary.map((part) => this.#parseCommentDisplayPart(part)) 48 | 49 | let markdown = superPartials.comment(filteredComment, options) 50 | 51 | if (options?.showSummary === false) { 52 | return markdown 53 | } 54 | 55 | for (const customCommentTag of customTags) { 56 | switch (customCommentTag.type) { 57 | case '@alpha': { 58 | markdown = this.#addReleaseStageAside(markdown, 'Alpha') 59 | break 60 | } 61 | case '@beta': { 62 | markdown = this.#addReleaseStageAside(markdown, 'Beta') 63 | break 64 | } 65 | case '@deprecated': { 66 | markdown = this.#addDeprecatedAside(markdown, customCommentTag.blockTag) 67 | break 68 | } 69 | case '@experimental': { 70 | markdown = this.#addReleaseStageAside(markdown, 'Experimental') 71 | break 72 | } 73 | } 74 | } 75 | 76 | return markdown 77 | }, 78 | } 79 | } 80 | 81 | override urlTo(reflection: Reflection): string { 82 | const outputDirectory = this.options.getValue('starlight-typedoc-output') 83 | const baseUrl = typeof outputDirectory === 'string' ? outputDirectory : '' 84 | 85 | return getRelativeURL(this.router.getFullUrl(reflection), baseUrl, this.page.url) 86 | } 87 | 88 | #parseCommentDisplayPart = (part: CommentDisplayPart): CommentDisplayPart => { 89 | if ( 90 | part.kind === 'inline-tag' && 91 | (part.tag === '@link' || part.tag === '@linkcode' || part.tag === '@linkplain') && 92 | part.target instanceof Reflection 93 | ) { 94 | return { 95 | ...part, 96 | target: this.urlTo(part.target), 97 | } 98 | } 99 | 100 | return part 101 | } 102 | 103 | #isCustomBlockCommentTagType = (tag: string): tag is CustomBlockTagType => { 104 | return customBlockTagTypes.includes(tag as CustomBlockTagType) 105 | } 106 | 107 | #isCustomModifierCommentTagType = (tag: string): tag is CustomModifierTagType => { 108 | return customModifiersTagTypes.includes(tag as CustomModifierTagType) 109 | } 110 | 111 | #addAside(markdown: string, ...args: Parameters) { 112 | return `${markdown}\n\n${getAsideMarkdown(...args)}` 113 | } 114 | 115 | #addDeprecatedAside(markdown: string, blockTag: CommentTag) { 116 | const content = 117 | blockTag.content.length > 0 118 | ? this.helpers.getCommentParts(blockTag.content) 119 | : 'This API is no longer supported and may be removed in a future release.' 120 | 121 | return this.#addAside(markdown, 'caution', 'Deprecated', content) 122 | } 123 | 124 | #addReleaseStageAside(markdown: string, title: string) { 125 | return this.#addAside( 126 | markdown, 127 | 'caution', 128 | title, 129 | 'This API should not be used in production and may be trimmed from a public release.', 130 | ) 131 | } 132 | } 133 | 134 | type CustomBlockTagType = (typeof customBlockTagTypes)[number] 135 | type CustomModifierTagType = (typeof customModifiersTagTypes)[number] 136 | 137 | type CustomTag = 138 | | { type: CustomModifierTagType } 139 | | { 140 | blockTag: CommentTag 141 | type: CustomBlockTagType 142 | } 143 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/libs/typedoc.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'node:fs' 2 | import path from 'node:path' 3 | import url from 'node:url' 4 | 5 | import type { AstroConfig, AstroIntegrationLogger } from 'astro' 6 | import { slug } from 'github-slugger' 7 | import { 8 | Application, 9 | PageEvent, 10 | TSConfigReader, 11 | type TypeDocOptions, 12 | ParameterType, 13 | RendererEvent, 14 | type PageDefinition, 15 | type ProjectReflection, 16 | } from 'typedoc' 17 | import type { MarkdownPageEvent, PluginOptions } from 'typedoc-plugin-markdown' 18 | 19 | import type { StarlightTypeDocOptions } from '..' 20 | 21 | import { StarlightTypeDocLogger } from './logger' 22 | import { addFrontmatter } from './markdown' 23 | import { getRelativeURL, getStarlightTypeDocOutputDirectory } from './starlight' 24 | import { StarlightTypeDocTheme } from './theme' 25 | 26 | const defaultTypeDocConfig: TypeDocConfig = { 27 | excludeInternal: true, 28 | excludePrivate: true, 29 | excludeProtected: true, 30 | githubPages: false, 31 | readme: 'none', 32 | theme: 'starlight-typedoc', 33 | } 34 | 35 | const markdownPluginConfig: TypeDocConfig = { 36 | hideBreadcrumbs: true, 37 | hidePageHeader: true, 38 | hidePageTitle: true, 39 | } 40 | 41 | export async function generateTypeDoc( 42 | options: StarlightTypeDocOptions, 43 | config: AstroConfig, 44 | logger: AstroIntegrationLogger, 45 | ) { 46 | const outputDirectory = options.output ?? 'api' 47 | 48 | const app = await bootstrapApp( 49 | options.entryPoints, 50 | options.tsconfig, 51 | options.typeDoc, 52 | { 53 | base: config.base, 54 | directory: outputDirectory, 55 | path: path.join(url.fileURLToPath(config.srcDir), 'content/docs', outputDirectory), 56 | }, 57 | options.pagination ?? false, 58 | logger, 59 | ) 60 | 61 | const definitions: TypeDocDefinitions = {} 62 | app.renderer.on(RendererEvent.END, (event) => { 63 | for (const page of event.pages) { 64 | if (!('id' in page.model)) continue 65 | definitions[page.model.id] = page.url 66 | } 67 | }) 68 | 69 | let reflections: ProjectReflection | undefined 70 | 71 | if (options.watch) { 72 | reflections = await new Promise((resolve) => { 73 | void app.convertAndWatch(async (reflections) => { 74 | await app.generateOutputs(reflections) 75 | resolve(reflections) 76 | }) 77 | }) 78 | } else { 79 | reflections = await app.convert() 80 | 81 | if ( 82 | (!reflections?.groups || reflections.groups.length === 0) && 83 | !reflections?.children?.some((child) => (child.groups ?? []).length > 0) 84 | ) { 85 | throw new NoReflectionsError() 86 | } 87 | 88 | await app.generateOutputs(reflections) 89 | } 90 | 91 | return { definitions, outputDirectory, reflections } 92 | } 93 | 94 | async function bootstrapApp( 95 | entryPoints: NonNullable, 96 | tsconfig: NonNullable, 97 | config: TypeDocConfig = {}, 98 | output: TypeDocOutput, 99 | pagination: boolean, 100 | logger: AstroIntegrationLogger, 101 | ) { 102 | const pagesToRemove: string[] = [] 103 | const outputDirectory = getStarlightTypeDocOutputDirectory(output.directory, output.base) 104 | 105 | const app = await Application.bootstrapWithPlugins({ 106 | ...defaultTypeDocConfig, 107 | ...markdownPluginConfig, 108 | ...config, 109 | // typedoc-plugin-markdown must be applied here so that it isn't overwritten by any additional applied plugins 110 | plugin: [...(config.plugin ?? []), 'typedoc-plugin-markdown'], 111 | entryPoints, 112 | tsconfig, 113 | outputs: [{ name: 'markdown', path: output.path }], 114 | }) 115 | app.logger = new StarlightTypeDocLogger(logger) 116 | app.options.addReader(new TSConfigReader()) 117 | app.renderer.defineTheme('starlight-typedoc', StarlightTypeDocTheme) 118 | app.renderer.on(PageEvent.BEGIN, (event) => { 119 | onRendererPageBegin(event as MarkdownPageEvent, outputDirectory, pagination) 120 | }) 121 | app.renderer.on(PageEvent.END, (event) => { 122 | const shouldRemovePage = onRendererPageEnd(event as MarkdownPageEvent, outputDirectory, pagination) 123 | if (shouldRemovePage) { 124 | pagesToRemove.push(event.filename) 125 | } 126 | }) 127 | app.renderer.on(RendererEvent.END, () => { 128 | onRendererEnd(pagesToRemove) 129 | }) 130 | app.options.addDeclaration({ 131 | defaultValue: outputDirectory, 132 | help: 'The starlight-typedoc output directory containing the generated documentation markdown files relative to the `src/content/docs/` directory.', 133 | name: 'starlight-typedoc-output', 134 | type: ParameterType.String, 135 | }) 136 | 137 | return app 138 | } 139 | 140 | function onRendererPageBegin(event: MarkdownPageEvent, outputDirectory: string, pagination: boolean) { 141 | if (event.frontmatter) { 142 | event.frontmatter = getModelFrontmatter(event, outputDirectory, { 143 | ...event.frontmatter, 144 | editUrl: false, 145 | next: pagination, 146 | prev: pagination, 147 | title: event.model.name, 148 | }) 149 | } 150 | } 151 | 152 | // Returning `true` will delete the page from the filesystem. 153 | function onRendererPageEnd(event: MarkdownPageEvent, outputDirectory: string, pagination: boolean) { 154 | if (!event.contents) { 155 | return false 156 | } else if (/^.+[/\\]README\.md$/.test(event.url)) { 157 | // Do not save `README.md` files for multiple entry points. 158 | // It is no longer supported in TypeDoc 0.26.0 to call `event.preventDefault()` to prevent the file from being saved. 159 | // https://github.com/TypeStrong/typedoc/commit/6e6b3b662c92b3d4bc24b6c6c0c6e227e063c759 160 | // event.preventDefault() 161 | return true 162 | } 163 | 164 | if (!event.frontmatter) { 165 | event.contents = addFrontmatter( 166 | event.contents, 167 | getModelFrontmatter(event, outputDirectory, { 168 | editUrl: false, 169 | next: pagination, 170 | prev: pagination, 171 | // Wrap in quotes to prevent issue with special characters in frontmatter 172 | title: `"${event.model.name}"`, 173 | }), 174 | ) 175 | } 176 | 177 | return false 178 | } 179 | 180 | function onRendererEnd(pagesToRemove: string[]) { 181 | for (const page of pagesToRemove) { 182 | fs.rmSync(page, { force: true }) 183 | } 184 | } 185 | 186 | function getModelFrontmatter( 187 | event: MarkdownPageEvent, 188 | outputDirectory: string, 189 | frontmatter: NonNullable, 190 | ) { 191 | const defaultSlug = slug(event.model.name) 192 | 193 | if (defaultSlug.length === 0) { 194 | frontmatter['slug'] = getRelativeURL(event.url, outputDirectory, event.url).replaceAll(/^\/|\/$/g, '') 195 | } 196 | 197 | return frontmatter 198 | } 199 | 200 | export class NoReflectionsError extends Error { 201 | constructor() { 202 | super('Failed to generate TypeDoc documentation.') 203 | } 204 | } 205 | 206 | export type TypeDocConfig = Partial & PluginOptions> 207 | export type TypeDocDefinitions = Record 208 | 209 | interface TypeDocOutput { 210 | base: string 211 | directory: string 212 | path: string 213 | } 214 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "starlight-typedoc", 3 | "version": "0.21.3", 4 | "license": "MIT", 5 | "description": "Starlight plugin to generate documentation from TypeScript using TypeDoc.", 6 | "author": "HiDeoo (https://hideoo.dev)", 7 | "type": "module", 8 | "main": "dist/index.cjs", 9 | "module": "dist/index.js", 10 | "types": "dist/index.d.ts", 11 | "exports": { 12 | ".": "./index.ts", 13 | "./package.json": "./package.json" 14 | }, 15 | "scripts": { 16 | "test": "pnpm test:unit && pnpm test:e2e", 17 | "test:unit": "vitest", 18 | "test:e2e": "pnpm run test:e2e:basics && pnpm run test:e2e:packages && pnpm run test:e2e:plugins", 19 | "test:e2e:basics": "cross-env TEST_TYPE=basics pnpm run playwright", 20 | "test:e2e:packages": "cross-env TEST_TYPE=packages pnpm run playwright", 21 | "test:e2e:plugins": "cross-env TEST_TYPE=plugins pnpm run playwright", 22 | "playwright": "playwright install --with-deps chromium && playwright test", 23 | "lint": "eslint . --cache --max-warnings=0" 24 | }, 25 | "dependencies": { 26 | "github-slugger": "^2.0.0" 27 | }, 28 | "devDependencies": { 29 | "@playwright/test": "^1.49.1", 30 | "@types/node": "^18.19.68", 31 | "cross-env": "^7.0.3", 32 | "typescript": "^5.7.2", 33 | "vitest": "2.1.6" 34 | }, 35 | "peerDependencies": { 36 | "@astrojs/starlight": ">=0.32.0", 37 | "typedoc": ">=0.28.0", 38 | "typedoc-plugin-markdown": ">=4.6.0" 39 | }, 40 | "engines": { 41 | "node": ">=18.17.1" 42 | }, 43 | "packageManager": "pnpm@8.6.1", 44 | "publishConfig": { 45 | "access": "public", 46 | "provenance": true 47 | }, 48 | "sideEffects": false, 49 | "keywords": [ 50 | "starlight", 51 | "plugin", 52 | "typedoc", 53 | "typescript", 54 | "documentation", 55 | "astro" 56 | ], 57 | "homepage": "https://github.com/HiDeoo/starlight-typedoc", 58 | "repository": { 59 | "type": "git", 60 | "url": "https://github.com/HiDeoo/starlight-typedoc.git", 61 | "directory": "packages/starlight-typedoc" 62 | }, 63 | "bugs": "https://github.com/HiDeoo/starlight-typedoc/issues" 64 | } 65 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test' 2 | 3 | export default defineConfig({ 4 | forbidOnly: !!process.env['CI'], 5 | projects: [ 6 | { 7 | name: 'chromium', 8 | use: { ...devices['Desktop Chrome'], headless: true }, 9 | }, 10 | ], 11 | testDir: `tests/e2e/${process.env['TEST_TYPE']}`, 12 | webServer: 13 | process.env['TEST_TYPE'] === 'basics' 14 | ? [ 15 | { 16 | command: 'pnpm build:single-entrypoints && pnpm preview:single-entrypoints', 17 | cwd: '../../example', 18 | reuseExistingServer: !process.env['CI'], 19 | url: 'http://localhost:4321', 20 | }, 21 | { 22 | command: 'pnpm build:multiple-entrypoints && pnpm preview:multiple-entrypoints', 23 | cwd: '../../example', 24 | reuseExistingServer: !process.env['CI'], 25 | url: 'http://localhost:4322/multiple-entrypoints/', 26 | }, 27 | ] 28 | : process.env['TEST_TYPE'] === 'plugins' 29 | ? [ 30 | { 31 | command: 'pnpm build:multiple-plugins && pnpm preview:multiple-plugins', 32 | cwd: '../../example', 33 | reuseExistingServer: !process.env['CI'], 34 | url: 'http://localhost:4321/multiple-plugins/', 35 | }, 36 | ] 37 | : [ 38 | { 39 | command: 'pnpm build:packages-entrypoints && pnpm preview:packages-entrypoints', 40 | cwd: '../../example', 41 | reuseExistingServer: !process.env['CI'], 42 | url: 'http://localhost:4321/packages-entrypoints/', 43 | }, 44 | ], 45 | }) 46 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/e2e/basics/asides.test.ts: -------------------------------------------------------------------------------- 1 | import type { DocPage } from '../fixtures/DocPage' 2 | import { expect, test } from '../test' 3 | 4 | test('should use an aside for the deprecated tag with no content', async ({ docPage }) => { 5 | await docPage.goto('functions/dothingb') 6 | 7 | const name = 'Deprecated' 8 | const { aside, title, content } = await getAside(docPage, name) 9 | 10 | await expect(aside).toBeVisible() 11 | expect(title).toBe(name) 12 | expect(content).toBe('This API is no longer supported and may be removed in a future release.') 13 | }) 14 | 15 | test('should use an aside for the deprecated tag with custom content', async ({ docPage }) => { 16 | await docPage.goto('functions/dothingc') 17 | 18 | const name = 'Deprecated' 19 | const { aside, title, content } = await getAside(docPage, name) 20 | 21 | await expect(aside).toBeVisible() 22 | expect(title).toBe(name) 23 | expect(content).toBe('Use the new doThingFaster function instead.') 24 | }) 25 | 26 | const releaseStageCases: [releaseStage: string, url: string][] = [ 27 | ['Alpha', 'classes/bar'], 28 | ['Beta', 'variables/anobject'], 29 | ['Experimental', 'interfaces/thing'], 30 | ] 31 | 32 | for (const [releaseStage, url] of releaseStageCases) { 33 | test(`should use an aside for the ${releaseStage.toLowerCase()} tag`, async ({ docPage }) => { 34 | await docPage.goto(url) 35 | 36 | const { aside, title, content } = await getAside(docPage, releaseStage) 37 | 38 | await expect(aside).toBeVisible() 39 | expect(title).toBe(releaseStage) 40 | expect(content).toBe('This API should not be used in production and may be trimmed from a public release.') 41 | }) 42 | } 43 | 44 | async function getAside(docPage: DocPage, name: string) { 45 | const aside = docPage.content.getByRole('complementary', { exact: true, name }) 46 | 47 | const paragraphs = aside.getByRole('paragraph', { includeHidden: true }) 48 | const title = await paragraphs.nth(0).textContent() 49 | const content = await paragraphs.nth(1).textContent() 50 | 51 | return { aside, title, content } 52 | } 53 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/e2e/basics/content.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '../test' 2 | 3 | test('should add titles to the frontmatter', async ({ docPage }) => { 4 | await docPage.goto('classes/foo') 5 | 6 | expect(docPage.title).toBe('Foo') 7 | 8 | await docPage.goto('functions/dothinga') 9 | 10 | expect(docPage.title).toBe('doThingA') 11 | }) 12 | 13 | test('should properly format links for a single entry point', async ({ docPage }) => { 14 | await docPage.goto('classes/classfoo') 15 | 16 | const barLinkLocators = await docPage.content.getByRole('link', { exact: true, name: 'Bar' }).all() 17 | const barLinkHrefs = await Promise.all(barLinkLocators.map((link) => link.getAttribute('href'))) 18 | 19 | expect(barLinkHrefs.every((href) => href === '/api/classes/classbar/')).toBe(true) 20 | }) 21 | 22 | test('should properly format links for multiple entry points', async ({ docPage }) => { 23 | docPage.useMultipleEntryPoints() 24 | 25 | await docPage.goto('modulefoo/classes/classfoo') 26 | 27 | const barLinkLocators = await docPage.content.getByRole('link', { exact: true, name: 'Bar' }).all() 28 | const barLinkHrefs = await Promise.all(barLinkLocators.map((link) => link.getAttribute('href'))) 29 | 30 | expect(barLinkHrefs.every((href) => href === '/api-multiple-entrypoints/modulebar/classes/classbar/')).toBe(true) 31 | }) 32 | 33 | test('should properly format links with anchors for a single entry point', async ({ docPage }) => { 34 | await docPage.goto('classes/foo') 35 | 36 | const barConstructorLinkHref = await docPage.content 37 | .getByRole('link', { exact: true, name: 'constructor' }) 38 | .getAttribute('href') 39 | 40 | expect(barConstructorLinkHref).toEqual('/api/classes/bar/#constructor') 41 | }) 42 | 43 | test('should properly format links with anchors for multiple entry points', async ({ docPage }) => { 44 | docPage.useMultipleEntryPoints() 45 | 46 | await docPage.goto('foo/classes/foo') 47 | 48 | const barConstructorLinkHref = await docPage.content 49 | .getByRole('link', { exact: true, name: 'constructor' }) 50 | .getAttribute('href') 51 | 52 | expect(barConstructorLinkHref).toEqual('/multiple-entrypoints/api-multiple-entrypoints/bar/classes/bar/#constructor') 53 | }) 54 | 55 | test('should disable edit links', async ({ docPage }) => { 56 | await docPage.goto('../guides/example') 57 | 58 | await expect(docPage.page.getByRole('link', { exact: true, name: 'Edit page' })).toBeVisible() 59 | 60 | await docPage.goto('classes/classbar') 61 | 62 | await expect(docPage.page.getByRole('link', { exact: true, name: 'Edit page' })).not.toBeVisible() 63 | }) 64 | 65 | test('should support TypeDoc plugins', async ({ docPage }) => { 66 | await docPage.goto('classes/foo') 67 | 68 | const mdnLink = docPage.page.getByRole('link', { exact: true, name: 'HTMLElement' }) 69 | 70 | await expect(mdnLink).toBeVisible() 71 | 72 | const mdnLinkHref = await mdnLink.getAttribute('href') 73 | 74 | expect(mdnLinkHref?.startsWith('https://developer.mozilla.org')).toBe(true) 75 | }) 76 | 77 | test('should support plugins customizing the frontmatter', async ({ docPage }) => { 78 | await docPage.goto('variables/anobject') 79 | 80 | const sidebarBadge = docPage.page.locator('a[aria-current="page"] .sl-badge') 81 | 82 | await expect(sidebarBadge).toBeVisible() 83 | 84 | expect(await sidebarBadge.textContent()).toBe('New') 85 | }) 86 | 87 | test('should properly format links in summary', async ({ docPage }) => { 88 | await docPage.goto('functions/dothingfaster') 89 | 90 | await docPage.content.getByRole('link', { exact: true, name: 'doThingB' }).click() 91 | await docPage.page.waitForURL('**/api/functions/dothingb/') 92 | }) 93 | 94 | test('should properly format links in block tag comments', async ({ docPage }) => { 95 | await docPage.goto('classes/foo') 96 | 97 | await docPage.content.locator('h4:has-text("See") + p a').click() 98 | await docPage.page.waitForURL('**/api/interfaces/thing/') 99 | }) 100 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/e2e/basics/pagination.test.ts: -------------------------------------------------------------------------------- 1 | import type { DocPage } from '../fixtures/DocPage' 2 | import { expect, test } from '../test' 3 | 4 | test('should not include pagination links by default', async ({ docPage }) => { 5 | await docPage.goto('classes/foo') 6 | 7 | const { next, prev } = getPrevNext(docPage) 8 | 9 | await expect(next).not.toBeVisible() 10 | await expect(prev).not.toBeVisible() 11 | }) 12 | 13 | test('should include pagination links if configured to do so', async ({ docPage }) => { 14 | docPage.useMultipleEntryPoints() 15 | 16 | await docPage.goto('bar/classes/bar') 17 | 18 | const { next, prev } = getPrevNext(docPage) 19 | 20 | await expect(next).toBeVisible() 21 | await expect(prev).toBeVisible() 22 | }) 23 | 24 | function getPrevNext(docPage: DocPage) { 25 | return { 26 | next: docPage.page.locator('a[rel="next"]'), 27 | prev: docPage.page.locator('a[rel="prev"]'), 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/e2e/basics/sidebar.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '../test' 2 | 3 | const singleEntrypointUrl = 'classes/foo' 4 | const multipleEntrypointsUrl = 'bar/classes/bar' 5 | 6 | test('should include the TypeDoc sidebar group for a single entry point', async ({ docPage }) => { 7 | await docPage.goto(singleEntrypointUrl) 8 | 9 | await expect(docPage.typeDocSidebarLabel).toBeVisible() 10 | }) 11 | 12 | test('should include the TypeDoc sidebar group for multiple entry points', async ({ docPage }) => { 13 | docPage.useMultipleEntryPoints() 14 | 15 | await docPage.goto(multipleEntrypointsUrl) 16 | 17 | await expect(docPage.typeDocSidebarLabel).toBeVisible() 18 | }) 19 | 20 | test('should not collapse the TypeDoc sidebar group by default', async ({ docPage }) => { 21 | await docPage.goto(singleEntrypointUrl) 22 | 23 | await docPage.page.getByRole('link', { name: 'Example Guide' }).click() 24 | 25 | expect(await docPage.isTypeDocSidebarCollapsed()).toBe(false) 26 | }) 27 | 28 | test('should collapse the TypeDoc sidebar group if specified', async ({ docPage }) => { 29 | docPage.useMultipleEntryPoints() 30 | 31 | await docPage.goto(multipleEntrypointsUrl) 32 | 33 | await docPage.page.getByRole('link', { name: 'Example Guide' }).first().click() 34 | 35 | expect(await docPage.isTypeDocSidebarCollapsed()).toBe(true) 36 | }) 37 | 38 | test('should generate the proper items for for a single entry point', async ({ docPage }) => { 39 | await docPage.goto(singleEntrypointUrl) 40 | 41 | const items = await docPage.getTypeDocSidebarItems() 42 | 43 | expect(items).toMatchObject([ 44 | { 45 | label: 'Enumerations', 46 | items: [{ name: 'ANumericEnum' }, { name: 'AStringEnum' }], 47 | collapsed: true, 48 | }, 49 | { 50 | label: 'Classes', 51 | items: [{ name: 'Bar' }, { name: 'Baz' }, { name: 'Foo' }], 52 | collapsed: true, 53 | }, 54 | { 55 | label: 'Interfaces', 56 | items: [{ name: 'Thing' }], 57 | collapsed: true, 58 | }, 59 | { 60 | label: 'Type Aliases', 61 | items: [{ name: 'Things' }], 62 | collapsed: true, 63 | }, 64 | { 65 | label: 'Variables', 66 | items: [{ name: 'anObject' }, { name: 'anObjectAsConst' }, { name: 'anUndefinedString' }, { name: 'aString' }], 67 | collapsed: true, 68 | }, 69 | { 70 | label: 'Functions', 71 | items: [ 72 | { name: '$' }, 73 | { name: 'doThingA' }, 74 | { name: 'doThingB' }, 75 | { name: 'doThingC' }, 76 | { name: 'doThingFaster' }, 77 | ], 78 | collapsed: true, 79 | }, 80 | { 81 | label: 'References', 82 | items: [{ name: 'doThingARef' }], 83 | collapsed: true, 84 | }, 85 | // `MyCustomGroup` defined in `fixtures/basics/src/Baz.ts` does not have a directory on disk which means it should 86 | // not be included in the sidebar. 87 | ]) 88 | }) 89 | 90 | test('should generate the proper items for for multiple entry points', async ({ docPage }) => { 91 | docPage.useMultipleEntryPoints() 92 | 93 | await docPage.goto(multipleEntrypointsUrl) 94 | 95 | const items = await docPage.getTypeDocSidebarItems() 96 | 97 | expect(items).toMatchObject([ 98 | { 99 | label: 'Bar', 100 | items: [ 101 | { 102 | label: 'Classes', 103 | items: [{ name: 'Bar' }], 104 | }, 105 | ], 106 | collapsed: true, 107 | }, 108 | { 109 | label: 'Foo', 110 | items: [ 111 | { 112 | label: 'Classes', 113 | items: [{ name: 'Foo' }], 114 | }, 115 | ], 116 | collapsed: true, 117 | }, 118 | ]) 119 | }) 120 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/e2e/basics/slug.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '../test' 2 | 3 | test('handles pages with a name that would lead to an empty slug', async ({ docPage }) => { 4 | await docPage.goto('functions/$') 5 | 6 | await expect(docPage.content.getByText('A function that print dollars.')).toBeVisible() 7 | expect(await docPage.sidebar.locator('a[aria-current="page"]').getAttribute('href')).toBe('/api/functions/$/') 8 | }) 9 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/e2e/fixtures/DocPage.ts: -------------------------------------------------------------------------------- 1 | import type { Locator, Page } from '@playwright/test' 2 | 3 | export class DocPage { 4 | title: string | null = null 5 | 6 | #useMultipleEntryPoints = false 7 | #usePackagesEntryPoints = false 8 | #useMultiplePlugins = false 9 | 10 | constructor(public readonly page: Page) {} 11 | 12 | async goto(url: string) { 13 | const baseDir = this.#useMultipleEntryPoints 14 | ? 'multiple-entrypoints/api-multiple-entrypoints' 15 | : this.#usePackagesEntryPoints 16 | ? 'packages-entrypoints/api-packages-entrypoints' 17 | : this.#useMultiplePlugins 18 | ? 'multiple-plugins' 19 | : 'api' 20 | const baseUrl = `http://localhost:${this.#useMultipleEntryPoints ? 4322 : 4321}/${baseDir}` 21 | 22 | await this.page.goto(`${baseUrl}${url.startsWith('/') ? url : `/${url}`}${url.endsWith('/') ? '' : '/'}`) 23 | 24 | const title = await this.content.getByRole('heading', { level: 1 }).textContent() 25 | this.title = title ? title.trim() : null 26 | } 27 | 28 | useMultipleEntryPoints() { 29 | this.#useMultipleEntryPoints = true 30 | } 31 | 32 | usePackagesEntryPoints() { 33 | this.#usePackagesEntryPoints = true 34 | } 35 | 36 | useMultiplePlugins() { 37 | this.#useMultiplePlugins = true 38 | } 39 | 40 | get content() { 41 | return this.page.getByRole('main') 42 | } 43 | 44 | get sidebar() { 45 | return this.page.getByRole('navigation', { name: 'Main' }) 46 | } 47 | 48 | get typeDocSidebarLabel() { 49 | return this.#typeDocSidebarRootDetails.getByText(this.#expectedTypeDocSidebarLabel, { 50 | exact: true, 51 | }) 52 | } 53 | 54 | get #expectedTypeDocSidebarLabel() { 55 | return this.#useMultipleEntryPoints || this.#usePackagesEntryPoints ? 'API' : 'API (auto-generated)' 56 | } 57 | 58 | get #typeDocSidebarRootDetails() { 59 | return this.sidebar 60 | .getByRole('listitem') 61 | .locator(`details:has(summary > div > span:has-text("${this.#expectedTypeDocSidebarLabel}"))`) 62 | } 63 | 64 | getTypeDocSidebarItems() { 65 | return this.#getTypeDocSidebarChildrenItems(this.#typeDocSidebarRootDetails.locator('> ul')) 66 | } 67 | 68 | async getSidebarItems() { 69 | return this.#getTypeDocSidebarChildrenItems(this.sidebar.locator('ul.top-level')) 70 | } 71 | 72 | async #getTypeDocSidebarChildrenItems(list: Locator): Promise { 73 | const items: TypeDocSidebarItem[] = [] 74 | 75 | for (const category of await list.locator('> li > details').all()) { 76 | items.push({ 77 | collapsed: !(await category.getAttribute('open')), 78 | label: await category.locator(`> summary > div > span:not(.sl-badge)`).textContent(), 79 | items: await this.#getTypeDocSidebarChildrenItems(category.locator('> ul')), 80 | }) 81 | } 82 | 83 | for (const link of await list.locator('> li > a > span:not(.sl-badge)').all()) { 84 | const name = await link.textContent() 85 | 86 | items.push({ name: name ? name.trim() : null }) 87 | } 88 | 89 | return items 90 | } 91 | 92 | async isTypeDocSidebarCollapsed() { 93 | return (await this.#typeDocSidebarRootDetails.getAttribute('open')) === null 94 | } 95 | } 96 | 97 | type TypeDocSidebarItem = TypeDocSidebarItemGroup | TypeDocSidebarItemLink 98 | 99 | interface TypeDocSidebarItemLink { 100 | name: string | null 101 | } 102 | 103 | interface TypeDocSidebarItemGroup { 104 | collapsed: boolean 105 | items: (TypeDocSidebarItemGroup | TypeDocSidebarItemLink)[] 106 | label: string | null 107 | } 108 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/e2e/packages/content.test.ts: -------------------------------------------------------------------------------- 1 | import { test } from '../test' 2 | 3 | test('should properly format links', async ({ docPage }) => { 4 | docPage.usePackagesEntryPoints() 5 | 6 | await docPage.goto('bar/functions/dobarbetter') 7 | 8 | await docPage.content.getByRole('link', { exact: true, name: 'DoBarBetterOptions' }).click() 9 | await docPage.page.waitForURL('**/api-packages-entrypoints/bar/interfaces/dobarbetteroptions/**') 10 | }) 11 | 12 | test('should properly format links in block tag comments', async ({ docPage }) => { 13 | docPage.usePackagesEntryPoints() 14 | 15 | await docPage.goto('foo/functions/dofoofaster') 16 | 17 | await docPage.content.getByRole('link', { exact: true, name: 'doFoo' }).click() 18 | await docPage.page.waitForURL('**/api-packages-entrypoints/foo/functions/dofoo/') 19 | }) 20 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/e2e/packages/sidebar.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '../test' 2 | 3 | const url = 'foo/functions/dofoo' 4 | 5 | test('should include the TypeDoc sidebar group', async ({ docPage }) => { 6 | docPage.usePackagesEntryPoints() 7 | 8 | await docPage.goto(url) 9 | 10 | await expect(docPage.typeDocSidebarLabel).toBeVisible() 11 | }) 12 | 13 | test('should generate the proper items for for multiple entry points', async ({ docPage }) => { 14 | docPage.usePackagesEntryPoints() 15 | 16 | await docPage.goto(url) 17 | 18 | const items = await docPage.getTypeDocSidebarItems() 19 | 20 | expect(items).toMatchObject([ 21 | { 22 | label: 'bar', 23 | items: [ 24 | { 25 | label: 'Interfaces', 26 | items: [{ name: 'DoBarBetterOptions' }], 27 | }, 28 | { 29 | label: 'Functions', 30 | items: [{ name: 'doBar' }, { name: 'doBarBetter' }], 31 | }, 32 | ], 33 | collapsed: true, 34 | }, 35 | { 36 | label: 'foo', 37 | items: [ 38 | { 39 | label: 'Functions', 40 | items: [{ name: 'doFoo' }, { name: 'doFooFaster' }], 41 | }, 42 | ], 43 | collapsed: true, 44 | }, 45 | ]) 46 | }) 47 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/e2e/plugins/sidebar.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '../test' 2 | 3 | test('should generate the proper items for for multiple plugins with different configurations', async ({ docPage }) => { 4 | docPage.useMultiplePlugins() 5 | 6 | await docPage.goto('api-multiple-plugins-bar/classes/bar') 7 | 8 | const items = await docPage.getSidebarItems() 9 | 10 | expect(items).toMatchObject([ 11 | { 12 | label: 'Bar Content', 13 | items: [ 14 | { 15 | collapsed: true, 16 | label: 'Bar API', 17 | items: [ 18 | { 19 | collapsed: true, 20 | label: 'Classes', 21 | items: [{ name: 'Bar' }], 22 | }, 23 | ], 24 | }, 25 | ], 26 | }, 27 | { 28 | label: 'Foo Content', 29 | items: [ 30 | { 31 | collapsed: true, 32 | label: 'Foo API', 33 | items: [ 34 | { 35 | collapsed: true, 36 | label: 'Classes', 37 | items: [{ name: 'Foo' }], 38 | }, 39 | ], 40 | }, 41 | ], 42 | }, 43 | ]) 44 | }) 45 | 46 | test('should support having a badge', async ({ docPage }) => { 47 | docPage.useMultiplePlugins() 48 | 49 | await docPage.goto('api-multiple-plugins-bar/classes/bar') 50 | 51 | await expect(docPage.page.locator('.sl-badge:has-text("generated")')).toBeVisible() 52 | }) 53 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/e2e/test.ts: -------------------------------------------------------------------------------- 1 | import { test as base } from '@playwright/test' 2 | 3 | import { DocPage } from './fixtures/DocPage' 4 | 5 | export { expect } from '@playwright/test' 6 | 7 | export const test = base.extend({ 8 | docPage: async ({ page }, use) => { 9 | const docPage = new DocPage(page) 10 | 11 | await use(docPage) 12 | }, 13 | }) 14 | 15 | interface Fixtures { 16 | docPage: DocPage 17 | } 18 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/unit/sidebar.test.ts: -------------------------------------------------------------------------------- 1 | import type { ProjectReflection } from 'typedoc' 2 | import { describe, expect, test } from 'vitest' 3 | 4 | import { typeDocSidebarGroup } from '../../index' 5 | import { getSidebarFromReflections, getSidebarWithoutReflections } from '../../libs/starlight' 6 | 7 | const gettingStartedLink = { 8 | label: 'Getting Started', 9 | link: '/guides/getting-started/', 10 | } 11 | 12 | describe('getSidebarFromReflections', () => { 13 | test('should not do anything for an undefined sidebar', () => { 14 | expect(getTestSidebar([])).toEqual([]) 15 | }) 16 | 17 | test('should not do anything for an empty sidebar', () => { 18 | expect(getTestSidebar([])).toEqual([]) 19 | }) 20 | 21 | test('should not do anything for a sidebar without a placeholder', () => { 22 | expect(getTestSidebar([gettingStartedLink])).toEqual([gettingStartedLink]) 23 | }) 24 | 25 | test('should replace a placeholder at the rool level', () => { 26 | expect(getTestSidebar([gettingStartedLink, typeDocSidebarGroup])).toMatchInlineSnapshot(` 27 | [ 28 | { 29 | "label": "Getting Started", 30 | "link": "/guides/getting-started/", 31 | }, 32 | { 33 | "collapsed": false, 34 | "items": [], 35 | "label": "API", 36 | }, 37 | ] 38 | `) 39 | }) 40 | 41 | test('should replace a nested placeholder', () => { 42 | expect( 43 | getTestSidebar([ 44 | { 45 | label: 'Guides', 46 | items: [typeDocSidebarGroup, gettingStartedLink], 47 | }, 48 | ]), 49 | ).toMatchInlineSnapshot(` 50 | [ 51 | { 52 | "items": [ 53 | { 54 | "collapsed": false, 55 | "items": [], 56 | "label": "API", 57 | }, 58 | { 59 | "label": "Getting Started", 60 | "link": "/guides/getting-started/", 61 | }, 62 | ], 63 | "label": "Guides", 64 | }, 65 | ] 66 | `) 67 | }) 68 | 69 | test('should replace multiple placeholders', () => { 70 | expect( 71 | getTestSidebar([ 72 | gettingStartedLink, 73 | { 74 | label: 'Guides', 75 | items: [gettingStartedLink, typeDocSidebarGroup], 76 | }, 77 | typeDocSidebarGroup, 78 | ]), 79 | ).toMatchInlineSnapshot(` 80 | [ 81 | { 82 | "label": "Getting Started", 83 | "link": "/guides/getting-started/", 84 | }, 85 | { 86 | "items": [ 87 | { 88 | "label": "Getting Started", 89 | "link": "/guides/getting-started/", 90 | }, 91 | { 92 | "collapsed": false, 93 | "items": [], 94 | "label": "API", 95 | }, 96 | ], 97 | "label": "Guides", 98 | }, 99 | { 100 | "collapsed": false, 101 | "items": [], 102 | "label": "API", 103 | }, 104 | ] 105 | `) 106 | }) 107 | }) 108 | 109 | describe('getSidebarWithoutReflections', () => { 110 | test('should not do anything for an undefined sidebar', () => { 111 | expect(getTestSidebarWithoutReflections([])).toEqual([]) 112 | }) 113 | 114 | test('should not do anything for an empty sidebar', () => { 115 | expect(getTestSidebarWithoutReflections([])).toEqual([]) 116 | }) 117 | 118 | test('should not do anything for a sidebar without a placeholder', () => { 119 | expect(getTestSidebarWithoutReflections([gettingStartedLink])).toEqual([gettingStartedLink]) 120 | }) 121 | 122 | test('should remove a placeholder at the rool level', () => { 123 | expect(getTestSidebarWithoutReflections([gettingStartedLink, typeDocSidebarGroup])).toMatchInlineSnapshot(` 124 | [ 125 | { 126 | "label": "Getting Started", 127 | "link": "/guides/getting-started/", 128 | }, 129 | ] 130 | `) 131 | }) 132 | 133 | test('should remove a nested placeholder', () => { 134 | expect( 135 | getTestSidebarWithoutReflections([ 136 | { 137 | label: 'Guides', 138 | items: [typeDocSidebarGroup, gettingStartedLink], 139 | }, 140 | ]), 141 | ).toMatchInlineSnapshot(` 142 | [ 143 | { 144 | "items": [ 145 | { 146 | "label": "Getting Started", 147 | "link": "/guides/getting-started/", 148 | }, 149 | ], 150 | "label": "Guides", 151 | }, 152 | ] 153 | `) 154 | }) 155 | 156 | test('should remove multiple placeholders', () => { 157 | expect( 158 | getTestSidebarWithoutReflections([ 159 | gettingStartedLink, 160 | { 161 | label: 'Guides', 162 | items: [gettingStartedLink, typeDocSidebarGroup], 163 | }, 164 | typeDocSidebarGroup, 165 | ]), 166 | ).toMatchInlineSnapshot(` 167 | [ 168 | { 169 | "label": "Getting Started", 170 | "link": "/guides/getting-started/", 171 | }, 172 | { 173 | "items": [ 174 | { 175 | "label": "Getting Started", 176 | "link": "/guides/getting-started/", 177 | }, 178 | ], 179 | "label": "Guides", 180 | }, 181 | ] 182 | `) 183 | }) 184 | }) 185 | 186 | function getTestSidebar(sidebar: Parameters[0]) { 187 | return getSidebarFromReflections(sidebar, typeDocSidebarGroup, {}, {} as ProjectReflection, {}, 'api') 188 | } 189 | 190 | function getTestSidebarWithoutReflections(sidebar: Parameters[0]) { 191 | return getSidebarWithoutReflections(sidebar, typeDocSidebarGroup) 192 | } 193 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/tests/unit/typedoc.test.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'node:fs' 2 | 3 | import type { AstroIntegrationLogger, AstroConfig } from 'astro' 4 | import { afterAll, afterEach, beforeAll, expect, test, vi } from 'vitest' 5 | 6 | import type { StarlightTypeDocOptions } from '../..' 7 | import { generateTypeDoc } from '../../libs/typedoc' 8 | 9 | vi.mock(import('node:fs'), async (importOriginal) => { 10 | const mod = await importOriginal() 11 | return { 12 | ...mod, 13 | mkdirSync: vi.fn(), 14 | rmSync: vi.fn(), 15 | writeFileSync: vi.fn(), 16 | } 17 | }) 18 | 19 | const starlightTypeDocOptions = { 20 | tsconfig: '../../fixtures/basics/tsconfig.json', 21 | typeDoc: { 22 | logLevel: 4, 23 | }, 24 | } satisfies Partial 25 | 26 | const starlightTypeDocAstroConfig: Partial = { 27 | // './src' is the default value supplied by Astro — https://docs.astro.build/en/reference/configuration-reference/#srcdir 28 | srcDir: new URL('src', import.meta.url), 29 | } 30 | 31 | beforeAll(() => { 32 | vi.spyOn(fs, 'mkdirSync').mockReturnValue(undefined) 33 | vi.spyOn(fs, 'rmSync').mockReturnValue(undefined) 34 | vi.spyOn(fs, 'writeFileSync').mockReturnValue(undefined) 35 | }) 36 | 37 | afterEach(() => { 38 | vi.clearAllMocks() 39 | }) 40 | 41 | afterAll(() => { 42 | vi.restoreAllMocks() 43 | }) 44 | 45 | test('should throw an error with no exports', async () => { 46 | await expect( 47 | generateTestTypeDoc({ 48 | ...starlightTypeDocOptions, 49 | entryPoints: ['../../fixtures/basics/src/noExports.ts'], 50 | }), 51 | ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Failed to generate TypeDoc documentation.]`) 52 | }) 53 | 54 | test('should support providing custom TypeDoc options', async () => { 55 | const options = { 56 | ...starlightTypeDocOptions, 57 | entryPoints: ['../../fixtures/basics/src/noDocs.ts'], 58 | } 59 | 60 | await expect(generateTestTypeDoc(options)).resolves.not.toThrow() 61 | 62 | await expect( 63 | generateTestTypeDoc({ 64 | ...options, 65 | typeDoc: { 66 | ...starlightTypeDocOptions.typeDoc, 67 | excludeNotDocumented: true, 68 | }, 69 | }), 70 | ).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Failed to generate TypeDoc documentation.]`) 71 | }) 72 | 73 | test('should generate the doc in `src/content/docs/api` by default', async () => { 74 | await generateTestTypeDoc({ 75 | ...starlightTypeDocOptions, 76 | entryPoints: ['../../fixtures/basics/src/functions.ts'], 77 | }) 78 | 79 | const mkdirSyncSpy = vi.mocked(fs.mkdirSync) 80 | 81 | expect(mkdirSyncSpy).toHaveBeenCalled() 82 | expect(mkdirSyncSpy.mock.calls[0]?.[0].toString()).toMatch(/src[/\\]content[/\\]docs[/\\]api$/) 83 | }) 84 | 85 | test('should generate the doc in `/content/docs/api` of the srcDir via the AstroConfig', async () => { 86 | await generateTestTypeDoc( 87 | { 88 | ...starlightTypeDocOptions, 89 | entryPoints: ['../../fixtures/basics/src/functions.ts'], 90 | }, 91 | { srcDir: new URL('www/src', import.meta.url) }, 92 | ) 93 | 94 | const mkdirSyncSpy = vi.mocked(fs.mkdirSync) 95 | 96 | expect(mkdirSyncSpy).toHaveBeenCalled() 97 | expect(mkdirSyncSpy.mock.calls[0]?.[0].toString()).toMatch(/www[/\\]src[/\\]content[/\\]docs[/\\]api$/) 98 | }) 99 | 100 | test('should generate the doc in a custom output directory relative to `src/content/docs/`', async () => { 101 | const output = 'dist-api' 102 | 103 | await generateTestTypeDoc({ 104 | ...starlightTypeDocOptions, 105 | entryPoints: ['../../fixtures/basics/src/functions.ts'], 106 | output, 107 | }) 108 | 109 | const mkdirSyncSpy = vi.mocked(fs.mkdirSync) 110 | 111 | expect(mkdirSyncSpy).toHaveBeenCalled() 112 | expect(mkdirSyncSpy.mock.calls[0]?.[0].toString()).toMatch( 113 | new RegExp(`src[/\\\\]content[/\\\\]docs[/\\\\]${output}$`), 114 | ) 115 | }) 116 | 117 | test('should not add `README.md` module files for multiple entry points', async () => { 118 | await generateTestTypeDoc({ 119 | ...starlightTypeDocOptions, 120 | entryPoints: ['../../fixtures/basics/src/Bar.ts', '../../fixtures/basics/src/Foo.ts'], 121 | }) 122 | 123 | const rmSyncSpy = vi.mocked(fs.rmSync) 124 | const filePaths = rmSyncSpy.mock.calls.map((call) => call[0].toString()) 125 | 126 | expect(rmSyncSpy).toHaveBeenCalled() 127 | expect(filePaths.filter((filePath) => /\/(?:Bar|Foo)\/README\.md$/.test(filePath)).length).toBe(2) 128 | }) 129 | 130 | test('should support overriding typedoc-plugin-markdown readme page generation', async () => { 131 | await generateTestTypeDoc({ 132 | ...starlightTypeDocOptions, 133 | typeDoc: { 134 | ...starlightTypeDocOptions.typeDoc, 135 | readme: 'README.md', 136 | }, 137 | entryPoints: ['../../fixtures/basics/src/Bar.ts', '../../fixtures/basics/src/Foo.ts'], 138 | }) 139 | 140 | const writeFileSyncSpy = vi.mocked(fs.writeFileSync) 141 | const filePaths = writeFileSyncSpy.mock.calls.map((call) => call[0].toString()) 142 | 143 | expect(filePaths.some((filePath) => filePath.endsWith('modules.md'))).toBe(true) 144 | expect(filePaths.some((filePath) => filePath.endsWith('README.md'))).toBe(true) 145 | }) 146 | 147 | test('should output modules with index', async () => { 148 | await generateTestTypeDoc({ 149 | ...starlightTypeDocOptions, 150 | typeDoc: { 151 | ...starlightTypeDocOptions.typeDoc, 152 | outputFileStrategy: 'modules', 153 | entryFileName: 'index.md', 154 | }, 155 | entryPoints: ['../../fixtures/basics/src/module.ts'], 156 | }) 157 | 158 | const writeFileSyncSpy = vi.mocked(fs.writeFileSync) 159 | const filePaths = writeFileSyncSpy.mock.calls.map((call) => call[0].toString()) 160 | 161 | expect(filePaths).toEqual([ 162 | expect.stringMatching(/index\.md$/), 163 | expect.stringMatching(/namespaces\/bar\.md$/), 164 | expect.stringMatching(/namespaces\/foo\.md$/), 165 | expect.stringMatching(/namespaces\/functions\.md$/), 166 | expect.stringMatching(/namespaces\/shared\.md$/), 167 | expect.stringMatching(/namespaces\/types\.md$/), 168 | ]) 169 | }) 170 | 171 | test('should output index with correct module path', async () => { 172 | await generateTestTypeDoc({ 173 | ...starlightTypeDocOptions, 174 | typeDoc: { 175 | ...starlightTypeDocOptions.typeDoc, 176 | outputFileStrategy: 'modules', 177 | entryFileName: 'index.md', 178 | }, 179 | entryPoints: ['../../fixtures/basics/src/module.ts'], 180 | }) 181 | 182 | const writeFileSyncSpy = vi.mocked(fs.writeFileSync) 183 | const [, content] = writeFileSyncSpy.mock.calls.find((call) => call[0].toString().endsWith('index.md')) as [ 184 | fs.PathOrFileDescriptor, 185 | string, 186 | ] 187 | 188 | expect( 189 | content.includes(` 190 | - [bar](/api/starlight-typedoc/namespaces/bar/) 191 | - [foo](/api/starlight-typedoc/namespaces/foo/) 192 | - [functions](/api/starlight-typedoc/namespaces/functions/) 193 | - [shared](/api/starlight-typedoc/namespaces/shared/) 194 | - [types](/api/starlight-typedoc/namespaces/types/)`), 195 | ).toBe(true) 196 | }) 197 | 198 | function generateTestTypeDoc( 199 | options: Parameters[0], 200 | config: Partial = starlightTypeDocAstroConfig, 201 | ) { 202 | return generateTypeDoc( 203 | { 204 | ...starlightTypeDocOptions, 205 | ...options, 206 | }, 207 | config as AstroConfig, 208 | { 209 | info() { 210 | // noop 211 | }, 212 | warn() { 213 | // noop 214 | }, 215 | } as unknown as AstroIntegrationLogger, 216 | ) 217 | } 218 | -------------------------------------------------------------------------------- /packages/starlight-typedoc/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config' 2 | 3 | export default defineConfig({ 4 | test: { 5 | include: ['tests/unit/**/*.test.ts'], 6 | server: { 7 | deps: { 8 | inline: ['typedoc'], 9 | }, 10 | }, 11 | }, 12 | }) 13 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'docs' 3 | - 'example' 4 | - 'fixtures/*' 5 | - 'packages/*' 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@hideoo/tsconfig", 3 | "include": ["docs/.astro/types.d.ts", "**/*"] 4 | } 5 | --------------------------------------------------------------------------------