Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vale/styles/Shorebird/Headings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ match: $sentence
exceptions:
- Shorebird
- Shorebird's
- Zap
- Flutter
- Flutter's
- Dart
Expand Down
69 changes: 68 additions & 1 deletion astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,75 @@

import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import sitemap from '@astrojs/sitemap';
import tailwindcss from '@tailwindcss/vite';
import starlightLinksValidator from 'starlight-links-validator';
import starlightAutoSidebar from 'starlight-auto-sidebar';
import starlightImageZoom from 'starlight-image-zoom';
import starlightThemeNova from 'starlight-theme-nova';
import opengraphImages from 'astro-opengraph-images';
import { renderer } from './src/og/renderer.tsx';
import { readFileSync } from 'node:fs';
import { globSync, readFileSync } from 'node:fs';
import { readFile, writeFile } from 'node:fs/promises';
import yaml from 'js-yaml';
import starlightLlmsTxt from 'starlight-llms-txt';
import { remarkReplaceVersions } from './src/plugins/replace-versions.ts';
import mermaid from 'astro-mermaid';
import remarkGfm from 'remark-gfm';
import { unified } from '@astrojs/markdown-remark';
import { unlistedPages } from './src/unlisted.ts';

const site = 'https://docs.shorebird.dev/';

// `starlight-llms-txt` joins pages with this string. The default is a bare
// blank line, which is indistinguishable from a paragraph break; an HTML
// comment gives `stripUnlistedFromLlmsFull` a reliable page boundary, and
// Markdown renderers ignore it.
const llmsPageSeparator = '\n\n<!-- page -->\n\n';

// The plugin's `exclude` option only filters `llms-small.txt` (deliberately,
// see its 0.2.1 changelog), so unlisted pages are stripped from
// `llms-full.txt` after the build instead. They are found by position, not by
// title, since titles are not unique (several pages are titled "Overview"):
// `demote` sorts them to the end of the file, in `unlistedPages` order.
const stripUnlistedFromLlmsFull = {
name: 'strip-unlisted-from-llms-full',
hooks: {
'astro:build:done': async ({ dir, logger }) => {
if (unlistedPages.length === 0) return;
const file = new URL('llms-full.txt', dir);
const pages = (await readFile(file, 'utf8')).split(llmsPageSeparator);
const kept = pages.slice(0, -unlistedPages.length);
const stripped = pages.slice(-unlistedPages.length);
// Check each stripped page is the expected one before writing, so a
// change in the plugin's output fails the build instead of silently
// dropping a real page. The plugin starts each page with `# <title>`.
// This hook runs after the content collection APIs are torn down, so
// titles are read off disk.
unlistedPages.forEach((id, i) => {
const [path] = globSync(
`src/content/docs/{${id},${id}/index}.{md,mdx}`,
);
if (!path) throw new Error(`Unlisted page "${id}" not found.`);
const [, frontmatter] =
/^---\r?\n([\s\S]*?)\r?\n---/.exec(readFileSync(path, 'utf8')) ?? [];
const data = yaml.load(frontmatter ?? '');
const title = data?.hero?.title || data?.title;
if (!title) throw new Error(`No title in ${path}.`);
if (!stripped[i]?.startsWith(`# ${title}\n`)) {
throw new Error(
`llms-full.txt: expected unlisted page "${id}" at position ${kept.length + i}, found "${stripped[i]?.split('\n', 1)[0]}".`,
);
}
});
await writeFile(file, kept.join(llmsPageSeparator));
logger.info(
`Stripped ${stripped.length} unlisted page(s) from llms-full.txt`,
);
},
},
};

// https://astro.build/config
export default defineConfig({
site,
Expand All @@ -29,6 +82,14 @@ export default defineConfig({
},
integrations: [
mermaid({ autoTheme: true }),
// Starlight adds `@astrojs/sitemap` itself unless it is already in this
// array, so configuring it here is what lets unlisted pages be filtered out.
sitemap({
filter: (page) =>
!unlistedPages.some(
(id) => page === `${site}${id}/` || page === `${site}${id}`,
),
}),
starlight({
expressiveCode: false,
title: 'Shorebird',
Expand Down Expand Up @@ -190,6 +251,11 @@ Developer & Agent Interfaces:
'Machine-readable agent capability card for agent-to-agent discovery',
},
],
pageSeparator: llmsPageSeparator,
// `exclude` only affects `llms-small.txt`, and `demote` lines
// unlisted pages up for `stripUnlistedFromLlmsFull`.
exclude: [...unlistedPages],
demote: [...unlistedPages],
}),
],
}),
Expand All @@ -206,6 +272,7 @@ Developer & Agent Interfaces:
},
render: renderer,
}),
stripUnlistedFromLlmsFull,
],
redirects: {
// Redirects to preserve legacy URLs & resolve agent probes.
Expand Down
14 changes: 4 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@astrojs/check": "^0.9.10",
"@astrojs/markdown-remark": "^7.3.1",
"@astrojs/mdx": "^8.0.1",
"@astrojs/sitemap": "^3.7.4",
"@astrojs/starlight": "^0.42.0",
"@lavamoat/preinstall-always-fail": "^3.0.0",
"@tailwindcss/vite": "^4.3.3",
Expand Down
Binary file added src/assets/zap_add_custom_connector.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/zap_custom_connector_form.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 11 additions & 2 deletions src/components/starlight/Head.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@
import Default from '@astrojs/starlight/components/Head.astro';
import { getImagePath } from 'astro-opengraph-images';
import { markdownSiblingPath } from '~/utils/markdown-path';
import { unlistedPages } from '~/unlisted';

const ogImageUrl = getImagePath({ url: Astro.url, site: Astro.site });

const markdownPath = markdownSiblingPath(Astro.url.pathname);
// Unlisted pages have no `.md` sibling (see `src/pages/[...slug].md.ts`).
const { entry } = Astro.locals.starlightRoute;
const markdownPath = unlistedPages.includes(entry.id)
? undefined
: markdownSiblingPath(Astro.url.pathname);
---

<Default><slot /></Default>
Expand All @@ -29,7 +34,11 @@ const markdownPath = markdownSiblingPath(Astro.url.pathname);
<meta name="author" content="Shorebird, Inc." />

<!-- AI Agent Discovery & Alternate Formats -->
<link rel="alternate" type="text/markdown" href={markdownPath} />
{
markdownPath && (
<link rel="alternate" type="text/markdown" href={markdownPath} />
)
}
<link
rel="alternate"
type="text/plain"
Expand Down
Loading
Loading