@tenphi/docs 0.17.1 → 0.18.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.
@@ -1,942 +0,0 @@
1
- import { glaze } from "@tenphi/glaze";
2
- import { mergeStyles } from "@tenphi/tasty/core";
3
- //#region src/types.ts
4
- /** Cookbook UI surfaces whose default Tasty styles can be customized. */
5
- const COOKBOOK_COMPONENT_NAMES = [
6
- "Card",
7
- "Callout",
8
- "CodeGroup",
9
- "Tab",
10
- "Footer",
11
- "Hero",
12
- "PageFrame",
13
- "HeaderFrame",
14
- "HeaderLinks",
15
- "Heading",
16
- "Layout",
17
- "SearchButton",
18
- "TableOfContentsLayout",
19
- "LanguageSelect",
20
- "Logo",
21
- "MarkdownCodeBlock",
22
- "MarkdownHeading",
23
- "MarkdownInlineCode",
24
- "MarkdownTable",
25
- "Mermaid",
26
- "MobileMenuFooter",
27
- "MobileMenuToggle",
28
- "MobileNavigationTabs",
29
- "MobileTableOfContents",
30
- "PackageVersion",
31
- "VersionSwitcher",
32
- "Preview",
33
- "Sidebar",
34
- "SocialIcons",
35
- "Steps",
36
- "Tabs",
37
- "TableOfContents",
38
- "ThemeSelect",
39
- "TopNavigation",
40
- "StarlightHeader"
41
- ];
42
- /** Named Tasty sub-elements available on each configurable Cookbook surface. */
43
- const COOKBOOK_COMPONENT_SUB_ELEMENTS = {
44
- PageFrame: [
45
- "MainFrame",
46
- "SidebarFrame",
47
- "Columns"
48
- ],
49
- HeaderFrame: [],
50
- Heading: [
51
- "Level1",
52
- "Level2",
53
- "Level3",
54
- "Level4",
55
- "Level5",
56
- "Level6",
57
- "PageTitle"
58
- ],
59
- HeaderLinks: [
60
- "Desktop",
61
- "Link",
62
- "HoverLink",
63
- "PrimaryLink",
64
- "HoverPrimaryLink",
65
- "Trigger",
66
- "HoverTrigger",
67
- "Panel",
68
- "PanelNavigation",
69
- "PanelLink",
70
- "FirstPanelLink",
71
- "Close",
72
- "HoverClose"
73
- ],
74
- Layout: ["LockedPage"],
75
- SearchButton: [
76
- "Label",
77
- "Shortcut",
78
- "Hover",
79
- "Active",
80
- "NativeIcon",
81
- "Icon"
82
- ],
83
- TableOfContentsLayout: ["Content"],
84
- Card: [
85
- "Heading2",
86
- "Heading3",
87
- "Paragraph"
88
- ],
89
- Callout: [
90
- "Title",
91
- "Body",
92
- "Tip",
93
- "Caution",
94
- "Danger"
95
- ],
96
- CodeGroup: [
97
- "Caption",
98
- "Pre",
99
- "Code"
100
- ],
101
- Tab: [
102
- "Heading",
103
- "Hidden",
104
- "HiddenHeading"
105
- ],
106
- Footer: [
107
- "Meta",
108
- "LoneMetaItem",
109
- "MetaLink",
110
- "HoverMetaLink",
111
- "Credit",
112
- "CreditLink",
113
- "HoverCreditLink"
114
- ],
115
- Hero: [
116
- "Visual",
117
- "Stack",
118
- "Copy",
119
- "Title",
120
- "Tagline",
121
- "Actions",
122
- "Action",
123
- "HoverAction",
124
- "PrimaryAction",
125
- "SecondaryAction",
126
- "MinimalAction",
127
- "ActionIcon"
128
- ],
129
- LanguageSelect: [
130
- "Label",
131
- "HoverLabel",
132
- "LabelIcon",
133
- "Select",
134
- "Caret",
135
- "Option"
136
- ],
137
- Logo: ["Svg", "Mark"],
138
- MarkdownCodeBlock: [
139
- "Pre",
140
- "CopyButton",
141
- "HoverCopyButton",
142
- "CopiedButton",
143
- "CopyIcon",
144
- "CopiedIcon"
145
- ],
146
- MarkdownHeading: [
147
- "Heading",
148
- "Heading1",
149
- "Heading2",
150
- "Heading3",
151
- "Heading4",
152
- "Heading5",
153
- "Heading6",
154
- "Link",
155
- "RevealedLink",
156
- "HoverLink",
157
- "LinkIcon",
158
- "CopiedLink",
159
- "CopiedLinkIcon",
160
- "CopiedIcon"
161
- ],
162
- MarkdownTable: [
163
- "Table",
164
- "Cell",
165
- "LastBodyRowCell",
166
- "HeaderCell"
167
- ],
168
- MarkdownInlineCode: [],
169
- Mermaid: [
170
- "Diagram",
171
- "Text",
172
- "MonoText"
173
- ],
174
- MobileMenuFooter: ["Social"],
175
- MobileMenuToggle: [
176
- "Control",
177
- "Icon",
178
- "Section",
179
- "Page",
180
- "HoverControl",
181
- "ActiveControl"
182
- ],
183
- MobileNavigationTabs: [
184
- "Trigger",
185
- "Marker",
186
- "Caret",
187
- "ExpandedCaret",
188
- "Label",
189
- "List",
190
- "Item",
191
- "Link",
192
- "HoverLink",
193
- "CurrentLink"
194
- ],
195
- MobileTableOfContents: [
196
- "Item",
197
- "Link",
198
- "LinkLabel",
199
- "HoverLink",
200
- "CurrentLink",
201
- "CurrentIndicator"
202
- ],
203
- PackageVersion: [],
204
- VersionSwitcher: [
205
- "Trigger",
206
- "Panel",
207
- "OpenPanel",
208
- "Link",
209
- "CurrentLink"
210
- ],
211
- Preview: [
212
- "Caption",
213
- "Stage",
214
- "Frame",
215
- "Code",
216
- "Summary",
217
- "Pre"
218
- ],
219
- Sidebar: [
220
- "Backdrop",
221
- "OpenBackdrop",
222
- "MobileHeading",
223
- "HomeLink",
224
- "HomeLogo",
225
- "HomeLabel",
226
- "Close",
227
- "HoverClose",
228
- "CloseIcon",
229
- "CurrentLink",
230
- "OpenPane",
231
- "Content",
232
- "Tree",
233
- "List",
234
- "Item",
235
- "TopLevelSpacing",
236
- "GroupSpacing",
237
- "NestedItem",
238
- "SectionHeading",
239
- "Control",
240
- "Summary",
241
- "GroupLabel",
242
- "GroupLabelText",
243
- "Link",
244
- "LinkLabel",
245
- "InteractiveControl",
246
- "SummaryMarker",
247
- "Caret",
248
- "ExpandedCaret",
249
- "LinkedSummary",
250
- "GroupLink",
251
- "LinkedSectionHeading",
252
- "SectionLink",
253
- "Badge",
254
- "TopLevelLink"
255
- ],
256
- Steps: ["Item", "Marker"],
257
- SocialIcons: [
258
- "Link",
259
- "HoverLink",
260
- "Icon"
261
- ],
262
- Tabs: [
263
- "List",
264
- "Button",
265
- "SelectedButton",
266
- "FocusedButton"
267
- ],
268
- TableOfContents: [
269
- "Heading",
270
- "List",
271
- "Item",
272
- "Link",
273
- "LinkLabel",
274
- "HoverLink",
275
- "CurrentLink"
276
- ],
277
- ThemeSelect: [
278
- "Trigger",
279
- "HoverTrigger",
280
- "ActiveTrigger",
281
- "Icon",
282
- "Panel",
283
- "OpenPanel",
284
- "Section",
285
- "SectionSpacing",
286
- "SectionLabel",
287
- "Option",
288
- "HoverOption",
289
- "CheckedOption",
290
- "FocusedOption",
291
- "Input",
292
- "OptionIcon",
293
- "Checkmark",
294
- "SelectedCheckmark"
295
- ],
296
- TopNavigation: [
297
- "Scrollbar",
298
- "Link",
299
- "HoverLink",
300
- "CurrentLink",
301
- "ActiveIndicator"
302
- ],
303
- StarlightHeader: [
304
- "Primary",
305
- "TitleAndSearch",
306
- "Title",
307
- "LogoLink",
308
- "Logo",
309
- "SiteTitle",
310
- "Search",
311
- "SearchElement",
312
- "Tools",
313
- "ToolItem",
314
- "Social",
315
- "MobileTheme"
316
- ]
317
- };
318
- //#endregion
319
- //#region src/config/index.ts
320
- const DEFAULT_BRAND = "okhsl(266 68% 48%)";
321
- const HEAD_KEYS = /* @__PURE__ */ new Set([
322
- "tag",
323
- "attrs",
324
- "content"
325
- ]);
326
- const SITE_ICON_KEYS = /* @__PURE__ */ new Set(["source", "background"]);
327
- const COLOR_DECLARATION_KEYS = /* @__PURE__ */ new Set([
328
- "from",
329
- "tone",
330
- "saturation",
331
- "hue",
332
- "darkHue",
333
- "darkSaturation",
334
- "base",
335
- "contrast",
336
- "mode",
337
- "autoFlip",
338
- "opacity",
339
- "pastel",
340
- "role",
341
- "inherit"
342
- ]);
343
- const ROOT_KEYS = /* @__PURE__ */ new Set([
344
- "root",
345
- "redirects",
346
- "site",
347
- "head",
348
- "editLink",
349
- "lastUpdated",
350
- "locales",
351
- "defaultLocale",
352
- "content",
353
- "navigation",
354
- "theme",
355
- "markdown",
356
- "search",
357
- "components",
358
- "build"
359
- ]);
360
- const OBJECT_KEYS = {
361
- site: /* @__PURE__ */ new Set([
362
- "title",
363
- "version",
364
- "versions",
365
- "description",
366
- "url",
367
- "repository",
368
- "favicon",
369
- "headerLinks"
370
- ]),
371
- content: /* @__PURE__ */ new Set([
372
- "sources",
373
- "allowOutsideRoot",
374
- "localizeRepositoryLinks",
375
- "frontmatter"
376
- ]),
377
- markdown: /* @__PURE__ */ new Set(["stripLeadingBadges", "rawHtml"]),
378
- search: /* @__PURE__ */ new Set(["enabled"]),
379
- components: /* @__PURE__ */ new Set(["overrides"]),
380
- theme: /* @__PURE__ */ new Set([
381
- "brand",
382
- "palette",
383
- "fonts",
384
- "states",
385
- "tokens",
386
- "presets",
387
- "styles",
388
- "customStyles",
389
- "contrastLevel"
390
- ]),
391
- build: /* @__PURE__ */ new Set([
392
- "strict",
393
- "ci",
394
- "cacheDir",
395
- "maxArtifactBytes",
396
- "maxUnpackedBytes",
397
- "maxFiles",
398
- "maxPathDepth",
399
- "maxAssetBytes"
400
- ])
401
- };
402
- var DocsConfigError = class extends Error {
403
- diagnostics;
404
- constructor(diagnostics) {
405
- super(diagnostics.map((diagnostic) => diagnostic.message).join("\n"));
406
- this.name = "DocsConfigError";
407
- this.diagnostics = diagnostics;
408
- }
409
- };
410
- function validateConfig(config) {
411
- const diagnostics = [];
412
- if (config === null || typeof config !== "object" || Array.isArray(config)) {
413
- invalid(diagnostics, "Documentation configuration must be an object.");
414
- return diagnostics;
415
- }
416
- if (config.root !== void 0 && typeof config.root !== "string") invalid(diagnostics, "root must be a directory path.");
417
- for (const key of Object.keys(config)) if (!ROOT_KEYS.has(key)) unknown(diagnostics, key);
418
- for (const [section, keys] of Object.entries(OBJECT_KEYS)) {
419
- const value = config[section];
420
- if (value === void 0) continue;
421
- if (!isRecord(value)) {
422
- invalid(diagnostics, `${section} must be an object.`);
423
- continue;
424
- }
425
- for (const key of Object.keys(value)) if (!keys.has(key)) unknown(diagnostics, `${section}.${key}`);
426
- }
427
- if (isRecord(config.site) && config.site.versions !== void 0) {
428
- if (!Array.isArray(config.site.versions) || config.site.versions.length < 2) invalid(diagnostics, "site.versions must contain at least two versions.");
429
- else {
430
- const bases = /* @__PURE__ */ new Set();
431
- for (const [index, version] of config.site.versions.entries()) {
432
- const path = `site.versions[${index}]`;
433
- if (!isRecord(version)) {
434
- invalid(diagnostics, `${path} must be an object.`);
435
- continue;
436
- }
437
- for (const key of Object.keys(version)) if (key !== "label" && key !== "routeBase") unknown(diagnostics, `${path}.${key}`);
438
- if (typeof version.label !== "string" || !version.label.trim()) invalid(diagnostics, `${path}.label must be a non-empty string.`);
439
- const base = version.routeBase;
440
- if (typeof base !== "string" || !base.startsWith("/") || base !== "/" && base.endsWith("/") || base.includes("//") || /[?#\\\s]/.test(base) || base.split("/").some((segment) => segment === "." || segment === "..")) invalid(diagnostics, `${path}.routeBase must be a root-relative route without a trailing slash.`);
441
- else if (bases.has(base)) invalid(diagnostics, `${path}.routeBase duplicates ${base}.`);
442
- else bases.add(base);
443
- }
444
- }
445
- }
446
- if (isRecord(config.site) && config.site.headerLinks !== void 0) {
447
- if (!Array.isArray(config.site.headerLinks)) invalid(diagnostics, "site.headerLinks must be an array.");
448
- else config.site.headerLinks.forEach((item, index) => {
449
- const path = `site.headerLinks[${index}]`;
450
- if (!isRecord(item)) {
451
- invalid(diagnostics, `${path} must be an object.`);
452
- return;
453
- }
454
- for (const key of Object.keys(item)) if (![
455
- "label",
456
- "link",
457
- "variant",
458
- "newTab"
459
- ].includes(key)) unknown(diagnostics, `${path}.${key}`);
460
- if (typeof item.label !== "string" || !item.label.trim()) invalid(diagnostics, `${path}.label must be a non-empty string.`);
461
- if (typeof item.link !== "string" || /[\\\s]/.test(item.link) || !(/^(?:\/(?!\/)|#)/.test(item.link) || isHttpUrl(item.link))) invalid(diagnostics, `${path}.link must be an HTTP(S) URL, root-relative route, or fragment.`);
462
- if (item.variant !== void 0 && !["default", "primary"].includes(item.variant)) invalid(diagnostics, `${path}.variant must be "default" or "primary".`);
463
- if (item.newTab !== void 0 && typeof item.newTab !== "boolean") invalid(diagnostics, `${path}.newTab must be a boolean.`);
464
- });
465
- }
466
- if (config.navigation !== void 0 && !Array.isArray(config.navigation)) {
467
- if (!isRecord(config.navigation)) invalid(diagnostics, "navigation must be an array or object.");
468
- else {
469
- for (const key of Object.keys(config.navigation)) if (!(/* @__PURE__ */ new Set(["items", "tabs"])).has(key)) unknown(diagnostics, `navigation.${key}`);
470
- if (config.navigation.items !== void 0 && !Array.isArray(config.navigation.items)) invalid(diagnostics, "navigation.items must be an array.");
471
- if (config.navigation.tabs !== void 0 && !Array.isArray(config.navigation.tabs)) invalid(diagnostics, "navigation.tabs must be an array.");
472
- }
473
- }
474
- if (Array.isArray(config.navigation)) validateNavigationItems(config.navigation, "navigation", diagnostics);
475
- else if (isRecord(config.navigation)) {
476
- if (Array.isArray(config.navigation.items)) validateNavigationItems(config.navigation.items, "navigation.items", diagnostics);
477
- if (Array.isArray(config.navigation.tabs)) for (const [index, tab] of config.navigation.tabs.entries()) {
478
- const path = `navigation.tabs[${index}]`;
479
- if (!isRecord(tab) || typeof tab.label !== "string" || typeof tab.link !== "string") invalid(diagnostics, `${path} must have a label and link.`);
480
- else if (tab.items !== void 0) validateNavigationItems(tab.items, `${path}.items`, diagnostics);
481
- }
482
- }
483
- if (config.redirects !== void 0 && (!isRecord(config.redirects) || Object.values(config.redirects).some((value) => typeof value !== "string"))) invalid(diagnostics, "redirects must map old routes to document routes.");
484
- const head = config.head;
485
- if (head !== void 0 && !Array.isArray(head)) invalid(diagnostics, "head must be an array.");
486
- else for (const [index, entry] of (head ?? []).entries()) {
487
- if (!isRecord(entry)) {
488
- invalid(diagnostics, `head[${index}] must be an object.`);
489
- continue;
490
- }
491
- for (const key of Object.keys(entry)) if (!HEAD_KEYS.has(key)) unknown(diagnostics, `head[${index}].${key}`);
492
- if (typeof entry.tag !== "string") invalid(diagnostics, `head[${index}].tag must be a string.`);
493
- if (entry.attrs !== void 0 && !isRecord(entry.attrs)) invalid(diagnostics, `head[${index}].attrs must be an object.`);
494
- else for (const [name, value] of Object.entries(entry.attrs ?? {})) if (value !== void 0 && typeof value !== "string" && typeof value !== "boolean") invalid(diagnostics, `head[${index}].attrs.${name} must be a string or boolean.`);
495
- if (entry.content !== void 0 && typeof entry.content !== "string") invalid(diagnostics, `head[${index}].content must be a string.`);
496
- }
497
- const favicon = config.site?.favicon;
498
- if (favicon !== void 0) {
499
- if (typeof favicon === "string") {
500
- if (favicon.trim() === "") invalid(diagnostics, "site.favicon must be a non-empty local path.");
501
- } else if (!isRecord(favicon)) invalid(diagnostics, "site.favicon must be a local path or an object with a source path.");
502
- else {
503
- for (const key of Object.keys(favicon)) if (!SITE_ICON_KEYS.has(key)) unknown(diagnostics, `site.favicon.${key}`);
504
- if (typeof favicon.source !== "string" || favicon.source.trim() === "") invalid(diagnostics, "site.favicon.source must be a non-empty string.");
505
- if (favicon.background !== void 0 && (typeof favicon.background !== "string" || favicon.background.trim() === "")) invalid(diagnostics, "site.favicon.background must be a non-empty color string.");
506
- }
507
- }
508
- for (const field of ["url", "repository"]) {
509
- const value = config.site?.[field];
510
- if (value === void 0) continue;
511
- if (typeof value !== "string" || !isHttpUrl(value)) invalid(diagnostics, `site.${field} must be an absolute HTTP(S) URL.`);
512
- }
513
- if (config.editLink !== void 0 && (!isRecord(config.editLink) || typeof config.editLink.baseUrl !== "string")) invalid(diagnostics, "editLink must contain a string baseUrl.");
514
- else if (config.editLink !== void 0) {
515
- for (const key of Object.keys(config.editLink)) if (key !== "baseUrl") unknown(diagnostics, `editLink.${key}`);
516
- try {
517
- new URL(config.editLink.baseUrl);
518
- } catch {
519
- invalid(diagnostics, "editLink.baseUrl must be an absolute URL.");
520
- }
521
- }
522
- if (config.lastUpdated !== void 0 && typeof config.lastUpdated !== "boolean") invalid(diagnostics, "lastUpdated must be a boolean.");
523
- if (config.locales !== void 0 && !isRecord(config.locales)) invalid(diagnostics, "locales must be an object.");
524
- else for (const [key, locale] of Object.entries(config.locales ?? {})) {
525
- if (!isRecord(locale) || typeof locale.label !== "string") {
526
- invalid(diagnostics, `locales.${key} must contain a string label.`);
527
- continue;
528
- }
529
- for (const property of Object.keys(locale)) if (![
530
- "label",
531
- "lang",
532
- "dir"
533
- ].includes(property)) unknown(diagnostics, `locales.${key}.${property}`);
534
- if (locale.lang !== void 0 && typeof locale.lang !== "string") invalid(diagnostics, `locales.${key}.lang must be a string.`);
535
- if (locale.dir !== void 0 && locale.dir !== "ltr" && locale.dir !== "rtl") invalid(diagnostics, `locales.${key}.dir must be "ltr" or "rtl".`);
536
- }
537
- if (config.defaultLocale !== void 0 && typeof config.defaultLocale !== "string") invalid(diagnostics, "defaultLocale must be a string.");
538
- else if (config.defaultLocale !== void 0 && (config.locales === void 0 || !(config.defaultLocale in config.locales))) invalid(diagnostics, `defaultLocale must match a key in locales; received "${config.defaultLocale}".`);
539
- if (config.content?.allowOutsideRoot !== void 0 && typeof config.content.allowOutsideRoot !== "boolean") invalid(diagnostics, "content.allowOutsideRoot must be a boolean.");
540
- if (config.content?.localizeRepositoryLinks !== void 0 && typeof config.content.localizeRepositoryLinks !== "boolean") invalid(diagnostics, "content.localizeRepositoryLinks must be a boolean.");
541
- const configuredSources = config.content?.sources;
542
- if (configuredSources !== void 0 && !Array.isArray(configuredSources)) invalid(diagnostics, "content.sources must be an array.");
543
- const sources = Array.isArray(configuredSources) ? configuredSources : [];
544
- for (const [index, source] of sources.entries()) {
545
- if (!isRecord(source)) {
546
- invalid(diagnostics, `content.sources[${index}] must be an object.`);
547
- continue;
548
- }
549
- const sourceRecord = source;
550
- const variants = [
551
- "file",
552
- "glob",
553
- "package",
554
- "openapi"
555
- ].filter((key) => key in source);
556
- if (variants.length !== 1) {
557
- diagnostics.push({
558
- code: "DOCS_CONFIG_INVALID",
559
- severity: "error",
560
- message: `content.sources[${index}] must select exactly one of file, glob, package, or openapi.`
561
- });
562
- continue;
563
- }
564
- const variant = variants[0];
565
- const allowed = {
566
- file: /* @__PURE__ */ new Set([
567
- "id",
568
- "root",
569
- "routeBase",
570
- "file",
571
- "route",
572
- "title",
573
- "description",
574
- "navigation"
575
- ]),
576
- glob: /* @__PURE__ */ new Set([
577
- "id",
578
- "root",
579
- "glob",
580
- "base",
581
- "routeBase",
582
- "exclude",
583
- "navigation"
584
- ]),
585
- package: /* @__PURE__ */ new Set([
586
- "id",
587
- "package",
588
- "registry",
589
- "include",
590
- "exclude",
591
- "index",
592
- "routeBase",
593
- "trust"
594
- ]),
595
- openapi: /* @__PURE__ */ new Set([
596
- "id",
597
- "root",
598
- "routeBase",
599
- "openapi"
600
- ])
601
- }[variant];
602
- for (const key of Object.keys(source)) if (!allowed.has(key)) unknown(diagnostics, `content.sources[${index}].${key}`);
603
- for (const field of [
604
- "id",
605
- "root",
606
- "route",
607
- "routeBase",
608
- "base",
609
- "index",
610
- "title",
611
- "description",
612
- "registry"
613
- ]) if (sourceRecord[field] !== void 0 && typeof sourceRecord[field] !== "string") invalid(diagnostics, `content.sources[${index}].${field} must be a string.`);
614
- if (typeof sourceRecord.id === "string" && !/^[a-zA-Z0-9_-]+$/.test(sourceRecord.id)) invalid(diagnostics, `content.sources[${index}].id must contain only letters, numbers, underscores, and hyphens.`);
615
- if (sourceRecord.trust !== void 0 && !["markdown", "mdx"].includes(String(sourceRecord.trust))) invalid(diagnostics, `content.sources[${index}].trust must be "markdown" or "mdx".`);
616
- if (typeof sourceRecord.registry === "string" && !isHttpUrl(sourceRecord.registry)) invalid(diagnostics, `content.sources[${index}].registry must be an absolute HTTP(S) URL.`);
617
- const selector = sourceRecord[variant];
618
- if (variant === "glob" ? typeof selector !== "string" && (!Array.isArray(selector) || selector.some((value) => typeof value !== "string")) : typeof selector !== "string" || selector.trim() === "") invalid(diagnostics, `content.sources[${index}].${variant} must be ${variant === "glob" ? "a string or string array" : "a non-empty string"}.`);
619
- for (const list of ["include", "exclude"]) {
620
- const value = sourceRecord[list];
621
- if (value !== void 0 && (!Array.isArray(value) || value.some((entry) => typeof entry !== "string"))) invalid(diagnostics, `content.sources[${index}].${list} must be a string array.`);
622
- }
623
- }
624
- if (config.markdown?.stripLeadingBadges !== void 0 && typeof config.markdown.stripLeadingBadges !== "boolean") invalid(diagnostics, "markdown.stripLeadingBadges must be a boolean.");
625
- if (config.markdown?.rawHtml !== void 0 && ![
626
- "allow",
627
- "sanitize",
628
- "strip",
629
- "reject"
630
- ].includes(config.markdown.rawHtml)) invalid(diagnostics, "markdown.rawHtml must be \"allow\", \"sanitize\", \"strip\", or \"reject\".");
631
- if (config.search?.enabled !== void 0 && typeof config.search.enabled !== "boolean") invalid(diagnostics, "search.enabled must be a boolean.");
632
- for (const key of ["strict", "ci"]) {
633
- const value = config.build?.[key];
634
- if (value !== void 0 && typeof value !== "boolean") invalid(diagnostics, `build.${key} must be a boolean.`);
635
- }
636
- for (const key of [
637
- "maxArtifactBytes",
638
- "maxUnpackedBytes",
639
- "maxFiles",
640
- "maxPathDepth",
641
- "maxAssetBytes"
642
- ]) {
643
- const value = config.build?.[key];
644
- if (value !== void 0 && (!Number.isSafeInteger(value) || value <= 0)) invalid(diagnostics, `build.${key} must be a positive integer.`);
645
- }
646
- const brand = config.theme?.brand;
647
- if (typeof brand === "object" && brand !== null && "contrast" in brand && !brand.unsafeContrast) {
648
- const target = brand.contrast?.apca;
649
- const normal = Array.isArray(target) ? target[0] : target;
650
- if (typeof normal === "number" && normal < 45) diagnostics.push({
651
- code: "DOCS_CONFIG_INVALID",
652
- severity: "error",
653
- message: "theme.brand.contrast.apca cannot be below 45 without unsafeContrast.",
654
- hint: "Remove the override, use 45 or higher, or explicitly set unsafeContrast: true."
655
- });
656
- }
657
- if (config.content?.frontmatter !== void 0 && !["preserve", "reject"].includes(config.content.frontmatter)) invalid(diagnostics, "content.frontmatter must be \"preserve\" or \"reject\".");
658
- for (const field of [
659
- "styles",
660
- "customStyles",
661
- "palette",
662
- "fonts",
663
- "tokens",
664
- "states",
665
- "presets"
666
- ]) if (config.theme?.[field] !== void 0 && !isRecord(config.theme[field])) invalid(diagnostics, `theme.${field} must be an object.`);
667
- if (isRecord(config.theme?.fonts)) for (const [role, font] of Object.entries(config.theme.fonts)) {
668
- const path = `theme.fonts.${role}`;
669
- if (![
670
- "body",
671
- "heading",
672
- "code"
673
- ].includes(role)) {
674
- unknown(diagnostics, path);
675
- continue;
676
- }
677
- if (font === void 0) continue;
678
- if (typeof font === "string") {
679
- if (!validFontFamily(font)) invalid(diagnostics, `${path} must name a Google Fonts family.`);
680
- continue;
681
- }
682
- if (!isRecord(font)) {
683
- invalid(diagnostics, `${path} must be a Google Fonts name or font definition.`);
684
- continue;
685
- }
686
- if ("google" in font) {
687
- for (const key of Object.keys(font)) if (!["google", "weights"].includes(key)) unknown(diagnostics, `${path}.${key}`);
688
- if (!validFontFamily(font.google)) invalid(diagnostics, `${path}.google must name a Google Fonts family.`);
689
- if (font.weights !== void 0 && (!Array.isArray(font.weights) || font.weights.length === 0 || font.weights.some((weight) => !Number.isInteger(weight) || weight < 1 || weight > 1e3))) invalid(diagnostics, `${path}.weights must be a non-empty array of font weights from 1 to 1000.`);
690
- } else {
691
- for (const key of Object.keys(font)) if (!["family", "files"].includes(key)) unknown(diagnostics, `${path}.${key}`);
692
- if (!validFontFamily(font.family)) invalid(diagnostics, `${path}.family must be a font family name.`);
693
- if (!Array.isArray(font.files) || font.files.length === 0) {
694
- invalid(diagnostics, `${path}.files must contain at least one font file.`);
695
- continue;
696
- }
697
- font.files.forEach((file, index) => {
698
- const filePath = `${path}.files[${index}]`;
699
- if (!isRecord(file)) {
700
- invalid(diagnostics, `${filePath} must be an object.`);
701
- return;
702
- }
703
- for (const key of Object.keys(file)) if (![
704
- "src",
705
- "weight",
706
- "style"
707
- ].includes(key)) unknown(diagnostics, `${filePath}.${key}`);
708
- if (typeof file.src !== "string" || !/^\/(?!\/)[^\s"'()\\?#]+\.(?:woff2?|ttf|otf)$/.test(file.src) || file.src.split("/").some((segment) => segment === "." || segment === "..")) invalid(diagnostics, `${filePath}.src must be a root-relative .woff2, .woff, .ttf, or .otf public path.`);
709
- if (file.weight !== void 0 && !(typeof file.weight === "number" && Number.isInteger(file.weight) && file.weight >= 1 && file.weight <= 1e3) && !(typeof file.weight === "string" && /^(?:[1-9]\d{0,2}|1000) (?:[1-9]\d{0,2}|1000)$/.test(file.weight) && Number(file.weight.split(" ")[0]) < Number(file.weight.split(" ")[1]))) invalid(diagnostics, `${filePath}.weight must be a weight from 1 to 1000 or an ascending range.`);
710
- if (file.style !== void 0 && !["normal", "italic"].includes(file.style)) invalid(diagnostics, `${filePath}.style must be normal or italic.`);
711
- });
712
- }
713
- }
714
- for (const [name, entry] of Object.entries(config.theme?.styles ?? {})) {
715
- if (!COOKBOOK_COMPONENT_NAMES.includes(name)) {
716
- unknown(diagnostics, `theme.styles.${name}; register custom components under theme.customStyles`);
717
- continue;
718
- }
719
- if (!isRecord(entry)) invalid(diagnostics, `theme.styles.${name} must be a Tasty style object.`);
720
- else {
721
- if ("mode" in entry) invalid(diagnostics, `theme.styles.${name} must contain only Tasty properties, without a mode wrapper.`);
722
- const elements = COOKBOOK_COMPONENT_SUB_ELEMENTS[name];
723
- for (const key of Object.keys(entry)) if (/^[A-Z]/.test(key) && !elements.includes(key)) unknown(diagnostics, `theme.styles.${name}.${key}`);
724
- }
725
- }
726
- for (const [name, entry] of Object.entries(config.theme?.customStyles ?? {})) {
727
- if (COOKBOOK_COMPONENT_NAMES.includes(name)) invalid(diagnostics, `Use theme.styles.${name} for a built-in component.`);
728
- if (!/^[A-Za-z][A-Za-z0-9_-]*$/.test(name) || !isRecord(entry) || "mode" in entry) invalid(diagnostics, `theme.customStyles.${name} must be a named partial Tasty style object.`);
729
- }
730
- for (const [name, value] of Object.entries(config.theme?.palette ?? {})) {
731
- if (![
732
- "surface",
733
- "header",
734
- "overlay",
735
- "text",
736
- "heading",
737
- "textSoft",
738
- "info",
739
- "success",
740
- "warning",
741
- "danger"
742
- ].includes(name)) unknown(diagnostics, `theme.palette.${name}`);
743
- validatePaletteColor(name, value, diagnostics);
744
- }
745
- if (brand !== void 0) try {
746
- if (isBrandDeclaration(brand)) glaze.color({
747
- hue: brand.hue,
748
- saturation: brand.saturation,
749
- tone: brand.tone
750
- }).resolve();
751
- else glaze.color({ from: typeof brand === "object" && brand !== null && "from" in brand ? brand.from : brand }).resolve();
752
- } catch (error) {
753
- invalid(diagnostics, `theme.brand: ${error instanceof Error ? error.message : String(error)}`);
754
- }
755
- const contrast = config.theme?.contrastLevel;
756
- if (contrast !== void 0 && contrast !== "auto" && (typeof contrast !== "number" || !Number.isFinite(contrast) || contrast < 0 || contrast > 100)) invalid(diagnostics, "theme.contrastLevel must be \"auto\" or a number from 0 to 100.");
757
- const componentOverrides = config.components?.overrides;
758
- if (componentOverrides !== void 0 && !isRecord(componentOverrides)) invalid(diagnostics, "components.overrides must be an object.");
759
- else for (const [name, override] of Object.entries(componentOverrides ?? {})) {
760
- if (typeof override === "string") continue;
761
- if (name === "Footer" && override === false) continue;
762
- invalid(diagnostics, `components.overrides.${name} must be an Astro component path${name === "Footer" ? " or false" : ""}.`);
763
- }
764
- return diagnostics;
765
- }
766
- function validatePaletteColor(name, value, diagnostics) {
767
- try {
768
- if (!isPaletteDeclaration(value)) {
769
- glaze.color({ from: value }).resolve();
770
- return;
771
- }
772
- for (const key of Object.keys(value)) if (!COLOR_DECLARATION_KEYS.has(key)) unknown(diagnostics, `theme.palette.${name}.${key}`);
773
- const colorName = name === "textSoft" ? "text-soft" : name;
774
- const theme = glaze(266, 68);
775
- theme.colors({
776
- surface: { tone: 98 },
777
- "surface-2": {
778
- base: "surface",
779
- tone: "-2"
780
- },
781
- "accent-surface": { tone: 48 },
782
- text: {
783
- base: "surface",
784
- contrast: { apca: 75 }
785
- },
786
- heading: {
787
- base: "surface",
788
- tone: 4
789
- },
790
- "text-soft": {
791
- base: "surface",
792
- contrast: { apca: 60 }
793
- },
794
- [colorName]: {
795
- tone: colorName === "surface" ? 98 : 50,
796
- ...value
797
- }
798
- });
799
- theme.resolve();
800
- } catch (error) {
801
- invalid(diagnostics, `theme.palette.${name}: ${error instanceof Error ? error.message : String(error)}`);
802
- }
803
- }
804
- function isPaletteDeclaration(value) {
805
- return typeof value === "object" && value !== null && !("h" in value || "r" in value || "c" in value);
806
- }
807
- function isBrandDeclaration(value) {
808
- return isRecord(value) && "hue" in value && "saturation" in value && "tone" in value;
809
- }
810
- function unknown(diagnostics, path) {
811
- diagnostics.push({
812
- code: "DOCS_CONFIG_UNKNOWN_KEY",
813
- severity: "error",
814
- message: `Unknown configuration key "${path}".`
815
- });
816
- }
817
- function invalid(diagnostics, message) {
818
- diagnostics.push({
819
- code: "DOCS_CONFIG_INVALID",
820
- severity: "error",
821
- message
822
- });
823
- }
824
- function isRecord(value) {
825
- return typeof value === "object" && value !== null && !Array.isArray(value);
826
- }
827
- function validFontFamily(value) {
828
- return typeof value === "string" && value.length <= 100 && /^[\p{L}\p{N} ._-]+$/u.test(value) && value.trim() === value && value.length > 0;
829
- }
830
- function isHttpUrl(value) {
831
- try {
832
- return ["http:", "https:"].includes(new URL(value).protocol);
833
- } catch {
834
- return false;
835
- }
836
- }
837
- function validateNavigationItems(items, path, diagnostics) {
838
- if (!Array.isArray(items)) {
839
- invalid(diagnostics, `${path} must be an array.`);
840
- return;
841
- }
842
- for (const [index, item] of items.entries()) {
843
- const itemPath = `${path}[${index}]`;
844
- if (typeof item === "string") continue;
845
- if (!isRecord(item) || typeof item.label !== "string") {
846
- invalid(diagnostics, `${itemPath} must be a route or a labeled navigation item.`);
847
- continue;
848
- }
849
- if (("items" in item || "autogenerate" in item) && item.link !== void 0 && (typeof item.link !== "string" || !/^\/(?!\/)[^?#\\\s]*$/.test(item.link) || item.link.includes("//") || item.link.split("/").some((segment) => segment === "." || segment === ".."))) invalid(diagnostics, `${itemPath}.link must be a root-relative page route without a query or fragment.`);
850
- if ("items" in item) validateNavigationItems(item.items, `${itemPath}.items`, diagnostics);
851
- else if ("autogenerate" in item) {
852
- if (!isRecord(item.autogenerate) || typeof item.autogenerate.directory !== "string") invalid(diagnostics, `${itemPath}.autogenerate.directory must be a directory path.`);
853
- } else if (typeof item.link !== "string") invalid(diagnostics, `${itemPath}.link must be a URL or route.`);
854
- }
855
- }
856
- function normalizeDocsConfig(config = {}) {
857
- const diagnostics = validateConfig(config);
858
- if (diagnostics.length > 0) throw new DocsConfigError(diagnostics);
859
- const navigation = Array.isArray(config.navigation) ? { items: config.navigation } : config.navigation ?? {};
860
- return {
861
- redirects: { ...config.redirects },
862
- site: { ...config.site },
863
- head: [...config.head ?? []],
864
- ...config.editLink ? { editLink: { ...config.editLink } } : {},
865
- lastUpdated: config.lastUpdated ?? false,
866
- ...config.locales ? { locales: { ...config.locales } } : {},
867
- ...config.defaultLocale ? { defaultLocale: config.defaultLocale } : {},
868
- content: {
869
- allowOutsideRoot: false,
870
- localizeRepositoryLinks: false,
871
- ...config.content
872
- },
873
- navigation,
874
- theme: {
875
- ...config.theme,
876
- brand: config.theme?.brand ?? DEFAULT_BRAND
877
- },
878
- markdown: {
879
- stripLeadingBadges: true,
880
- rawHtml: "sanitize",
881
- ...config.markdown
882
- },
883
- search: {
884
- enabled: true,
885
- ...config.search
886
- },
887
- components: { ...config.components },
888
- build: {
889
- strict: true,
890
- ci: process.env.CI === "true",
891
- base: "/",
892
- cacheDir: "",
893
- maxArtifactBytes: 26214400,
894
- maxUnpackedBytes: 104857600,
895
- maxFiles: 1e4,
896
- maxPathDepth: 24,
897
- maxAssetBytes: 20971520,
898
- ...config.build
899
- }
900
- };
901
- }
902
- function defineDocsConfig(config) {
903
- const diagnostics = validateConfig(config);
904
- if (diagnostics.length > 0) throw new DocsConfigError(diagnostics);
905
- return config;
906
- }
907
- /** Compose presets. Color roles and fonts replace, other objects merge, and styles use Tasty semantics. */
908
- function mergeDocsConfig(...configs) {
909
- const merge = (base, next) => {
910
- const result = { ...base };
911
- for (const [key, value] of Object.entries(next)) {
912
- if (value === void 0) continue;
913
- result[key] = isRecord(value) ? merge(isRecord(result[key]) ? result[key] : {}, value) : Array.isArray(value) ? [...value] : value;
914
- }
915
- return result;
916
- };
917
- return defineDocsConfig(configs.reduce((base, next) => {
918
- const diagnostics = validateConfig(next);
919
- if (diagnostics.length) throw new DocsConfigError(diagnostics);
920
- const result = merge(base, next);
921
- if (next.theme?.brand !== void 0) result.theme.brand = next.theme.brand;
922
- if (next.theme?.palette) result.theme.palette = {
923
- ...base.theme?.palette,
924
- ...Object.fromEntries(Object.entries(next.theme.palette).filter(([, color]) => color !== void 0))
925
- };
926
- if (next.theme?.fonts) result.theme.fonts = {
927
- ...base.theme?.fonts,
928
- ...Object.fromEntries(Object.entries(next.theme.fonts).filter(([, font]) => font !== void 0))
929
- };
930
- for (const field of ["styles", "customStyles"]) {
931
- if (!next.theme?.[field]) continue;
932
- const styles = { ...base.theme?.[field] };
933
- for (const [name, configured] of Object.entries(next.theme[field])) styles[name] = mergeStyles(styles[name] ?? {}, configured);
934
- result.theme[field] = styles;
935
- }
936
- return result;
937
- }, {}));
938
- }
939
- //#endregion
940
- export { validateConfig as a, normalizeDocsConfig as i, defineDocsConfig as n, COOKBOOK_COMPONENT_NAMES as o, mergeDocsConfig as r, COOKBOOK_COMPONENT_SUB_ELEMENTS as s, DocsConfigError as t };
941
-
942
- //# sourceMappingURL=config-CW0iIYp8.js.map