@tenphi/starlight 0.6.0 → 0.7.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/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { n as starlight, t as createStarlightCollection } from "./content-Cwk8rm6i.js";
2
- import { a as resolveNavigationLayout } from "./navigation-CC0dlAL8.js";
2
+ import { a as resolveNavigationLayout } from "./navigation-CkQXI2Zb.js";
3
3
  import { createRequire } from "node:module";
4
4
  import { existsSync } from "node:fs";
5
- import { cp, mkdir, readFile } from "node:fs/promises";
5
+ import { cp, mkdir, readFile, readdir, unlink, writeFile } from "node:fs/promises";
6
6
  import { dirname, extname, join } from "node:path";
7
7
  import { fileURLToPath, pathToFileURL } from "node:url";
8
8
  import { COOKBOOK_COMPONENT_NAMES, assertValidDocs, createDocsGraph } from "@tenphi/docs";
@@ -109,80 +109,39 @@ function resolveDocsTheme(theme = {}) {
109
109
  ...theme.contrastLevel !== void 0 ? { contrastLevel: theme.contrastLevel } : {}
110
110
  };
111
111
  const surfaceFrom = theme.palette?.surface ?? "#ffffff";
112
- const surface = glaze.color({
112
+ const resolvedSurfaceSeed = glaze.color({
113
113
  from: surfaceFrom,
114
114
  mode: "auto",
115
115
  darkSaturation: .35
116
- });
117
- const surface2 = glaze.color({
118
- from: surfaceFrom,
119
- base: surface,
120
- tone: "-2",
121
- mode: "auto",
122
- saturationFactor: .9,
123
- darkSaturation: .325
124
- }, glazeOptions);
125
- const surface3 = glaze.color({
126
- from: surfaceFrom,
127
- base: surface2,
128
- tone: "-2",
129
- mode: "auto",
130
- saturationFactor: .8,
131
- darkSaturation: .3
132
- }, glazeOptions);
133
- const text = glaze.color({
134
- from: theme.palette?.text ?? "#20232a",
135
- base: surface,
136
- role: "text",
137
- contrast: { apca: [75, 90] },
138
- mode: "auto"
139
- }, glazeOptions);
140
- const textSoft = glaze.color({
141
- from: theme.palette?.textSoft ?? "#626875",
142
- base: surface,
143
- role: "text",
144
- contrast: { apca: [60, 75] },
145
- mode: "auto"
146
- }, glazeOptions);
147
- const accentText = glaze.color({
148
- from: brand.from,
149
- base: surface,
150
- role: "text",
151
- contrast: { apca: authoredTarget },
152
- mode: "auto"
153
- }, glazeOptions);
154
- const focus = glaze.color({
155
- from: brand.from,
156
- base: surface,
157
- role: "border",
158
- contrast: { apca: authoredTarget },
159
- mode: "auto"
160
- }, glazeOptions);
161
- const accentSurface = glaze.color({
162
- from: brand.from,
163
- mode: "fixed"
164
- });
165
- const accentSurfaceText = glaze.color({
166
- from: "#ffffff",
167
- base: accentSurface,
168
- role: "text",
169
- contrast: { apca: 60 },
170
- mode: "auto"
171
- });
172
- const resolvedSurface = surface.resolve();
173
- const resolvedAccent = accentText.resolve();
174
- const shadowTheme = glaze({
175
- hue: variantToOkhsl(resolvedSurface.light).h,
176
- saturation: variantToOkhsl(resolvedSurface.light).s * 100,
177
- darkHue: variantToOkhsl(resolvedSurface.dark).h,
178
- darkSaturation: variantToOkhsl(resolvedSurface.dark).s * 100
116
+ }).resolve();
117
+ const lightSurface = variantToOkhsl(resolvedSurfaceSeed.light);
118
+ const darkSurface = variantToOkhsl(resolvedSurfaceSeed.dark);
119
+ const colorTheme = glaze({
120
+ hue: lightSurface.h,
121
+ saturation: lightSurface.s * 100,
122
+ darkHue: darkSurface.h,
123
+ darkSaturation: Math.min(100, darkSurface.s * 100 / .35)
179
124
  }, void 0, glazeOptions);
180
- shadowTheme.colors({
125
+ colorTheme.colors({
181
126
  surface: {
182
127
  from: surfaceFrom,
183
128
  mode: "auto",
184
129
  darkSaturation: .35
185
130
  },
131
+ "surface-2": {
132
+ base: "surface",
133
+ tone: "-2",
134
+ mode: "auto",
135
+ saturation: .75,
136
+ darkSaturation: .275
137
+ },
138
+ "surface-3": {
139
+ base: "surface-2",
140
+ tone: "-2",
141
+ mode: "auto",
142
+ saturation: .65,
143
+ darkSaturation: .25
144
+ },
186
145
  text: {
187
146
  from: theme.palette?.text ?? "#20232a",
188
147
  base: "surface",
@@ -190,14 +149,176 @@ function resolveDocsTheme(theme = {}) {
190
149
  contrast: { apca: [75, 90] },
191
150
  mode: "auto"
192
151
  },
152
+ "text-soft": {
153
+ from: theme.palette?.textSoft ?? "#626875",
154
+ base: "surface",
155
+ role: "text",
156
+ contrast: { apca: [60, 75] },
157
+ mode: "auto"
158
+ },
159
+ "text-muted": mix("surface", "text", 66),
160
+ "surface-2-hover": mix("surface-2", "text", [3, 6]),
161
+ "surface-2-pressed": mix("surface-2", "text", [9, 14]),
162
+ "surface-3-hover": mix("surface-3", "text", [3, 6]),
163
+ "surface-3-pressed": mix("surface-3", "text", [9, 14]),
164
+ "accent-text": {
165
+ from: brand.from,
166
+ base: "surface",
167
+ role: "text",
168
+ contrast: { apca: [normalTarget, highTarget] },
169
+ mode: "auto"
170
+ },
171
+ focus: {
172
+ from: brand.from,
173
+ base: "surface",
174
+ role: "border",
175
+ contrast: { apca: [normalTarget, highTarget] },
176
+ mode: "auto"
177
+ },
178
+ "accent-surface": {
179
+ from: brand.from,
180
+ mode: "fixed"
181
+ },
182
+ "accent-surface-text": {
183
+ from: "#ffffff",
184
+ base: "accent-surface",
185
+ role: "text",
186
+ contrast: { apca: [60, 75] },
187
+ mode: "auto"
188
+ },
189
+ "accent-surface-subtle": mix("surface", "accent-surface", [12, 18]),
190
+ "accent-surface-2-subtle": mix("surface-2", "accent-surface", [12, 18]),
193
191
  shadow: {
194
192
  type: "shadow",
195
193
  bg: "surface",
196
194
  fg: "text",
197
195
  intensity: [12, 20],
198
196
  tuning: { alphaMax: .28 }
197
+ },
198
+ overlay: {
199
+ type: "mix",
200
+ base: "surface",
201
+ target: "text",
202
+ value: [58, 68],
203
+ blend: "transparent"
204
+ },
205
+ clear: {
206
+ from: "#ffffff",
207
+ mode: "fixed",
208
+ opacity: 0
209
+ },
210
+ ...statusColors("orange", "#d97706"),
211
+ ...statusColors("green", "#16a34a"),
212
+ ...statusColors("blue", "#2563eb"),
213
+ ...statusColors("purple", "#9333ea"),
214
+ ...statusColors("red", "#dc2626")
215
+ });
216
+ const resolvedBrandSeed = glaze.color({
217
+ from: brand.from,
218
+ mode: "fixed"
219
+ }).resolve();
220
+ const lightBrand = variantToOkhsl(resolvedBrandSeed.light);
221
+ const darkBrand = variantToOkhsl(resolvedBrandSeed.dark);
222
+ const borderTheme = glaze({
223
+ hue: lightBrand.h,
224
+ saturation: lightBrand.s * 100,
225
+ darkHue: darkBrand.h,
226
+ darkSaturation: darkBrand.s * 100
227
+ }, void 0, glazeOptions);
228
+ borderTheme.colors({
229
+ surface: {
230
+ from: surfaceFrom,
231
+ mode: "auto",
232
+ darkSaturation: .35
233
+ },
234
+ border: {
235
+ base: "surface",
236
+ tone: ["-9", "-22"],
237
+ saturation: .205,
238
+ mode: "auto"
239
+ },
240
+ "border-strong": {
241
+ base: "surface",
242
+ tone: ["-20", "-38"],
243
+ saturation: .205,
244
+ mode: "auto"
199
245
  }
200
246
  });
247
+ const syntaxTheme = glaze(210, 90, glazeOptions);
248
+ syntaxTheme.colors({
249
+ bg: {
250
+ tone: 100,
251
+ saturation: .1
252
+ },
253
+ text: {
254
+ base: "bg",
255
+ tone: 0,
256
+ contrast: { wcag: ["AA", "AAA"] },
257
+ saturation: 0
258
+ },
259
+ comment: {
260
+ base: "bg",
261
+ contrast: { wcag: ["AA", "AAA"] },
262
+ saturation: .01,
263
+ hue: 210
264
+ },
265
+ punctuation: {
266
+ base: "bg",
267
+ contrast: { wcag: [6, "AAA"] },
268
+ saturation: .01,
269
+ hue: 210
270
+ },
271
+ keyword: {
272
+ base: "bg",
273
+ contrast: { wcag: ["AA", "AAA"] },
274
+ saturation: 80
275
+ },
276
+ string: {
277
+ base: "bg",
278
+ contrast: { wcag: ["AA", "AAA"] },
279
+ saturation: 80,
280
+ hue: 15
281
+ },
282
+ token: {
283
+ base: "bg",
284
+ contrast: { wcag: ["AA", "AAA"] },
285
+ saturation: 80,
286
+ hue: 125
287
+ },
288
+ property: {
289
+ base: "bg",
290
+ contrast: { wcag: ["AA", "AAA"] },
291
+ saturation: 80,
292
+ hue: 155
293
+ },
294
+ number: {
295
+ base: "bg",
296
+ contrast: { wcag: ["AA", "AAA"] },
297
+ saturation: 80,
298
+ hue: 70
299
+ },
300
+ function: {
301
+ base: "bg",
302
+ contrast: { wcag: ["AA", "AAA"] },
303
+ saturation: 80,
304
+ hue: 210
305
+ },
306
+ value: {
307
+ base: "bg",
308
+ contrast: { wcag: ["AA", "AAA"] },
309
+ saturation: 80,
310
+ hue: 210
311
+ },
312
+ operator: {
313
+ base: "bg",
314
+ contrast: { wcag: ["AA", "AAA"] },
315
+ saturation: 80,
316
+ hue: 340
317
+ }
318
+ });
319
+ const resolvedColors = colorTheme.resolve();
320
+ const resolvedSurface = requiredResolvedColor(resolvedColors, "surface");
321
+ const resolvedAccent = requiredResolvedColor(resolvedColors, "accent-text");
201
322
  const scores = {
202
323
  light: score(resolvedAccent.light, resolvedSurface.light),
203
324
  dark: score(resolvedAccent.dark, resolvedSurface.dark),
@@ -215,20 +336,39 @@ function resolveDocsTheme(theme = {}) {
215
336
  });
216
337
  }
217
338
  const outputOptions = { modes: { highContrast: true } };
218
- const shadow = shadowTheme.json(outputOptions).shadow;
219
- if (!shadow) throw new Error("The Glaze shadow token failed to resolve.");
339
+ const tastyOptions = {
340
+ ...outputOptions,
341
+ states: {
342
+ dark: "theme=dark | (@media(prefers-color-scheme: dark) & :not([data-theme]))",
343
+ highContrast: "contrast=more | (@media(prefers-contrast: more) & :not([data-contrast]))"
344
+ }
345
+ };
346
+ const resolvedPalette = colorTheme.json(outputOptions);
347
+ const colorTokens = colorTheme.tasty(tastyOptions);
348
+ const borderTokens = borderTheme.tasty(tastyOptions);
349
+ const syntaxTokens = glaze.palette({ syntax: syntaxTheme }).tasty({
350
+ ...tastyOptions,
351
+ prefix: true,
352
+ primary: false
353
+ });
220
354
  return {
221
355
  colors: {
222
- surface: surface.json(outputOptions),
223
- surface2: surface2.json(outputOptions),
224
- surface3: surface3.json(outputOptions),
225
- text: text.json(outputOptions),
226
- textSoft: textSoft.json(outputOptions),
227
- accentText: accentText.json(outputOptions),
228
- accentSurface: accentSurface.json(outputOptions),
229
- accentSurfaceText: accentSurfaceText.json(outputOptions),
230
- focus: focus.json(outputOptions),
231
- shadow
356
+ surface: requiredJsonColor(resolvedPalette, "surface"),
357
+ surface2: requiredJsonColor(resolvedPalette, "surface-2"),
358
+ surface3: requiredJsonColor(resolvedPalette, "surface-3"),
359
+ text: requiredJsonColor(resolvedPalette, "text"),
360
+ textSoft: requiredJsonColor(resolvedPalette, "text-soft"),
361
+ accentText: requiredJsonColor(resolvedPalette, "accent-text"),
362
+ accentSurface: requiredJsonColor(resolvedPalette, "accent-surface"),
363
+ accentSurfaceText: requiredJsonColor(resolvedPalette, "accent-surface-text"),
364
+ focus: requiredJsonColor(resolvedPalette, "focus"),
365
+ shadow: requiredJsonColor(resolvedPalette, "shadow")
366
+ },
367
+ colorTokens: {
368
+ ...colorTokens,
369
+ "#border": requiredJsonColor(borderTokens, "#border"),
370
+ "#border-strong": requiredJsonColor(borderTokens, "#border-strong"),
371
+ ...syntaxTokens
232
372
  },
233
373
  tokens: resolveThemeTokens(theme.tokens),
234
374
  presets: resolveTypographyPresets(theme.presets),
@@ -236,6 +376,44 @@ function resolveDocsTheme(theme = {}) {
236
376
  diagnostics
237
377
  };
238
378
  }
379
+ function mix(base, target, value, space = "okhsl") {
380
+ return {
381
+ type: "mix",
382
+ base,
383
+ target,
384
+ value,
385
+ space
386
+ };
387
+ }
388
+ function statusColors(name, from) {
389
+ return {
390
+ [name]: {
391
+ from,
392
+ base: "surface",
393
+ role: "border",
394
+ contrast: { apca: [30, 45] },
395
+ mode: "auto"
396
+ },
397
+ [`${name}-text`]: {
398
+ from,
399
+ base: "surface",
400
+ role: "text",
401
+ contrast: { apca: [60, 75] },
402
+ mode: "auto"
403
+ },
404
+ [`${name}-surface`]: mix("surface", name, [12, 18], "srgb")
405
+ };
406
+ }
407
+ function requiredResolvedColor(colors, name) {
408
+ const color = colors.get(name);
409
+ if (!color) throw new Error(`The Glaze ${name} color failed to resolve.`);
410
+ return color;
411
+ }
412
+ function requiredJsonColor(colors, name) {
413
+ const color = colors[name];
414
+ if (!color) throw new Error(`The Glaze ${name} token failed to export.`);
415
+ return color;
416
+ }
239
417
  function normalizeBrand(brand) {
240
418
  if (typeof brand === "object" && brand !== null && "from" in brand) return brand;
241
419
  return { from: brand ?? "#315efb" };
@@ -248,6 +426,183 @@ function luminance(variant) {
248
426
  return relativeLuminanceFromLinearRgb(okhslToLinearSrgb(h, s, l, variant.pastel));
249
427
  }
250
428
  //#endregion
429
+ //#region src/theme/shiki-theme.ts
430
+ const comment = "var(--syntax-comment-color)";
431
+ const punctuation = "var(--syntax-punctuation-color)";
432
+ const keyword = "var(--syntax-keyword-color)";
433
+ const string = "var(--syntax-string-color)";
434
+ const token = "var(--syntax-token-color)";
435
+ const property = "var(--syntax-property-color)";
436
+ const number = "var(--syntax-number-color)";
437
+ const func = "var(--syntax-function-color)";
438
+ const value = "var(--syntax-value-color)";
439
+ const operator = "var(--syntax-operator-color)";
440
+ const foreground = "var(--syntax-text-color)";
441
+ const background = "var(--syntax-bg-color)";
442
+ /**
443
+ * Shiki performs the grammatical classification, while every emitted color
444
+ * remains a reference to a Glaze-generated token owned by Tasty.
445
+ */
446
+ const tastyCodeTheme = {
447
+ name: "tasty-code",
448
+ type: "light",
449
+ fg: foreground,
450
+ bg: background,
451
+ colors: {
452
+ "editor.background": background,
453
+ "editor.foreground": foreground
454
+ },
455
+ settings: [
456
+ {
457
+ scope: [
458
+ "comment",
459
+ "comment.line",
460
+ "comment.block",
461
+ "punctuation.definition.comment"
462
+ ],
463
+ settings: {
464
+ foreground: comment,
465
+ fontStyle: "italic"
466
+ }
467
+ },
468
+ {
469
+ scope: [
470
+ "keyword",
471
+ "keyword.control",
472
+ "keyword.other",
473
+ "storage.type",
474
+ "storage.modifier",
475
+ "keyword.control.at-rule.tasty",
476
+ "keyword.control.at-rule.media.tasty",
477
+ "keyword.control.at-rule.media-type.tasty",
478
+ "keyword.control.at-rule.starting.tasty",
479
+ "keyword.control.state-alias.tasty"
480
+ ],
481
+ settings: { foreground: keyword }
482
+ },
483
+ {
484
+ scope: [
485
+ "string",
486
+ "string.quoted",
487
+ "string.template",
488
+ "string.quoted.attribute-value.tasty",
489
+ "string.unquoted.attribute-value.tasty"
490
+ ],
491
+ settings: { foreground: string }
492
+ },
493
+ {
494
+ scope: [
495
+ "support.constant.color.tasty-token",
496
+ "support.constant.color.tasty-token.builtin",
497
+ "constant.other.color.tasty-token",
498
+ "constant.other.color.tasty",
499
+ "constant.other.color.hex",
500
+ "constant.other.color.rgb-value"
501
+ ],
502
+ settings: { foreground: token }
503
+ },
504
+ {
505
+ scope: [
506
+ "constant.numeric",
507
+ "constant.numeric.tasty",
508
+ "constant.numeric.custom-unit.tasty",
509
+ "constant.numeric.css-with-unit",
510
+ "constant.numeric.bare.tasty",
511
+ "constant.numeric.css",
512
+ "constant.numeric.keyframe-step.tasty",
513
+ "constant.language.boolean.tasty"
514
+ ],
515
+ settings: { foreground: number }
516
+ },
517
+ {
518
+ scope: ["support.type.property-name.tasty", "variable.other.constant.tasty"],
519
+ settings: { foreground: property }
520
+ },
521
+ {
522
+ scope: ["variable", "variable.other"],
523
+ settings: { foreground }
524
+ },
525
+ {
526
+ scope: [
527
+ "entity.name.function",
528
+ "support.function",
529
+ "support.function.misc.css",
530
+ "entity.name.tag",
531
+ "entity.name.tag.tsx",
532
+ "entity.name.type.tasty",
533
+ "entity.name.tag.tasty"
534
+ ],
535
+ settings: { foreground: func }
536
+ },
537
+ {
538
+ scope: [
539
+ "support.constant.property-value.tasty",
540
+ "support.constant.property-value.tasty-display",
541
+ "support.constant.property-value.tasty-directional",
542
+ "support.constant.property-value.tasty-preset",
543
+ "support.constant.property-value.tasty-shape",
544
+ "support.constant.property-value.tasty-scrollbar",
545
+ "support.constant.property-value.tasty-state",
546
+ "support.constant.property-value.tasty-cursor",
547
+ "support.constant.property-value.tasty-overflow",
548
+ "support.constant.property-value.tasty-position",
549
+ "support.constant.property-value.tasty-flex",
550
+ "support.constant.property-value.tasty-font",
551
+ "support.constant.property-value.tasty-text",
552
+ "support.constant.property-value.tasty-alignment",
553
+ "support.constant.property-value.tasty-border-style",
554
+ "support.constant.property-value.tasty-whitespace",
555
+ "support.constant.property-value.tasty-global",
556
+ "support.constant.property-value.tasty-transition",
557
+ "support.constant.property-value.css-syntax",
558
+ "entity.other.attribute-name",
559
+ "entity.other.attribute-name.tasty",
560
+ "entity.other.attribute-name.pseudo-class.tasty",
561
+ "entity.other.attribute-name.pseudo-class.css",
562
+ "entity.other.attribute-name.class.tasty",
563
+ "entity.other.attribute-name.pseudo-element.css",
564
+ "punctuation.definition.entity.css"
565
+ ],
566
+ settings: { foreground: value }
567
+ },
568
+ {
569
+ scope: [
570
+ "keyword.operator",
571
+ "keyword.operator.logical.tasty",
572
+ "keyword.operator.arithmetic.css",
573
+ "keyword.operator.assignment",
574
+ "keyword.operator.selector-affix.tasty",
575
+ "keyword.operator.attribute-selector.tasty",
576
+ "keyword.operator.comparison.tasty"
577
+ ],
578
+ settings: { foreground: operator }
579
+ },
580
+ {
581
+ scope: [
582
+ "punctuation.definition.string",
583
+ "punctuation.separator",
584
+ "punctuation.definition.block",
585
+ "punctuation.definition.array",
586
+ "punctuation.section",
587
+ "punctuation.definition.auto-calc",
588
+ "punctuation.definition.attribute-selector",
589
+ "punctuation.definition.pseudo-class",
590
+ "punctuation.definition.fallback",
591
+ "meta.brace"
592
+ ],
593
+ settings: { foreground: punctuation }
594
+ },
595
+ {
596
+ scope: ["keyword.control.at-rule", "entity.name.tag.class.css"],
597
+ settings: { foreground: keyword }
598
+ },
599
+ {
600
+ scope: ["support.type.property-name.css", "meta.property-name.css"],
601
+ settings: { foreground: property }
602
+ }
603
+ ]
604
+ };
605
+ //#endregion
251
606
  //#region src/theme/tasty-config.ts
252
607
  const TASTY_UNITS = {
253
608
  x: "var(--gap)",
@@ -255,42 +610,11 @@ const TASTY_UNITS = {
255
610
  cr: "var(--card-radius)",
256
611
  bw: "var(--border-width)"
257
612
  };
258
- const LIGHT = "theme=light | (@media(prefers-color-scheme: light) & :not([data-theme]))";
259
- const HIGH_CONTRAST = "contrast=more | (@media(prefers-contrast: more) & :not([data-contrast]))";
260
613
  function tastyTokens(theme) {
261
614
  const tokens = Object.fromEntries(Object.entries(theme.tokens).filter(([name]) => name.startsWith("$")));
262
- Object.assign(tokens, {
263
- "#surface": colorStates(theme.colors.surface),
264
- "#surface-2": colorStates(theme.colors.surface2),
265
- "#surface-3": colorStates(theme.colors.surface3),
266
- "#surface-2-hover": mix("#text", 3, "#surface-2"),
267
- "#surface-2-pressed": mix("#text", 9, "#surface-2"),
268
- "#surface-3-hover": mix("#text", 3, "#surface-3"),
269
- "#surface-3-pressed": mix("#text", 9, "#surface-3"),
270
- "#text": colorStates(theme.colors.text),
271
- "#text-soft": colorStates(theme.colors.textSoft),
272
- "#border": mix("#text", 18, "#surface"),
273
- "#border-strong": mix("#text", 34, "#surface"),
274
- "#accent-text": colorStates(theme.colors.accentText),
275
- "#accent-surface": colorStates(theme.colors.accentSurface),
276
- "#accent-surface-text": colorStates(theme.colors.accentSurfaceText),
277
- "#focus": colorStates(theme.colors.focus),
278
- "#shadow": colorStates(theme.colors.shadow),
279
- "#overlay": mix("#text", 58, "transparent")
280
- });
615
+ Object.assign(tokens, theme.colorTokens);
281
616
  return tokens;
282
617
  }
283
- function colorStates(colors) {
284
- return {
285
- "": colors.dark,
286
- [LIGHT]: colors.light,
287
- [HIGH_CONTRAST]: colors.darkContrast,
288
- [`(${LIGHT}) & (${HIGH_CONTRAST})`]: colors.lightContrast
289
- };
290
- }
291
- function mix(foreground, percentage, background) {
292
- return `color-mix(in oklab, ${foreground} ${percentage}%, ${background})`;
293
- }
294
618
  //#endregion
295
619
  //#region src/components/component-styles.ts
296
620
  const sharedConfiguration = globalThis;
@@ -334,8 +658,6 @@ function cookbook(options = {}) {
334
658
  if (docsTheme.diagnostics.some((diagnostic) => diagnostic.severity === "error")) throw new Error(docsTheme.diagnostics.map((diagnostic) => diagnostic.message).join("\n"));
335
659
  configureTastyTheme(options.config?.theme, docsTheme);
336
660
  configureComponentStyles(options.config?.theme?.styles);
337
- const searchClientPath = fileURLToPath(new URL("./client/search.js", import.meta.url));
338
- const appearanceClientPath = fileURLToPath(new URL("./client/appearance.js", import.meta.url));
339
661
  const components = {
340
662
  Header: fileURLToPath(new URL("./overrides/Header.astro", import.meta.url)),
341
663
  Sidebar: fileURLToPath(new URL("./overrides/Sidebar.astro", import.meta.url)),
@@ -348,18 +670,11 @@ function cookbook(options = {}) {
348
670
  islands: false,
349
671
  css: { mode: "extract" }
350
672
  });
351
- const starlightIntegration = starlight({
352
- title: options.config?.site?.title ?? "Documentation",
353
- ...options.config?.site?.description ? { description: options.config.site.description } : {},
354
- ...options.config?.search?.enabled === false ? { pagefind: false } : {},
355
- components,
356
- sidebar: starlightSidebar(navigation)
357
- });
358
- let inner = [tasty, starlightIntegration];
673
+ let inner = [tasty];
359
674
  let projectRoot = options.root;
360
675
  let graphConfig = options.config;
361
676
  let graph;
362
- let usingStarlight = false;
677
+ let usingContentCollection = false;
363
678
  async function loadGraph(refresh = false) {
364
679
  if (!graph || refresh) {
365
680
  graph = await createDocsGraph({
@@ -374,11 +689,7 @@ function cookbook(options = {}) {
374
689
  name: "cookbook",
375
690
  hooks: {
376
691
  "astro:config:setup": async (context) => {
377
- inner = [
378
- (await importNative(pathToFileURL(astroReactIntegration).href)).default(),
379
- tasty,
380
- starlightIntegration
381
- ];
692
+ const react = (await importNative(pathToFileURL(astroReactIntegration).href)).default();
382
693
  if (context.config.integrations.some((integration) => integration.name === "@astrojs/starlight")) throw new Error("Cookbook already includes Starlight. Remove the direct @astrojs/starlight integration before continuing.");
383
694
  projectRoot ??= fileURLToPath(context.config.root);
384
695
  const base = options.config?.build?.base ?? context.config.base;
@@ -389,10 +700,41 @@ function cookbook(options = {}) {
389
700
  base
390
701
  }
391
702
  };
392
- usingStarlight = hasContentConfig(context.config.srcDir);
703
+ usingContentCollection = hasContentConfig(context.config.srcDir);
704
+ const starlightIntegration = starlight({
705
+ title: options.config?.site?.title ?? "Documentation",
706
+ expressiveCode: false,
707
+ ...options.config?.site?.description ? { description: options.config.site.description } : {},
708
+ ...options.config?.search?.enabled === false ? { pagefind: false } : {},
709
+ ...!usingContentCollection ? { disable404Route: true } : {},
710
+ components,
711
+ sidebar: usingContentCollection ? starlightSidebar(navigation) : []
712
+ });
713
+ inner = [
714
+ react,
715
+ tasty,
716
+ starlightIntegration
717
+ ];
718
+ let markdownRuntime = {
719
+ image: context.config.image,
720
+ markdown: {
721
+ ...context.config.markdown,
722
+ syntaxHighlight: "shiki",
723
+ shikiConfig: {
724
+ ...context.config.markdown.shikiConfig,
725
+ theme: tastyCodeTheme
726
+ }
727
+ },
728
+ srcDir: context.config.srcDir
729
+ };
730
+ let markdownRenderer;
393
731
  context.updateConfig({
394
732
  base,
395
733
  output: "static",
734
+ markdown: {
735
+ syntaxHighlight: "shiki",
736
+ shikiConfig: { theme: tastyCodeTheme }
737
+ },
396
738
  vite: {
397
739
  ssr: { external: [
398
740
  "@tenphi/docs",
@@ -400,10 +742,30 @@ function cookbook(options = {}) {
400
742
  "react-dom",
401
743
  "react-dom/server"
402
744
  ] },
403
- plugins: [virtualDocsPlugin(async () => {
745
+ plugins: [stripStarlightStylesPlugin(starlightRoot), virtualDocsPlugin(async () => {
404
746
  const loaded = await loadGraph();
405
747
  return {
406
- entries: loaded.entries,
748
+ entries: usingContentCollection ? loaded.entries : await Promise.all(loaded.entries.map(async (entry) => {
749
+ const { image, markdown, srcDir } = markdownRuntime;
750
+ markdownRenderer ??= markdown.processor.createRenderer({
751
+ image,
752
+ syntaxHighlight: markdown.syntaxHighlight,
753
+ shikiConfig: markdown.shikiConfig,
754
+ gfm: markdown.gfm,
755
+ smartypants: markdown.smartypants
756
+ });
757
+ const rendered = await (await markdownRenderer).render(entry.transformedBody, {
758
+ frontmatter: entry.frontmatter,
759
+ fileURL: starlightContentUrl(entry.route, srcDir)
760
+ });
761
+ return {
762
+ ...entry,
763
+ rendered: {
764
+ html: rendered.code,
765
+ headings: rendered.metadata.headings
766
+ }
767
+ };
768
+ })),
407
769
  routes: loaded.routes,
408
770
  site: documentedSite(loaded),
409
771
  base: loaded.config.build.base,
@@ -435,35 +797,49 @@ function cookbook(options = {}) {
435
797
  }
436
798
  });
437
799
  await callInner(inner.slice(0, 2), "astro:config:setup", context);
438
- if (!usingStarlight) {
800
+ if (!usingContentCollection) {
439
801
  graph = await createDocsGraph({
440
802
  root: projectRoot,
441
803
  config: graphConfig
442
804
  });
443
805
  assertValidDocs(graph);
444
- if (graph.config.search.enabled) context.injectScript("page", `import ${JSON.stringify(searchClientPath)};`);
445
- context.injectScript("page", `import ${JSON.stringify(appearanceClientPath)};`);
446
806
  context.injectRoute({
447
807
  pattern: "[...route]",
448
808
  entrypoint: new URL("./routes/DocsPage.astro", import.meta.url),
449
809
  prerender: true
450
810
  });
451
- return;
452
811
  }
453
812
  const starlightWithPlugins = inner[2];
454
813
  if (starlightWithPlugins) {
455
814
  const selfIndex = context.config.integrations.findIndex((integration) => integration.name === "cookbook");
456
815
  context.config.integrations.splice(selfIndex + 1, 0, starlightWithPlugins);
457
816
  try {
458
- await callInner([starlightWithPlugins], "astro:config:setup", context);
817
+ await callInner([starlightWithPlugins], "astro:config:setup", usingContentCollection ? context : withoutStarlightDocsRoute(context));
459
818
  } finally {
460
819
  const placeholderIndex = context.config.integrations.indexOf(starlightWithPlugins);
461
820
  if (placeholderIndex >= 0) context.config.integrations.splice(placeholderIndex, 1);
462
821
  }
463
822
  }
823
+ context.config.integrations.push({
824
+ name: "cookbook-markdown-renderer",
825
+ hooks: { "astro:config:setup": ({ config }) => {
826
+ markdownRuntime = {
827
+ image: config.image,
828
+ markdown: {
829
+ ...config.markdown,
830
+ syntaxHighlight: "shiki",
831
+ shikiConfig: {
832
+ ...config.markdown.shikiConfig,
833
+ theme: tastyCodeTheme
834
+ }
835
+ },
836
+ srcDir: config.srcDir
837
+ };
838
+ } }
839
+ });
464
840
  },
465
841
  "astro:config:done": async (context) => {
466
- await callInner(usingStarlight ? inner : inner.slice(0, 2), "astro:config:done", context);
842
+ await callInner(inner, "astro:config:done", context);
467
843
  },
468
844
  "astro:server:setup": async ({ server }) => {
469
845
  let assets = docsAssetMap(await loadGraph());
@@ -505,32 +881,63 @@ function cookbook(options = {}) {
505
881
  },
506
882
  "astro:build:start": async (context) => {
507
883
  await loadGraph();
508
- await callInner(usingStarlight ? inner : inner.slice(0, 2), "astro:build:start", context);
884
+ await callInner(inner, "astro:build:start", context);
509
885
  },
510
886
  "astro:build:done": async (context) => {
511
- await callInner(usingStarlight ? inner : inner.slice(0, 2), "astro:build:done", context);
512
- if (!graph) return;
887
+ await callInner(inner, "astro:build:done", context);
513
888
  const output = fileURLToPath(context.dir);
889
+ for (const relativePath of await readdir(output, { recursive: true })) {
890
+ if (extname(relativePath) !== ".html") continue;
891
+ const path = join(output, relativePath);
892
+ const html = await readFile(path, "utf8");
893
+ const sanitized = html.replace(/\s*<link\b(?=[^>]*rel="stylesheet")(?=[^>]*href="data:text\/css,")[^>]*>/g, "").replace(/\s*<style>\s*<\/style>/g, "").replace(/\sstyle="--sl-icon-size:\s*([^;\"]+);?"/g, " width=\"$1\" height=\"$1\"").replace(/\sstyle="--depth:\s*([^;\"]+);?"/g, " data-depth=\"$1\"").replace(/(<kbd\b[^>]*)\sstyle="display:\s*none;?"([^>]*>)/g, "$1$2").replace(/(<dialog\b[^>]*)\sstyle="padding:\s*0;?"([^>]*>)/g, "$1$2");
894
+ if (sanitized !== html) await writeFile(path, sanitized);
895
+ }
896
+ const pagefindOutput = join(output, "pagefind");
897
+ if (existsSync(pagefindOutput)) {
898
+ for (const name of await readdir(pagefindOutput)) if (extname(name) === ".css") await unlink(join(pagefindOutput, name));
899
+ }
900
+ if (!graph) return;
514
901
  for (const asset of graph.assets) {
515
902
  if (!asset.sourcePath || !asset.publicPath) continue;
516
903
  const target = join(output, asset.publicPath.replace(/^\//, ""));
517
904
  await mkdir(dirname(target), { recursive: true });
518
905
  await cp(asset.sourcePath, target);
519
906
  }
520
- if (!usingStarlight && graph.config.search.enabled) {
521
- const { close, createIndex } = await import("pagefind");
522
- const created = await createIndex({ rootSelector: "[data-pagefind-body]" });
523
- if (!created.index || created.errors.length > 0) throw new Error(`Pagefind failed to start: ${created.errors.join("; ")}`);
524
- const indexed = await created.index.addDirectory({ path: output });
525
- if (indexed.errors.length > 0) throw new Error(`Pagefind failed to index the site: ${indexed.errors.join("; ")}`);
526
- const written = await created.index.writeFiles({ outputPath: join(output, "pagefind") });
527
- await close();
528
- if (written.errors.length > 0) throw new Error(`Pagefind failed to write the index: ${written.errors.join("; ")}`);
529
- }
530
907
  }
531
908
  }
532
909
  };
533
910
  }
911
+ function stripStarlightStylesPlugin(root) {
912
+ const normalizedRoot = root.replaceAll("\\", "/");
913
+ const emptyPrintId = "\0cookbook:empty-starlight-print";
914
+ return {
915
+ name: "cookbook-strip-starlight-css",
916
+ enforce: "pre",
917
+ resolveId(source, importer) {
918
+ if (importer?.replaceAll("\\", "/").startsWith(`${normalizedRoot}/`) && source.endsWith("/style/print.css?url&no-inline")) return emptyPrintId;
919
+ },
920
+ load(id) {
921
+ if (id === emptyPrintId) return "export default \"data:text/css,\";";
922
+ },
923
+ transform(code, id) {
924
+ const normalizedId = id.replaceAll("\\", "/");
925
+ if (!normalizedId.startsWith(`${normalizedRoot}/`)) return void 0;
926
+ const [pathname, query = ""] = normalizedId.split("?", 2);
927
+ const isStylesheet = pathname?.endsWith(".css");
928
+ const isAstroStyle = pathname?.endsWith(".astro") && query.includes("type=style");
929
+ if (!isStylesheet && !isAstroStyle) return void 0;
930
+ return {
931
+ code: "",
932
+ map: null
933
+ };
934
+ }
935
+ };
936
+ }
937
+ function starlightContentUrl(route, srcDir) {
938
+ const slug = route === "/" ? "index" : route.replace(/^\/+|\/+$/g, "");
939
+ return new URL(`content/docs/${slug}.md`, srcDir);
940
+ }
534
941
  function docsAssetMap(graph) {
535
942
  return new Map(graph.assets.flatMap((asset) => asset.publicPath && asset.sourcePath ? [[asset.publicPath, asset]] : []));
536
943
  }
@@ -618,6 +1025,14 @@ async function callInner(integrations, hook, context) {
618
1025
  if (typeof handler === "function") await handler(context);
619
1026
  }
620
1027
  }
1028
+ function withoutStarlightDocsRoute(context) {
1029
+ return new Proxy(context, { get(target, property, receiver) {
1030
+ if (property !== "injectRoute") return Reflect.get(target, property, receiver);
1031
+ return (route) => {
1032
+ if (route.pattern !== "[...slug]") context.injectRoute(route);
1033
+ };
1034
+ } });
1035
+ }
621
1036
  function virtualDocsPlugin(getContent, layout) {
622
1037
  const configId = "\0virtual:cookbook/config";
623
1038
  const layoutId = "\0virtual:cookbook/layout";