mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
@@ -9,7 +9,11 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||
cdroot
|
||||
|
||||
set +e
|
||||
find . -regex ".*\.go" | grep -v "./enterprise" | grep -v ./scripts/auditdocgen/ --include="*.go" | xargs grep -n "github.com/coder/coder/enterprise"
|
||||
find . -regex ".*\.go" |
|
||||
grep -v "./enterprise" |
|
||||
grep -v ./scripts/auditdocgen/ --include="*.go" |
|
||||
grep -v ./scripts/clidocgen/ --include="*.go" |
|
||||
xargs grep -n "github.com/coder/coder/enterprise"
|
||||
# reverse the exit code because we want this script to fail if grep finds anything.
|
||||
status=$?
|
||||
set -e
|
||||
|
||||
@@ -1,49 +1,56 @@
|
||||
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||
# {{ .Name }}
|
||||
{{ if .Cmd.Long }}
|
||||
{{ .Cmd.Long }}
|
||||
{{ else }}
|
||||
{{ .Cmd.Short }}
|
||||
# {{ fullName . }}
|
||||
|
||||
{{ with .Short }}
|
||||
{{ . }}
|
||||
|
||||
{{ end }}
|
||||
|
||||
{{- if .Cmd.Runnable}}
|
||||
{{ with .Aliases }}
|
||||
Aliases:
|
||||
{{- range $index, $alias := . }}
|
||||
* {{ $alias }}
|
||||
{{- end }}
|
||||
{{ end }}
|
||||
|
||||
{{- if .Use }}
|
||||
## Usage
|
||||
```console
|
||||
{{.Cmd.UseLine}}
|
||||
{{ .FullUsage }}
|
||||
```
|
||||
{{end}}
|
||||
|
||||
{{- if .Cmd.HasExample}}
|
||||
## Examples
|
||||
{{- if .Long}}
|
||||
## Description
|
||||
```console
|
||||
{{.Cmd.Example}}
|
||||
{{.Long}}
|
||||
```
|
||||
{{end}}
|
||||
|
||||
{{- range $index, $cmd := .VisibleSubcommands }}
|
||||
{{- range $index, $cmd := visibleSubcommands . }}
|
||||
{{- if eq $index 0 }}
|
||||
## Subcommands
|
||||
| Name | Purpose |
|
||||
| ---- | ----- |
|
||||
{{- end }}
|
||||
| [{{ $cmd.Name | wrapCode }}](./{{if $.AtRoot}}cli/{{end}}{{commandURI $cmd}}) | {{ $cmd.Short }} |
|
||||
| [{{ $cmd.Name | wrapCode }}](./{{if atRoot $}}cli/{{end}}{{commandURI $cmd}}) | {{ $cmd.Short }} |
|
||||
{{- end}}
|
||||
{{ "" }}
|
||||
{{- range $index, $flag := .Flags }}
|
||||
{{- range $index, $opt := visibleOptions . }}
|
||||
{{- if eq $index 0 }}
|
||||
## Flags
|
||||
## Options
|
||||
{{- end }}
|
||||
### --{{ $flag.Name }}{{ if $flag.Shorthand}}, -{{ $flag.Shorthand }}{{end}}
|
||||
{{ $flag.Usage | stripEnv | newLinesToBr }}
|
||||
<br/>
|
||||
| | |
|
||||
| --- | --- |
|
||||
{{- with $flag.Usage | parseEnv }}
|
||||
| Consumes | {{ . | wrapCode }} |
|
||||
### {{ with $opt.FlagShorthand}}-{{ . }}, {{end}}--{{ $opt.Flag }}
|
||||
{{" "}}
|
||||
{{ tableHeader }}
|
||||
| Type | {{ $opt.Value.Type | wrapCode }} |
|
||||
{{- with $opt.Env }}
|
||||
| Environment | {{ (print "$" .) | wrapCode }} |
|
||||
{{- end }}
|
||||
{{- with $flag.DefValue }}
|
||||
| Default | {{" "}} {{- . | wrapCode }} |
|
||||
{{- with $opt.Default }}
|
||||
| Default | {{- . | wrapCode }} |
|
||||
{{ "" }}
|
||||
{{ end }}
|
||||
{{ "" }}
|
||||
{{ $opt.Description | newLinesToBr }}
|
||||
{{- end}}
|
||||
|
||||
+66
-93
@@ -5,17 +5,15 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/acarl005/stripansi"
|
||||
|
||||
"github.com/coder/coder/buildinfo"
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
"github.com/coder/flog"
|
||||
)
|
||||
|
||||
@@ -24,84 +22,73 @@ var commandTemplateRaw string
|
||||
|
||||
var commandTemplate *template.Template
|
||||
|
||||
var envRegex = regexp.MustCompile(`Consumes (\$\w+).?$`)
|
||||
|
||||
func parseEnv(flagUsage string) string {
|
||||
flagUsage = stripansi.Strip(flagUsage)
|
||||
|
||||
ss := envRegex.FindStringSubmatch(flagUsage)
|
||||
if len(ss) == 0 {
|
||||
return ""
|
||||
}
|
||||
return ss[len(ss)-1]
|
||||
}
|
||||
|
||||
func stripEnv(flagUsage string) string {
|
||||
flagUsage = stripansi.Strip(flagUsage)
|
||||
ss := envRegex.FindStringSubmatch(flagUsage)
|
||||
if len(ss) == 0 {
|
||||
return flagUsage
|
||||
}
|
||||
return strings.TrimSpace(strings.ReplaceAll(flagUsage, ss[0], ""))
|
||||
}
|
||||
|
||||
func init() {
|
||||
commandTemplate = template.Must(
|
||||
template.New("command.tpl").Funcs(template.FuncMap{
|
||||
"visibleSubcommands": func(cmd *clibase.Cmd) []*clibase.Cmd {
|
||||
var visible []*clibase.Cmd
|
||||
for _, sub := range cmd.Children {
|
||||
if sub.Hidden {
|
||||
continue
|
||||
}
|
||||
visible = append(visible, sub)
|
||||
}
|
||||
return visible
|
||||
},
|
||||
"visibleOptions": func(cmd *clibase.Cmd) []clibase.Option {
|
||||
var visible []clibase.Option
|
||||
for _, opt := range cmd.Options {
|
||||
if opt.Hidden {
|
||||
continue
|
||||
}
|
||||
visible = append(visible, opt)
|
||||
}
|
||||
return visible
|
||||
},
|
||||
"atRoot": func(cmd *clibase.Cmd) bool {
|
||||
return cmd.FullName() == "coder"
|
||||
},
|
||||
"newLinesToBr": func(s string) string {
|
||||
return strings.ReplaceAll(s, "\n", "<br/>")
|
||||
},
|
||||
"wrapCode": func(s string) string {
|
||||
return fmt.Sprintf("<code>%s</code>", s)
|
||||
},
|
||||
"parseEnv": parseEnv,
|
||||
"stripEnv": stripEnv,
|
||||
"commandURI": func(cmd *cobra.Command) string {
|
||||
"commandURI": func(cmd *clibase.Cmd) string {
|
||||
return strings.TrimSuffix(
|
||||
fmtDocFilename(cmd),
|
||||
".md",
|
||||
)
|
||||
},
|
||||
"fullName": fullName,
|
||||
"tableHeader": func() string {
|
||||
return `| | |
|
||||
| --- | --- |`
|
||||
},
|
||||
},
|
||||
).Parse(strings.TrimSpace(commandTemplateRaw)),
|
||||
)
|
||||
}
|
||||
|
||||
func writeCommand(w io.Writer, cmd *cobra.Command) error {
|
||||
var (
|
||||
flags []*pflag.Flag
|
||||
inheritedFlags []*pflag.Flag
|
||||
)
|
||||
cmd.Flags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Hidden {
|
||||
return
|
||||
}
|
||||
flags = append(flags, f)
|
||||
})
|
||||
cmd.InheritedFlags().VisitAll(func(f *pflag.Flag) {
|
||||
if f.Hidden {
|
||||
return
|
||||
}
|
||||
inheritedFlags = append(inheritedFlags, f)
|
||||
})
|
||||
func fullName(cmd *clibase.Cmd) string {
|
||||
if cmd.FullName() == "coder" {
|
||||
return "coder"
|
||||
}
|
||||
return strings.TrimPrefix(cmd.FullName(), "coder ")
|
||||
}
|
||||
|
||||
func fmtDocFilename(cmd *clibase.Cmd) string {
|
||||
if cmd.FullName() == "coder" {
|
||||
// Special case for index.
|
||||
return "../cli.md"
|
||||
}
|
||||
name := strings.ReplaceAll(fullName(cmd), " ", "_")
|
||||
return fmt.Sprintf("%s.md", name)
|
||||
}
|
||||
|
||||
func writeCommand(w io.Writer, cmd *clibase.Cmd) error {
|
||||
var b strings.Builder
|
||||
err := commandTemplate.Execute(&b, map[string]any{
|
||||
"Name": fullCommandName(cmd),
|
||||
"Cmd": cmd,
|
||||
"Flags": flags,
|
||||
"InheritedFlags": inheritedFlags,
|
||||
"AtRoot": cmd.Parent() == nil,
|
||||
"VisibleSubcommands": func() []*cobra.Command {
|
||||
var scs []*cobra.Command
|
||||
for _, sub := range cmd.Commands() {
|
||||
if sub.Hidden {
|
||||
continue
|
||||
}
|
||||
scs = append(scs, sub)
|
||||
}
|
||||
return scs
|
||||
}(),
|
||||
})
|
||||
err := commandTemplate.Execute(&b, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -118,41 +105,25 @@ func writeCommand(w io.Writer, cmd *cobra.Command) error {
|
||||
}
|
||||
content = strings.ReplaceAll(content, cwd, ".")
|
||||
|
||||
homedir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
content = strings.ReplaceAll(content, homedir, "~")
|
||||
|
||||
_, err = w.Write([]byte(content))
|
||||
return err
|
||||
}
|
||||
|
||||
func fullCommandName(cmd *cobra.Command) string {
|
||||
name := cmd.Name()
|
||||
if cmd.Parent() != nil {
|
||||
return fullCommandName(cmd.Parent()) + " " + name
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func fmtDocFilename(cmd *cobra.Command) string {
|
||||
fullName := fullCommandName(cmd)
|
||||
if fullName == "coder" {
|
||||
// Special case for index.
|
||||
return "../cli.md"
|
||||
}
|
||||
name := strings.ReplaceAll(fullName, " ", "_")
|
||||
return fmt.Sprintf("%s.md", name)
|
||||
}
|
||||
|
||||
func generateDocsTree(cmd *cobra.Command, basePath string) error {
|
||||
func genTree(dir string, cmd *clibase.Cmd, wroteLog map[string]*clibase.Cmd) error {
|
||||
if cmd.Hidden {
|
||||
return nil
|
||||
}
|
||||
|
||||
if cmd.Name() == "server" {
|
||||
// The server command is now managed by clibase and needs a new generator.
|
||||
return nil
|
||||
}
|
||||
|
||||
path := filepath.Join(dir, fmtDocFilename(cmd))
|
||||
// Write out root.
|
||||
fi, err := os.OpenFile(
|
||||
filepath.Join(basePath, fmtDocFilename(cmd)),
|
||||
path,
|
||||
os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o644,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -165,11 +136,13 @@ func generateDocsTree(cmd *cobra.Command, basePath string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
flog.Info("Generated docs for %q at %v", fullCommandName(cmd), fi.Name())
|
||||
|
||||
// Recursively generate docs.
|
||||
for _, subcommand := range cmd.Commands() {
|
||||
err = generateDocsTree(subcommand, basePath)
|
||||
flog.Successf(
|
||||
"wrote\t%s",
|
||||
fi.Name(),
|
||||
)
|
||||
wroteLog[path] = cmd
|
||||
for _, sub := range cmd.Children {
|
||||
err = genTree(dir, sub, wroteLog)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_parseEnv(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
type args struct {
|
||||
flagUsage string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
"no env",
|
||||
args{"Perform a trial run with no changes made, showing a diff at the end."},
|
||||
"",
|
||||
},
|
||||
{
|
||||
"env",
|
||||
args{`Specifies the path to an SSH config.
|
||||
Consumes $CODER_SSH_CONFIG_FILE`},
|
||||
"$CODER_SSH_CONFIG_FILE",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := parseEnv(tt.args.flagUsage); got != tt.want {
|
||||
t.Errorf("parseEnv() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+122
-56
@@ -2,14 +2,17 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/coder/coder/cli"
|
||||
"github.com/coder/coder/cli/clibase"
|
||||
"github.com/coder/coder/enterprise/cli"
|
||||
"github.com/coder/flog"
|
||||
)
|
||||
|
||||
// route is an individual page object in the docs manifest.json.
|
||||
type route struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
@@ -19,90 +22,153 @@ type route struct {
|
||||
Children []route `json:"children,omitempty"`
|
||||
}
|
||||
|
||||
// manifest describes the entire documentation index.
|
||||
type manifest struct {
|
||||
Versions []string `json:"versions,omitempty"`
|
||||
Routes []route `json:"routes,omitempty"`
|
||||
}
|
||||
|
||||
func main() {
|
||||
func unsetCoderEnv() {
|
||||
for _, env := range os.Environ() {
|
||||
if strings.HasPrefix(env, "CODER_") {
|
||||
split := strings.SplitN(env, "=", 2)
|
||||
if err := os.Unsetenv(split[0]); err != nil {
|
||||
log.Fatal("Unable to unset ", split[0], ": ", err)
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, v := range map[string]string{
|
||||
"CODER_CONFIG_DIR": "~/.config/coderv2",
|
||||
"CODER_CACHE_DIRECTORY": "~/.cache/coder",
|
||||
} {
|
||||
if err := os.Setenv(k, v); err != nil {
|
||||
log.Fatal("Unable to set default value for ", k, ": ", err)
|
||||
}
|
||||
|
||||
func deleteEmptyDirs(dir string) error {
|
||||
return filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
ents, err := os.ReadDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ents) == 0 {
|
||||
flog.Infof("deleting empty dir\t %v", path)
|
||||
err = os.Remove(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Get the cmd CLI
|
||||
cmd := cli.Root(cli.AGPL())
|
||||
func main() {
|
||||
unsetCoderEnv()
|
||||
|
||||
// Get paths
|
||||
basePath := os.Getenv("BASE_PATH")
|
||||
if basePath == "" {
|
||||
log.Fatal("BASE_PATH should be defined")
|
||||
}
|
||||
markdownDocsDir := path.Join(basePath, "docs/cli")
|
||||
manifestFilepath := path.Join(basePath, "docs/manifest.json")
|
||||
|
||||
// Generate markdown
|
||||
err := generateDocsTree(cmd, markdownDocsDir)
|
||||
workdir, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal("Error on generating CLI markdown docs: ", err)
|
||||
flog.Fatalf("getwd: %v", err)
|
||||
}
|
||||
root := (&cli.RootCmd{})
|
||||
|
||||
// wroteMap indexes file paths to commands.
|
||||
wroteMap := make(map[string]*clibase.Cmd)
|
||||
|
||||
var (
|
||||
docsDir = filepath.Join(workdir, "docs")
|
||||
cliMarkdownDir = filepath.Join(docsDir, "cli")
|
||||
)
|
||||
|
||||
cmd, err := root.Command(root.EnterpriseSubcommands())
|
||||
if err != nil {
|
||||
flog.Fatalf("creating command: %v", err)
|
||||
}
|
||||
err = genTree(
|
||||
cliMarkdownDir,
|
||||
cmd,
|
||||
wroteMap,
|
||||
)
|
||||
if err != nil {
|
||||
flog.Fatalf("generating markdowns: %v", err)
|
||||
}
|
||||
|
||||
// Create CLI routes
|
||||
var cliRoutes []route
|
||||
files, err := os.ReadDir(markdownDocsDir)
|
||||
// Delete old files
|
||||
err = filepath.Walk(cliMarkdownDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
_, ok := wroteMap[path]
|
||||
if !ok {
|
||||
flog.Infof("deleting old doc\t %v", path)
|
||||
if err := os.Remove(path); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal("Error on loading docs/cli files: ", err)
|
||||
}
|
||||
for _, file := range files {
|
||||
// Remove file extension and prefix
|
||||
title := strings.Replace(file.Name(), ".md", "", 1)
|
||||
title = strings.Replace(title, "coder_", "", 1)
|
||||
title = strings.Replace(title, "_", " ", -1)
|
||||
|
||||
cliRoutes = append(cliRoutes, route{
|
||||
Title: title,
|
||||
Path: "./cli/" + file.Name(),
|
||||
})
|
||||
flog.Fatalf("deleting old docs: %v", err)
|
||||
}
|
||||
|
||||
// Read manifest
|
||||
jsonFile, err := os.ReadFile(manifestFilepath)
|
||||
err = deleteEmptyDirs(cliMarkdownDir)
|
||||
if err != nil {
|
||||
log.Fatal("Error on open docs/manifest.json: ", err)
|
||||
}
|
||||
var manifest manifest
|
||||
err = json.Unmarshal(jsonFile, &manifest)
|
||||
if err != nil {
|
||||
log.Fatal("Error on unmarshal manifest.json: ", err)
|
||||
flog.Fatalf("deleting empty dirs: %v", err)
|
||||
}
|
||||
|
||||
// Update manifest
|
||||
for i, r := range manifest.Routes {
|
||||
if r.Title != "Command Line" {
|
||||
manifestPath := filepath.Join(docsDir, "manifest.json")
|
||||
|
||||
manifestByt, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
flog.Fatalf("reading manifest: %v", err)
|
||||
}
|
||||
|
||||
var manifest manifest
|
||||
err = json.Unmarshal(manifestByt, &manifest)
|
||||
if err != nil {
|
||||
flog.Fatalf("unmarshalling manifest: %v", err)
|
||||
}
|
||||
|
||||
var found bool
|
||||
for i := range manifest.Routes {
|
||||
rt := &manifest.Routes[i]
|
||||
if rt.Title != "Command Line" {
|
||||
continue
|
||||
}
|
||||
rt.Children = nil
|
||||
found = true
|
||||
for path, cmd := range wroteMap {
|
||||
relPath, err := filepath.Rel(docsDir, path)
|
||||
if err != nil {
|
||||
flog.Fatalf("getting relative path: %v", err)
|
||||
}
|
||||
rt.Children = append(rt.Children, route{
|
||||
Title: fullName(cmd),
|
||||
Description: cmd.Short,
|
||||
Path: relPath,
|
||||
})
|
||||
}
|
||||
// Sort children by title because wroteMap iteration is
|
||||
// non-deterministic.
|
||||
sort.Slice(rt.Children, func(i, j int) bool {
|
||||
return rt.Children[i].Title < rt.Children[j].Title
|
||||
})
|
||||
}
|
||||
|
||||
manifest.Routes[i].Children = cliRoutes
|
||||
break
|
||||
if !found {
|
||||
flog.Fatalf("could not find Command Line route in manifest")
|
||||
}
|
||||
manifestFile, err := json.MarshalIndent(manifest, "", " ")
|
||||
|
||||
manifestByt, err = json.MarshalIndent(manifest, "", " ")
|
||||
if err != nil {
|
||||
log.Fatal("Error on marshal manifest.json: ", err)
|
||||
flog.Fatalf("marshaling manifest: %v", err)
|
||||
}
|
||||
err = os.WriteFile(manifestFilepath, manifestFile, 0o644) // #nosec
|
||||
|
||||
err = os.WriteFile(manifestPath, manifestByt, 0o600)
|
||||
if err != nil {
|
||||
log.Fatal("Error on write update on manifest.json: ", err)
|
||||
flog.Fatalf("writing manifest: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user