Skip to content

Commit e56fd7f

Browse files
fix/doc: document search-jobs, debug, snapshot subcommands; list legacy commands in root index
`src doc` only expands legacy command groups that are registered in the `commanders` map. `search-jobs`, `debug` and `snapshot` were never added, so each rendered as a single leaf page with the group help text and their subcommands (19 pages) were never documented. The root index.md also only listed urfave/cli commands: since #1304, `maps.Copy(subcommands, rootSubcommands)` copied in the wrong direction (`maps.Copy(dst, src)`), dropping every legacy command from the landing page. `teams` is intentionally left as a single page: teams were removed in Sourcegraph 7.0 (#1257). Add cmd/src/doc_test.go so both regressions fail CI in future: a golden file list (mirrors OUTPUT_FILES in sourcegraph/sourcegraph doc/cli/references/BUILD.bazel), a check that no leaf page lists "The commands are:", and a check that the root index links every command. Part of https://linear.app/sourcegraph/issue/FE-502 Amp-Thread-ID: https://ampcode.com/threads/T-01a08410-86ca-72be-9928-2810e837fae1 Co-authored-by: Amp <amp@ampcode.com>
1 parent ba67acb commit e56fd7f

3 files changed

Lines changed: 231 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ All notable changes to `src-cli` are documented in this file.
1818
- HTTP requests now fail instead of hanging forever if the server does not start responding within 1 minute. Set the `SRC_RESPONSE_HEADER_TIMEOUT` environment variable to change this timeout, or to `0` to disable it. Responses that stream data for a long time (for example, large search job results) are not affected.
1919
- `src search-jobs logs` and `src search-jobs results` now use the standard API client, gaining proxy support, `-insecure-skip-verify`, and cross-host redirect protection, and now report an error on non-200 responses instead of writing the error page into the output.
2020

21+
### Fixed
22+
23+
- `src doc` now generates reference pages for the `search-jobs`, `debug`, and `snapshot` subcommands, and the root `index.md` again lists legacy commands (`batch`, `repos`, `search`, ...) alongside the `urfave/cli` ones.
24+
2125
### Removed
2226

2327
- Removed `src sbom` and `src signature` commands. SBOMs and container signatures are no longer published as of Sourcegraph 7.1.0.

cmd/src/doc.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,15 @@ Examples:
5757
}
5858

5959
commanders := map[string]*commander{
60-
"": &commands,
61-
"batch": &batchCommands,
62-
"config": &configCommands,
63-
"extsvc": &extsvcCommands,
64-
"code-intel": &codeintelCommands,
65-
"repos": &reposCommands,
60+
"": &commands,
61+
"batch": &batchCommands,
62+
"config": &configCommands,
63+
"extsvc": &extsvcCommands,
64+
"code-intel": &codeintelCommands,
65+
"repos": &reposCommands,
66+
"search-jobs": &searchJobsCommands,
67+
"debug": &debugCommands,
68+
"snapshot": &snapshotCommands,
6669
}
6770

6871
rootSubcommands := map[string]string{}
@@ -108,7 +111,7 @@ Examples:
108111
}
109112

110113
if groupName == "" {
111-
maps.Copy(subcommands, rootSubcommands)
114+
maps.Copy(rootSubcommands, subcommands)
112115
continue
113116
}
114117

cmd/src/doc_test.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package main
2+
3+
import (
4+
"io/fs"
5+
"os"
6+
"path/filepath"
7+
"slices"
8+
"sort"
9+
"strings"
10+
"testing"
11+
12+
"github.com/google/go-cmp/cmp"
13+
)
14+
15+
// legacyGroupsWithoutSubcommandPages lists legacy command groups that are
16+
// deliberately left out of the commanders map in doc.go, so they render as a
17+
// single page instead of a directory of subcommand pages. Every entry needs a
18+
// reason.
19+
var legacyGroupsWithoutSubcommandPages = map[string]string{
20+
"teams.md": "teams were removed in Sourcegraph 7.0 (see checkTeamsAvailability); don't expand docs for a removed feature",
21+
}
22+
23+
// expectedDocFiles is the full set of files 'src doc' is expected to write,
24+
// relative to the output directory. Update it deliberately when adding or
25+
// removing commands; sourcegraph/sourcegraph's doc/cli/references/BUILD.bazel
26+
// OUTPUT_FILES must be kept in sync with this list.
27+
var expectedDocFiles = []string{
28+
"abc/index.md",
29+
"abc/variables/delete.md",
30+
"abc/variables/index.md",
31+
"abc/variables/set.md",
32+
"api.md",
33+
"auth/index.md",
34+
"auth/token.md",
35+
"batch/apply.md",
36+
"batch/exec.md",
37+
"batch/index.md",
38+
"batch/new.md",
39+
"batch/preview.md",
40+
"batch/remote.md",
41+
"batch/repositories.md",
42+
"batch/validate.md",
43+
"code-intel/index.md",
44+
"code-intel/upload.md",
45+
"codeowners/create.md",
46+
"codeowners/delete.md",
47+
"codeowners/get.md",
48+
"codeowners/index.md",
49+
"codeowners/update.md",
50+
"config/edit.md",
51+
"config/get.md",
52+
"config/index.md",
53+
"config/list.md",
54+
"debug/compose.md",
55+
"debug/index.md",
56+
"debug/kube.md",
57+
"debug/server.md",
58+
"extsvc/create.md",
59+
"extsvc/edit.md",
60+
"extsvc/index.md",
61+
"extsvc/list.md",
62+
"index.md",
63+
"login.md",
64+
"lsp.md",
65+
"orgs/create.md",
66+
"orgs/delete.md",
67+
"orgs/get.md",
68+
"orgs/index.md",
69+
"orgs/list.md",
70+
"orgs/members/add.md",
71+
"orgs/members/index.md",
72+
"orgs/members/remove.md",
73+
"repos/add-metadata.md",
74+
"repos/delete-metadata.md",
75+
"repos/delete.md",
76+
"repos/get.md",
77+
"repos/index.md",
78+
"repos/list.md",
79+
"repos/update-metadata.md",
80+
"search-jobs/cancel.md",
81+
"search-jobs/create.md",
82+
"search-jobs/delete.md",
83+
"search-jobs/get.md",
84+
"search-jobs/index.md",
85+
"search-jobs/list.md",
86+
"search-jobs/logs.md",
87+
"search-jobs/restart.md",
88+
"search-jobs/results.md",
89+
"search.md",
90+
"serve-git.md",
91+
"snapshot/databases.md",
92+
"snapshot/index.md",
93+
"snapshot/restore.md",
94+
"snapshot/summary.md",
95+
"snapshot/test.md",
96+
"snapshot/upload.md",
97+
"teams.md",
98+
"users/create.md",
99+
"users/delete.md",
100+
"users/get.md",
101+
"users/index.md",
102+
"users/list.md",
103+
"users/prune.md",
104+
"users/tag.md",
105+
"version.md",
106+
}
107+
108+
func runDocCommand(t *testing.T) (dir string, files []string) {
109+
t.Helper()
110+
111+
var docCmd *command
112+
for _, cmd := range commands {
113+
if cmd.flagSet.Name() == "doc" {
114+
docCmd = cmd
115+
break
116+
}
117+
}
118+
if docCmd == nil {
119+
t.Fatal("'doc' command not registered")
120+
}
121+
122+
dir = t.TempDir()
123+
if err := docCmd.handler([]string{"-o", dir}); err != nil {
124+
t.Fatalf("src doc: %v", err)
125+
}
126+
127+
err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error {
128+
if err != nil {
129+
return err
130+
}
131+
if d.IsDir() {
132+
return nil
133+
}
134+
rel, err := filepath.Rel(dir, path)
135+
if err != nil {
136+
return err
137+
}
138+
files = append(files, filepath.ToSlash(rel))
139+
return nil
140+
})
141+
if err != nil {
142+
t.Fatal(err)
143+
}
144+
sort.Strings(files)
145+
return dir, files
146+
}
147+
148+
func TestDocGeneratesExpectedFiles(t *testing.T) {
149+
_, got := runDocCommand(t)
150+
151+
want := append([]string(nil), expectedDocFiles...)
152+
sort.Strings(want)
153+
154+
if diff := cmp.Diff(want, got); diff != "" {
155+
t.Errorf("generated file list mismatch (-want +got):\n%s\n"+
156+
"If you added or removed a command, update expectedDocFiles here and "+
157+
"OUTPUT_FILES in sourcegraph/sourcegraph doc/cli/references/BUILD.bazel.", diff)
158+
}
159+
}
160+
161+
// A legacy command group that is not registered in the commanders map in
162+
// doc.go is rendered as a single leaf page containing only its group help,
163+
// and none of its subcommands get a page. Legacy group help text lists
164+
// subcommands under "The commands are:", so a leaf page containing that
165+
// phrase means a group was missed.
166+
func TestDocLegacyGroupsHaveSubcommandPages(t *testing.T) {
167+
dir, files := runDocCommand(t)
168+
169+
for _, rel := range files {
170+
if strings.HasSuffix(rel, "/index.md") || rel == "index.md" {
171+
continue
172+
}
173+
if _, ok := legacyGroupsWithoutSubcommandPages[rel]; ok {
174+
continue
175+
}
176+
content, err := os.ReadFile(filepath.Join(dir, rel))
177+
if err != nil {
178+
t.Fatal(err)
179+
}
180+
if strings.Contains(string(content), "The commands are:") {
181+
t.Errorf("%s is a leaf page but lists subcommands: add its command group to the commanders map in doc.go "+
182+
"(or, if intentional, to legacyGroupsWithoutSubcommandPages with a reason)", rel)
183+
}
184+
}
185+
186+
for rel := range legacyGroupsWithoutSubcommandPages {
187+
if !slices.Contains(files, rel) {
188+
t.Errorf("legacyGroupsWithoutSubcommandPages entry %q was not generated; remove the stale entry", rel)
189+
}
190+
}
191+
}
192+
193+
// The root index must link every top-level command, both legacy (commander)
194+
// and migrated (urfave/cli) ones.
195+
func TestDocRootIndexListsAllCommands(t *testing.T) {
196+
dir, _ := runDocCommand(t)
197+
198+
index, err := os.ReadFile(filepath.Join(dir, "index.md"))
199+
if err != nil {
200+
t.Fatal(err)
201+
}
202+
203+
var missing []string
204+
for _, cmd := range commands {
205+
name := cmd.flagSet.Name()
206+
if name == "doc" || name == "publish" {
207+
continue
208+
}
209+
if !strings.Contains(string(index), "[`"+name+"`](") {
210+
missing = append(missing, name)
211+
}
212+
}
213+
if len(missing) > 0 {
214+
sort.Strings(missing)
215+
t.Errorf("root index.md is missing legacy commands: %v", missing)
216+
}
217+
}

0 commit comments

Comments
 (0)