@hgdc/docs 0.0.0-stage → 0.1.1

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 (173) hide show
  1. package/bin/hgdc-docs.mjs +48 -0
  2. package/docs.schema.json +294 -0
  3. package/package.json +51 -4
  4. package/src/build/auto-import.ts +74 -0
  5. package/src/build/markdown.ts +114 -0
  6. package/src/build/outputs.ts +91 -0
  7. package/src/build/public-files.ts +35 -0
  8. package/src/components/Accordion.astro +12 -0
  9. package/src/components/AccordionGroup.astro +4 -0
  10. package/src/components/Card.astro +15 -0
  11. package/src/components/CardGroup.astro +8 -0
  12. package/src/components/Check.astro +10 -0
  13. package/src/components/CodeGroup.astro +4 -0
  14. package/src/components/Columns.astro +8 -0
  15. package/src/components/Danger.astro +10 -0
  16. package/src/components/Frame.astro +11 -0
  17. package/src/components/Info.astro +10 -0
  18. package/src/components/Note.astro +10 -0
  19. package/src/components/Step.astro +11 -0
  20. package/src/components/Steps.astro +4 -0
  21. package/src/components/Tab.astro +9 -0
  22. package/src/components/Tabs.astro +9 -0
  23. package/src/components/Tip.astro +10 -0
  24. package/src/components/Warning.astro +10 -0
  25. package/src/core/answer-markdown.ts +109 -0
  26. package/src/core/assistant-api.ts +65 -0
  27. package/src/core/chunk.ts +134 -0
  28. package/src/core/config.ts +153 -0
  29. package/src/core/corpus.ts +42 -0
  30. package/src/core/json-schema.ts +12 -0
  31. package/src/core/llms.ts +95 -0
  32. package/src/core/navigation.ts +88 -0
  33. package/src/core/theme.ts +111 -0
  34. package/src/integration.ts +229 -0
  35. package/src/locals.d.ts +6 -0
  36. package/src/overrides/Assistant.astro +72 -0
  37. package/src/overrides/Head.astro +12 -0
  38. package/src/overrides/PageFrame.astro +15 -0
  39. package/src/overrides/PageTitle.astro +94 -0
  40. package/src/overrides/Sidebar.astro +60 -0
  41. package/src/overrides/route-data.ts +33 -0
  42. package/src/styles/theme.css +773 -0
  43. package/src/virtual.d.ts +6 -0
  44. package/src/widget/client.ts +252 -0
  45. package/template/.astro/collections/docs.schema.json +651 -0
  46. package/template/.astro/content-assets.mjs +1 -0
  47. package/template/.astro/content-modules.mjs +18 -0
  48. package/template/.astro/content.d.ts +179 -0
  49. package/template/.astro/types.d.ts +2 -0
  50. package/template/.build/.well-known/llms.txt +38 -0
  51. package/template/.build/404.html +42 -0
  52. package/template/.build/_astro/Assistant.astro_astro_type_script_index_0_lang.DWgmlr85.js +4 -0
  53. package/template/.build/_astro/MobileTableOfContents.astro_astro_type_script_index_0_lang.C3fM4Kra.js +1 -0
  54. package/template/.build/_astro/Search.astro_astro_type_script_index_0_lang.g9hoxVJg.js +1 -0
  55. package/template/.build/_astro/TableOfContents.astro_astro_type_script_index_0_lang.BpJaGz7G.js +1 -0
  56. package/template/.build/_astro/common.8GHCkW7i.css +3 -0
  57. package/template/.build/_astro/ec.0vx5m.js +3 -0
  58. package/template/.build/_astro/ec.8nqtp.css +1 -0
  59. package/template/.build/_astro/geist-cyrillic-ext-wght-normal.DjL33-gN.woff2 +0 -0
  60. package/template/.build/_astro/geist-cyrillic-wght-normal.BEAKL7Jp.woff2 +0 -0
  61. package/template/.build/_astro/geist-latin-ext-wght-normal.DC-KSUi6.woff2 +0 -0
  62. package/template/.build/_astro/geist-latin-wght-normal.BgDaEnEv.woff2 +0 -0
  63. package/template/.build/_astro/geist-vietnamese-wght-normal.6IgcOCM7.woff2 +0 -0
  64. package/template/.build/_astro/inter-cyrillic-ext-wght-normal.BOeWTOD4.woff2 +0 -0
  65. package/template/.build/_astro/inter-cyrillic-wght-normal.DqGufNeO.woff2 +0 -0
  66. package/template/.build/_astro/inter-greek-ext-wght-normal.DlzME5K_.woff2 +0 -0
  67. package/template/.build/_astro/inter-greek-wght-normal.CkhJZR-_.woff2 +0 -0
  68. package/template/.build/_astro/inter-latin-ext-wght-normal.DO1Apj_S.woff2 +0 -0
  69. package/template/.build/_astro/inter-latin-wght-normal.Dx4kXJAl.woff2 +0 -0
  70. package/template/.build/_astro/inter-vietnamese-wght-normal.CBcvBZtf.woff2 +0 -0
  71. package/template/.build/_astro/logo-clair.BqNh6JNi.svg +1 -0
  72. package/template/.build/_astro/logo-sombre.BkgsFXxh.svg +1 -0
  73. package/template/.build/_astro/page.cNngjUgL.js +1 -0
  74. package/template/.build/_astro/print.ehPL0gv-.css +1 -0
  75. package/template/.build/_astro/starlight-toc.D4UDIxQL.js +1 -0
  76. package/template/.build/_astro/ui-core.Clh5jUUO.js +2 -0
  77. package/template/.build/_hgdc/corpus.json +1 -0
  78. package/template/.build/demarrer/premiere-doc/index.html +84 -0
  79. package/template/.build/demarrer/premiere-doc.md +65 -0
  80. package/template/.build/explications/donnees/index.html +114 -0
  81. package/template/.build/explications/donnees.md +28 -0
  82. package/template/.build/explications/fonctionnement/index.html +92 -0
  83. package/template/.build/explications/fonctionnement.md +32 -0
  84. package/template/.build/guides/agents/index.html +113 -0
  85. package/template/.build/guides/agents.md +40 -0
  86. package/template/.build/guides/assistant/index.html +117 -0
  87. package/template/.build/guides/assistant.md +42 -0
  88. package/template/.build/guides/composants/index.html +97 -0
  89. package/template/.build/guides/composants.md +103 -0
  90. package/template/.build/guides/ecrire/index.html +115 -0
  91. package/template/.build/guides/ecrire.md +63 -0
  92. package/template/.build/guides/navigation/index.html +89 -0
  93. package/template/.build/guides/navigation.md +63 -0
  94. package/template/.build/guides/publier/index.html +86 -0
  95. package/template/.build/guides/publier.md +65 -0
  96. package/template/.build/guides/theme/index.html +86 -0
  97. package/template/.build/guides/theme.md +33 -0
  98. package/template/.build/images/favicon.svg +1 -0
  99. package/template/.build/images/logo-clair.svg +1 -0
  100. package/template/.build/images/logo-sombre.svg +1 -0
  101. package/template/.build/index.html +87 -0
  102. package/template/.build/index.md +35 -0
  103. package/template/.build/llms-full.txt +830 -0
  104. package/template/.build/llms.txt +38 -0
  105. package/template/.build/pagefind/fragment/fr-ch_148313b.pf_fragment +0 -0
  106. package/template/.build/pagefind/fragment/fr-ch_424fd16.pf_fragment +0 -0
  107. package/template/.build/pagefind/fragment/fr-ch_4f8575c.pf_fragment +0 -0
  108. package/template/.build/pagefind/fragment/fr-ch_5a4d77d.pf_fragment +0 -0
  109. package/template/.build/pagefind/fragment/fr-ch_6c36edc.pf_fragment +0 -0
  110. package/template/.build/pagefind/fragment/fr-ch_73d7887.pf_fragment +0 -0
  111. package/template/.build/pagefind/fragment/fr-ch_8234ed3.pf_fragment +0 -0
  112. package/template/.build/pagefind/fragment/fr-ch_8f8a19e.pf_fragment +0 -0
  113. package/template/.build/pagefind/fragment/fr-ch_9f1b981.pf_fragment +0 -0
  114. package/template/.build/pagefind/fragment/fr-ch_a19039d.pf_fragment +0 -0
  115. package/template/.build/pagefind/fragment/fr-ch_d72cc1e.pf_fragment +0 -0
  116. package/template/.build/pagefind/fragment/fr-ch_d732215.pf_fragment +0 -0
  117. package/template/.build/pagefind/fragment/fr-ch_e2c46e3.pf_fragment +0 -0
  118. package/template/.build/pagefind/fragment/fr-ch_e78d58b.pf_fragment +0 -0
  119. package/template/.build/pagefind/fragment/fr-ch_e92f972.pf_fragment +0 -0
  120. package/template/.build/pagefind/index/fr-ch_f94c95d.pf_index +0 -0
  121. package/template/.build/pagefind/pagefind-component-ui.css +1509 -0
  122. package/template/.build/pagefind/pagefind-component-ui.js +56 -0
  123. package/template/.build/pagefind/pagefind-entry.json +1 -0
  124. package/template/.build/pagefind/pagefind-highlight.js +1070 -0
  125. package/template/.build/pagefind/pagefind-modular-ui.css +214 -0
  126. package/template/.build/pagefind/pagefind-modular-ui.js +8 -0
  127. package/template/.build/pagefind/pagefind-ui.css +1 -0
  128. package/template/.build/pagefind/pagefind-ui.js +2 -0
  129. package/template/.build/pagefind/pagefind-worker.js +6 -0
  130. package/template/.build/pagefind/pagefind.fr-ch_fb78ab42961f4.pf_meta +0 -0
  131. package/template/.build/pagefind/pagefind.js +6 -0
  132. package/template/.build/pagefind/wasm.fr-ch.pagefind +0 -0
  133. package/template/.build/pagefind/wasm.unknown.pagefind +0 -0
  134. package/template/.build/reference/api-assistant/index.html +161 -0
  135. package/template/.build/reference/api-assistant.md +60 -0
  136. package/template/.build/reference/cli/index.html +153 -0
  137. package/template/.build/reference/cli.md +31 -0
  138. package/template/.build/reference/docs-json/index.html +192 -0
  139. package/template/.build/reference/docs-json.md +43 -0
  140. package/template/.build/reference/mcp/index.html +151 -0
  141. package/template/.build/reference/mcp.md +49 -0
  142. package/template/astro.config.ts +6 -0
  143. package/template/node_modules/.vite/deps/_metadata.json +74 -0
  144. package/template/node_modules/.vite/deps/astro-B3JHJyiv.js +434 -0
  145. package/template/node_modules/.vite/deps/astro-B3JHJyiv.js.map +1 -0
  146. package/template/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  147. package/template/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  148. package/template/node_modules/.vite/deps/astro_n_axobject-query.js +2705 -0
  149. package/template/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  150. package/template/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  151. package/template/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  152. package/template/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  153. package/template/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  154. package/template/node_modules/.vite/deps/audit-Do8QH17W.js +1543 -0
  155. package/template/node_modules/.vite/deps/audit-Do8QH17W.js.map +1 -0
  156. package/template/node_modules/.vite/deps/highlight-BLtvqtzO.js +48 -0
  157. package/template/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +1 -0
  158. package/template/node_modules/.vite/deps/icons-C92i61-2.js +42 -0
  159. package/template/node_modules/.vite/deps/icons-C92i61-2.js.map +1 -0
  160. package/template/node_modules/.vite/deps/package.json +3 -0
  161. package/template/node_modules/.vite/deps/rolldown-runtime-C3I60J0A.js +4 -0
  162. package/template/node_modules/.vite/deps/settings-BYmTKrLi.js +189 -0
  163. package/template/node_modules/.vite/deps/settings-BYmTKrLi.js.map +1 -0
  164. package/template/node_modules/.vite/deps/toolbar-CEKhukuK.js +461 -0
  165. package/template/node_modules/.vite/deps/toolbar-CEKhukuK.js.map +1 -0
  166. package/template/node_modules/.vite/deps/ui-library-BO4--ANp.js +1015 -0
  167. package/template/node_modules/.vite/deps/ui-library-BO4--ANp.js.map +1 -0
  168. package/template/node_modules/.vite/deps/window-vo3A6TMn.js +32 -0
  169. package/template/node_modules/.vite/deps/window-vo3A6TMn.js.map +1 -0
  170. package/template/node_modules/.vite/deps/xray-Lf9x6g8P.js +112 -0
  171. package/template/node_modules/.vite/deps/xray-Lf9x6g8P.js.map +1 -0
  172. package/template/src/content.config.ts +21 -0
  173. package/README.md +0 -3
@@ -0,0 +1,229 @@
1
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync } from "node:fs"
2
+ import { dirname, join, relative, resolve } from "node:path"
3
+ import { fileURLToPath } from "node:url"
4
+
5
+ import { unified } from "@astrojs/markdown-remark"
6
+ import starlight from "@astrojs/starlight"
7
+ import type { AstroIntegration, AstroUserConfig } from "astro"
8
+
9
+ import { remarkAutoImport } from "./build/auto-import.ts"
10
+ import { publicFiles } from "./build/public-files.ts"
11
+ import { writeMachineReadable } from "./build/outputs.ts"
12
+ import { parseDocsConfig, withHostProject, type DocsConfig } from "./core/config.ts"
13
+ import { missingPages, normalizeNavigation, pageToSlug, type NavItem, type NavTree } from "./core/navigation.ts"
14
+ import { themeCss, themeFor } from "./core/theme.ts"
15
+
16
+ /** Où lire la doc du client et où écrire le site : posé par la CLI `hgdc-docs`. */
17
+ export type SiteOptions = {
18
+ contentDir: string
19
+ outDir: string
20
+ /** Adresse publique du site, pour les liens absolus de `llms.txt` et le sitemap. */
21
+ site: string | null
22
+ /** API de l'assistant (console HGDC Docs). */
23
+ apiEndpoint: string
24
+ /** Projet de la console annoncé par HGDC Host (`HGDC_DOCS_PROJECT`), si docs.json n'en a pas. */
25
+ project: string | null
26
+ }
27
+
28
+ export const DEFAULT_API = "https://docs.hgdc.ch"
29
+ const here = dirname(fileURLToPath(import.meta.url))
30
+ const file = (path: string) => resolve(here, path)
31
+
32
+ export function readSiteOptions(env: NodeJS.ProcessEnv = process.env): SiteOptions {
33
+ const contentDir = resolve(env.HGDC_DOCS_DIR ?? process.cwd())
34
+ const vercelUrl = env.VERCEL_PROJECT_PRODUCTION_URL ? `https://${env.VERCEL_PROJECT_PRODUCTION_URL}` : null
35
+ return {
36
+ contentDir,
37
+ outDir: resolve(env.HGDC_DOCS_OUT ?? join(contentDir, "dist")),
38
+ site: env.HGDC_DOCS_SITE ?? vercelUrl,
39
+ apiEndpoint: env.HGDC_DOCS_API ?? DEFAULT_API,
40
+ project: env.HGDC_DOCS_PROJECT ?? null,
41
+ }
42
+ }
43
+
44
+ export class DocsConfigError extends Error {}
45
+
46
+ export function loadDocsConfig(contentDir: string, hostProject: string | null = null): DocsConfig {
47
+ const path = join(contentDir, "docs.json")
48
+ if (!existsSync(path)) throw new DocsConfigError(`Aucun docs.json dans ${contentDir}. Crée-le à la racine de la doc.`)
49
+ const parsed = parseDocsConfig(readFileSync(path, "utf8"))
50
+ const result = parsed.ok ? withHostProject(parsed.config, hostProject ?? undefined) : parsed
51
+ if (!result.ok) throw new DocsConfigError(`docs.json invalide :\n- ${result.errors.join("\n- ")}`)
52
+ return result.config
53
+ }
54
+
55
+ /** Ce que les composants du modèle lisent par `virtual:hgdc-docs/config`. */
56
+ export type VirtualConfig = {
57
+ config: DocsConfig
58
+ tree: NavTree
59
+ apiEndpoint: string
60
+ site: string | null
61
+ }
62
+
63
+ const IGNORED_DIRS = new Set(["node_modules", "dist", ".hgdc-docs", ".git", ".github", ".vercel"])
64
+
65
+ function collectPublic(contentDir: string, declared: ReadonlyArray<string>): string {
66
+ const target = join(contentDir, ".hgdc-docs", "public")
67
+ rmSync(target, { recursive: true, force: true })
68
+ mkdirSync(target, { recursive: true })
69
+ for (const file of publicFiles(contentDir, declared)) cpSync(file.from, join(target, file.to))
70
+ return target
71
+ }
72
+
73
+ /** Pages du dépôt (`guides/publier`) et leur `sidebarTitle` éventuel (champ Mintlify). */
74
+ function scanPages(contentDir: string): Map<string, string | null> {
75
+ const pages = new Map<string, string | null>()
76
+ const walk = (dir: string) => {
77
+ for (const name of readdirSync(dir)) {
78
+ if (name.startsWith(".") || IGNORED_DIRS.has(name)) continue
79
+ const path = join(dir, name)
80
+ if (statSync(path).isDirectory()) walk(path)
81
+ else if (/\.mdx?$/.test(name) && !name.startsWith("_")) {
82
+ const page = relative(contentDir, path)
83
+ .replaceAll("\\", "/")
84
+ .replace(/\.mdx?$/, "")
85
+ const frontmatter = /^---\r?\n([\s\S]*?)\r?\n---/.exec(readFileSync(path, "utf8"))?.[1] ?? ""
86
+ const label = /^sidebarTitle:\s*["']?(.+?)["']?\s*$/m.exec(frontmatter)?.[1] ?? null
87
+ pages.set(pageToSlug(page), label)
88
+ }
89
+ }
90
+ }
91
+ walk(contentDir)
92
+ return pages
93
+ }
94
+
95
+ type SidebarItem = { slug: string; label?: string } | { label: string; collapsed: boolean; items: Array<SidebarItem> }
96
+
97
+ function sidebarItems(items: Array<NavItem>, labels: ReadonlyMap<string, string | null>): Array<SidebarItem> {
98
+ return items.map((item) => {
99
+ if (item.type === "group")
100
+ return { label: item.label, collapsed: !item.expanded, items: sidebarItems(item.items, labels) }
101
+ const slug = pageToSlug(item.page)
102
+ const label = labels.get(slug)
103
+ return label ? { slug, label } : { slug }
104
+ })
105
+ }
106
+
107
+ /**
108
+ * Une entrée de premier niveau par couple (version, onglet), dans l'ordre de l'arbre ;
109
+ * `route-data.ts` ne garde ensuite que celle de la page affichée.
110
+ */
111
+ export function starlightSidebar(
112
+ tree: NavTree,
113
+ labels: ReadonlyMap<string, string | null> = new Map(),
114
+ ): Array<SidebarItem> {
115
+ const sections = tree.versions.flatMap((version) => version.tabs)
116
+ if (sections.length === 1) return sidebarItems(sections[0]!.items, labels)
117
+ return tree.versions.flatMap((version) =>
118
+ version.tabs.map((tab) => ({
119
+ label: [version.label, tab.label].filter(Boolean).join(" · ") || "Documentation",
120
+ collapsed: false,
121
+ items: sidebarItems(tab.items, labels),
122
+ })),
123
+ )
124
+ }
125
+
126
+ const SOCIAL_ICONS = new Set([
127
+ "github",
128
+ "gitlab",
129
+ "linkedin",
130
+ "x.com",
131
+ "mastodon",
132
+ "discord",
133
+ "youtube",
134
+ "instagram",
135
+ "bluesky",
136
+ ])
137
+
138
+ function virtualConfigPlugin(data: VirtualConfig) {
139
+ const id = "virtual:hgdc-docs/config"
140
+ return {
141
+ name: "hgdc-docs-config",
142
+ resolveId: (source: string) => (source === id ? `\0${id}` : undefined),
143
+ load: (source: string) => (source === `\0${id}` ? `export default ${JSON.stringify(data)}` : undefined),
144
+ }
145
+ }
146
+
147
+ function machineReadableOutputs(data: VirtualConfig, options: SiteOptions): AstroIntegration {
148
+ return {
149
+ name: "hgdc-docs-outputs",
150
+ hooks: {
151
+ "astro:build:done": async ({ dir, pages, logger }) => {
152
+ const written = await writeMachineReadable({
153
+ outDir: fileURLToPath(dir),
154
+ pathnames: pages.map((page) => page.pathname),
155
+ config: data.config,
156
+ tree: data.tree,
157
+ site: options.site,
158
+ })
159
+ logger.info(`llms.txt, llms-full.txt, ${written.pages} pages Markdown et le corpus de l'assistant écrits.`)
160
+ // Le build tourne dans le paquet, où ses dépendances se résolvent quel que soit le
161
+ // gestionnaire du client (npm, pnpm, bun isolé), puis le site seul est copié chez lui.
162
+ rmSync(options.outDir, { recursive: true, force: true })
163
+ cpSync(fileURLToPath(dir), options.outDir, { recursive: true, filter: (path) => !path.includes(".prerender") })
164
+ logger.info(`Site écrit dans ${options.outDir}`)
165
+ },
166
+ },
167
+ }
168
+ }
169
+
170
+ export function createAstroConfig(options: SiteOptions = readSiteOptions()): AstroUserConfig {
171
+ const config = loadDocsConfig(options.contentDir, options.project)
172
+ const tree = normalizeNavigation(config)
173
+ const pages = scanPages(options.contentDir)
174
+ const missing = missingPages(tree, new Set(pages.keys()))
175
+ if (missing.length)
176
+ throw new DocsConfigError(
177
+ `docs.json cite des pages absentes du dossier :\n- ${missing.map((page) => `${page}.mdx`).join("\n- ")}`,
178
+ )
179
+ const data: VirtualConfig = { config, tree, apiEndpoint: options.apiEndpoint, site: options.site }
180
+ const theme = themeFor(config.colors?.primary, config.colors?.dark)
181
+ const logo = typeof config.logo === "string" ? { light: config.logo } : config.logo
182
+ const asset = (path: string) => join(options.contentDir, path.replace(/^\//, ""))
183
+ const social = Object.entries(config.footer?.socials ?? {})
184
+ .filter(([icon]) => SOCIAL_ICONS.has(icon))
185
+ .map(([icon, href]) => ({ icon: (icon === "x.com" ? "x.com" : icon) as "github", label: icon, href }))
186
+
187
+ return {
188
+ root: file("../template"),
189
+ srcDir: file("../template/src"),
190
+ publicDir: collectPublic(
191
+ options.contentDir,
192
+ [logo?.light, logo?.dark, config.favicon].filter((p): p is string => Boolean(p)),
193
+ ),
194
+ outDir: file("../template/.build"),
195
+ cacheDir: join(options.contentDir, ".hgdc-docs", "cache"),
196
+ site: options.site ?? undefined,
197
+ trailingSlash: "always",
198
+ markdown: { processor: unified({ remarkPlugins: [remarkAutoImport] }) },
199
+ vite: {
200
+ plugins: [virtualConfigPlugin(data)],
201
+ server: { fs: { allow: [options.contentDir, file("..")] } },
202
+ },
203
+ integrations: [
204
+ starlight({
205
+ title: config.name,
206
+ description: config.description,
207
+ locales: { root: { label: "Français", lang: config.language === "fr" ? "fr-CH" : config.language } },
208
+ logo: logo
209
+ ? { light: asset(logo.light), dark: asset(logo.dark ?? logo.light), replacesTitle: false }
210
+ : undefined,
211
+ favicon: config.favicon ?? "/favicon.svg",
212
+ social,
213
+ sidebar: starlightSidebar(tree, pages),
214
+ customCss: [file("./styles/theme.css")],
215
+ head: [{ tag: "style", content: themeCss(theme) }],
216
+ routeMiddleware: file("./overrides/route-data.ts"),
217
+ components: {
218
+ Head: file("./overrides/Head.astro"),
219
+ Sidebar: file("./overrides/Sidebar.astro"),
220
+ PageTitle: file("./overrides/PageTitle.astro"),
221
+ PageFrame: file("./overrides/PageFrame.astro"),
222
+ },
223
+ expressiveCode: { themes: ["github-dark-default", "github-light-default"] },
224
+ pagination: true,
225
+ }),
226
+ machineReadableOutputs(data, options),
227
+ ],
228
+ }
229
+ }
@@ -0,0 +1,6 @@
1
+ declare namespace App {
2
+ interface Locals {
3
+ /** Index de la section (version, onglet) de la page, posé par `route-data.ts`. */
4
+ hgdcSection?: number
5
+ }
6
+ }
@@ -0,0 +1,72 @@
1
+ ---
2
+ import data from "virtual:hgdc-docs/config"
3
+
4
+ // Widget de l'assistant : panneau latéral non modal (la page reste lisible), plein écran
5
+ // sur téléphone. Logique dans `../widget/client.ts`.
6
+ const endpoint = new URL("/api/v1/ask", data.config.assistant?.endpoint ?? data.apiEndpoint).toString()
7
+ ---
8
+
9
+ <hgdc-assistant data-endpoint={endpoint} data-project={data.config.project} data-name={data.config.name}>
10
+ <button type="button" class="hgdc-launcher" data-open aria-haspopup="dialog" aria-expanded="false">
11
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="18" height="18">
12
+ <path d="M12 3l1.9 4.6L18.5 9.5l-4.6 1.9L12 16l-1.9-4.6L5.5 9.5l4.6-1.9z" fill="currentColor"></path>
13
+ <path d="M18.5 15l.8 1.9 1.9.8-1.9.8-.8 1.9-.8-1.9-1.9-.8 1.9-.8z" fill="currentColor"></path>
14
+ </svg>
15
+ <span>Demander à l'IA</span>
16
+ <kbd class="hgdc-kbd" data-shortcut aria-hidden="true">Ctrl I</kbd>
17
+ </button>
18
+
19
+ <section class="hgdc-panel" role="dialog" aria-modal="false" aria-labelledby="hgdc-assistant-title" hidden>
20
+ <header class="hgdc-panel__header">
21
+ <div>
22
+ <h2 id="hgdc-assistant-title">Assistant</h2>
23
+ <p>Répond à partir de cette documentation, avec ses sources.</p>
24
+ </div>
25
+ <div class="hgdc-panel__tools">
26
+ <button type="button" class="hgdc-icon-button" data-reset aria-label="Nouvelle conversation" hidden>
27
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="18" height="18"
28
+ ><path d="M4 12a8 8 0 1 0 2.3-5.7M4 4v4h4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
29
+ ></path></svg
30
+ >
31
+ </button>
32
+ <button type="button" class="hgdc-icon-button" data-close aria-label="Fermer l'assistant">
33
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="18" height="18"
34
+ ><path d="M6 6l12 12M18 6 6 18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"
35
+ ></path></svg
36
+ >
37
+ </button>
38
+ </div>
39
+ </header>
40
+
41
+ <div class="hgdc-panel__body" data-log aria-live="polite">
42
+ <div class="hgdc-empty" data-empty>
43
+ <p class="hgdc-empty__title">Pose ta question sur {data.config.name}.</p>
44
+ <p>
45
+ Je cherche dans les pages de cette documentation et je cite mes sources. Si la réponse n'y est pas, je te le
46
+ dis.
47
+ </p>
48
+ </div>
49
+ </div>
50
+
51
+ <form class="hgdc-panel__form" data-form>
52
+ <label class="sr-only" for="hgdc-question">Ta question</label>
53
+ <textarea id="hgdc-question" data-input rows="1" maxlength="1000" placeholder="Comment publier ma doc ?" required
54
+ ></textarea>
55
+ <button type="submit" class="hgdc-send" data-send aria-label="Envoyer la question">
56
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="18" height="18"
57
+ ><path d="M12 19V5M5 12l7-7 7 7" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
58
+ ></path></svg
59
+ >
60
+ </button>
61
+ </form>
62
+ <p class="hgdc-panel__note">
63
+ Réponses générées par IA (Claude, d'Anthropic), à vérifier. Les questions sont conservées 90 jours pour améliorer cette documentation.
64
+ </p>
65
+ </section>
66
+ </hgdc-assistant>
67
+
68
+ <script>
69
+ import { defineAssistant } from "../widget/client.ts"
70
+
71
+ defineAssistant()
72
+ </script>
@@ -0,0 +1,12 @@
1
+ ---
2
+ import Default from "@astrojs/starlight/components/Head.astro"
3
+
4
+ import { markdownPath } from "../core/llms.ts"
5
+
6
+ // llmstxt.org : chaque page annonce sa version Markdown et l'index qui la décrit.
7
+ const markdown = markdownPath(Astro.url.pathname)
8
+ ---
9
+
10
+ <Default><slot /></Default>
11
+ <link rel="alternate" type="text/markdown" href={markdown} />
12
+ <link rel="describedby" type="text/markdown" href="/llms.txt" />
@@ -0,0 +1,15 @@
1
+ ---
2
+ import Default from "@astrojs/starlight/components/PageFrame.astro"
3
+ import data from "virtual:hgdc-docs/config"
4
+
5
+ import Assistant from "./Assistant.astro"
6
+
7
+ const assistant = data.config.project && data.config.assistant?.enabled !== false
8
+ ---
9
+
10
+ <Default>
11
+ <slot name="header" slot="header" />
12
+ <slot name="sidebar" slot="sidebar" />
13
+ <slot />
14
+ </Default>
15
+ {assistant && <Assistant />}
@@ -0,0 +1,94 @@
1
+ ---
2
+ import Default from "@astrojs/starlight/components/PageTitle.astro"
3
+ import data from "virtual:hgdc-docs/config"
4
+
5
+ import { markdownPath } from "../core/llms.ts"
6
+
7
+ const markdown = markdownPath(Astro.url.pathname)
8
+ const assistant = Boolean(data.config.project) && data.config.assistant?.enabled !== false
9
+ ---
10
+
11
+ <div class="hgdc-title">
12
+ <Default><slot /></Default>
13
+ <div class="hgdc-page-actions not-content" data-hgdc-skip data-pagefind-ignore>
14
+ <button type="button" class="hgdc-action" data-hgdc-copy={markdown}>
15
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16"
16
+ ><rect x="8" y="8" width="12" height="12" rx="3" fill="none" stroke="currentColor" stroke-width="1.8"
17
+ ></rect><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2" fill="none" stroke="currentColor" stroke-width="1.8"
18
+ ></path></svg
19
+ >
20
+ <span data-label>Copier pour un LLM</span>
21
+ </button>
22
+ <details class="hgdc-menu">
23
+ <summary class="hgdc-action hgdc-action--icon" aria-label="Autres formats de la page">
24
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16"
25
+ ><path d="m6 9 6 6 6-6" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"></path></svg
26
+ >
27
+ </summary>
28
+ <ul class="hgdc-menu__list">
29
+ <li><a href={markdown}>Voir en Markdown</a></li>
30
+ <li><a data-hgdc-open="claude" href="https://claude.ai/new" target="_blank" rel="noopener">Ouvrir dans Claude</a></li>
31
+ <li><a data-hgdc-open="chatgpt" href="https://chatgpt.com/" target="_blank" rel="noopener">Ouvrir dans ChatGPT</a></li>
32
+ <li><a href="/llms.txt">Index pour les agents (llms.txt)</a></li>
33
+ </ul>
34
+ </details>
35
+ {
36
+ assistant && (
37
+ <button type="button" class="hgdc-action" data-hgdc-ask>
38
+ <svg aria-hidden="true" viewBox="0 0 24 24" width="16" height="16">
39
+ <path d="M12 3l1.9 4.6L18.5 9.5l-4.6 1.9L12 16l-1.9-4.6L5.5 9.5l4.6-1.9z" fill="currentColor" />
40
+ </svg>
41
+ <span>Demander à l'assistant</span>
42
+ </button>
43
+ )
44
+ }
45
+ <span class="sr-only" role="status" aria-live="polite" data-hgdc-copy-status></span>
46
+ </div>
47
+ </div>
48
+
49
+ <script>
50
+ const status = document.querySelector<HTMLElement>("[data-hgdc-copy-status]")
51
+
52
+ for (const button of document.querySelectorAll<HTMLButtonElement>("[data-hgdc-copy]")) {
53
+ const label = button.querySelector<HTMLElement>("[data-label]")!
54
+ button.addEventListener("click", async () => {
55
+ const done = (text: string) => {
56
+ label.textContent = text
57
+ if (status) status.textContent = text
58
+ setTimeout(() => (label.textContent = "Copier pour un LLM"), 2000)
59
+ }
60
+ try {
61
+ const response = await fetch(button.dataset.hgdcCopy!)
62
+ if (!response.ok) throw new Error(String(response.status))
63
+ await navigator.clipboard.writeText(await response.text())
64
+ done("Page copiée")
65
+ } catch {
66
+ done("Copie impossible")
67
+ }
68
+ })
69
+ }
70
+
71
+ // « Ouvrir dans… » : l'assistant externe lit la version Markdown de cette page.
72
+ const markdownUrl = new URL(document.querySelector<HTMLElement>("[data-hgdc-copy]")!.dataset.hgdcCopy!, location.href)
73
+ const prompt = `Lis ${markdownUrl.href} et réponds à mes questions sur cette page.`
74
+ for (const link of document.querySelectorAll<HTMLAnchorElement>("[data-hgdc-open]")) {
75
+ const base = link.dataset.hgdcOpen === "claude" ? "https://claude.ai/new" : "https://chatgpt.com/"
76
+ link.href = `${base}?q=${encodeURIComponent(prompt)}`
77
+ }
78
+
79
+ // Le menu se ferme au clic extérieur et à Échap, comme un menu natif.
80
+ for (const menu of document.querySelectorAll<HTMLDetailsElement>(".hgdc-menu")) {
81
+ document.addEventListener("click", (event) => {
82
+ if (menu.open && !menu.contains(event.target as Node)) menu.open = false
83
+ })
84
+ menu.addEventListener("keydown", (event) => {
85
+ if (event.key === "Escape" && menu.open) {
86
+ menu.open = false
87
+ menu.querySelector("summary")?.focus()
88
+ }
89
+ })
90
+ }
91
+
92
+ for (const button of document.querySelectorAll("[data-hgdc-ask]"))
93
+ button.addEventListener("click", () => document.dispatchEvent(new CustomEvent("hgdc:assistant-open")))
94
+ </script>
@@ -0,0 +1,60 @@
1
+ ---
2
+ import Default from "@astrojs/starlight/components/Sidebar.astro"
3
+ import data from "virtual:hgdc-docs/config"
4
+
5
+ import { pagesOf, pageToPath } from "../core/navigation.ts"
6
+
7
+ // Onglets et versions de docs.json, en tête de la sidebar : visibles sur ordinateur et dans
8
+ // le menu mobile, sans toucher à la hauteur de l'en-tête de Starlight.
9
+ const sections = data.tree.versions.flatMap((version) => version.tabs.map((tab) => ({ version, tab })))
10
+ const index = Astro.locals.hgdcSection ?? 0
11
+ const current = sections[index] ?? sections[0]!
12
+ const firstPath = (items: Parameters<typeof pagesOf>[0]) => {
13
+ const first = pagesOf(items)[0]
14
+ return first ? pageToPath(first) : "/"
15
+ }
16
+ const tabs = current.version.tabs.filter((tab) => tab.label)
17
+ const versions = data.tree.versions.filter((version) => version.label)
18
+ const tabPosition = current.version.tabs.indexOf(current.tab)
19
+ const versionHref = (version: (typeof versions)[number]) =>
20
+ firstPath((version.tabs[tabPosition] ?? version.tabs[0]!).items)
21
+ ---
22
+
23
+ {
24
+ (tabs.length > 1 || versions.length > 1) && (
25
+ <div class="hgdc-sections">
26
+ {versions.length > 1 && (
27
+ <label class="hgdc-version">
28
+ <span class="sr-only">Version de la documentation</span>
29
+ <select data-hgdc-version>
30
+ {versions.map((version) => (
31
+ <option value={versionHref(version)} selected={version === current.version}>
32
+ {version.label}
33
+ {version.isDefault ? " (actuelle)" : ""}
34
+ </option>
35
+ ))}
36
+ </select>
37
+ </label>
38
+ )}
39
+ {tabs.length > 1 && (
40
+ <nav aria-label="Sections de la documentation">
41
+ <ul class="hgdc-tabs">
42
+ {tabs.map((tab) => (
43
+ <li>
44
+ <a href={firstPath(tab.items)} aria-current={tab === current.tab ? "page" : undefined}>
45
+ {tab.label}
46
+ </a>
47
+ </li>
48
+ ))}
49
+ </ul>
50
+ </nav>
51
+ )}
52
+ </div>
53
+ )
54
+ }
55
+ <Default><slot /></Default>
56
+
57
+ <script>
58
+ for (const select of document.querySelectorAll<HTMLSelectElement>("[data-hgdc-version]"))
59
+ select.addEventListener("change", () => window.location.assign(select.value))
60
+ </script>
@@ -0,0 +1,33 @@
1
+ import { defineRouteMiddleware, type StarlightRouteData } from "@astrojs/starlight/route-data"
2
+ import data from "virtual:hgdc-docs/config"
3
+
4
+ type Entry = StarlightRouteData["sidebar"][number]
5
+ type Link = Extract<Entry, { type: "link" }>
6
+
7
+ const containsCurrent = (entry: Entry): boolean =>
8
+ entry.type === "link" ? entry.isCurrent : entry.entries.some(containsCurrent)
9
+
10
+ const links = (entries: Array<Entry>): Array<Link> =>
11
+ entries.flatMap((entry) => (entry.type === "link" ? [entry] : links(entry.entries)))
12
+
13
+ /**
14
+ * Plusieurs versions ou onglets : la sidebar de Starlight en contient une entrée par
15
+ * couple (version, onglet). On ne garde que celle de la page affichée, et la pagination
16
+ * précédent / suivant reste dans cette section.
17
+ */
18
+ export const onRequest = defineRouteMiddleware((context) => {
19
+ const route = context.locals.starlightRoute
20
+ const sections = data.tree.versions.flatMap((version) => version.tabs)
21
+ if (sections.length > 1) {
22
+ const index = Math.max(route.sidebar.findIndex(containsCurrent), 0)
23
+ const section = route.sidebar[index]
24
+ if (section?.type === "group") route.sidebar = section.entries
25
+ context.locals.hgdcSection = index
26
+ }
27
+ const all = links(route.sidebar)
28
+ const current = all.findIndex((link) => link.isCurrent)
29
+ if (current !== -1 && route.pagination) {
30
+ route.pagination.prev = all[current - 1]
31
+ route.pagination.next = all[current + 1]
32
+ }
33
+ })