@openlaboratory/open-doc 0.1.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.
Files changed (61) hide show
  1. package/README.md +91 -0
  2. package/app/.astro/collections/docs.schema.json +24 -0
  3. package/app/.astro/content-assets.mjs +1 -0
  4. package/app/.astro/content-modules.mjs +4 -0
  5. package/app/.astro/content.d.ts +218 -0
  6. package/app/.astro/data-store.json +1 -0
  7. package/app/.astro/settings.json +5 -0
  8. package/app/.astro/types.d.ts +2 -0
  9. package/app/astro.config.mjs +43 -0
  10. package/app/node_modules/.astro/data-store.json +1 -0
  11. package/app/node_modules/.vite/deps/@astrojs_react_client__js.js +163 -0
  12. package/app/node_modules/.vite/deps/@astrojs_react_client__js.js.map +7 -0
  13. package/app/node_modules/.vite/deps/_metadata.json +67 -0
  14. package/app/node_modules/.vite/deps/astro___aria-query.js +6776 -0
  15. package/app/node_modules/.vite/deps/astro___aria-query.js.map +7 -0
  16. package/app/node_modules/.vite/deps/astro___axobject-query.js +3754 -0
  17. package/app/node_modules/.vite/deps/astro___axobject-query.js.map +7 -0
  18. package/app/node_modules/.vite/deps/astro___cssesc.js +99 -0
  19. package/app/node_modules/.vite/deps/astro___cssesc.js.map +7 -0
  20. package/app/node_modules/.vite/deps/chunk-55ZOATU5.js +305 -0
  21. package/app/node_modules/.vite/deps/chunk-55ZOATU5.js.map +7 -0
  22. package/app/node_modules/.vite/deps/chunk-5WRI5ZAA.js +30 -0
  23. package/app/node_modules/.vite/deps/chunk-5WRI5ZAA.js.map +7 -0
  24. package/app/node_modules/.vite/deps/chunk-FEZZJEG2.js +6935 -0
  25. package/app/node_modules/.vite/deps/chunk-FEZZJEG2.js.map +7 -0
  26. package/app/node_modules/.vite/deps/package.json +3 -0
  27. package/app/node_modules/.vite/deps/react-dom.js +6 -0
  28. package/app/node_modules/.vite/deps/react-dom.js.map +7 -0
  29. package/app/node_modules/.vite/deps/react.js +5 -0
  30. package/app/node_modules/.vite/deps/react.js.map +7 -0
  31. package/app/node_modules/.vite/deps/react_jsx-dev-runtime.js +39 -0
  32. package/app/node_modules/.vite/deps/react_jsx-dev-runtime.js.map +7 -0
  33. package/app/node_modules/.vite/deps/react_jsx-runtime.js +57 -0
  34. package/app/node_modules/.vite/deps/react_jsx-runtime.js.map +7 -0
  35. package/app/src/components/DocsMobileNav.tsx +124 -0
  36. package/app/src/components/DocsSearch.tsx +315 -0
  37. package/app/src/components/DocsSidebar.astro +46 -0
  38. package/app/src/components/DocsTableOfContents.tsx +92 -0
  39. package/app/src/components/Navbar.astro +39 -0
  40. package/app/src/components/SocialIcon.astro +54 -0
  41. package/app/src/components/ThemeToggle.tsx +62 -0
  42. package/app/src/content.config.ts +17 -0
  43. package/app/src/env.d.ts +7 -0
  44. package/app/src/integrations/open-doc-config.mjs +65 -0
  45. package/app/src/layouts/DocsLayout.astro +369 -0
  46. package/app/src/lib/config.ts +36 -0
  47. package/app/src/lib/navigation.ts +68 -0
  48. package/app/src/lib/withBase.ts +11 -0
  49. package/app/src/pages/404.astro +24 -0
  50. package/app/src/pages/[...slug].astro +34 -0
  51. package/app/src/pages/index.astro +107 -0
  52. package/app/src/styles/global.css +324 -0
  53. package/app/tailwind.config.mjs +53 -0
  54. package/app/tsconfig.json +11 -0
  55. package/bin/open-doc.js +2 -0
  56. package/dist/chunk-BRUM67K7.js +30 -0
  57. package/dist/cli.d.ts +2 -0
  58. package/dist/cli.js +268 -0
  59. package/dist/index.d.ts +116 -0
  60. package/dist/index.js +8 -0
  61. package/package.json +77 -0
@@ -0,0 +1,92 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+
3
+ interface Heading {
4
+ depth: number
5
+ slug: string
6
+ text: string
7
+ }
8
+
9
+ interface Props {
10
+ headings: Heading[]
11
+ }
12
+
13
+ /** Right-hand "On this page" list with scroll-spy via IntersectionObserver. */
14
+ export function DocsTableOfContents({ headings }: Props) {
15
+ const [activeId, setActiveId] = useState('')
16
+ const navRef = useRef<HTMLElement>(null)
17
+ const filtered = headings.filter((h) => h.depth === 2 || h.depth === 3)
18
+
19
+ useEffect(() => {
20
+ if (filtered.length === 0) return
21
+
22
+ const observer = new IntersectionObserver(
23
+ (entries) => {
24
+ for (const entry of entries) {
25
+ if (entry.isIntersecting) {
26
+ setActiveId(entry.target.id)
27
+ break
28
+ }
29
+ }
30
+ },
31
+ { rootMargin: '-80px 0px -70% 0px', threshold: 0 },
32
+ )
33
+
34
+ filtered.forEach(({ slug }) => {
35
+ const el = document.getElementById(slug)
36
+ if (el) observer.observe(el)
37
+ })
38
+
39
+ // The bottom rootMargin means the last heading may never "activate"; force it
40
+ // active once the page is scrolled to the bottom.
41
+ const onScroll = () => {
42
+ if (window.innerHeight + window.scrollY >= document.documentElement.scrollHeight - 2) {
43
+ setActiveId(filtered[filtered.length - 1].slug)
44
+ }
45
+ }
46
+ window.addEventListener('scroll', onScroll, { passive: true })
47
+
48
+ return () => {
49
+ observer.disconnect()
50
+ window.removeEventListener('scroll', onScroll)
51
+ }
52
+ }, [filtered])
53
+
54
+ // Keep the active item visible within the (scrollable) TOC.
55
+ useEffect(() => {
56
+ if (!activeId || !navRef.current) return
57
+ const link = navRef.current.querySelector(`a[href="#${CSS.escape(activeId)}"]`)
58
+ link?.scrollIntoView({ block: 'nearest' })
59
+ }, [activeId])
60
+
61
+ if (filtered.length === 0) return null
62
+
63
+ return (
64
+ <nav ref={navRef} aria-label="On this page">
65
+ <p className="mb-3 text-[11px] font-semibold uppercase tracking-widest text-foreground/40">
66
+ On this page
67
+ </p>
68
+ <ul className="flex flex-col gap-0.5">
69
+ {filtered.map((heading) => {
70
+ const isActive = activeId === heading.slug
71
+ return (
72
+ <li key={heading.slug}>
73
+ <a
74
+ href={`#${heading.slug}`}
75
+ aria-current={isActive ? 'location' : undefined}
76
+ className={[
77
+ 'block rounded py-1 text-sm leading-snug transition-colors',
78
+ heading.depth === 3 ? 'pl-3' : '',
79
+ isActive
80
+ ? 'font-medium text-foreground'
81
+ : 'text-foreground/50 hover:text-foreground/80',
82
+ ].join(' ')}
83
+ >
84
+ {heading.text}
85
+ </a>
86
+ </li>
87
+ )
88
+ })}
89
+ </ul>
90
+ </nav>
91
+ )
92
+ }
@@ -0,0 +1,39 @@
1
+ ---
2
+ import config from 'virtual:open-doc-config'
3
+ import { withBase } from '../lib/withBase'
4
+ import { ThemeToggle } from './ThemeToggle'
5
+ import SocialIcon from './SocialIcon.astro'
6
+
7
+ const { logo, title, social, themeToggle } = config
8
+ ---
9
+
10
+ <header
11
+ class="sticky top-0 z-40 h-[57px] border-b border-foreground/[0.08] bg-surface-sidebar/80 backdrop-blur supports-[backdrop-filter]:bg-surface-sidebar/70"
12
+ >
13
+ <div class="flex h-full items-center justify-between gap-4 px-4 sm:px-6">
14
+ <a
15
+ href={withBase(logo.href)}
16
+ class="flex items-center gap-2.5 text-foreground/90 transition-colors hover:text-foreground"
17
+ >
18
+ {logo.src && <img src={withBase(logo.src)} alt={title} class="h-6 w-auto" />}
19
+ {logo.text && <span class="text-sm font-semibold tracking-tight">{logo.text}</span>}
20
+ </a>
21
+
22
+ <div class="flex items-center gap-1">
23
+ {
24
+ social.map((item) => (
25
+ <a
26
+ href={item.href}
27
+ target="_blank"
28
+ rel="noopener noreferrer"
29
+ class="flex h-8 w-8 items-center justify-center rounded-md text-foreground/55 transition-colors hover:bg-foreground/[0.06] hover:text-foreground/90"
30
+ aria-label={item.label ?? item.icon ?? 'Link'}
31
+ >
32
+ <SocialIcon name={item.icon} />
33
+ </a>
34
+ ))
35
+ }
36
+ {themeToggle && <ThemeToggle client:load />}
37
+ </div>
38
+ </div>
39
+ </header>
@@ -0,0 +1,54 @@
1
+ ---
2
+ // Renders a brand glyph for a social link. Falls back to a generic link icon.
3
+ interface Props {
4
+ name?: string
5
+ class?: string
6
+ }
7
+ const { name, class: className = 'h-[18px] w-[18px]' } = Astro.props
8
+
9
+ // Brand glyphs (single-path, 24x24, fill=currentColor).
10
+ const brands: Record<string, string> = {
11
+ github:
12
+ 'M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61-.546-1.387-1.333-1.757-1.333-1.757-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0112 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222 0 1.606-.014 2.898-.014 3.293 0 .322.216.694.825.576C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12',
13
+ x: 'M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z',
14
+ twitter:
15
+ 'M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z',
16
+ discord:
17
+ 'M20.317 4.369a19.791 19.791 0 00-4.885-1.515.074.074 0 00-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 00-5.487 0 12.64 12.64 0 00-.617-1.25.077.077 0 00-.079-.037A19.736 19.736 0 003.677 4.37a.07.07 0 00-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 00.031.057 19.9 19.9 0 005.993 3.03.078.078 0 00.084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 00-.041-.106 13.107 13.107 0 01-1.872-.892.077.077 0 01-.008-.128c.126-.094.252-.192.371-.291a.074.074 0 01.077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 01.078.009c.12.099.245.198.372.292a.077.077 0 01-.006.127c-.598.35-1.22.645-1.873.892a.077.077 0 00-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 00.084.028 19.839 19.839 0 006.002-3.03.077.077 0 00.032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 00-.031-.03zM8.02 15.331c-1.183 0-2.157-1.086-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.332-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.086-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.332-.946 2.418-2.157 2.418z',
18
+ linkedin:
19
+ 'M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z',
20
+ youtube:
21
+ 'M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z',
22
+ mastodon:
23
+ 'M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.073 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 00.023-.043v-1.809a.052.052 0 00-.02-.041.053.053 0 00-.046-.01 20.282 20.282 0 01-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 01-.319-1.433.053.053 0 01.066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z',
24
+ slack:
25
+ 'M5.042 15.165a2.528 2.528 0 01-2.52 2.523A2.528 2.528 0 010 15.165a2.527 2.527 0 012.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 012.521-2.52 2.527 2.527 0 012.521 2.52v6.313A2.528 2.528 0 018.834 24a2.528 2.528 0 01-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 01-2.521-2.52A2.528 2.528 0 018.834 0a2.528 2.528 0 012.521 2.522v2.52H8.834zm0 1.271a2.528 2.528 0 012.521 2.521 2.528 2.528 0 01-2.521 2.521H2.522A2.528 2.528 0 010 8.834a2.528 2.528 0 012.522-2.521h6.312zm10.122 2.521a2.528 2.528 0 012.522-2.521A2.528 2.528 0 0124 8.834a2.528 2.528 0 01-2.522 2.521h-2.522V8.834zm-1.268 0a2.528 2.528 0 01-2.523 2.521 2.527 2.527 0 01-2.52-2.521V2.522A2.527 2.527 0 0115.165 0a2.528 2.528 0 012.523 2.522v6.312zm-2.523 10.122a2.528 2.528 0 012.523 2.522A2.528 2.528 0 0115.165 24a2.527 2.527 0 01-2.52-2.522v-2.522h2.52zm0-1.268a2.527 2.527 0 01-2.52-2.523 2.526 2.526 0 012.52-2.52h6.313A2.527 2.527 0 0124 15.165a2.528 2.528 0 01-2.522 2.523h-6.313z',
26
+ bluesky:
27
+ 'M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 01-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.789.624-6.479 0-.688-.139-1.86-.902-2.203-.659-.299-1.664-.621-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8z',
28
+ }
29
+
30
+ const path = (name && brands[name]) || null
31
+ ---
32
+
33
+ {
34
+ path ? (
35
+ <svg class={className} viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
36
+ <path d={path} />
37
+ </svg>
38
+ ) : (
39
+ <svg
40
+ class={className}
41
+ viewBox="0 0 24 24"
42
+ fill="none"
43
+ stroke="currentColor"
44
+ stroke-width="2"
45
+ aria-hidden="true"
46
+ >
47
+ <path
48
+ stroke-linecap="round"
49
+ stroke-linejoin="round"
50
+ d="M13.19 8.688a4.5 4.5 0 011.242 7.244l-4.5 4.5a4.5 4.5 0 01-6.364-6.364l1.757-1.757m13.35-.622l1.757-1.757a4.5 4.5 0 00-6.364-6.364l-4.5 4.5a4.5 4.5 0 001.242 7.244"
51
+ />
52
+ </svg>
53
+ )
54
+ }
@@ -0,0 +1,62 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ const STORAGE_KEY = 'open-doc-theme'
4
+
5
+ /** Header button that toggles the `.dark` class and persists the choice. */
6
+ export function ThemeToggle() {
7
+ const [isDark, setIsDark] = useState(true)
8
+
9
+ useEffect(() => {
10
+ setIsDark(document.documentElement.classList.contains('dark'))
11
+ }, [])
12
+
13
+ const toggle = () => {
14
+ const next = !isDark
15
+ setIsDark(next)
16
+ document.documentElement.classList.toggle('dark', next)
17
+ try {
18
+ localStorage.setItem(STORAGE_KEY, next ? 'dark' : 'light')
19
+ } catch {
20
+ /* storage unavailable — ignore */
21
+ }
22
+ }
23
+
24
+ return (
25
+ <button
26
+ type="button"
27
+ onClick={toggle}
28
+ className="flex h-8 w-8 items-center justify-center rounded-md text-foreground/55 transition-colors hover:bg-foreground/[0.06] hover:text-foreground/90"
29
+ aria-label={isDark ? 'Switch to light theme' : 'Switch to dark theme'}
30
+ >
31
+ {isDark ? (
32
+ <svg
33
+ className="h-[18px] w-[18px]"
34
+ fill="none"
35
+ viewBox="0 0 24 24"
36
+ stroke="currentColor"
37
+ strokeWidth={2}
38
+ >
39
+ <path
40
+ strokeLinecap="round"
41
+ strokeLinejoin="round"
42
+ d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"
43
+ />
44
+ </svg>
45
+ ) : (
46
+ <svg
47
+ className="h-[18px] w-[18px]"
48
+ fill="none"
49
+ viewBox="0 0 24 24"
50
+ stroke="currentColor"
51
+ strokeWidth={2}
52
+ >
53
+ <path
54
+ strokeLinecap="round"
55
+ strokeLinejoin="round"
56
+ d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
57
+ />
58
+ </svg>
59
+ )}
60
+ </button>
61
+ )
62
+ }
@@ -0,0 +1,17 @@
1
+ import { defineCollection, z } from 'astro:content'
2
+ import { glob } from 'astro/loaders'
3
+
4
+ // The CLI sets OPEN_DOC_CONTENT_DIR to the consumer's content directory.
5
+ const base = process.env.OPEN_DOC_CONTENT_DIR ?? './content'
6
+
7
+ const docs = defineCollection({
8
+ // Underscore-prefixed files/folders (e.g. _components) are ignored as pages,
9
+ // so they can hold partials & components imported by your MDX.
10
+ loader: glob({ pattern: ['**/*.{md,mdx}', '!**/_*', '!**/_*/**'], base }),
11
+ schema: z.object({
12
+ title: z.string(),
13
+ description: z.string().optional(),
14
+ }),
15
+ })
16
+
17
+ export const collections = { docs }
@@ -0,0 +1,7 @@
1
+ /// <reference path="../.astro/types.d.ts" />
2
+ /// <reference types="astro/client" />
3
+
4
+ declare module 'virtual:open-doc-config' {
5
+ const config: import('./lib/config').ResolvedConfig
6
+ export default config
7
+ }
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Internal Astro integration that wires the consumer's project into the bundled
3
+ * app:
4
+ * • exposes the resolved config as the `virtual:open-doc-config` module
5
+ * • aliases `@docs` to the consumer's content directory
6
+ * • allows Vite to read files from the (external) content + public dirs
7
+ *
8
+ * The resolved config is passed from the CLI via OPEN_DOC_CONFIG_JSON.
9
+ *
10
+ * @param {{ contentDir?: string, publicDir?: string }} options
11
+ * @returns {import('astro').AstroIntegration}
12
+ */
13
+ export function openDocConfig({ contentDir, publicDir } = {}) {
14
+ const configJson = process.env.OPEN_DOC_CONFIG_JSON || '{}'
15
+ const allow = [contentDir, publicDir].filter(Boolean)
16
+
17
+ return {
18
+ name: 'open-doc:config',
19
+ hooks: {
20
+ 'astro:config:setup': ({ command, updateConfig }) => {
21
+ // For `build`, bundle open-doc's runtime deps into the SSR output so the
22
+ // build is self-contained under every package manager (their files live
23
+ // in open-doc's node_modules, not the consumer's). In `dev` they must
24
+ // stay external — bundling CommonJS React breaks the dev SSR runtime.
25
+ const noExternal =
26
+ command === 'build' ? ['fuse.js', 'react', 'react-dom', '@astrojs/react'] : []
27
+
28
+ updateConfig({
29
+ vite: {
30
+ plugins: [virtualConfigPlugin(configJson)],
31
+ resolve: {
32
+ alias: contentDir ? { '@docs': contentDir } : {},
33
+ // One React instance, even if a consumer installs their own for MDX.
34
+ dedupe: ['react', 'react-dom'],
35
+ },
36
+ ssr: { noExternal },
37
+ // Merged additively with Vite's defaults by Astro's config merge.
38
+ server: { fs: { allow } },
39
+ },
40
+ })
41
+ },
42
+ },
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Emits the resolved config object as a virtual ES module so any `.astro` /
48
+ * `.tsx` component can `import config from 'virtual:open-doc-config'`.
49
+ *
50
+ * @param {string} json
51
+ * @returns {import('vite').Plugin}
52
+ */
53
+ function virtualConfigPlugin(json) {
54
+ const id = 'virtual:open-doc-config'
55
+ const resolvedId = '\0' + id
56
+ return {
57
+ name: 'open-doc:virtual-config',
58
+ resolveId(source) {
59
+ if (source === id) return resolvedId
60
+ },
61
+ load(thisId) {
62
+ if (thisId === resolvedId) return `export default ${json}`
63
+ },
64
+ }
65
+ }