Repository navigation
Expand file tree
/
Copy pathdocusaurus.config.js
More file actions
466 lines (441 loc) · 19.2 KB
/
Copy pathdocusaurus.config.js
File metadata and controls
466 lines (441 loc) · 19.2 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
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
const { REDIRECTS } = require("./redirects");
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require("prism-react-renderer").themes.github;
const darkCodeTheme = require("prism-react-renderer").themes.dracula;
require("dotenv").config();
// The build sentinel resolves its own commit, inside plugins/build-info.js.
// It is NOT resolved here and handed over as a plugin option, because
// Docusaurus serialises this config -- options included -- into `main.js`.
//
// That is how a clock got into the client bundle and moved its content hash on
// every build. Removing the clock was not enough: the commit sha stayed, and
// every deploy is a new commit, so a documentation-only change still rehashed
// the bundle and rewrote all 265 pages against a one-year immutable header.
//
// Nothing here needs the commit. Both consumers are inside that plugin.
/** @type {import('@docusaurus/types').Config} */
const config = {
title:
process.env.PROD == "true" ? "Market Data" : "Market Data Docs (staging)",
tagline: "Complete Documentation For All Market Data Products & Services",
url:
process.env.PROD == "true"
? "https://www.marketdata.app/"
: "https://www-staging.marketdata.app/",
baseUrl: "/docs/",
trailingSlash: true,
noIndex: process.env.PROD !== "true",
// "warn" on deploys so a stray link never blocks a production release, but
// "throw" under STRICT_LINKS so the PR check (yarn lint:links) fails instead
// of letting broken links reach the site. This was "ignore" and silently
// accumulated 14 broken links.
onBrokenLinks: process.env.STRICT_LINKS === "true" ? "throw" : "warn",
// 3.10 CHECKS ANCHORS, AND 3.0.1 NEVER DID. The first build after the
// upgrade reported 254 broken anchors. 234 were OUR OWN FAULT and not broken
// at all: `src/theme/Heading` was a stale fork of the 3.0.1 file, and
// upstream had since added `brokenLinks.collectAnchor(id)` -- the call by
// which a page registers the anchors it defines. Without it every page
// declared none, so every table-of-contents self-link looked broken.
// Deleting that swizzle fixed all 234.
//
// The remaining 26 were real and every one is now fixed. They had been
// broken since they were written: `#MarketDataClient` where the generated id
// is lowercase, `#Logger` where the section is called Logging, and two that
// named documentation which does not exist at all.
//
// GATED THE SAME WAY AS `onBrokenLinks`, and that is the point of the
// exercise. A deploy builds with "warn" so a stray anchor cannot block a
// release; `pnpm run lint:links` builds with "throw", so the PR check is
// what keeps a broken one off the site. An anchor is the half of a link that
// nothing else can see: `onBrokenLinks` proves the PAGE exists and says
// nothing about the fragment, so a deep link can point into a page that
// renders perfectly and still land the reader nowhere.
onBrokenAnchors: process.env.STRICT_LINKS === "true" ? "throw" : "warn",
// Opting into v4 behaviour while still on 3.10, so the major is a version
// bump rather than a migration. Each flag is enabled deliberately; see the
// notes on `storage` below for the one that needed a counterweight.
future: {
v4: true,
},
// THE COUNTERWEIGHT TO `future.v4.siteStorageNamespacing`, AND IT IS
// DELIBERATE RATHER THAN A CLIMBDOWN.
//
// That flag namespaces browser-storage keys -- `theme` becomes `theme-f3b`,
// a hash of the site's url and baseUrl. It exists so that two Docusaurus
// sites sharing one domain cannot read each other's preferences.
//
// WE WANT THEM TO. `theme` is a contract across the whole origin: the
// marketing half at www.marketdata.app/ and the docs at /docs/ are one site
// to a reader, and the theme has to follow them across the seam.
// `plugins/theme-cookie-sync.js` seeds it from the .marketdata.app cookie
// before Docusaurus reads it, and @marketdataapp/ui's theme.js -- used by
// every other property -- reads and writes the same unprefixed key.
//
// Turning v4 on without this was measured, not assumed: the built bootstrap
// script read `localStorage.getItem("theme-f3b")` while our bridge still
// wrote `theme`, so a reader who chose dark mode on the marketing site would
// have arrived here in light mode. Nothing would have failed; the theme
// would just have stopped following them.
//
// Namespacing it properly is not an option: the hash is derived from the
// url, so staging and production would disagree with each other AND with the
// marketing half, which cannot know either value.
storage: {
namespace: false,
},
markdown: {
hooks: {
// Moved out of the top level in 3.10, where `siteConfig.onBrokenMarkdownLinks`
// is deprecated and warns on every build. It is removed in v4.
onBrokenMarkdownLinks:
process.env.STRICT_LINKS === "true" ? "throw" : "warn",
},
},
favicon: "img/favicon.ico",
organizationName: "marketdata",
projectName: "documentation",
headTags: [
// The Cloudflare Zaraz loader, and PRODUCTION ONLY.
//
// Zaraz is configured per ZONE, and www. and www-staging. are both in the
// marketdata.app zone. A staging build that loads this fires the same tools
// into the same GA4 property as production, and polluted analytics cannot be
// un-collected. The gate is therefore the same `PROD` flag that drives
// `noIndex` and `url` above, which `deploy-docs.yml` sets only on `main`.
//
// The tag lives here rather than in `src/theme/Root.js` because the client
// bundle cannot read `process.env.PROD`; only this file can. It previously
// gated on `NODE_ENV`, which every `yarn build` sets, so staging loaded it.
//
// The attributes match `ZARAZ_LOADER_ATTRS` in MarketDataApp/website's
// `src/lib/site-env.mjs` — the marketing half of the same origin — and each
// one is load-bearing:
// data-cfasync="false" keeps Rocket Loader from deferring the loader
// past the pageview it exists to record
// referrerpolicy="origin" sends the origin, not the measured page's path
//
// Cloudflare answers /cdn-cgi/zaraz/i.js at the edge. No origin serves it.
...(process.env.PROD == "true"
? [
{
tagName: "script",
attributes: {
"data-cfasync": "false",
src: "/cdn-cgi/zaraz/i.js",
referrerpolicy: "origin",
},
},
]
: []),
{
tagName: "meta",
attributes: {
name: "algolia-site-verification",
content: "BAA3BC0EFD344D0C",
},
},
{
tagName: "link",
attributes: {
rel: "stylesheet",
href: "/docs/css/components.no-reset.css",
},
},
],
i18n: {
defaultLocale: "en",
locales: ["en"],
},
presets: [
[
"classic",
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: false,
blog: false,
theme: {
customCss: ["./src/css/custom.css"],
},
sitemap:
process.env.PROD == "true"
? {
changefreq: "weekly",
priority: 0.5,
// Matched against the path INCLUDING `baseUrl`, which is why
// these name `/docs/`. Two patterns because `**` does not match
// the section root's own empty remainder.
//
// This list used to carry `/tags/**`, which HAD NEVER MATCHED
// ANYTHING: tag routes are generated per docs instance, so they
// are `/docs/api/tags/…` and `/docs/sheets/tags/…` — neither the
// prefix nor the middle segment is optional. Seven tag pages were
// in the production sitemap for as long as the site has had one.
// The pages are gone as of 2026-09-04, retired with the `tags:`
// front matter that generated them and redirected in redirects.js,
// so the pattern went with them rather than being corrected.
//
// A pattern that matches nothing is indistinguishable from one
// that matches everything it should, because both build clean.
// Count the locs; do not read the config.
//
// `/internal/` is our own reference material.
// `plugins/noindex-head.js` marks every page there noindex, and
// D2 fails when the sitemap advertises a noindex route — so
// without these the build goes red rather than shipping a
// contradiction.
// `/docs/search/` is the Algolia UI: a form with no content
// of its own, whose every result already has an indexable page
// of its own. Its result views are the SAME route with a query
// string, so nothing else needs listing either.
//
// This settles a disagreement rather than inventing a rule.
// The marketing half of the origin has always excluded its own
// `/search/` and `/review/*`, and the two halves have been
// named by ONE sitemap index since 2026-09-04 -- so until this,
// a crawler saw one origin declaring one convention twice,
// differently. Both halves also already agreed that internal
// pages stay out; the search page was the only difference.
//
// `plugins/noindex-head.js` marks the same route noindex, and
// the two must agree: `lint:seo` D2 fails a build that
// advertises a noindex route, so one without the other goes
// red rather than shipping a contradiction.
ignorePatterns: [
"/docs/internal/**",
"/docs/internal/",
"/docs/search/",
],
filename: "sitemap.xml",
}
: {},
}),
],
],
clientModules: [
'./src/clientModules/themeCookieSync.js',
'./src/clientModules/navbarOverflow.js',
// Recovers a reader whose session outlived the build it started in. Every
// deploy strands whoever is mid-session: their HTML names chunk hashes the
// new deployment no longer contains. MarketData-App/website#98 measured it
// twice in one day. See lib/chunk-reload.js.
'./src/clientModules/chunkReload.js',
],
plugins: [
'./plugins/build-info',
'./plugins/theme-cookie-sync',
'./plugins/markdown-twins',
'./plugins/not-found-head',
'./plugins/noindex-head',
'./plugins/redirects-file',
[
"@docusaurus/plugin-content-docs",
/** @type {import('@docusaurus/plugin-content-docs').Options} */
{
id: "api",
path: "api",
routeBasePath: "api",
sidebarPath: "./sidebars.js",
editUrl: ({ docPath }) => {
const host = process.env.PROD == "true" ? "www.marketdata.app" : "www-staging.marketdata.app";
return `https://${host}/docs/api/${docPath.replace(/\.mdx?$/, '.md')}`;
},
// Read from git history, so it needs full history at build time:
// deploy-docs.yml and pr-checks.yml check out with fetch-depth: 0.
// With a shallow clone every page reports the same date.
showLastUpdateTime: true,
},
],
[
"@docusaurus/plugin-content-docs",
/** @type {import('@docusaurus/plugin-content-docs').Options} */
{
id: "sdk",
path: "sdk",
routeBasePath: "sdk",
editUrl: ({ docPath }) => {
const host = process.env.PROD == "true" ? "www.marketdata.app" : "www-staging.marketdata.app";
return `https://${host}/docs/sdk/${docPath.replace(/\.mdx?$/, '.md')}`;
},
// Read from git history, so it needs full history at build time:
// deploy-docs.yml and pr-checks.yml check out with fetch-depth: 0.
// With a shallow clone every page reports the same date.
showLastUpdateTime: true,
sidebarPath: "./sidebars.js",
},
],
[
"@docusaurus/plugin-content-docs",
/** @type {import('@docusaurus/plugin-content-docs').Options} */
{
id: "sheets",
path: "sheets",
routeBasePath: "sheets",
editUrl: ({ docPath }) => {
const host = process.env.PROD == "true" ? "www.marketdata.app" : "www-staging.marketdata.app";
return `https://${host}/docs/sheets/${docPath.replace(/\.mdx?$/, '.md')}`;
},
// Read from git history, so it needs full history at build time:
// deploy-docs.yml and pr-checks.yml check out with fetch-depth: 0.
// With a shallow clone every page reports the same date.
showLastUpdateTime: true,
sidebarPath: "./sidebars.js",
},
],
[
"@docusaurus/plugin-content-docs",
/** @type {import('@docusaurus/plugin-content-docs').Options} */
{
id: "account",
path: "account",
routeBasePath: "account",
editUrl: ({ docPath }) => {
const host = process.env.PROD == "true" ? "www.marketdata.app" : "www-staging.marketdata.app";
return `https://${host}/docs/account/${docPath.replace(/\.mdx?$/, '.md')}`;
},
// Read from git history, so it needs full history at build time:
// deploy-docs.yml and pr-checks.yml check out with fetch-depth: 0.
// With a shallow clone every page reports the same date.
showLastUpdateTime: true,
sidebarPath: "./sidebars.js",
},
],
[
"@docusaurus/plugin-content-docs",
/** @type {import('@docusaurus/plugin-content-docs').Options} */
{
// Our own reference material. Reachable by typing the URL, absent from
// the navbar because nothing in `themeConfig.navbar.items` names it.
//
// NOT `unlisted: true`. That front matter hides a page from the SIDEBAR
// in a production build, which is the opposite of what this section is
// for: the whole point is that landing on /docs/internal/<page> gives
// you the section's menu.
//
// `noindex` is stamped onto the built pages by `plugins/noindex-head.js`
// instead, and `lint:seo` M1 fails the build if a page arrives without
// it. A `<head>` block in the MDX was tried first and does not work --
// it renders as literal text and produces no tag. See lib/noindex-head.js.
//
// llms.txt does not read that tag. `internal` is excluded by stem in
// lib/llms-txt.js, because postBuild hooks run concurrently and reading
// a stamped page would be a race.
id: "internal",
path: "internal",
routeBasePath: "internal",
editUrl: ({ docPath }) => {
const host = process.env.PROD == "true" ? "www.marketdata.app" : "www-staging.marketdata.app";
return `https://${host}/docs/internal/${docPath.replace(/\.mdx?$/, '.md')}`;
},
// Read from git history, so it needs full history at build time:
// deploy-docs.yml and pr-checks.yml check out with fetch-depth: 0.
// With a shallow clone every page reports the same date.
showLastUpdateTime: true,
sidebarPath: "./sidebars.js",
},
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
/**
* The social card every page falls back to.
*
* Docusaurus emits `<meta name="twitter:card" content="summary_large_image">`
* UNCONDITIONALLY (theme-classic SiteMetadata), while `og:image` and
* `twitter:image` appear only when this key is set. It was not set, so all
* 270 pages promised a large-image card and supplied no image, and every
* shared docs link rendered as a bare URL. `lint:seo` rule F2 gates the
* pair now, so the promise and the image cannot come apart again.
*
* Cropped to 1200x630 from "Facebook Cover Data" in the brand Social Media
* Kit. The source carries a 1px semi-transparent frame (alpha 128, corners
* 64) that flattens to a visible hairline, so the crop is inset 2px first.
*
* A page overrides it with frontmatter `image:`.
*/
image: 'img/social-card.png',
// Docusaurus emits no alt text for the card, and the two networks read
// different names: Open Graph og:image:alt, X twitter:image:alt.
metadata: [
{
property: 'og:image:alt',
content:
'Market Data — Get Data Anywhere, beside an illustration of charts and dashboards',
},
{
name: 'twitter:image:alt',
content:
'Market Data — Get Data Anywhere, beside an illustration of charts and dashboards',
},
],
algolia: {
appId: "IUHZFO750H",
apiKey: "c29b76b827a4fa1a0ac3abe15f69ec5c",
indexName: "Market Data Documentation",
},
navbar: {
title: "",
logo: {
alt: "My Logo",
src: "img/pngs/logo.png",
href: "/",
srcDark: "img/pngs/darkmode-logo.png",
},
items: [
{
to: "/api",
label: "API",
position: "left",
},
{
to: "/sdk",
label: "SDKs",
position: "left",
},
{
to: "/sheets",
label: "Sheets Add-On",
position: "left",
},
{
to: "/account",
label: "Accounts & Billing",
position: "left",
},
{
type: "search",
position: "right",
},
{
type: "custom-UserProfile",
position: "right",
},
],
},
footer: {
style: "light",
copyright: `<div class="footer-custom-container" >
<p>© ${new Date().getFullYear()} Market Data. All Rights Reserved</p>
<div class="footer-custom">
<a target="_blank" style="font-size:15px" href="https://www.trustpilot.com/review/www.marketdata.app">See our Reviews On TrustPilot</a>
<a target="_blank" style="font-size:15px" href="https://www.marketdata.app/privacy/">Privacy Policy</a>
<a target="_blank" style="font-size:15px" href="https://www.marketdata.app/terms/">Terms & Conditions</a>
<a target="_blank" style="font-size:15px" href="https://www.marketdata.app/status/">System Status</a>
</div>
</div>`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
// Every language used in a ``` fence that Prism does not bundle by
// default has to be listed here, or the block renders as plain text
// with no error -- the page still builds and still looks like a code
// block, so nothing catches it but reading one. `csharp` was missing
// while 99 fences used it. scripts/check-highlighting.js now fails
// the build instead.
additionalLanguages: ['json', 'http', 'php', 'bash', 'excel-formula', 'java', 'kotlin', 'groovy', 'csharp', 'powershell', 'batch', 'ini'],
},
}),
};
module.exports = config;