-
Notifications
You must be signed in to change notification settings - Fork 65
Expand file tree
/
Copy pathastro.config.mjs
More file actions
329 lines (320 loc) · 14 KB
/
Copy pathastro.config.mjs
File metadata and controls
329 lines (320 loc) · 14 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
// @ts-check
import fs from 'node:fs';
import { execSync } from 'node:child_process';
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
import starlight from '@astrojs/starlight';
import starlightSidebarTopics from 'starlight-sidebar-topics';
import starlightLinksValidator from 'starlight-links-validator';
import starlightLlmsTxt from 'starlight-llms-txt';
import mermaid from 'astro-mermaid';
const streamelementsGrammar = JSON.parse(
fs.readFileSync('./grammars/streamelements.tmLanguage.json', 'utf-8'),
);
// Preserve old Docusaurus URLs that moved during the revamp. Astro builds
// these as meta-refresh pages (works on any host / local preview); the
// emitRedirectsFile integration below also writes them to dist/_redirects so
// Cloudflare Workers serves real HTTP 301s, which take precedence there.
const redirects = {
'/chatbot/gettingstarted/commands': '/chatbot/getting-started',
'/chatbot/gettingstarted/timers': '/chatbot/getting-started',
// Pages whose old URLs came from Docusaurus id: frontmatter, not filenames.
'/chatbot/modules/emote-bingo': '/chatbot/modules/bingo',
'/overlays/custom-code-alertboxes': '/overlays/custom-code-in-alertbox',
'/overlays/custom-widget-events': '/overlays/events',
'/chatbot/changelog': '/changelog',
'/api': 'https://dev.streamelements.com/',
'/patch-notes': '/changelog',
// Legacy feed URLs are served as real feeds (src/pages/patch-notes/) since
// feed readers don't follow meta-refresh redirects.
'/patch-notes/rss-feed-test': '/changelog',
'/patch-notes/patch-notes-coming-soon': '/changelog',
'/patch-notes/archive': '/changelog',
'/patch-notes/tags': '/changelog',
'/websockets/topics/chatbot/chatbot-status': '/websockets/topics/chatbot-status',
// Old URL used the page's Docusaurus id (chatbot-counters), not its filename.
'/websockets/topics/chatbot/chatbot-counters': '/websockets/topics/chatbot-counters',
'/websockets/topics/chatbot/modules-emotecombo': '/websockets/topics/chatbot-modules-emotecombo',
'/websockets/topics/chatbot/modules-pyramid': '/websockets/topics/chatbot-modules-pyramid',
'/websockets/topics/chatbot/timeout': '/websockets/topics/chatbot-timeout',
};
/** Most recent git commit date per content file, for sitemap <lastmod>.
One `git log` pass; the first time a file appears is its newest commit. */
function buildLastmodMap() {
const map = new Map();
const out = execSync('git log --format=%x00%cI --name-only -- src/content', {
encoding: 'utf8',
maxBuffer: 64 * 1024 * 1024,
});
let current = null;
for (const raw of out.split('\n')) {
if (raw.startsWith('\0')) {
current = raw.slice(1).trim();
} else {
const line = raw.trim();
if (line && current && !map.has(line)) map.set(line, current);
}
}
return map;
}
const lastmodMap = buildLastmodMap();
/** Maps a built page URL back to its source file's last commit date. */
function lastmodFor(url) {
const path = new URL(url).pathname.replace(/\/$/, '').replace(/^\//, '');
const candidates = [];
if (path === '') {
candidates.push('src/content/docs/index.mdx');
} else if (path.startsWith('changelog/post/')) {
const slug = path.slice('changelog/post/'.length);
for (const key of lastmodMap.keys()) {
if (key.startsWith('src/content/changelog/') && key.endsWith(`/${slug}.mdx`)) candidates.push(key);
}
} else {
for (const ext of ['md', 'mdx']) {
candidates.push(`src/content/docs/${path}.${ext}`, `src/content/docs/${path}/index.${ext}`);
}
}
for (const c of candidates) {
const date = lastmodMap.get(c);
if (date) return date;
}
return undefined;
}
/** Writes dist/_redirects (Cloudflare redirects file) from the map above.
Each path is emitted with and without a trailing slash so both forms 301. */
function emitRedirectsFile() {
return {
name: 'emit-cloudflare-redirects',
hooks: {
'astro:build:done': ({ dir }) => {
const lines = Object.entries(redirects).flatMap(([from, to]) => {
const variants = from.endsWith('/') ? [from] : [from, `${from}/`];
return variants.map((path) => `${path} ${to} 301`);
});
// Old Docusaurus sitemap location, still registered with search
// engines. Edge-only (not in the Astro redirects map): an HTML
// meta-refresh page is useless to XML consumers.
lines.push('/sitemap.xml /sitemap-index.xml 301');
fs.writeFileSync(new URL('_redirects', dir), `${lines.join('\n')}\n`);
},
},
};
}
export default defineConfig({
site: 'https://docs.streamelements.com',
// URLs have no trailing slash (parity with the old Docusaurus site, so
// inbound links and search results resolve without a 301 hop). Output stays
// in directory format (/path/index.html); Cloudflare serves /path from it
// via html_handling: 'drop-trailing-slash' in wrangler.jsonc.
trailingSlash: 'never',
redirects,
integrations: [
emitRedirectsFile(),
// Starlight skips its own sitemap integration when one is already present.
// This one adds <lastmod> from each page's last git commit date.
sitemap({
serialize(item) {
const lastmod = lastmodFor(item.url);
return lastmod ? { ...item, lastmod } : item;
},
}),
mermaid({ autoTheme: true }),
starlight({
title: 'StreamElements Docs',
description: 'The official documentation for StreamElements',
// Brand mark from @streamelements/assets (true vector); the PNG/ICO
// fallbacks below mirror the dashboard's favicon set.
favicon: '/favicon.svg',
social: [
{ icon: 'github', label: 'GitHub', href: 'https://github.com/StreamElements/docs' },
{ icon: 'discord', label: 'Discord', href: 'https://discord.gg/se' },
{ icon: 'twitch', label: 'Twitch', href: 'https://www.twitch.tv/streamelements' },
{ icon: 'x.com', label: 'X', href: 'https://twitter.com/StreamElements' },
],
editLink: {
baseUrl: 'https://github.com/StreamElements/docs/edit/master/',
},
lastUpdated: true,
customCss: ['@fontsource-variable/inter', './src/styles/custom.css'],
head: [
// Favicon fallbacks + touch icons (same set as the dashboard app).
{ tag: 'link', attrs: { rel: 'icon', type: 'image/png', sizes: '96x96', href: '/favicon-96x96.png' } },
{ tag: 'link', attrs: { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' } },
{ tag: 'link', attrs: { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' } },
{ tag: 'link', attrs: { rel: 'manifest', href: '/site.webmanifest' } },
{
tag: 'script',
attrs: { src: 'https://docs.streamelements.com/z/i.js', referrerpolicy: 'origin', defer: true },
},
{ tag: 'link', attrs: { rel: 'preconnect', href: 'https://docs.streamelements.com' } },
{
tag: 'meta',
attrs: { property: 'og:image', content: 'https://docs.streamelements.com/img/open_graph_preview.jpg' },
},
{ tag: 'meta', attrs: { name: 'twitter:card', content: 'summary_large_image' } },
{ tag: 'meta', attrs: { name: 'twitter:site', content: '@StreamElements' } },
{ tag: 'meta', attrs: { name: 'twitter:creator', content: '@StreamElements' } },
{ tag: 'meta', attrs: { name: 'robots', content: 'max-image-preview:large' } },
],
components: {
PageTitle: './src/components/overrides/PageTitle.astro',
Head: './src/components/overrides/Head.astro',
},
expressiveCode: {
defaultProps: { wrap: true },
// Match the code-block frame to the card family (ChatExample, CommandInfo,
// VariableInfo): same system radius and hairline border, so an example's
// input code block and its output chat card read as siblings.
styleOverrides: {
borderRadius: 'var(--se-radius)',
borderColor: 'var(--sl-color-gray-5)',
},
shiki: {
langs: [streamelementsGrammar],
},
},
plugins: [
starlightLinksValidator({
errorOnLocalLinks: false,
// Changelog routes are custom pages the validator can't see.
exclude: ['/changelog', '/changelog/**'],
}),
starlightLlmsTxt(),
starlightSidebarTopics(
[
{
label: 'Chatbot',
link: '/chatbot',
icon: 'comment',
items: [
{ label: 'Overview', slug: 'chatbot' },
{ label: 'Getting Started', slug: 'chatbot/getting-started' },
{
label: 'Guides',
items: [
{ label: 'Overview', slug: 'chatbot/guides' },
{ label: 'Death Counter', slug: 'chatbot/guides/death-counter' },
{ label: 'Lurk Command', slug: 'chatbot/guides/lurk-command' },
{ label: 'Countdown Command', slug: 'chatbot/guides/countdown-command' },
{ label: 'Viewer Input', slug: 'chatbot/guides/viewer-input' },
],
},
{
label: 'Commands',
items: [
{ label: 'Overview', slug: 'chatbot/commands' },
{ label: 'Custom Commands', slug: 'chatbot/commands/custom' },
{
label: 'Default Commands',
collapsed: true,
items: [{ autogenerate: { directory: 'chatbot/commands/default' } }],
},
],
},
{ label: 'Variables', collapsed: true, items: [{ autogenerate: { directory: 'chatbot/variables' } }] },
{ label: 'Modules', collapsed: true, items: [{ autogenerate: { directory: 'chatbot/modules' } }] },
{ label: 'Spam Filters', collapsed: true, items: [{ autogenerate: { directory: 'chatbot/filters' } }] },
{ label: 'Timers', slug: 'chatbot/timers' },
{ label: 'Quotes', slug: 'chatbot/quotes' },
{ label: 'Counters', slug: 'chatbot/counters' },
{ label: 'Troubleshooting', slug: 'chatbot/troubleshooting' },
],
},
{
label: 'Overlays',
link: '/overlays',
icon: 'desktop',
items: [
{ label: 'Overview', slug: 'overlays' },
{ label: 'Getting Started', slug: 'overlays/getting-started' },
{
label: 'Your First Custom Widget',
slug: 'overlays/first-custom-widget',
badge: { text: 'Tutorial', variant: 'tip' },
},
{
label: 'Custom Widgets',
items: [
{ label: 'Code Editor', slug: 'overlays/widget-structure' },
{ label: 'SE_API Reference', slug: 'overlays/custom-widget' },
{ label: 'Widget Events', slug: 'overlays/events' },
{ label: 'Session Data Reference', slug: 'overlays/session-data' },
],
},
{ label: 'Custom Code in AlertBoxes', slug: 'overlays/custom-code-in-alertbox' },
{ label: 'Editor Shortcuts', slug: 'overlays/overlay-editor-shortcuts' },
],
},
{
label: 'WebSockets',
link: '/websockets',
icon: 'server',
items: [
{ label: 'Introduction', slug: 'websockets' },
{ label: 'Client Examples', slug: 'websockets/examples' },
{
label: 'Topics',
items: [
{ label: 'Overview', slug: 'websockets/topics' },
{
label: 'Session & Stream',
items: [
'websockets/topics/channel-session-update',
'websockets/topics/channel-session-reset',
'websockets/topics/channel-stream-status',
],
},
{
label: 'Activity & Engagement',
items: [
'websockets/topics/channel-activities',
'websockets/topics/channel-chat-message',
'websockets/topics/channel-tips',
'websockets/topics/channel-tips-moderation',
'websockets/topics/channel-contests',
'websockets/topics/channel-giveaway',
'websockets/topics/channel-songrequest',
],
},
{
label: 'Overlays',
items: [
'websockets/topics/channel-overlay-action',
'websockets/topics/channel-overlay-broadcast',
'websockets/topics/channel-overlay-update',
],
},
{
label: 'Chatbot',
items: [
'websockets/topics/chatbot-status',
'websockets/topics/chatbot-counters',
'websockets/topics/chatbot-modules-emotecombo',
'websockets/topics/chatbot-modules-pyramid',
{ slug: 'websockets/topics/chatbot-timeout', badge: { text: 'In dev', variant: 'caution' } },
],
},
],
},
],
},
{
label: 'API Reference',
link: 'https://dev.streamelements.com/',
icon: 'external',
attrs: { target: '_blank', rel: 'noopener' },
},
{
label: 'Changelog',
link: '/changelog',
icon: 'rocket',
},
],
{
exclude: ['/', '/404'],
},
),
],
}),
],
});