diff --git a/.github/workflows/generate-docs.yml b/.github/workflows/generate-docs.yml index 8b68c8b2..cfc1ee37 100644 --- a/.github/workflows/generate-docs.yml +++ b/.github/workflows/generate-docs.yml @@ -21,7 +21,11 @@ on: env: DOCS_TOKEN: ${{ secrets.DOCS_TOKEN }} TARGET_REPOSITORY: OctopusDeploy/docs - TARGET_DIRECTORY: src/pages/docs/octopus-rest-api/cli + # The command pages. _generated is the docs repo's convention for a folder a + # generator owns outright, served from the folder above it by an Astro route. + TARGET_DIRECTORY: src/pages/docs/cli/_generated + # The rows the hand-written overview page at /docs/cli imports. + COMMAND_LIST_PATH: src/shared-content/cli/commands.include.md # Always sync to the same branch in case there's already an open PR, in which case it'll be updated SYNC_BRANCH: cli-docs-sync PR_LABEL: cli-docs-sync @@ -56,7 +60,13 @@ jobs: - name: Generate cli docs working-directory: cli - run: mkdir -p ../docs/$TARGET_DIRECTORY && go run cmd/gen-docs/main.go --website --doc-path ../docs/$TARGET_DIRECTORY --relative-base-path /docs/octopus-rest-api/cli + run: | + mkdir -p ../docs/$TARGET_DIRECTORY + go run cmd/gen-docs/main.go \ + --website \ + --doc-path ../docs/$TARGET_DIRECTORY \ + --command-list-path ../docs/$COMMAND_LIST_PATH \ + --relative-base-path /docs/cli - name: Commit and push id: commit @@ -69,10 +79,10 @@ jobs: git config user.email 'bob@octopus.com' git config user.name octobob - git add --all -- "$TARGET_DIRECTORY" + git add --all -- "$TARGET_DIRECTORY" "$COMMAND_LIST_PATH" - # Safety net: only the target folder should have been touched - unexpected_changes="$(git diff --cached --name-only -- . ":(exclude)$TARGET_DIRECTORY")" + # Safety net: only the generated files should have been touched + unexpected_changes="$(git diff --cached --name-only -- . ":(exclude)$TARGET_DIRECTORY" ":(exclude)$COMMAND_LIST_PATH")" if [ -n "$unexpected_changes" ]; then echo "Found unexpected staged changes, aborting" >&2 echo "$unexpected_changes" >&2 diff --git a/cmd/gen-docs/main.go b/cmd/gen-docs/main.go index bccfe691..684c7dbd 100644 --- a/cmd/gen-docs/main.go +++ b/cmd/gen-docs/main.go @@ -83,6 +83,7 @@ func run(args []string) error { website := flags.BoolP("website", "", false, "Generate website pages") dir := flags.StringP("doc-path", "", "", "Path directory where you want generate doc files") relativeBasePath := flags.StringP("relative-base-path", "", "", "Relative base path for generating index links") + commandListPath := flags.StringP("command-list-path", "", "", "File to write the command list include to. Skipped when unset") help := flags.BoolP("help", "h", false, "Help about any command") if err := flags.Parse(args); err != nil { @@ -127,17 +128,39 @@ func run(args []string) error { return err } - filename := filepath.Join(*dir, "index.md") - f, err := os.Create(filename) + written := make(map[string]bool, pageCollection.Pages.Len()) + for _, page := range *pageCollection.Pages { + written[filepath.Base(page.OutputFile)] = true + } + + // A generator failure that produced almost nothing would otherwise sync as + // a mass deletion. The real count is in the hundreds. + if len(written) < minimumPageCount { + return fmt.Errorf("only generated %d page(s), expected at least %d, so something went wrong", len(written), minimumPageCount) + } + + if err := pruneStalePages(*dir, written); err != nil { + return err + } + + if *commandListPath == "" { + return nil + } + if err := os.MkdirAll(filepath.Dir(*commandListPath), 0755); err != nil { + return err + } + + // Not `f`: that is the command factory, several lines up. + out, err := os.Create(*commandListPath) if err != nil { return err } - defer f.Close() - t := template.Must(template.New("index-template").Parse(indexTemplate)) + defer out.Close() + sort.Sort(pageCollection.Pages) - t.Execute(f, pageCollection) - return nil + t := template.Must(template.New("command-list-template").Parse(commandListTemplate)) + return t.Execute(out, pageCollection) } header := &doc.GenManHeader{ @@ -154,6 +177,32 @@ func run(args []string) error { return nil } +// Below this, assume the generator broke rather than that the CLI lost most of +// its commands. +const minimumPageCount = 50 + +// pruneStalePages removes the .mdx files in dir that this run did not write, so +// a renamed or removed command takes its page with it. +func pruneStalePages(dir string, written map[string]bool) error { + entries, err := os.ReadDir(dir) + if err != nil { + return err + } + + for _, entry := range entries { + name := entry.Name() + if entry.IsDir() || filepath.Ext(name) != ".mdx" || written[name] { + continue + } + if err := os.Remove(filepath.Join(dir, name)); err != nil { + return err + } + fmt.Fprintf(os.Stderr, "removed stale page %s\n", name) + } + + return nil +} + func GenMarkdownCustom(cmd *cobra.Command, w io.Writer, info *TemplateInformation) error { cmd.InitDefaultHelpCmd() cmd.InitDefaultHelpFlag() @@ -214,7 +263,7 @@ func contentEqualIgnoringModDate(a, b []byte) bool { } const documentationTemplate = `--- -layout: src/layouts/Default.astro +layout: src/layouts/Cli.astro pubDate: 2023-01-01 modDate: {{.Date}} title: {{.Title}} @@ -263,32 +312,12 @@ Use "{{.CommandPath}} [command] --help" for more information about a command.{{e ## Learn more -- [Octopus CLI](/docs/octopus-rest-api/cli) +- [Octopus CLI](/docs/cli) - [Creating API keys](/docs/octopus-rest-api/how-to-create-an-api-key) ` -const indexTemplate = `--- -layout: src/layouts/Default.astro -pubDate: 2023-01-01 -modDate: 2023-01-01 -title: CLI -description: The all-new Octopus CLI -navOrder: 100 -hideInThisSection: true ---- - -The Octopus CLI is a command line tool that builds on top of the [Octopus Deploy REST API](/docs/octopus-rest-api). With the Octopus CLI you can push your application packages for deployment as either Zip or NuGet packages, and manage your environments, deployments, projects, and workers. - -The Octopus CLI can be used on Windows, Mac, Linux and Docker. For installation options and direct downloads, visit the [CLI Readme](https://github.com/OctopusDeploy/cli/blob/main/README.md). - -:::div{.hint} -The Octopus CLI is built and maintained by the Octopus Deploy team, but it is also open source. You can [view the Octopus CLI project on GitHub](https://github.com/OctopusDeploy/cli), which leans heavily on the [go-octopusdeploy library](https://github.com/OctopusDeploy/go-octopusdeploy). -::: - -## Commands {#octopusCommandLine-Commands} - -` + "`octopus` supports the following commands:" + - ` -{{range .Pages}} -- **[{{.Title}}]({{.RelativePath}})**: {{.Command.Short}}.{{end}} -` +// The rows the overview page lists. The prose around them, and the frontmatter, +// belong to the docs repo: this is imported by src/pages/docs/cli/index.mdx as a +// shared-content include, so a wording change there needs no release here. +const commandListTemplate = `{{range .Pages}}- **[{{.Title}}]({{.RelativePath}})**: {{.Command.Short}}. +{{end}}`