Release Please / Release Please (push) Skipped
Release / GoReleaser (push) Skipped
Tests / Secret scan (gitleaks) (push) Skipped
Tests / Test (Go 1.25) (push) Skipped
Tests / Test (Go stable) (push) Skipped
Tests / Secret scan (gitleaks) (pull_request) Successful in 4s
Tests / Test (Go 1.25) (pull_request) Successful in 59s
Tests / Test (Go stable) (pull_request) Successful in 1m7s
Showroom-safe: the tree no longer carries internal hosts, IPs, ports, personal names, client domains or client file names. Behaviour is unchanged and now supplied per deployment. - catalog: hardcoded mail-org / project classifiers become a YAML-driven Classifier (OO_CATALOG_CONFIG or --config); neutral default classifies nothing as work. New catalog/classify.go + example + tests. - storage_fallback: drop the baked-in MinIO endpoint IP; require MINIO_ENDPOINT (+ keys) from the env. - kontolink: build DocEditor links from $ONLYOFFICE_URL instead of a hardcoded portal host; kontoblatt: no client file id in the output name. - oo: load .env CLI-wide (bootstrap.LoadEnv in execute) so non-authenticating commands (catalog scans) also see config. - genericize comments/docs/fixtures (AGENTS, README, .env.example, crm-associations, catalog tests, ES/pdfattach tests, mails).
118 lines
2.8 KiB
Go
118 lines
2.8 KiB
Go
package catalog
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// ScanProjectsRoot finds git roots under root (max depth) and emits company rows.
|
|
func ScanProjectsRoot(root string, maxDepth int) (*Document, error) {
|
|
return ScanProjectsRootOpts(root, maxDepth, ScanOptions{})
|
|
}
|
|
|
|
// ScanProjectsRootOpts is ScanProjectsRoot with deployment classification rules.
|
|
func ScanProjectsRootOpts(root string, maxDepth int, opts ScanOptions) (*Document, error) {
|
|
root = filepath.Clean(root)
|
|
if maxDepth <= 0 {
|
|
maxDepth = 4
|
|
}
|
|
cl := opts.Classifier
|
|
if cl == nil {
|
|
cl = DefaultClassifier()
|
|
}
|
|
st, err := os.Stat(root)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !st.IsDir() {
|
|
return nil, fmt.Errorf("not a directory: %s", root)
|
|
}
|
|
|
|
var entries []Entry
|
|
err = walkGitRoots(root, root, 0, maxDepth, cl, &entries)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Also top-level dirs as company stubs (even without git) — coverage 1B.
|
|
dents, _ := os.ReadDir(root)
|
|
for _, d := range dents {
|
|
if !d.IsDir() || strings.HasPrefix(d.Name(), ".") {
|
|
continue
|
|
}
|
|
name := d.Name()
|
|
skip := map[string]struct{}{
|
|
"node_modules": {}, "vendor": {}, ".cache": {},
|
|
}
|
|
if _, ok := skip[name]; ok {
|
|
continue
|
|
}
|
|
path := filepath.Join(root, name)
|
|
id := EntryID("company", "", name)
|
|
role, zone := cl.ClassifyProject(name, "")
|
|
entries = append(entries, Entry{
|
|
ID: id,
|
|
Kind: "company",
|
|
Name: name,
|
|
Sources: []string{path},
|
|
Zone: zone,
|
|
Role: role,
|
|
Status: "new",
|
|
Notes: "top_level_dir",
|
|
})
|
|
}
|
|
|
|
return MergeDocs(&Document{Entries: entries}), nil
|
|
}
|
|
|
|
func walkGitRoots(root, dir string, depth, maxDepth int, cl *Classifier, out *[]Entry) error {
|
|
if depth > maxDepth {
|
|
return nil
|
|
}
|
|
// is this a git root?
|
|
if st, err := os.Stat(filepath.Join(dir, ".git")); err == nil && (st.IsDir() || st.Mode().IsRegular()) {
|
|
name := filepath.Base(dir)
|
|
remote := gitRemoteOrigin(dir)
|
|
role, zone := cl.ClassifyProject(name, remote)
|
|
*out = append(*out, Entry{
|
|
ID: EntryID("company", "", name),
|
|
Kind: "company",
|
|
Name: name,
|
|
Sources: []string{dir},
|
|
Remote: remote,
|
|
GitRoot: dir,
|
|
Zone: zone,
|
|
Role: role,
|
|
Status: "new",
|
|
})
|
|
return nil // do not descend into nested repos from a git root
|
|
}
|
|
dents, err := os.ReadDir(dir)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
for _, d := range dents {
|
|
if !d.IsDir() {
|
|
continue
|
|
}
|
|
name := d.Name()
|
|
if name == ".git" || name == "node_modules" || name == "vendor" || name == ".venv" || name == "dist" {
|
|
continue
|
|
}
|
|
_ = walkGitRoots(root, filepath.Join(dir, name), depth+1, maxDepth, cl, out)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func gitRemoteOrigin(dir string) string {
|
|
cmd := exec.Command("git", "-C", dir, "remote", "get-url", "origin")
|
|
b, err := cmd.Output()
|
|
if err != nil {
|
|
return ""
|
|
}
|
|
return strings.TrimSpace(string(b))
|
|
}
|