# Vanilla Astro

`starlight-pydocs/astro` is an ordinary Astro integration. It injects the same generated pages,
renders the same components and serves the same `symbols.json`, `objects.inv` and `llms.txt` as the
Starlight plugin. It cannot give you Starlight's shell.

## What changes without Starlight

The generated pages, every component, the search index, the Sphinx inventory and `llms.txt` all work
unchanged. Two things do not:

- **No full-text search.** Pagefind belongs to Starlight, so nothing indexes the prose of the
  generated pages. The [symbol search](/starlight-pydocs/guides/search/) box does not depend on
  Starlight. It still appears automatically on each package's root page, but it matches object names,
  not prose.
- **No sidebar and no prev/next links.** Both come from Starlight's route data, and a plain Astro
  site has no equivalent. Build your own navigation from the `symbols.json` endpoint or the
  [Content Layer loader](#the-content-layer-loader): every object carries the page slug it is
  documented on and its heading anchor.

`@astrojs/starlight` is an optional peer dependency. Nothing in this integration's module graph
imports it, so you never have to install it.

## Set it up

1. Install the package:

   ```sh
   npm install starlight-pydocs
   ```

2. Make sure that Griffe is available in your system's Python, or that
   [uv](https://docs.astral.sh/uv/) is installed.

   If you prefer, run Griffe separately and point the plugin at the JSON file it writes. See
   [Without Python](/starlight-pydocs/guides/getting-started/#without-python) for more information.

3. Add the integration. The export default defineConfig({
   integrations: [
       pydocs({
         packages: [{ name: 'mypkg', search: ['../src'] }],
         inventories: ['python'],
       }),
     ],
   });
   ```

4. Start the dev server. `mypkg` is documented at `/api/mypkg/`, one page per module, and each
   package also gets `symbols.json`, `objects.inv` and `llms.txt` alongside its pages.

Every option is the same as the Starlight plugin's, listed in
[Configuration](/starlight-pydocs/guides/configuration/). This integration adds one option and
disables another. `layout` names a component of your own to wrap the generated pages.
`injectStyles` has no effect here, because a plain Astro project has no `customCss` for the
integration to append to.

## Where the files go

- astro.config.mjs the integration, and your markdown processor
- src/
  - layouts/
    - ApiLayout.astro optional, replaces the built-in page layout
  - pages/
    - index.astro your own pages, where Autodoc goes
  - content.config.ts optional, for the Content Layer loader
  - middleware.ts optional, translates the generated pages
- package.json

Nothing under `src/pages/` corresponds to the generated pages. The integration injects a catch-all
route, so `/api/mypkg/…` exists without a file of yours behind it.

## The stylesheet

Import `starlight-pydocs/styles` once from any layout or page that renders the components. The
built-in layout imports it already, so this only matters for files you write yourself:

```astro title="src/pages/index.astro"
---
export default defineConfig({
  markdown: {
    processor: unified(),
    shikiConfig: { themes: { light: 'github-light', dark: 'github-dark' }, defaultColor: false },
  },
  integrations: [pydocs({ packages: [{ name: 'mypkg', search: ['../src'] }] })],
});
```

Whatever the engine, docstring code fences are highlighted the way the rest of the site's Markdown
is. On Astro 7.0.x, where `markdown.processor` does not exist yet, install
`@astrojs/markdown-remark` and it is used automatically.

## Components in your own pages

`<Autodoc>` documents one object wherever you put it. `<SymbolSearch>` is the search box the
generated root pages use. Both work in a hand-written page:

```astro title="src/pages/index.astro" {9}
---
export const collections = {
  api: defineCollection({
    loader: pydocsLoader({ name: 'mypkg', search: ['../src'] }),
  }),
};
```

`pydocsLoader` takes the per-package options plus `runner`, `cacheDir` and `projectRoot`. This lets
a collection read a pre-generated dump (`source: { file: './dumps/mypkg.json' }`) while the
integration extracts, or the other way round.

Each entry's `id` is the object's dotted path, and its data is typed:

```astro title="src/pages/symbols.astro" {18}
---
import 'starlight-pydocs/styles';

const entries = (await getCollection('api')).sort((left, right) => left.data.path.localeCompare(right.data.path));
const deprecated = entries.filter((entry) => entry.data.deprecated);
---

<table class="pyd-summary-table">
  <thead>
    <tr><th scope="col">Path</th><th scope="col">Kind</th><th scope="col">Signature</th><th scope="col">Summary</th></tr>
  </thead>
  <tbody>
    {
      entries.map((entry) => (
        <tr data-symbol-path={entry.data.path}>
          <th scope="row">
            <a href={`/${entry.data.page}/#${entry.data.anchor}`}>
              <code>{entry.data.path}</code>
            </a>
          </th>
          <td>{entry.data.kind}</td>
          <td><code>{entry.data.signature}</code></td>
          <td>{entry.data.brief}</td>
        </tr>
      ))
    }
  </tbody>
</table>

<p>{deprecated.length} deprecated objects.</p>
```

| Field                    | Meaning                                                                    |
| ------------------------ | -------------------------------------------------------------------------- |
| `path`, `canonicalPath`  | Documented path (also the anchor) and the path griffe defined it at        |
| `name`, `kind`, `labels` | Short name, object kind, griffe's labels                                   |
| `brief`, `docstring`     | First docstring line as plain text, and the whole docstring                |
| `signature`              | Plain-text signature; empty for modules                                    |
| `page`, `anchor`         | Page slug the object is documented on, and its heading anchor              |
| `deprecated`             | `true` when anything marks it deprecated                                   |
| `addedIn`                | Release the object first appeared in; absent unless `versions.refs` is set |

`page` is the full page slug (`api/mypkg/report`), so a link is that slug plus the anchor, adjusted
for your site's `base` if it has one.

## A working example

`examples/vanilla` in the repository is a complete site built this way: the integration on the
unified pipeline, a hand-written page with `<Autodoc>` and `<SymbolSearch>`, and a loader page. It
is one of the fixtures the end-to-end tests run against, so everything on this page is exercised
in CI.