@f5-sales-demo/docs-theme 4.11.6 → 4.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/Footer.astro +0 -12
- package/components/Head.astro +7 -1
- package/components/LinkCard.astro +0 -87
- package/components/LocalHeader.astro +57 -0
- package/components/ProviderHeader.astro +2 -119
- package/components/SharedHeader.astro +16 -147
- package/components/SiteTitle.astro +3 -27
- package/config.ts +16 -340
- package/index.ts +12 -3
- package/package.json +7 -4
- package/src/shared/baseline.json +30 -0
- package/src/shared/bootstrap.mjs +135 -0
- package/src/shared/contract.mjs +110 -0
- package/src/shared/localize.mjs +15 -0
- package/src/shared/runtime.tsx +124 -0
- package/src/shared/search-integration.mjs +16 -0
- package/src/shared/settings.mjs +19 -0
- package/src/utils/search-sites.ts +3 -32
- package/src/utils/showcase-navigation.test.ts +2 -32
- package/src/utils/statistics-navigation.test.ts +5 -29
- package/styles/shell.css +242 -0
package/config.ts
CHANGED
|
@@ -23,13 +23,12 @@ import { defaultLocale as f5xcDefaultLocale, f5xcDefaultLocales } from './src/i1
|
|
|
23
23
|
import { mobileLabels } from './src/i18n/mega-menu-translations.ts';
|
|
24
24
|
import { sidebarTranslations } from './src/i18n/translations.ts';
|
|
25
25
|
import remarkMermaid from './src/plugins/remark-mermaid.mjs';
|
|
26
|
+
import { sharedSearchIntegration } from './src/shared/search-integration.mjs';
|
|
27
|
+
import { sharedMode } from './src/shared/settings.mjs';
|
|
26
28
|
import canonicalLlms from './src/utils/canonical-llms.mjs';
|
|
27
|
-
import { providerMegaMenu } from './src/utils/canonical-provider.ts';
|
|
28
29
|
import { menuLocalizationPolicy } from './src/utils/menu-localization.ts';
|
|
29
|
-
import { stagedProviderSections } from './src/utils/provider-sections.ts';
|
|
30
30
|
import { publicationSidebar, repositoryPublicationProfile } from './src/utils/publication-profiles.ts';
|
|
31
|
-
import {
|
|
32
|
-
import { federatedSearchSites } from './src/utils/search-sites';
|
|
31
|
+
import { localSearchSites as federatedSearchSites } from './src/utils/search-sites';
|
|
33
32
|
import providerSharedAssets from './src/utils/share-provider-assets.mjs';
|
|
34
33
|
import { statisticsScriptMarkdown } from './src/utils/statistics-script-markdown.mjs';
|
|
35
34
|
import { buildSubcategorySidebar } from './src/utils/subcategory-sidebar.ts';
|
|
@@ -126,333 +125,7 @@ export interface F5xcDocsConfigOptions {
|
|
|
126
125
|
};
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
export const defaultMegaMenuItems: MegaMenuItem[] = [
|
|
130
|
-
{
|
|
131
|
-
label: 'Demos',
|
|
132
|
-
content: {
|
|
133
|
-
layout: 'grid',
|
|
134
|
-
columns: 2,
|
|
135
|
-
categories: [
|
|
136
|
-
{
|
|
137
|
-
title: 'Security',
|
|
138
|
-
items: [
|
|
139
|
-
{
|
|
140
|
-
label: 'Web App & API Protection',
|
|
141
|
-
description: 'Deploy an application security demo with Terraform and follow its protection walkthroughs.',
|
|
142
|
-
href: 'https://f5-sales-demo.github.io/webapp-api-protection/en/',
|
|
143
|
-
icon: resolveMegaMenuIcon('f5xc:web-app-and-api-protection'),
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
label: 'API Protection',
|
|
147
|
-
description: 'Find guides to API discovery, schema validation, and request controls.',
|
|
148
|
-
href: 'https://f5-sales-demo.github.io/api-protection/en/',
|
|
149
|
-
icon: resolveMegaMenuIcon('f5xc:application-traffic-insight'),
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
label: 'Bot Defense Advanced',
|
|
153
|
-
description: 'Review behavioral bot detection and mitigation scenarios.',
|
|
154
|
-
href: 'https://f5-sales-demo.github.io/bot-advanced/en/',
|
|
155
|
-
icon: resolveMegaMenuIcon('f5xc:bot-defense'),
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
label: 'Bot Defense Standard',
|
|
159
|
-
description: 'Review bot classification, verified crawlers, and blocking policies.',
|
|
160
|
-
href: 'https://f5-sales-demo.github.io/bot-standard/en/',
|
|
161
|
-
icon: resolveMegaMenuIcon('f5xc:bot-defense'),
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
label: 'Client-Side Defense',
|
|
165
|
-
description: 'Deploy browser script monitoring with an API or Terraform workflow.',
|
|
166
|
-
href: 'https://f5-sales-demo.github.io/csd/en/',
|
|
167
|
-
icon: resolveMegaMenuIcon('f5xc:client-side-defense'),
|
|
168
|
-
},
|
|
169
|
-
{
|
|
170
|
-
label: 'DDoS Mitigation',
|
|
171
|
-
description: 'Review distributed denial-of-service defenses and demo scenarios.',
|
|
172
|
-
href: 'https://f5-sales-demo.github.io/ddos/en/',
|
|
173
|
-
icon: resolveMegaMenuIcon('f5xc:ddos-and-transit-services'),
|
|
174
|
-
},
|
|
175
|
-
{
|
|
176
|
-
label: 'Web App Scanning',
|
|
177
|
-
description: 'Find web application vulnerability scanning guides.',
|
|
178
|
-
href: 'https://f5-sales-demo.github.io/was/en/',
|
|
179
|
-
icon: resolveMegaMenuIcon('f5xc:web-app-scanning'),
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
label: 'Custom responses',
|
|
183
|
-
description: 'Configure application replies, redirects, and security response pages.',
|
|
184
|
-
href: 'https://f5-sales-demo.github.io/custom-responses/en/',
|
|
185
|
-
icon: resolveMegaMenuIcon('f5xc:web-app-and-api-protection'),
|
|
186
|
-
},
|
|
187
|
-
],
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
title: 'Networking and performance',
|
|
191
|
-
items: [
|
|
192
|
-
{
|
|
193
|
-
label: 'Multi-Cloud Networking',
|
|
194
|
-
description: 'Find site deployment and cross-cloud connectivity guides.',
|
|
195
|
-
href: 'https://f5-sales-demo.github.io/multi-cloud-networking/en/',
|
|
196
|
-
icon: resolveMegaMenuIcon('f5xc:multi-cloud-network-connect'),
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
label: 'Canada topology',
|
|
200
|
-
description: 'Deploy Canadian application hosting and demonstrate regional access controls.',
|
|
201
|
-
href: 'https://f5-sales-demo.github.io/canada/en/',
|
|
202
|
-
icon: {
|
|
203
|
-
body: fs
|
|
204
|
-
.readFileSync(new URL('./assets/canada-flag.svg', import.meta.url), 'utf8')
|
|
205
|
-
.replace(/<svg[^>]*>|<\/svg>/g, ''),
|
|
206
|
-
width: 640,
|
|
207
|
-
height: 480,
|
|
208
|
-
mode: 'original',
|
|
209
|
-
},
|
|
210
|
-
},
|
|
211
|
-
{
|
|
212
|
-
label: 'Content Delivery Network',
|
|
213
|
-
description: 'Review content delivery network (CDN) caching and origin configuration.',
|
|
214
|
-
href: 'https://f5-sales-demo.github.io/cdn/en/',
|
|
215
|
-
icon: resolveMegaMenuIcon('f5xc:content-delivery-network'),
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
label: 'DNS Management',
|
|
219
|
-
description: 'Find Domain Name System (DNS) zone and load balancing guides.',
|
|
220
|
-
href: 'https://f5-sales-demo.github.io/dns/en/',
|
|
221
|
-
icon: resolveMegaMenuIcon('f5xc:dns-management'),
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
label: 'NGINX One',
|
|
225
|
-
description: 'Review NGINX instance visibility and configuration management.',
|
|
226
|
-
href: 'https://f5-sales-demo.github.io/nginx/en/',
|
|
227
|
-
icon: resolveMegaMenuIcon('f5xc:nginx-one'),
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
},
|
|
231
|
-
],
|
|
232
|
-
footer: {
|
|
233
|
-
label: 'Demo catalog',
|
|
234
|
-
href: 'https://f5-sales-demo.github.io/en/demos/',
|
|
235
|
-
description: 'All demonstration and capability guides',
|
|
236
|
-
},
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
{
|
|
240
|
-
label: 'Demo environment',
|
|
241
|
-
content: {
|
|
242
|
-
layout: 'list',
|
|
243
|
-
categories: [
|
|
244
|
-
{
|
|
245
|
-
title: 'Deployment resources',
|
|
246
|
-
items: [
|
|
247
|
-
{
|
|
248
|
-
label: 'Origin server',
|
|
249
|
-
description: 'Compare the Azure full-origin stack with the separate AWS Juice Shop deployment.',
|
|
250
|
-
href: 'https://f5-sales-demo.github.io/origin-server/en/',
|
|
251
|
-
icon: resolveMegaMenuIcon('f5xc:distributed-apps'),
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
label: 'Traffic generator',
|
|
255
|
-
description:
|
|
256
|
-
'Choose Azure or AWS deployment guides for controlled security traffic and browser scenarios.',
|
|
257
|
-
href: 'https://f5-sales-demo.github.io/traffic-generator/en/',
|
|
258
|
-
icon: resolveMegaMenuIcon('f5xc:application-traffic-insight'),
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
label: 'CDN simulator',
|
|
262
|
-
description: 'Deploy an Azure proxy that adds CDN headers to origin requests.',
|
|
263
|
-
href: 'https://f5-sales-demo.github.io/cdn-simulator/en/',
|
|
264
|
-
icon: resolveMegaMenuIcon('f5xc:content-delivery-network'),
|
|
265
|
-
},
|
|
266
|
-
],
|
|
267
|
-
},
|
|
268
|
-
],
|
|
269
|
-
footer: {
|
|
270
|
-
label: 'Demo resource catalog',
|
|
271
|
-
href: 'https://f5-sales-demo.github.io/demo-resources/en/',
|
|
272
|
-
description: 'Azure and AWS offerings vary by component',
|
|
273
|
-
},
|
|
274
|
-
},
|
|
275
|
-
},
|
|
276
|
-
{
|
|
277
|
-
label: 'Operations',
|
|
278
|
-
content: {
|
|
279
|
-
layout: 'list',
|
|
280
|
-
categories: [
|
|
281
|
-
{
|
|
282
|
-
title: 'Queries and administration',
|
|
283
|
-
items: [
|
|
284
|
-
{
|
|
285
|
-
label: 'Statistics',
|
|
286
|
-
description: 'Query access logs, application metrics, and security telemetry through the API.',
|
|
287
|
-
href: 'https://f5-sales-demo.github.io/statistics/en/',
|
|
288
|
-
icon: resolveMegaMenuIcon('f5xc:doc'),
|
|
289
|
-
},
|
|
290
|
-
{
|
|
291
|
-
label: 'Observability',
|
|
292
|
-
description: 'Find monitoring, metrics, tracing, and alerting guides.',
|
|
293
|
-
href: 'https://f5-sales-demo.github.io/observability/en/',
|
|
294
|
-
icon: resolveMegaMenuIcon('f5xc:observability'),
|
|
295
|
-
},
|
|
296
|
-
{
|
|
297
|
-
label: 'Administration',
|
|
298
|
-
description: 'Find tenant, namespace, and role management guides.',
|
|
299
|
-
href: 'https://f5-sales-demo.github.io/administration/en/',
|
|
300
|
-
icon: resolveMegaMenuIcon('f5xc:administration'),
|
|
301
|
-
},
|
|
302
|
-
],
|
|
303
|
-
},
|
|
304
|
-
],
|
|
305
|
-
},
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
label: 'Developer tools',
|
|
309
|
-
content: {
|
|
310
|
-
layout: 'grid',
|
|
311
|
-
columns: 2,
|
|
312
|
-
categories: [
|
|
313
|
-
{
|
|
314
|
-
title: 'Tools and automation',
|
|
315
|
-
items: [
|
|
316
|
-
{
|
|
317
|
-
label: 'xcsh',
|
|
318
|
-
description:
|
|
319
|
-
'Install the independent terminal assistant for engineering and F5 Distributed Cloud workflows.',
|
|
320
|
-
href: 'https://f5-sales-demo.github.io/xcsh/en/',
|
|
321
|
-
icon: resolveMegaMenuIcon('carbon:terminal'),
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
label: 'Terraform provider',
|
|
325
|
-
description: 'Configure F5 Distributed Cloud resources with Terraform.',
|
|
326
|
-
href: 'https://f5-sales-demo.github.io/terraform-provider-xcsh/',
|
|
327
|
-
icon: resolveMegaMenuIcon('f5xc:doc'),
|
|
328
|
-
},
|
|
329
|
-
{
|
|
330
|
-
label: 'xcsh GitHub Action',
|
|
331
|
-
description: 'Run pinned xcsh manifest operations in GitHub Actions.',
|
|
332
|
-
href: 'https://f5-sales-demo.github.io/xcsh-action/en/',
|
|
333
|
-
icon: resolveMegaMenuIcon('carbon:workflow-automation'),
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
label: 'VS Code extension',
|
|
337
|
-
description: 'Author manifests and manage F5 Distributed Cloud resources in Visual Studio Code.',
|
|
338
|
-
href: 'https://f5-sales-demo.github.io/vscode-xcsh/en/',
|
|
339
|
-
icon: resolveMegaMenuIcon('carbon:code'),
|
|
340
|
-
},
|
|
341
|
-
{
|
|
342
|
-
label: 'xcsh Chrome extension',
|
|
343
|
-
description: 'Connect xcsh to the F5 Distributed Cloud console through a local browser bridge.',
|
|
344
|
-
href: 'https://f5-sales-demo.github.io/xcsh-chrome-extension/en/',
|
|
345
|
-
icon: resolveMegaMenuIcon('carbon:application-web'),
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
label: 'APT repository',
|
|
349
|
-
description: 'Install signed Debian and Ubuntu packages for xcsh and supporting tools.',
|
|
350
|
-
href: 'https://f5-sales-demo.github.io/apt-repo/en/',
|
|
351
|
-
icon: resolveMegaMenuIcon('f5xc:doc'),
|
|
352
|
-
},
|
|
353
|
-
],
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
title: 'Specifications and plugins',
|
|
357
|
-
items: [
|
|
358
|
-
{
|
|
359
|
-
label: 'API specifications',
|
|
360
|
-
description: 'Find validated OpenAPI specifications and the specification update pipeline.',
|
|
361
|
-
href: 'https://f5-sales-demo.github.io/api-specs/en/',
|
|
362
|
-
icon: resolveMegaMenuIcon('f5xc:data-intelligence'),
|
|
363
|
-
},
|
|
364
|
-
{
|
|
365
|
-
label: 'Enriched API specifications',
|
|
366
|
-
description: 'Browse OpenAPI schemas with additional constraints and examples.',
|
|
367
|
-
href: 'https://f5-sales-demo.github.io/api-specs-enriched/en/',
|
|
368
|
-
icon: resolveMegaMenuIcon('f5xc:data-intelligence'),
|
|
369
|
-
},
|
|
370
|
-
{
|
|
371
|
-
label: 'xcsh marketplace',
|
|
372
|
-
description: 'Find xcsh plugins for documentation, sales, cloud, security, and desktop work.',
|
|
373
|
-
href: 'https://f5-sales-demo.github.io/marketplace/en/',
|
|
374
|
-
icon: resolveMegaMenuIcon('f5xc:ai_assistant_logo'),
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
label: 'Marketplace for Claude Code',
|
|
378
|
-
description: 'Find Language Server Protocol integrations and developer tools for Claude Code.',
|
|
379
|
-
href: 'https://f5-sales-demo.github.io/marketplace-claude-code/en/',
|
|
380
|
-
icon: resolveMegaMenuIcon('f5xc:doc'),
|
|
381
|
-
},
|
|
382
|
-
{
|
|
383
|
-
label: 'Console catalog',
|
|
384
|
-
description: 'Browse documented console routes and browser automation workflows.',
|
|
385
|
-
href: 'https://f5-sales-demo.github.io/console/en/',
|
|
386
|
-
icon: resolveMegaMenuIcon('f5xc:ai_assistant_logo'),
|
|
387
|
-
},
|
|
388
|
-
],
|
|
389
|
-
},
|
|
390
|
-
],
|
|
391
|
-
},
|
|
392
|
-
},
|
|
393
|
-
{
|
|
394
|
-
label: 'Ecosystem',
|
|
395
|
-
content: {
|
|
396
|
-
layout: 'list',
|
|
397
|
-
categories: [
|
|
398
|
-
{
|
|
399
|
-
title: 'Public projects',
|
|
400
|
-
items: [
|
|
401
|
-
{
|
|
402
|
-
label: 'F5 documentation corpus',
|
|
403
|
-
description: 'Browse curated F5 documentation and its Markdown sources.',
|
|
404
|
-
href: 'https://f5-sales-demo.github.io/html-to-markdown/',
|
|
405
|
-
icon: resolveMegaMenuIcon('f5xc:doc'),
|
|
406
|
-
},
|
|
407
|
-
{
|
|
408
|
-
label: 'Ecosystem directory',
|
|
409
|
-
description: 'Documentation, tools, and public project sources',
|
|
410
|
-
href: 'https://f5-sales-demo.github.io/en/ecosystem/',
|
|
411
|
-
icon: resolveMegaMenuIcon('f5xc:doc'),
|
|
412
|
-
},
|
|
413
|
-
],
|
|
414
|
-
},
|
|
415
|
-
],
|
|
416
|
-
footer: {
|
|
417
|
-
label: 'Project sources',
|
|
418
|
-
href: 'https://github.com/f5-sales-demo',
|
|
419
|
-
description: 'Public repositories in the community organization',
|
|
420
|
-
},
|
|
421
|
-
},
|
|
422
|
-
},
|
|
423
|
-
{
|
|
424
|
-
label: 'F5 services',
|
|
425
|
-
content: {
|
|
426
|
-
layout: 'list',
|
|
427
|
-
categories: [
|
|
428
|
-
{
|
|
429
|
-
title: 'Official documentation and services',
|
|
430
|
-
items: [
|
|
431
|
-
{
|
|
432
|
-
label: 'F5 Distributed Cloud console',
|
|
433
|
-
description: 'Management service; sign-in required',
|
|
434
|
-
href: 'https://console.ves.volterra.io',
|
|
435
|
-
icon: resolveMegaMenuIcon('f5xc:platform'),
|
|
436
|
-
},
|
|
437
|
-
{
|
|
438
|
-
label: 'F5 Distributed Cloud documentation',
|
|
439
|
-
description: 'Official public product documentation',
|
|
440
|
-
href: 'https://docs.cloud.f5.com',
|
|
441
|
-
icon: resolveMegaMenuIcon('f5xc:doc'),
|
|
442
|
-
},
|
|
443
|
-
{
|
|
444
|
-
label: 'MyF5 support',
|
|
445
|
-
description: 'Support portal; sign-in required for account services',
|
|
446
|
-
href: 'https://my.f5.com/manage/s/',
|
|
447
|
-
icon: resolveMegaMenuIcon('f5xc:support'),
|
|
448
|
-
},
|
|
449
|
-
],
|
|
450
|
-
},
|
|
451
|
-
],
|
|
452
|
-
},
|
|
453
|
-
},
|
|
454
|
-
];
|
|
455
|
-
|
|
128
|
+
export const defaultMegaMenuItems: MegaMenuItem[] = [];
|
|
456
129
|
const defaultHead: HeadEntry[] = [
|
|
457
130
|
{
|
|
458
131
|
tag: 'script',
|
|
@@ -528,6 +201,7 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
|
|
|
528
201
|
const githubRepository = options.githubRepository || process.env.GITHUB_REPOSITORY || '';
|
|
529
202
|
const publicationProfile = repositoryPublicationProfile(githubRepository);
|
|
530
203
|
const favicon = options.favicon || publicationProfile?.favicon;
|
|
204
|
+
if (options.favicon) process.env.DOCS_CUSTOM_FAVICON = options.favicon;
|
|
531
205
|
let llmsOptionalLinks: Array<{ title: string; url: string }> = options.llmsOptionalLinks || [];
|
|
532
206
|
if (!options.llmsOptionalLinks && process.env.LLMS_OPTIONAL_LINKS) {
|
|
533
207
|
try {
|
|
@@ -568,19 +242,18 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
|
|
|
568
242
|
console.warn('[docs-theme] OPENAPI_SPECS_CONFIG contains invalid JSON; skipping OpenAPI plugin.', e);
|
|
569
243
|
}
|
|
570
244
|
}
|
|
571
|
-
const
|
|
572
|
-
|
|
573
|
-
(canonicalProvider
|
|
574
|
-
? [...defaultMegaMenuItems, ...providerMegaMenu(base, stagedProviderSections(canonicalProvider.manifest))]
|
|
575
|
-
: defaultMegaMenuItems);
|
|
245
|
+
const mode = sharedMode(githubRepository);
|
|
246
|
+
const megaMenuItems = options.megaMenuItems || defaultMegaMenuItems;
|
|
576
247
|
const head =
|
|
577
248
|
options.head ||
|
|
578
249
|
(canonicalProvider || githubRepository === 'f5-sales-demo/f5-sales-demo.github.io' ? [] : defaultHead);
|
|
579
|
-
const logo =
|
|
250
|
+
const logo =
|
|
251
|
+
options.logo ||
|
|
252
|
+
(mode === 'local' ? { src: '@f5-sales-demo/docs-theme/assets/f5-distributed-cloud.svg' } : undefined);
|
|
580
253
|
const additionalRemarkPlugins = options.additionalRemarkPlugins || [];
|
|
581
254
|
const additionalIntegrations = options.additionalIntegrations || [];
|
|
582
255
|
|
|
583
|
-
const federatedSearch = !canonicalProvider && options.federatedSearch !== false;
|
|
256
|
+
const federatedSearch = mode === 'local' && !canonicalProvider && options.federatedSearch !== false;
|
|
584
257
|
const normalizedBase = base.replace(/\/+$/, '');
|
|
585
258
|
const repositoryCorpusPolicy = progressiveCorpusPolicy(llmsConfig);
|
|
586
259
|
const progressiveCorpus =
|
|
@@ -610,7 +283,7 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
|
|
|
610
283
|
: undefined;
|
|
611
284
|
|
|
612
285
|
const starlightPlugins: StarlightPlugin[] = [
|
|
613
|
-
starlightMegaMenu({ items: megaMenuItems, mobileLabels }),
|
|
286
|
+
...(mode === 'local' ? [starlightMegaMenu({ items: megaMenuItems, mobileLabels })] : []),
|
|
614
287
|
starlightVideosPlugin(),
|
|
615
288
|
starlightImageZoom(),
|
|
616
289
|
f5xcDocsTheme(githubRepository),
|
|
@@ -734,7 +407,10 @@ export function createF5xcDocsConfig(options: F5xcDocsConfigOptions = {}) {
|
|
|
734
407
|
return defineConfig({
|
|
735
408
|
site,
|
|
736
409
|
base,
|
|
737
|
-
vite: {
|
|
410
|
+
vite: {
|
|
411
|
+
plugins: mode === 'local' ? [menuLocalizationPolicy()] : [sharedSearchIntegration()],
|
|
412
|
+
...(canonicalProvider ? { build: { assetsInlineLimit: 0 } } : {}),
|
|
413
|
+
},
|
|
738
414
|
...(canonicalProvider
|
|
739
415
|
? {
|
|
740
416
|
experimental: { collectionStorage: { type: 'chunked' as const, chunkSize: 1024 * 1024 } },
|
package/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { StarlightPlugin } from '@astrojs/starlight/types';
|
|
2
2
|
import { translations } from './src/i18n/translations.ts';
|
|
3
|
+
import { sharedMode } from './src/shared/settings.mjs';
|
|
3
4
|
|
|
4
5
|
export default function f5xcDocsTheme(repository = process.env.GITHUB_REPOSITORY || ''): StarlightPlugin {
|
|
5
6
|
return {
|
|
@@ -19,12 +20,20 @@ export default function f5xcDocsTheme(repository = process.env.GITHUB_REPOSITORY
|
|
|
19
20
|
updateConfig({
|
|
20
21
|
customCss: [
|
|
21
22
|
...(config.customCss ?? []),
|
|
22
|
-
'
|
|
23
|
-
|
|
23
|
+
...(sharedMode(repository) === 'local'
|
|
24
|
+
? [
|
|
25
|
+
'@f5-sales-demo/docs-theme/fonts/font-face.css',
|
|
26
|
+
'@f5-sales-demo/docs-theme/styles/custom.css',
|
|
27
|
+
'@f5-sales-demo/docs-theme/styles/shell.css',
|
|
28
|
+
]
|
|
29
|
+
: []),
|
|
24
30
|
],
|
|
25
31
|
components: {
|
|
26
32
|
...config.components,
|
|
27
|
-
Header:
|
|
33
|
+
Header:
|
|
34
|
+
sharedMode(repository) === 'local'
|
|
35
|
+
? '@f5-sales-demo/docs-theme/components/LocalHeader.astro'
|
|
36
|
+
: '@f5-sales-demo/docs-theme/components/SharedHeader.astro',
|
|
28
37
|
Head: '@f5-sales-demo/docs-theme/components/Head.astro',
|
|
29
38
|
...(process.env.DOCS_PROFILE === 'canonical-provider'
|
|
30
39
|
? {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@f5-sales-demo/docs-theme",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.12.0",
|
|
4
4
|
"description": "F5 Distributed Cloud branded Starlight documentation theme",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -48,7 +48,9 @@
|
|
|
48
48
|
"./components/SharedHeader.astro": "./components/SharedHeader.astro",
|
|
49
49
|
"./components/Head.astro": "./components/Head.astro",
|
|
50
50
|
"./assets/canada-flag.svg": "./assets/canada-flag.svg",
|
|
51
|
-
"./assets/canada-favicon.svg": "./assets/canada-favicon.svg"
|
|
51
|
+
"./assets/canada-favicon.svg": "./assets/canada-favicon.svg",
|
|
52
|
+
"./styles/shell.css": "./styles/shell.css",
|
|
53
|
+
"./components/LocalHeader.astro": "./components/LocalHeader.astro"
|
|
52
54
|
},
|
|
53
55
|
"files": [
|
|
54
56
|
"index.ts",
|
|
@@ -100,10 +102,11 @@
|
|
|
100
102
|
"build": "node scripts/run-astro-with-content.mjs build",
|
|
101
103
|
"dev": "node scripts/run-astro-with-content.mjs dev",
|
|
102
104
|
"preview": "astro preview",
|
|
103
|
-
"test": "vitest run",
|
|
105
|
+
"test": "vitest run && npm run test:shared",
|
|
104
106
|
"test:watch": "vitest",
|
|
105
107
|
"test:visual": "playwright test",
|
|
106
|
-
"test:visual:update": "playwright test --update-snapshots"
|
|
108
|
+
"test:visual:update": "playwright test --update-snapshots",
|
|
109
|
+
"test:shared": "node --test tests/shared-shell.node.mjs"
|
|
107
110
|
},
|
|
108
111
|
"peerDependencies": {
|
|
109
112
|
"@astrojs/markdown-remark": "^7.2.4",
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pointer": {
|
|
3
|
+
"contract": "v1",
|
|
4
|
+
"release": {
|
|
5
|
+
"url": "https://f5-sales-demo.github.io/shared/v1/releases/e3434cae2b7a79b34171b674e9378cdf3bfe3965071ca9c9b231e2d819f035e4.json",
|
|
6
|
+
"sha256": "e3434cae2b7a79b34171b674e9378cdf3bfe3965071ca9c9b231e2d819f035e4",
|
|
7
|
+
"bytes": 5766
|
|
8
|
+
}
|
|
9
|
+
},
|
|
10
|
+
"assets": {
|
|
11
|
+
"css": {
|
|
12
|
+
"url": "https://f5-sales-demo.github.io/shared/assets/b208db097ace5238f60a621abd4b03965c6c0bb5a35f1d498ea76172b23f6f29.css",
|
|
13
|
+
"sha256": "b208db097ace5238f60a621abd4b03965c6c0bb5a35f1d498ea76172b23f6f29",
|
|
14
|
+
"bytes": 44879,
|
|
15
|
+
"extension": "css"
|
|
16
|
+
},
|
|
17
|
+
"logo": {
|
|
18
|
+
"url": "https://f5-sales-demo.github.io/shared/assets/487f0f7a70ffaa67c7127b2eef2b094c6eae42d8d6e6ba0a14c784013b2c6d48.svg",
|
|
19
|
+
"sha256": "487f0f7a70ffaa67c7127b2eef2b094c6eae42d8d6e6ba0a14c784013b2c6d48",
|
|
20
|
+
"bytes": 2294,
|
|
21
|
+
"extension": "svg"
|
|
22
|
+
},
|
|
23
|
+
"favicon": {
|
|
24
|
+
"url": "https://f5-sales-demo.github.io/shared/assets/3402d9a74d6a627f098da467fda72cde93ee686ecb1ceba2c96d1f4bd18857b3.svg",
|
|
25
|
+
"sha256": "3402d9a74d6a627f098da467fda72cde93ee686ecb1ceba2c96d1f4bd18857b3",
|
|
26
|
+
"bytes": 4282,
|
|
27
|
+
"extension": "svg"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { ROOT, validateConsumer, validateData, validateManifest, validatePointer } from './contract.mjs';
|
|
2
|
+
|
|
3
|
+
const CACHE_KEY = 'f5-shared-v1:last-success';
|
|
4
|
+
const TIMEOUT = 12000;
|
|
5
|
+
export async function verifiedBytes(receipt, fetcher = fetch, cryptoProvider = crypto) {
|
|
6
|
+
const response = await fetcher(receipt.url, { signal: AbortSignal.timeout(TIMEOUT) });
|
|
7
|
+
if (!response.ok) throw new Error(`Shared asset unavailable: ${response.status}`);
|
|
8
|
+
const bytes = await response.arrayBuffer();
|
|
9
|
+
const digest = Array.from(new Uint8Array(await cryptoProvider.subtle.digest('SHA-256', bytes)), (value) =>
|
|
10
|
+
value.toString(16).padStart(2, '0'),
|
|
11
|
+
).join('');
|
|
12
|
+
if (bytes.byteLength !== receipt.bytes || digest !== receipt.sha256)
|
|
13
|
+
throw new Error('Shared asset integrity mismatch');
|
|
14
|
+
return bytes;
|
|
15
|
+
}
|
|
16
|
+
export async function loadRelease(pointer, fetcher = fetch, cryptoProvider = crypto) {
|
|
17
|
+
validatePointer(pointer);
|
|
18
|
+
const manifest = validateManifest(
|
|
19
|
+
JSON.parse(new TextDecoder().decode(await verifiedBytes(pointer.release, fetcher, cryptoProvider))),
|
|
20
|
+
);
|
|
21
|
+
const entries = Object.entries(manifest.assets);
|
|
22
|
+
const buffers = await Promise.all(entries.map(([, asset]) => verifiedBytes(asset, fetcher, cryptoProvider)));
|
|
23
|
+
const dataIndex = entries.findIndex(([key]) => key === 'data');
|
|
24
|
+
const data = validateData(JSON.parse(new TextDecoder().decode(buffers[dataIndex])));
|
|
25
|
+
return { pointer, manifest, data };
|
|
26
|
+
}
|
|
27
|
+
export async function selectRelease({ baseline, fetcher = fetch, storage, activate, cryptoProvider = crypto }) {
|
|
28
|
+
let cached;
|
|
29
|
+
try {
|
|
30
|
+
cached = JSON.parse(storage?.getItem(CACHE_KEY) || 'null');
|
|
31
|
+
} catch {
|
|
32
|
+
/* Storage may be unavailable. */
|
|
33
|
+
}
|
|
34
|
+
let active;
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetcher(`${ROOT}v1/current.json`, {
|
|
37
|
+
cache: 'no-store',
|
|
38
|
+
signal: AbortSignal.timeout(TIMEOUT),
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) throw new Error('Active release unavailable');
|
|
41
|
+
active = validatePointer(await response.json());
|
|
42
|
+
} catch {
|
|
43
|
+
/* Try the previously verified release, then the build baseline. */
|
|
44
|
+
}
|
|
45
|
+
const attempted = new Set();
|
|
46
|
+
for (const pointer of [active, cached, baseline]) {
|
|
47
|
+
if (!pointer || attempted.has(pointer.release?.sha256)) continue;
|
|
48
|
+
attempted.add(pointer.release?.sha256);
|
|
49
|
+
try {
|
|
50
|
+
const release = await loadRelease(pointer, fetcher, cryptoProvider);
|
|
51
|
+
await activate(release);
|
|
52
|
+
try {
|
|
53
|
+
storage?.setItem(CACHE_KEY, JSON.stringify(pointer));
|
|
54
|
+
} catch {
|
|
55
|
+
/* Cache is optional. */
|
|
56
|
+
}
|
|
57
|
+
return release;
|
|
58
|
+
} catch {
|
|
59
|
+
/* A partial release must never replace the working shell. */
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function integrity(receipt) {
|
|
65
|
+
return `sha256-${btoa(String.fromCharCode(...receipt.sha256.match(/../g).map((pair) => parseInt(pair, 16))))}`;
|
|
66
|
+
}
|
|
67
|
+
async function loadElement(element, parent) {
|
|
68
|
+
await new Promise((resolve, reject) => {
|
|
69
|
+
const timer = setTimeout(() => {
|
|
70
|
+
element.remove();
|
|
71
|
+
reject(new Error('Shared load timed out'));
|
|
72
|
+
}, TIMEOUT);
|
|
73
|
+
element.onload = () => {
|
|
74
|
+
clearTimeout(timer);
|
|
75
|
+
resolve();
|
|
76
|
+
};
|
|
77
|
+
element.onerror = () => {
|
|
78
|
+
clearTimeout(timer);
|
|
79
|
+
element.remove();
|
|
80
|
+
reject(new Error('Shared load failed'));
|
|
81
|
+
};
|
|
82
|
+
parent.append(element);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
export async function startSharedShell(consumer, baseline) {
|
|
86
|
+
validateConsumer(consumer);
|
|
87
|
+
let storage;
|
|
88
|
+
try {
|
|
89
|
+
storage = window.localStorage;
|
|
90
|
+
} catch {
|
|
91
|
+
/* Browser policy can reject even property access. */
|
|
92
|
+
}
|
|
93
|
+
return selectRelease({
|
|
94
|
+
baseline,
|
|
95
|
+
storage,
|
|
96
|
+
activate: async (release) => {
|
|
97
|
+
const { css, runtime } = release.manifest.assets;
|
|
98
|
+
const link = document.createElement('link');
|
|
99
|
+
Object.assign(link, {
|
|
100
|
+
rel: 'stylesheet',
|
|
101
|
+
href: css.url,
|
|
102
|
+
integrity: integrity(css),
|
|
103
|
+
crossOrigin: 'anonymous',
|
|
104
|
+
media: 'not all',
|
|
105
|
+
});
|
|
106
|
+
let prepared;
|
|
107
|
+
try {
|
|
108
|
+
await loadElement(link, document.head);
|
|
109
|
+
window.F5SharedV1 ||= new Map();
|
|
110
|
+
if (!window.F5SharedV1.has(runtime.url)) {
|
|
111
|
+
const script = document.createElement('script');
|
|
112
|
+
Object.assign(script, {
|
|
113
|
+
type: 'module',
|
|
114
|
+
src: runtime.url,
|
|
115
|
+
integrity: integrity(runtime),
|
|
116
|
+
crossOrigin: 'anonymous',
|
|
117
|
+
});
|
|
118
|
+
await loadElement(script, document.head);
|
|
119
|
+
}
|
|
120
|
+
const implementation = window.F5SharedV1.get(runtime.url);
|
|
121
|
+
if (!implementation) throw new Error('Shared runtime did not register');
|
|
122
|
+
prepared = implementation.prepare(consumer, release);
|
|
123
|
+
prepared.commit();
|
|
124
|
+
link.media = 'all';
|
|
125
|
+
document.querySelector('[data-f5-baseline-css]')?.remove();
|
|
126
|
+
document.documentElement.dataset.f5SharedRelease = release.pointer.release.sha256;
|
|
127
|
+
document.dispatchEvent(new CustomEvent('f5:shared-ready', { detail: release.pointer }));
|
|
128
|
+
} catch (error) {
|
|
129
|
+
prepared?.dispose();
|
|
130
|
+
link.remove();
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
});
|
|
135
|
+
}
|