sourcey 3.5.1 → 3.5.2

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 (35) hide show
  1. package/README.md +90 -10
  2. package/dist/cli.js +122 -3
  3. package/dist/components/layout/Page.d.ts.map +1 -1
  4. package/dist/components/layout/Page.js +4 -1
  5. package/dist/config.d.ts +60 -0
  6. package/dist/config.d.ts.map +1 -1
  7. package/dist/config.js +28 -3
  8. package/dist/core/godoc-introspector.d.ts +26 -0
  9. package/dist/core/godoc-introspector.d.ts.map +1 -0
  10. package/dist/core/godoc-introspector.js +144 -0
  11. package/dist/core/godoc-loader.d.ts +34 -0
  12. package/dist/core/godoc-loader.d.ts.map +1 -0
  13. package/dist/core/godoc-loader.js +491 -0
  14. package/dist/core/godoc-types.d.ts +109 -0
  15. package/dist/core/godoc-types.d.ts.map +1 -0
  16. package/dist/core/godoc-types.js +8 -0
  17. package/dist/core/markdown-loader.d.ts +10 -0
  18. package/dist/core/markdown-loader.d.ts.map +1 -1
  19. package/dist/core/search-indexer.d.ts.map +1 -1
  20. package/dist/core/search-indexer.js +9 -0
  21. package/dist/core/sourcey-godoc/cmd/sourcey-godoc/main.go +736 -0
  22. package/dist/core/sourcey-godoc/cmd/sourcey-godoc/site.go +497 -0
  23. package/dist/core/sourcey-godoc/cmd/sourcey-godoc/site_test.go +89 -0
  24. package/dist/core/sourcey-godoc/doc.go +11 -0
  25. package/dist/core/sourcey-godoc/go.mod +3 -0
  26. package/dist/dev-server.d.ts.map +1 -1
  27. package/dist/dev-server.js +42 -0
  28. package/dist/index.d.ts +1 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +1 -0
  31. package/dist/site-assembly.d.ts +3 -0
  32. package/dist/site-assembly.d.ts.map +1 -1
  33. package/dist/site-assembly.js +30 -1
  34. package/dist/themes/default/sourcey.css +244 -0
  35. package/package.json +13 -4
@@ -0,0 +1,497 @@
1
+ package main
2
+
3
+ import (
4
+ "go/doc/comment"
5
+ "html"
6
+ "os"
7
+ "path/filepath"
8
+ "regexp"
9
+ "sort"
10
+ "strings"
11
+ )
12
+
13
+ const siteCSS = `:root {
14
+ color-scheme: light dark;
15
+ --bg: #f8f8f5;
16
+ --fg: #161616;
17
+ --muted: #656565;
18
+ --border: #deded8;
19
+ --panel: #ffffff;
20
+ --accent: #2558d4;
21
+ --code-bg: #f1f1ed;
22
+ }
23
+ @media (prefers-color-scheme: dark) {
24
+ :root {
25
+ --bg: #111111;
26
+ --fg: #f3f3ef;
27
+ --muted: #a7a7a0;
28
+ --border: #30302c;
29
+ --panel: #171717;
30
+ --accent: #8eadff;
31
+ --code-bg: #20201c;
32
+ }
33
+ }
34
+ * { box-sizing: border-box; }
35
+ body {
36
+ margin: 0;
37
+ background: var(--bg);
38
+ color: var(--fg);
39
+ font: 16px/1.55 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
40
+ }
41
+ a { color: var(--accent); text-decoration: none; }
42
+ a:hover { text-decoration: underline; }
43
+ .shell {
44
+ display: grid;
45
+ grid-template-columns: minmax(220px, 280px) minmax(0, 1fr);
46
+ min-height: 100vh;
47
+ }
48
+ aside {
49
+ border-right: 1px solid var(--border);
50
+ padding: 28px 22px;
51
+ background: var(--panel);
52
+ }
53
+ main {
54
+ max-width: 1040px;
55
+ padding: 44px min(6vw, 72px);
56
+ }
57
+ .brand { font-weight: 700; margin: 0 0 8px; }
58
+ .module { color: var(--muted); font-size: 0.9rem; word-break: break-all; }
59
+ .nav { list-style: none; margin: 24px 0 0; padding: 0; }
60
+ .nav li { margin: 6px 0; }
61
+ h1, h2, h3 { line-height: 1.2; }
62
+ h1 { font-size: clamp(2rem, 4vw, 3rem); margin: 0 0 12px; }
63
+ h2 { margin-top: 44px; padding-top: 20px; border-top: 1px solid var(--border); }
64
+ h3 { margin-top: 28px; }
65
+ .import-path, .synopsis, .meta { color: var(--muted); }
66
+ .card {
67
+ display: block;
68
+ border: 1px solid var(--border);
69
+ border-radius: 8px;
70
+ padding: 16px;
71
+ margin: 12px 0;
72
+ background: var(--panel);
73
+ }
74
+ pre {
75
+ overflow-x: auto;
76
+ padding: 14px;
77
+ border-radius: 8px;
78
+ background: var(--code-bg);
79
+ border: 1px solid var(--border);
80
+ }
81
+ code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }
82
+ .doc { max-width: 78ch; }
83
+ .field-list { padding-left: 18px; }
84
+ .field-list li { margin: 8px 0; }
85
+ @media (max-width: 780px) {
86
+ .shell { display: block; }
87
+ aside { border-right: 0; border-bottom: 1px solid var(--border); }
88
+ main { padding: 28px 20px; }
89
+ }`
90
+
91
+ var slugCleaner = regexp.MustCompile(`[^a-zA-Z0-9_-]+`)
92
+
93
+ func writeSite(cfg *config, snap *snapshot) error {
94
+ outDir := cfg.out
95
+ if outDir == "" {
96
+ outDir = "site"
97
+ }
98
+ if err := os.MkdirAll(outDir, 0o755); err != nil {
99
+ return err
100
+ }
101
+
102
+ if err := writeSnapshot(filepath.Join(outDir, "sourcey-godoc.json"), snap); err != nil {
103
+ return err
104
+ }
105
+ if err := writeFile(outDir, "sourcey-godoc.css", siteCSS); err != nil {
106
+ return err
107
+ }
108
+ if err := writeFile(outDir, "index.html", renderIndex(cfg, snap)); err != nil {
109
+ return err
110
+ }
111
+ for _, pkg := range snap.Packages {
112
+ if err := writeFile(outDir, packageFile(pkg, snap.ModulePath), renderPackage(cfg, snap, pkg)); err != nil {
113
+ return err
114
+ }
115
+ }
116
+ if err := writeFile(outDir, "llms.txt", renderLLMSText(cfg, snap)); err != nil {
117
+ return err
118
+ }
119
+ if err := writeFile(outDir, "llms-full.txt", renderLLMSFull(cfg, snap)); err != nil {
120
+ return err
121
+ }
122
+ return nil
123
+ }
124
+
125
+ func writeFile(root, rel, text string) error {
126
+ path := filepath.Join(root, filepath.FromSlash(rel))
127
+ if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
128
+ return err
129
+ }
130
+ return os.WriteFile(path, []byte(text), 0o644)
131
+ }
132
+
133
+ func renderIndex(cfg *config, snap *snapshot) string {
134
+ title := siteTitle(cfg, snap)
135
+ var body strings.Builder
136
+ body.WriteString(`<h1>`)
137
+ body.WriteString(esc(title))
138
+ body.WriteString(`</h1>`)
139
+ if snap.ModulePath != "" {
140
+ body.WriteString(`<p class="module"><code>`)
141
+ body.WriteString(esc(snap.ModulePath))
142
+ body.WriteString(`</code></p>`)
143
+ }
144
+ body.WriteString(`<p class="synopsis">Native Go package documentation generated by sourcey-godoc.</p>`)
145
+ for _, pkg := range snap.Packages {
146
+ body.WriteString(`<a class="card" href="`)
147
+ body.WriteString(esc(packageFile(pkg, snap.ModulePath)))
148
+ body.WriteString(`"><strong>`)
149
+ body.WriteString(esc(packageTitle(pkg, snap.ModulePath)))
150
+ body.WriteString(`</strong>`)
151
+ if pkg.Synopsis != "" {
152
+ body.WriteString(`<p>`)
153
+ body.WriteString(esc(pkg.Synopsis))
154
+ body.WriteString(`</p>`)
155
+ }
156
+ body.WriteString(`<p class="import-path"><code>`)
157
+ body.WriteString(esc(pkg.ImportPath))
158
+ body.WriteString(`</code></p></a>`)
159
+ }
160
+ return page(title, snap, body.String())
161
+ }
162
+
163
+ func renderPackage(cfg *config, snap *snapshot, pkg pkgOut) string {
164
+ var body strings.Builder
165
+ body.WriteString(`<h1>`)
166
+ body.WriteString(esc(packageTitle(pkg, snap.ModulePath)))
167
+ body.WriteString(`</h1><p class="import-path"><code>import "`)
168
+ body.WriteString(esc(pkg.ImportPath))
169
+ body.WriteString(`"</code></p>`)
170
+ if pkg.Doc != "" {
171
+ body.WriteString(`<div class="doc">`)
172
+ body.WriteString(renderDoc(pkg.Doc))
173
+ body.WriteString(`</div>`)
174
+ }
175
+ if len(pkg.Consts) > 0 {
176
+ body.WriteString(`<h2 id="constants">Constants</h2>`)
177
+ body.WriteString(renderValues("const", pkg.Consts))
178
+ }
179
+ if len(pkg.Vars) > 0 {
180
+ body.WriteString(`<h2 id="variables">Variables</h2>`)
181
+ body.WriteString(renderValues("var", pkg.Vars))
182
+ }
183
+ if len(pkg.Funcs) > 0 {
184
+ body.WriteString(`<h2 id="functions">Functions</h2>`)
185
+ for _, fn := range pkg.Funcs {
186
+ body.WriteString(renderFunc(fn))
187
+ }
188
+ }
189
+ if len(pkg.Types) > 0 {
190
+ body.WriteString(`<h2 id="types">Types</h2>`)
191
+ for _, typ := range pkg.Types {
192
+ body.WriteString(renderType(typ))
193
+ }
194
+ }
195
+ if len(pkg.Examples) > 0 {
196
+ body.WriteString(`<h2 id="examples">Examples</h2>`)
197
+ for _, ex := range pkg.Examples {
198
+ body.WriteString(renderExample(ex))
199
+ }
200
+ }
201
+ return page(packageTitle(pkg, snap.ModulePath), snap, body.String())
202
+ }
203
+
204
+ func renderValues(kind string, values []valueOut) string {
205
+ seen := map[string]bool{}
206
+ var body strings.Builder
207
+ for _, value := range values {
208
+ body.WriteString(`<section id="`)
209
+ body.WriteString(esc(kind + "-" + symbolSlug(value.Name)))
210
+ body.WriteString(`">`)
211
+ if value.Doc != "" {
212
+ body.WriteString(`<div class="doc">`)
213
+ body.WriteString(renderDoc(value.Doc))
214
+ body.WriteString(`</div>`)
215
+ }
216
+ if !seen[value.Declaration] {
217
+ seen[value.Declaration] = true
218
+ body.WriteString(codeBlock(value.Declaration))
219
+ }
220
+ body.WriteString(`</section>`)
221
+ }
222
+ return body.String()
223
+ }
224
+
225
+ func renderFunc(fn funcOut) string {
226
+ var body strings.Builder
227
+ body.WriteString(`<section id="func-`)
228
+ body.WriteString(esc(symbolSlug(fn.Name)))
229
+ body.WriteString(`"><h3>`)
230
+ body.WriteString(esc(fn.Signature))
231
+ body.WriteString(`</h3>`)
232
+ if fn.Doc != "" {
233
+ body.WriteString(`<div class="doc">`)
234
+ body.WriteString(renderDoc(fn.Doc))
235
+ body.WriteString(`</div>`)
236
+ }
237
+ for _, ex := range fn.Examples {
238
+ body.WriteString(renderExample(ex))
239
+ }
240
+ body.WriteString(`</section>`)
241
+ return body.String()
242
+ }
243
+
244
+ func renderType(typ typeOut) string {
245
+ var body strings.Builder
246
+ body.WriteString(`<section id="type-`)
247
+ body.WriteString(esc(symbolSlug(typ.Name)))
248
+ body.WriteString(`"><h3>type `)
249
+ body.WriteString(esc(typ.Name))
250
+ body.WriteString(`</h3>`)
251
+ if typ.Doc != "" {
252
+ body.WriteString(`<div class="doc">`)
253
+ body.WriteString(renderDoc(typ.Doc))
254
+ body.WriteString(`</div>`)
255
+ }
256
+ body.WriteString(codeBlock(typ.Declaration))
257
+ if len(typ.Fields) > 0 {
258
+ heading := "Fields"
259
+ if typ.Kind == "interface" {
260
+ heading = "Methods"
261
+ }
262
+ body.WriteString(`<h4>`)
263
+ body.WriteString(heading)
264
+ body.WriteString(`</h4><ul class="field-list">`)
265
+ for _, field := range typ.Fields {
266
+ body.WriteString(`<li><code>`)
267
+ body.WriteString(esc(field.Name))
268
+ if !field.Embedded && field.Type != "" {
269
+ body.WriteString(` `)
270
+ body.WriteString(esc(field.Type))
271
+ }
272
+ body.WriteString(`</code>`)
273
+ if field.Tag != "" {
274
+ body.WriteString(` <code>`)
275
+ body.WriteString(esc("`" + field.Tag + "`"))
276
+ body.WriteString(`</code>`)
277
+ }
278
+ if field.Doc != "" {
279
+ body.WriteString(`<div class="doc">`)
280
+ body.WriteString(renderDoc(field.Doc))
281
+ body.WriteString(`</div>`)
282
+ }
283
+ body.WriteString(`</li>`)
284
+ }
285
+ body.WriteString(`</ul>`)
286
+ }
287
+ for _, method := range typ.Methods {
288
+ body.WriteString(renderFunc(method))
289
+ }
290
+ for _, ex := range typ.Examples {
291
+ body.WriteString(renderExample(ex))
292
+ }
293
+ body.WriteString(`</section>`)
294
+ return body.String()
295
+ }
296
+
297
+ func renderExample(ex exOut) string {
298
+ var body strings.Builder
299
+ body.WriteString(`<details><summary>Example`)
300
+ if ex.Suffix != "" {
301
+ body.WriteString(`: `)
302
+ body.WriteString(esc(strings.ReplaceAll(ex.Suffix, "_", " ")))
303
+ }
304
+ body.WriteString(`</summary>`)
305
+ if ex.Doc != "" {
306
+ body.WriteString(`<div class="doc">`)
307
+ body.WriteString(renderDoc(ex.Doc))
308
+ body.WriteString(`</div>`)
309
+ }
310
+ body.WriteString(codeBlock(ex.Code))
311
+ if ex.Output != "" {
312
+ body.WriteString(`<p class="meta">Output:</p>`)
313
+ body.WriteString(codeBlock(ex.Output))
314
+ }
315
+ body.WriteString(`</details>`)
316
+ return body.String()
317
+ }
318
+
319
+ func page(title string, snap *snapshot, body string) string {
320
+ var nav strings.Builder
321
+ nav.WriteString(`<li><a href="index.html">Overview</a></li>`)
322
+ for _, pkg := range snap.Packages {
323
+ nav.WriteString(`<li><a href="`)
324
+ nav.WriteString(esc(packageFile(pkg, snap.ModulePath)))
325
+ nav.WriteString(`">`)
326
+ nav.WriteString(esc(packageTitle(pkg, snap.ModulePath)))
327
+ nav.WriteString(`</a></li>`)
328
+ }
329
+ return `<!doctype html>
330
+ <html lang="en">
331
+ <head>
332
+ <meta charset="utf-8">
333
+ <meta name="viewport" content="width=device-width, initial-scale=1">
334
+ <title>` + esc(title) + `</title>
335
+ <link rel="stylesheet" href="sourcey-godoc.css">
336
+ </head>
337
+ <body><div class="shell">
338
+ <aside><p class="brand">sourcey-godoc</p><p class="module"><code>` + esc(snap.ModulePath) + `</code></p><ul class="nav">` + nav.String() + `</ul></aside>
339
+ <main>` + body + `</main>
340
+ </div></body>
341
+ </html>
342
+ `
343
+ }
344
+
345
+ func renderLLMSText(cfg *config, snap *snapshot) string {
346
+ var body strings.Builder
347
+ body.WriteString("# ")
348
+ body.WriteString(siteTitle(cfg, snap))
349
+ body.WriteString("\n\n")
350
+ if snap.ModulePath != "" {
351
+ body.WriteString("Module: `")
352
+ body.WriteString(snap.ModulePath)
353
+ body.WriteString("`\n\n")
354
+ }
355
+ for _, pkg := range snap.Packages {
356
+ body.WriteString("- [")
357
+ body.WriteString(packageTitle(pkg, snap.ModulePath))
358
+ body.WriteString("](")
359
+ body.WriteString(packageFile(pkg, snap.ModulePath))
360
+ body.WriteString(")")
361
+ if pkg.Synopsis != "" {
362
+ body.WriteString(" - ")
363
+ body.WriteString(pkg.Synopsis)
364
+ }
365
+ body.WriteString("\n")
366
+ }
367
+ return body.String()
368
+ }
369
+
370
+ func renderLLMSFull(cfg *config, snap *snapshot) string {
371
+ var body strings.Builder
372
+ body.WriteString("# ")
373
+ body.WriteString(siteTitle(cfg, snap))
374
+ body.WriteString("\n\n")
375
+ for _, pkg := range snap.Packages {
376
+ body.WriteString("## ")
377
+ body.WriteString(pkg.ImportPath)
378
+ body.WriteString("\n\n")
379
+ if pkg.Doc != "" {
380
+ body.WriteString(docText(pkg.Doc))
381
+ body.WriteString("\n\n")
382
+ }
383
+ writeDeclarations(&body, "Constants", valueDeclarations(pkg.Consts))
384
+ writeDeclarations(&body, "Variables", valueDeclarations(pkg.Vars))
385
+ writeDeclarations(&body, "Functions", funcDeclarations(pkg.Funcs))
386
+ writeDeclarations(&body, "Types", typeDeclarations(pkg.Types))
387
+ }
388
+ return body.String()
389
+ }
390
+
391
+ func writeDeclarations(body *strings.Builder, heading string, declarations []string) {
392
+ if len(declarations) == 0 {
393
+ return
394
+ }
395
+ body.WriteString("### ")
396
+ body.WriteString(heading)
397
+ body.WriteString("\n\n```go\n")
398
+ for _, decl := range declarations {
399
+ body.WriteString(decl)
400
+ if !strings.HasSuffix(decl, "\n") {
401
+ body.WriteString("\n")
402
+ }
403
+ }
404
+ body.WriteString("```\n\n")
405
+ }
406
+
407
+ func valueDeclarations(values []valueOut) []string {
408
+ seen := map[string]bool{}
409
+ out := []string{}
410
+ for _, value := range values {
411
+ if value.Declaration != "" && !seen[value.Declaration] {
412
+ seen[value.Declaration] = true
413
+ out = append(out, value.Declaration)
414
+ }
415
+ }
416
+ return out
417
+ }
418
+
419
+ func funcDeclarations(funcs []funcOut) []string {
420
+ out := make([]string, 0, len(funcs))
421
+ for _, fn := range funcs {
422
+ out = append(out, fn.Signature)
423
+ }
424
+ sort.Strings(out)
425
+ return out
426
+ }
427
+
428
+ func typeDeclarations(types []typeOut) []string {
429
+ out := make([]string, 0, len(types))
430
+ for _, typ := range types {
431
+ out = append(out, typ.Declaration)
432
+ }
433
+ sort.Strings(out)
434
+ return out
435
+ }
436
+
437
+ func siteTitle(cfg *config, snap *snapshot) string {
438
+ if cfg.title != "" {
439
+ return cfg.title
440
+ }
441
+ if snap.ModulePath != "" {
442
+ return snap.ModulePath
443
+ }
444
+ return "Go API"
445
+ }
446
+
447
+ func packageTitle(pkg pkgOut, modulePath string) string {
448
+ rel := relativePackage(pkg.ImportPath, modulePath)
449
+ if rel == "." || rel == "" {
450
+ return pkg.ImportPath
451
+ }
452
+ return rel
453
+ }
454
+
455
+ func packageFile(pkg pkgOut, modulePath string) string {
456
+ rel := relativePackage(pkg.ImportPath, modulePath)
457
+ if rel == "." || rel == "" {
458
+ return "package-root.html"
459
+ }
460
+ return "pkg-" + slugCleaner.ReplaceAllString(strings.ReplaceAll(rel, "/", "-"), "") + ".html"
461
+ }
462
+
463
+ func relativePackage(importPath, modulePath string) string {
464
+ if modulePath == "" {
465
+ return importPath
466
+ }
467
+ if importPath == modulePath {
468
+ return "."
469
+ }
470
+ return strings.TrimPrefix(importPath, modulePath+"/")
471
+ }
472
+
473
+ func renderDoc(text string) string {
474
+ var parser comment.Parser
475
+ doc := parser.Parse(text)
476
+ var printer comment.Printer
477
+ return string(printer.HTML(doc))
478
+ }
479
+
480
+ func docText(text string) string {
481
+ var parser comment.Parser
482
+ doc := parser.Parse(text)
483
+ var printer comment.Printer
484
+ return strings.TrimSpace(string(printer.Text(doc)))
485
+ }
486
+
487
+ func codeBlock(text string) string {
488
+ return "<pre><code>" + esc(text) + "</code></pre>"
489
+ }
490
+
491
+ func symbolSlug(name string) string {
492
+ return slugCleaner.ReplaceAllString(name, "_")
493
+ }
494
+
495
+ func esc(text string) string {
496
+ return html.EscapeString(text)
497
+ }
@@ -0,0 +1,89 @@
1
+ package main
2
+
3
+ import (
4
+ "os"
5
+ "path/filepath"
6
+ "strings"
7
+ "testing"
8
+ )
9
+
10
+ func TestWriteSiteGeneratesBrowsableDocsAndAgentSurfaces(t *testing.T) {
11
+ out := t.TempDir()
12
+ snap := &snapshot{
13
+ SchemaVersion: schemaVersion,
14
+ Source: source,
15
+ ModulePath: "example.com/project",
16
+ Packages: []pkgOut{{
17
+ ImportPath: "example.com/project/internal/core",
18
+ Name: "core",
19
+ Synopsis: "Package core owns the contract.",
20
+ Doc: "Package core owns the contract.\n\nIt has a second paragraph.",
21
+ Dir: "internal/core",
22
+ Files: []string{"core.go"},
23
+ Funcs: []funcOut{{
24
+ Name: "Run",
25
+ Doc: "Run executes the plan.",
26
+ Signature: "func Run() error",
27
+ }},
28
+ Types: []typeOut{{
29
+ Name: "Plan",
30
+ Doc: "Plan describes work.",
31
+ Declaration: "type Plan struct {\n\tName string `json:\"name\"`\n}",
32
+ Kind: "struct",
33
+ Fields: []fieldOut{{
34
+ Name: "Name",
35
+ Doc: "Name is the plan name.",
36
+ Type: "string",
37
+ Tag: `json:"name"`,
38
+ }},
39
+ }},
40
+ }},
41
+ }
42
+
43
+ if err := writeSite(&config{out: out, title: "Project Go API"}, snap); err != nil {
44
+ t.Fatalf("writeSite failed: %v", err)
45
+ }
46
+
47
+ for _, rel := range []string{
48
+ "index.html",
49
+ "pkg-internal-core.html",
50
+ "sourcey-godoc.css",
51
+ "sourcey-godoc.json",
52
+ "llms.txt",
53
+ "llms-full.txt",
54
+ } {
55
+ if _, err := os.Stat(filepath.Join(out, rel)); err != nil {
56
+ t.Fatalf("expected %s: %v", rel, err)
57
+ }
58
+ }
59
+
60
+ pkgHTML := readTestFile(t, filepath.Join(out, "pkg-internal-core.html"))
61
+ for _, want := range []string{
62
+ "func Run() error",
63
+ "type Plan",
64
+ "Name is the plan name.",
65
+ "It has a second paragraph.",
66
+ } {
67
+ if !strings.Contains(pkgHTML, want) {
68
+ t.Fatalf("package page missing %q\n%s", want, pkgHTML)
69
+ }
70
+ }
71
+
72
+ llms := readTestFile(t, filepath.Join(out, "llms.txt"))
73
+ if !strings.Contains(llms, "pkg-internal-core.html") {
74
+ t.Fatalf("llms.txt does not link package page:\n%s", llms)
75
+ }
76
+ llmsFull := readTestFile(t, filepath.Join(out, "llms-full.txt"))
77
+ if !strings.Contains(llmsFull, "func Run() error") {
78
+ t.Fatalf("llms-full.txt missing declaration:\n%s", llmsFull)
79
+ }
80
+ }
81
+
82
+ func readTestFile(t *testing.T, path string) string {
83
+ t.Helper()
84
+ data, err := os.ReadFile(path)
85
+ if err != nil {
86
+ t.Fatal(err)
87
+ }
88
+ return string(data)
89
+ }
@@ -0,0 +1,11 @@
1
+ // Package sourceygodoc documents the Sourcey Go documentation generator module.
2
+ //
3
+ // The module's primary surface is the sourcey-godoc CLI:
4
+ //
5
+ // go install github.com/sourcey/sourcey/go/sourcey-godoc/cmd/sourcey-godoc@latest
6
+ //
7
+ // The CLI reads a Go module through the Go toolchain and generates a static
8
+ // Go documentation site or Sourcey's portable godoc snapshot format. Sourcey's
9
+ // TypeScript CLI embeds this same module for live-mode docs builds, so the
10
+ // standalone CLI and Sourcey runtime share one implementation.
11
+ package sourceygodoc
@@ -0,0 +1,3 @@
1
+ module github.com/sourcey/sourcey/go/sourcey-godoc
2
+
3
+ go 1.21
@@ -1 +1 @@
1
- {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AA8CA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4W7E"}
1
+ {"version":3,"file":"dev-server.d.ts","sourceRoot":"","sources":["../src/dev-server.ts"],"names":[],"mappings":"AA+CA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmZ7E"}
@@ -11,6 +11,7 @@ import { normalizeMcpSpec } from "./core/mcp-normalizer.js";
11
11
  import { buildNavFromSpec, buildSiteNavigation, withActivePage } from "./core/navigation.js";
12
12
  import { loadDocsPage, slugFromPath } from "./core/markdown-loader.js";
13
13
  import { loadDoxygenTab } from "./core/doxygen-loader.js";
14
+ import { loadGodocTab } from "./core/godoc-loader.js";
14
15
  import { buildSearchIndex } from "./core/search-indexer.js";
15
16
  import { createRenderOptions } from "./renderer/html-builder.js";
16
17
  import { assembleSite, buildSiteConfig, collectDocsPagesByTab, enforceChangelogDiagnostics, formatChangelogDiagnostic, rebuildMarkdownTabNavigation, resolveInternalLinks, } from "./site-assembly.js";
@@ -49,6 +50,12 @@ export async function startDevServer(options) {
49
50
  watchPaths.push(tab.mcp);
50
51
  if (tab.doxygen)
51
52
  watchPaths.push(tab.doxygen.xml);
53
+ if (tab.godoc) {
54
+ if (tab.godoc.snapshot)
55
+ watchPaths.push(tab.godoc.snapshot);
56
+ // The Go module directory is large; watching every .go file slows the
57
+ // dev server. Restart manually after broad source edits.
58
+ }
52
59
  if (tab.groups) {
53
60
  for (const group of tab.groups) {
54
61
  for (const page of group.pages) {
@@ -70,6 +77,9 @@ export async function startDevServer(options) {
70
77
  if (tab.doxygen) {
71
78
  map.set(resolve(tab.doxygen.xml), { kind: "doxygen", tabSlug: tab.slug, xmlDir: tab.doxygen.xml });
72
79
  }
80
+ if (tab.godoc?.snapshot) {
81
+ map.set(resolve(tab.godoc.snapshot), { kind: "godoc", tabSlug: tab.slug, snapshotPath: tab.godoc.snapshot });
82
+ }
73
83
  if (tab.groups) {
74
84
  for (const group of tab.groups) {
75
85
  for (const page of group.pages) {
@@ -182,6 +192,38 @@ export async function startDevServer(options) {
182
192
  else
183
193
  data.siteTabs.push(navTab);
184
194
  }
195
+ else if (content.kind === "godoc") {
196
+ const tab = config.tabs.find((candidate) => candidate.slug === content.tabSlug);
197
+ if (!tab?.godoc)
198
+ return;
199
+ log(`rebuilding godoc tab "${tab.label}"`);
200
+ const { pages, navTab } = await loadGodocTab(tab.godoc, tab.slug, tab.label, {
201
+ repo: config.repo,
202
+ editBranch: config.editBranch,
203
+ editBasePath: tab.godoc.sourceBasePath,
204
+ });
205
+ if (cache !== snapshot)
206
+ return;
207
+ for (const [key, page] of data.pageMap) {
208
+ if (page.tabSlug === content.tabSlug)
209
+ data.pageMap.delete(key);
210
+ }
211
+ for (const [slug, page] of pages) {
212
+ const outputPath = pageOutputPath(tab.slug, slug, config.prettyUrls);
213
+ data.pageMap.set(outputPath, {
214
+ outputPath,
215
+ spec: data.primarySpec,
216
+ currentPage: { kind: "markdown", markdown: page },
217
+ tabSlug: tab.slug,
218
+ pageSlug: slug,
219
+ });
220
+ }
221
+ const idx = data.siteTabs.findIndex((candidate) => candidate.slug === content.tabSlug);
222
+ if (idx !== -1)
223
+ data.siteTabs[idx] = navTab;
224
+ else
225
+ data.siteTabs.push(navTab);
226
+ }
185
227
  else if (content.kind === "openapi") {
186
228
  log(`reparsing spec ${shortPath(content.specPath)}`);
187
229
  const loaded = await loadSpec(content.specPath);
package/dist/index.d.ts CHANGED
@@ -30,6 +30,7 @@ export interface SiteBuildResult {
30
30
  outputDir: string;
31
31
  pageCount: number;
32
32
  changelogDiagnostics: ChangelogDiagnostic[];
33
+ godocDiagnostics: import("./core/godoc-loader.js").GodocLoaderDiagnostic[];
33
34
  /** @internal specs by tab slug, for buildDocs compat */
34
35
  _specs?: Map<string, NormalizedSpec>;
35
36
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAA8B,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkBlD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;CAC7C;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAkB3E;AAMD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;IAC5C,wDAAwD;IACxD,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACtC;AAED,wBAAsB,aAAa,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAwF5F;AAyED,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAG1D,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,mBAAmB,EAA8B,cAAc,EAAE,MAAM,iBAAiB,CAAC;AACvG,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAkBlD,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;CAC7C;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAkB3E;AAMD,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,cAAc,CAAC;IACxB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,oBAAoB,EAAE,mBAAmB,EAAE,CAAC;IAC5C,gBAAgB,EAAE,OAAO,wBAAwB,EAAE,qBAAqB,EAAE,CAAC;IAC3E,wDAAwD;IACxD,MAAM,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACtC;AAED,wBAAsB,aAAa,CAAC,OAAO,GAAE,gBAAqB,GAAG,OAAO,CAAC,eAAe,CAAC,CAyF5F;AAyED,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAG1D,YAAY,EACV,cAAc,EACd,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -98,6 +98,7 @@ export async function buildSiteDocs(options = {}) {
98
98
  outputDir,
99
99
  pageCount: sitePages.length,
100
100
  changelogDiagnostics: assembled.changelogDiagnostics,
101
+ godocDiagnostics: assembled.godocDiagnostics,
101
102
  _specs: assembled.specsBySlug,
102
103
  };
103
104
  }