refactor: rename universal_rebuild/ → provider/
- Rename directory - Update all 9 devops scripts - Update 4 generator source files - Rebuild all binaries
This commit is contained in:
@@ -1,289 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// ops_docs_gen: builds per-service operations docs from resources_ops_yaml.
|
||||
//
|
||||
// Env:
|
||||
// - NUBES_OPS_YAML_DIR (default: <repo>/resources_ops_yaml)
|
||||
// - NUBES_OPS_DOCS_DIR (default: <repo>/../docs/30_registry/resources/operations)
|
||||
|
||||
func main() {
|
||||
repoRoot, err := findRepoRoot()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
yamlDir := strings.TrimSpace(os.Getenv("NUBES_OPS_YAML_DIR"))
|
||||
if yamlDir == "" {
|
||||
yamlDir = filepath.Join(repoRoot, "resources_ops_yaml")
|
||||
}
|
||||
|
||||
docsDir := strings.TrimSpace(os.Getenv("NUBES_OPS_DOCS_DIR"))
|
||||
if docsDir == "" {
|
||||
root := filepath.Dir(repoRoot)
|
||||
docsDir = filepath.Join(root, "docs", "30_registry", "resources", "operations")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(docsDir, 0o755); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
entries := []docEntry{}
|
||||
|
||||
err = filepath.WalkDir(yamlDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") {
|
||||
return nil
|
||||
}
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var spec ServiceOpsSpec
|
||||
if err := yaml.Unmarshal(b, &spec); err != nil {
|
||||
return err
|
||||
}
|
||||
if spec.ServiceID == 0 || spec.Name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("%d_%s.md", spec.ServiceID, sanitizeName(spec.Name))
|
||||
outPath := filepath.Join(docsDir, fileName)
|
||||
if err := writeServiceDoc(outPath, spec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entries = append(entries, docEntry{
|
||||
ServiceID: spec.ServiceID,
|
||||
Name: spec.Name,
|
||||
File: fileName,
|
||||
Title: serviceTitle(spec),
|
||||
OpCount: len(spec.Operations),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sort.Slice(entries, func(i, j int) bool { return entries[i].ServiceID < entries[j].ServiceID })
|
||||
if err := writeIndex(filepath.Join(docsDir, "index.md"), entries); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Generated %d operations docs in %s\n", len(entries), docsDir)
|
||||
}
|
||||
|
||||
type docEntry struct {
|
||||
ServiceID int
|
||||
Name string
|
||||
File string
|
||||
Title string
|
||||
OpCount int
|
||||
}
|
||||
|
||||
func writeServiceDoc(path string, spec ServiceOpsSpec) error {
|
||||
var b strings.Builder
|
||||
b.WriteString(fmt.Sprintf("# Operations for nubes_%s\n\n", spec.Name))
|
||||
b.WriteString(fmt.Sprintf("Service ID: `%d`\n\n", spec.ServiceID))
|
||||
|
||||
if title := serviceTitle(spec); title != "" {
|
||||
b.WriteString(fmt.Sprintf("Service: %s\n\n", title))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(spec.ServiceMan) != "" {
|
||||
b.WriteString("## UI description\n\n")
|
||||
b.WriteString(spec.ServiceMan)
|
||||
b.WriteString("\n\n")
|
||||
}
|
||||
|
||||
b.WriteString("## Operations\n\n")
|
||||
b.WriteString("Non-CRUD operations are typically executed via an action resource.\n")
|
||||
b.WriteString("Domain operations (for example create_user, create_database) are best modeled as separate resources.\n\n")
|
||||
if len(spec.Operations) == 0 {
|
||||
b.WriteString("No operations found.\n")
|
||||
return os.WriteFile(path, []byte(b.String()), 0o644)
|
||||
}
|
||||
|
||||
for _, op := range spec.Operations {
|
||||
b.WriteString(fmt.Sprintf("- `%s` (id: %d)\n", op.Name, op.ID))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
for _, op := range spec.Operations {
|
||||
b.WriteString(fmt.Sprintf("## Operation: %s\n\n", op.Name))
|
||||
b.WriteString(fmt.Sprintf("Operation ID: `%d`\n\n", op.ID))
|
||||
if strings.TrimSpace(op.Man) != "" {
|
||||
b.WriteString(op.Man)
|
||||
b.WriteString("\n\n")
|
||||
}
|
||||
if len(op.Params) == 0 {
|
||||
b.WriteString("No parameters.\n\n")
|
||||
continue
|
||||
}
|
||||
|
||||
b.WriteString("| Code | Type | Required | Default | ID | RefSvcId | ValueList | Func | Regex | Min | Max | Sensitive | DependsOn |\n")
|
||||
b.WriteString("|---|---|---|---|---|---|---|---|---|---|---|---|---|\n")
|
||||
for _, p := range op.Params {
|
||||
b.WriteString(fmt.Sprintf("| `%s` | `%s` | `%t` | `%s` | `%d` | `%s` | `%s` | `%s` | `%s` | `%s` | `%s` | `%t` | `%s` |\n",
|
||||
esc(p.Code),
|
||||
esc(p.DataType),
|
||||
p.Required,
|
||||
esc(formatValue(p.Default)),
|
||||
p.ID,
|
||||
esc(formatRef(p.RefSvcId)),
|
||||
esc(formatList(p.ValueList)),
|
||||
esc(p.Func),
|
||||
esc(p.Regex),
|
||||
esc(formatValue(p.MinValue)),
|
||||
esc(formatValue(p.MaxValue)),
|
||||
p.IsSensitive,
|
||||
esc(formatValue(p.DependsOn)),
|
||||
))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
return os.WriteFile(path, []byte(b.String()), 0o644)
|
||||
}
|
||||
|
||||
func writeIndex(path string, entries []docEntry) error {
|
||||
var b strings.Builder
|
||||
b.WriteString("# Operations by service\n\n")
|
||||
b.WriteString("Auto-generated list of available operations per service.\n\n")
|
||||
b.WriteString("Note: non-CRUD operations should be invoked via an action resource,\n")
|
||||
b.WriteString("and domain operations are best represented as dedicated resources.\n\n")
|
||||
for _, e := range entries {
|
||||
b.WriteString(fmt.Sprintf("- %d - nubes_%s (%d ops): [%s](%s)\n", e.ServiceID, e.Name, e.OpCount, e.Title, e.File))
|
||||
}
|
||||
return os.WriteFile(path, []byte(b.String()), 0o644)
|
||||
}
|
||||
|
||||
func sanitizeName(name string) string {
|
||||
name = strings.ToLower(strings.TrimSpace(name))
|
||||
name = strings.ReplaceAll(name, " ", "_")
|
||||
name = strings.ReplaceAll(name, "/", "_")
|
||||
name = strings.ReplaceAll(name, "\\", "_")
|
||||
name = strings.ReplaceAll(name, ":", "_")
|
||||
name = strings.ReplaceAll(name, "-", "_")
|
||||
return name
|
||||
}
|
||||
|
||||
func serviceTitle(spec ServiceOpsSpec) string {
|
||||
if strings.TrimSpace(spec.ServiceDisplayName) != "" {
|
||||
return spec.ServiceDisplayName
|
||||
}
|
||||
if strings.TrimSpace(spec.ServiceShortName) != "" {
|
||||
return spec.ServiceShortName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func formatList(items []string) string {
|
||||
if len(items) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(items, ",")
|
||||
}
|
||||
|
||||
func formatRef(value *int) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%d", *value)
|
||||
}
|
||||
|
||||
func formatValue(value interface{}) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
switch t := value.(type) {
|
||||
case string:
|
||||
return t
|
||||
default:
|
||||
b, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%v", value)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
func esc(value string) string {
|
||||
return strings.ReplaceAll(value, "|", "\\|")
|
||||
}
|
||||
|
||||
func findRepoRoot() (string, error) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
current := wd
|
||||
for i := 0; i < 8; i++ {
|
||||
candidate := filepath.Join(current, "resources_yaml")
|
||||
info, err := os.Stat(candidate)
|
||||
if err == nil && info.IsDir() {
|
||||
return current, nil
|
||||
}
|
||||
parent := filepath.Dir(current)
|
||||
if parent == current {
|
||||
break
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
return "", fmt.Errorf("failed to locate universal_rebuild repo root")
|
||||
}
|
||||
|
||||
// ===== spec =====
|
||||
|
||||
type ServiceOpsSpec struct {
|
||||
Name string `yaml:"name"`
|
||||
ServiceID int `yaml:"service_id"`
|
||||
ServiceDisplayName string `yaml:"service_display_name,omitempty"`
|
||||
ServiceShortName string `yaml:"service_short_name,omitempty"`
|
||||
ServiceMan string `yaml:"service_man,omitempty"`
|
||||
Operations []OperationSpec `yaml:"operations"`
|
||||
}
|
||||
|
||||
type OperationSpec struct {
|
||||
Name string `yaml:"name"`
|
||||
ID int `yaml:"id"`
|
||||
Man string `yaml:"man,omitempty"`
|
||||
Params []ParamSpec `yaml:"params"`
|
||||
}
|
||||
|
||||
type ParamSpec struct {
|
||||
ID int `yaml:"id"`
|
||||
Code string `yaml:"code"`
|
||||
DataType string `yaml:"data_type,omitempty"`
|
||||
Required bool `yaml:"required"`
|
||||
Default interface{} `yaml:"default,omitempty"`
|
||||
ValueList []string `yaml:"value_list,omitempty"`
|
||||
RefSvcId *int `yaml:"ref_svc_id,omitempty"`
|
||||
Func string `yaml:"func,omitempty"`
|
||||
Regex string `yaml:"regex,omitempty"`
|
||||
UniqueScope string `yaml:"unique_scope,omitempty"`
|
||||
MaxLength *int `yaml:"maxlength,omitempty"`
|
||||
MinLength *int `yaml:"minlength,omitempty"`
|
||||
MaxValue interface{} `yaml:"maxvalue,omitempty"`
|
||||
MinValue interface{} `yaml:"minvalue,omitempty"`
|
||||
Descr string `yaml:"descr,omitempty"`
|
||||
Man string `yaml:"man,omitempty"`
|
||||
Sort *int `yaml:"sort,omitempty"`
|
||||
DependsOn interface{} `yaml:"depends_on,omitempty"`
|
||||
IsModifiable *bool `yaml:"is_modifiable,omitempty"`
|
||||
IsSensitive bool `yaml:"is_sensitive,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
// Package docs — генерация ops-документации.
|
||||
package docs
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"ops-generator/internal/types"
|
||||
)
|
||||
|
||||
// Entry — запись в индексе документации.
|
||||
type Entry struct {
|
||||
ServiceID int
|
||||
Name string
|
||||
File string
|
||||
Title string
|
||||
OpCount int
|
||||
}
|
||||
|
||||
// ServiceDoc генерирует Markdown-документацию операций сервиса.
|
||||
func ServiceDoc(path string, spec types.ServiceOpsSpec) error {
|
||||
var b strings.Builder
|
||||
b.WriteString(fmt.Sprintf("# Operations for nubes_%s\n\n", spec.Name))
|
||||
b.WriteString(fmt.Sprintf("Service ID: `%d`\n\n", spec.ServiceID))
|
||||
|
||||
if t := Title(spec); t != "" {
|
||||
b.WriteString(fmt.Sprintf("Service: %s\n\n", t))
|
||||
}
|
||||
|
||||
if strings.TrimSpace(spec.ServiceMan) != "" {
|
||||
b.WriteString("## UI description\n\n")
|
||||
b.WriteString(spec.ServiceMan)
|
||||
b.WriteString("\n\n")
|
||||
}
|
||||
|
||||
b.WriteString("## Operations\n\n")
|
||||
b.WriteString("Non-CRUD operations are typically executed via an action resource.\n")
|
||||
b.WriteString("Domain operations (for example create_user, create_database) are best modeled as separate resources.\n\n")
|
||||
if len(spec.Operations) == 0 {
|
||||
b.WriteString("No operations found.\n")
|
||||
return os.WriteFile(path, []byte(b.String()), 0o644)
|
||||
}
|
||||
|
||||
for _, op := range spec.Operations {
|
||||
b.WriteString(fmt.Sprintf("- `%s` (id: %d)\n", op.Name, op.ID))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
|
||||
for _, op := range spec.Operations {
|
||||
b.WriteString(fmt.Sprintf("## Operation: %s\n\n", op.Name))
|
||||
b.WriteString(fmt.Sprintf("Operation ID: `%d`\n\n", op.ID))
|
||||
if strings.TrimSpace(op.Man) != "" {
|
||||
b.WriteString(op.Man)
|
||||
b.WriteString("\n\n")
|
||||
}
|
||||
if len(op.Params) == 0 {
|
||||
b.WriteString("No parameters.\n\n")
|
||||
continue
|
||||
}
|
||||
|
||||
b.WriteString("| Code | Type | Required | Default | ID | RefSvcId | ValueList | Func | Regex | Min | Max | Sensitive | DependsOn |\n")
|
||||
b.WriteString("|---|---|---|---|---|---|---|---|---|---|---|---|---|\n")
|
||||
for _, p := range op.Params {
|
||||
b.WriteString(fmt.Sprintf("| `%s` | `%s` | `%t` | `%s` | `%d` | `%s` | `%s` | `%s` | `%s` | `%s` | `%s` | `%t` | `%s` |\n",
|
||||
Esc(p.Code),
|
||||
Esc(p.DataType),
|
||||
p.Required,
|
||||
Esc(Value(p.Default)),
|
||||
p.ID,
|
||||
Esc(Ref(p.RefSvcId)),
|
||||
Esc(List(p.ValueList)),
|
||||
Esc(p.Func),
|
||||
Esc(p.Regex),
|
||||
Esc(Value(p.MinValue)),
|
||||
Esc(Value(p.MaxValue)),
|
||||
p.IsSensitive,
|
||||
Esc(Value(p.DependsOn)),
|
||||
))
|
||||
}
|
||||
b.WriteString("\n")
|
||||
}
|
||||
|
||||
return os.WriteFile(path, []byte(b.String()), 0o644)
|
||||
}
|
||||
|
||||
// Index генерирует index.md со списком всех операций.
|
||||
func Index(path string, entries []Entry) error {
|
||||
var b strings.Builder
|
||||
b.WriteString("# Operations by service\n\n")
|
||||
b.WriteString("Auto-generated list of available operations per service.\n\n")
|
||||
b.WriteString("Note: non-CRUD operations should be invoked via an action resource,\n")
|
||||
b.WriteString("and domain operations are best represented as dedicated resources.\n\n")
|
||||
for _, e := range entries {
|
||||
b.WriteString(fmt.Sprintf("- %d - nubes_%s (%d ops): [%s](%s)\n", e.ServiceID, e.Name, e.OpCount, e.Title, e.File))
|
||||
}
|
||||
return os.WriteFile(path, []byte(b.String()), 0o644)
|
||||
}
|
||||
|
||||
// Name нормализует имя файла.
|
||||
func Name(name string) string {
|
||||
name = strings.ToLower(strings.TrimSpace(name))
|
||||
name = strings.ReplaceAll(name, " ", "_")
|
||||
name = strings.ReplaceAll(name, "/", "_")
|
||||
name = strings.ReplaceAll(name, "\\", "_")
|
||||
name = strings.ReplaceAll(name, ":", "_")
|
||||
name = strings.ReplaceAll(name, "-", "_")
|
||||
return name
|
||||
}
|
||||
|
||||
// Title возвращает отображаемое имя сервиса.
|
||||
func Title(spec types.ServiceOpsSpec) string {
|
||||
if strings.TrimSpace(spec.ServiceDisplayName) != "" {
|
||||
return spec.ServiceDisplayName
|
||||
}
|
||||
if strings.TrimSpace(spec.ServiceShortName) != "" {
|
||||
return spec.ServiceShortName
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// List форматирует список значений.
|
||||
func List(items []string) string {
|
||||
if len(items) == 0 {
|
||||
return ""
|
||||
}
|
||||
return strings.Join(items, ",")
|
||||
}
|
||||
|
||||
// Ref форматирует RefSvcId.
|
||||
func Ref(value *int) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%d", *value)
|
||||
}
|
||||
|
||||
// Value форматирует значение параметра.
|
||||
func Value(value interface{}) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
switch t := value.(type) {
|
||||
case string:
|
||||
return t
|
||||
default:
|
||||
b, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return fmt.Sprintf("%v", value)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
}
|
||||
|
||||
// Esc экранирует pipe-символы для Markdown-таблиц.
|
||||
func Esc(value string) string {
|
||||
return strings.ReplaceAll(value, "|", "\\|")
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Package types — структуры данных для ops-документации.
|
||||
package types
|
||||
|
||||
// ServiceOpsSpec — YAML-спек операций сервиса.
|
||||
type ServiceOpsSpec struct {
|
||||
Name string `yaml:"name"`
|
||||
ServiceID int `yaml:"service_id"`
|
||||
ServiceDisplayName string `yaml:"service_display_name,omitempty"`
|
||||
ServiceShortName string `yaml:"service_short_name,omitempty"`
|
||||
ServiceMan string `yaml:"service_man,omitempty"`
|
||||
Operations []OperationSpec `yaml:"operations"`
|
||||
}
|
||||
|
||||
// OperationSpec — одна операция.
|
||||
type OperationSpec struct {
|
||||
Name string `yaml:"name"`
|
||||
ID int `yaml:"id"`
|
||||
Man string `yaml:"man,omitempty"`
|
||||
Params []ParamSpec `yaml:"params"`
|
||||
}
|
||||
|
||||
// ParamSpec — параметр операции.
|
||||
type ParamSpec struct {
|
||||
ID int `yaml:"id"`
|
||||
Code string `yaml:"code"`
|
||||
DataType string `yaml:"data_type,omitempty"`
|
||||
Required bool `yaml:"required"`
|
||||
Default interface{} `yaml:"default,omitempty"`
|
||||
ValueList []string `yaml:"value_list,omitempty"`
|
||||
RefSvcId *int `yaml:"ref_svc_id,omitempty"`
|
||||
Func string `yaml:"func,omitempty"`
|
||||
Regex string `yaml:"regex,omitempty"`
|
||||
UniqueScope string `yaml:"unique_scope,omitempty"`
|
||||
MaxLength *int `yaml:"maxlength,omitempty"`
|
||||
MinLength *int `yaml:"minlength,omitempty"`
|
||||
MaxValue interface{} `yaml:"maxvalue,omitempty"`
|
||||
MinValue interface{} `yaml:"minvalue,omitempty"`
|
||||
Descr string `yaml:"descr,omitempty"`
|
||||
Man string `yaml:"man,omitempty"`
|
||||
Sort *int `yaml:"sort,omitempty"`
|
||||
DependsOn interface{} `yaml:"depends_on,omitempty"`
|
||||
IsModifiable *bool `yaml:"is_modifiable,omitempty"`
|
||||
IsSensitive bool `yaml:"is_sensitive,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// ops-generator — генератор per-service operations документации.
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"ops-generator/internal/docs"
|
||||
"ops-generator/internal/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
repoRoot, err := findRepoRoot()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
yamlDir := strings.TrimSpace(os.Getenv("NUBES_OPS_YAML_DIR"))
|
||||
if yamlDir == "" {
|
||||
yamlDir = filepath.Join(repoRoot, "resources_ops_yaml")
|
||||
}
|
||||
|
||||
docsDir := strings.TrimSpace(os.Getenv("NUBES_OPS_DOCS_DIR"))
|
||||
if docsDir == "" {
|
||||
root := filepath.Dir(repoRoot)
|
||||
docsDir = filepath.Join(root, "docs", "30_registry", "resources", "operations")
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(docsDir, 0o755); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
entries := []docs.Entry{}
|
||||
|
||||
err = filepath.WalkDir(yamlDir, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() || !strings.HasSuffix(d.Name(), ".yaml") {
|
||||
return nil
|
||||
}
|
||||
b, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var spec types.ServiceOpsSpec
|
||||
if err := yaml.Unmarshal(b, &spec); err != nil {
|
||||
return err
|
||||
}
|
||||
if spec.ServiceID == 0 || spec.Name == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("%d_%s.md", spec.ServiceID, docs.Name(spec.Name))
|
||||
outPath := filepath.Join(docsDir, fileName)
|
||||
if err := docs.ServiceDoc(outPath, spec); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entries = append(entries, docs.Entry{
|
||||
ServiceID: spec.ServiceID,
|
||||
Name: spec.Name,
|
||||
File: fileName,
|
||||
Title: docs.Title(spec),
|
||||
OpCount: len(spec.Operations),
|
||||
})
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
sort.Slice(entries, func(i, j int) bool { return entries[i].ServiceID < entries[j].ServiceID })
|
||||
if err := docs.Index(filepath.Join(docsDir, "index.md"), entries); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("Generated %d operations docs in %s\n", len(entries), docsDir)
|
||||
}
|
||||
|
||||
func findRepoRoot() (string, error) {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
current := wd
|
||||
for i := 0; i < 8; i++ {
|
||||
candidate := filepath.Join(current, "resources_yaml")
|
||||
info, err := os.Stat(candidate)
|
||||
if err == nil && info.IsDir() {
|
||||
return current, nil
|
||||
}
|
||||
parent := filepath.Dir(current)
|
||||
if parent == current {
|
||||
break
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
return "", fmt.Errorf("failed to locate provider repo root")
|
||||
}
|
||||
Reference in New Issue
Block a user