feat: soporte projects y vaults en registry
Añade tablas projects y vaults a registry.db con FTS5, modelos Go,
parser de project.md y vault.yaml, CRUD completo en store, hashing
determinista, validación, y soporte en el indexer para escanear
projects/{name}/ con sus apps, analysis y vaults anidados.
Migration 010 crea las tablas, triggers FTS5, y columna project_id
en apps/analysis. El indexer preserva records remotos (repo_url) al
reindexar, igual que apps/analysis.
This commit is contained in:
@@ -103,6 +103,27 @@ type rawAnalysis struct {
|
||||
RepoURL string `yaml:"repo_url"`
|
||||
}
|
||||
|
||||
// rawProject mirrors the YAML frontmatter of a project .md file.
|
||||
type rawProject struct {
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description"`
|
||||
Tags []string `yaml:"tags"`
|
||||
RepoURL string `yaml:"repo_url"`
|
||||
}
|
||||
|
||||
// rawVaultFile mirrors the YAML of a vault.yaml manifest file.
|
||||
type rawVaultFile struct {
|
||||
Vaults []rawVaultEntry `yaml:"vaults"`
|
||||
}
|
||||
|
||||
// rawVaultEntry describes a single vault in vault.yaml.
|
||||
type rawVaultEntry struct {
|
||||
Name string `yaml:"name"`
|
||||
Description string `yaml:"description"`
|
||||
Path string `yaml:"path"`
|
||||
Tags []string `yaml:"tags"`
|
||||
}
|
||||
|
||||
// extractFrontmatter splits a .md file into YAML frontmatter and body.
|
||||
func extractFrontmatter(data []byte) ([]byte, []byte, error) {
|
||||
content := data
|
||||
@@ -356,6 +377,99 @@ func ParseAnalysisMD(path string, root string) (*Analysis, error) {
|
||||
return an, nil
|
||||
}
|
||||
|
||||
// ParseProjectMD parses a project .md file into a Project.
|
||||
func ParseProjectMD(path string, root string) (*Project, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", path, err)
|
||||
}
|
||||
|
||||
fm, body, err := extractFrontmatter(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parsing %s: %w", path, err)
|
||||
}
|
||||
|
||||
var raw rawProject
|
||||
if err := yaml.Unmarshal(fm, &raw); err != nil {
|
||||
return nil, fmt.Errorf("parsing YAML in %s: %w", path, err)
|
||||
}
|
||||
|
||||
if raw.Name == "" {
|
||||
return nil, fmt.Errorf("%s: name is required", path)
|
||||
}
|
||||
if raw.Description == "" {
|
||||
return nil, fmt.Errorf("%s: description is required", path)
|
||||
}
|
||||
|
||||
sections := extractSections(body)
|
||||
|
||||
p := &Project{
|
||||
ID: raw.Name,
|
||||
Name: raw.Name,
|
||||
Description: raw.Description,
|
||||
Tags: raw.Tags,
|
||||
RepoURL: raw.RepoURL,
|
||||
DirPath: filepath.Join("projects", raw.Name),
|
||||
Documentation: sections.documentation,
|
||||
Notes: sections.notes,
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// ParseVaultYAML parses a vault.yaml manifest into a slice of Vaults.
|
||||
// projectID is the owning project ID, or "" for registry-level vaults.
|
||||
// vaultsDir is the directory containing vault.yaml (used to detect symlinks).
|
||||
func ParseVaultYAML(path string, projectID string, vaultsDir string) ([]*Vault, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading %s: %w", path, err)
|
||||
}
|
||||
|
||||
var raw rawVaultFile
|
||||
if err := yaml.Unmarshal(data, &raw); err != nil {
|
||||
return nil, fmt.Errorf("parsing YAML in %s: %w", path, err)
|
||||
}
|
||||
|
||||
var vaults []*Vault
|
||||
for _, rv := range raw.Vaults {
|
||||
if rv.Name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
suffix := projectID
|
||||
if suffix == "" {
|
||||
suffix = "registry"
|
||||
}
|
||||
id := rv.Name + "_" + suffix
|
||||
|
||||
// Detect if the vault entry on disk is a symlink
|
||||
isSymlink := false
|
||||
vaultPath := rv.Path
|
||||
entryPath := filepath.Join(vaultsDir, rv.Name)
|
||||
if fi, err := os.Lstat(entryPath); err == nil {
|
||||
if fi.Mode()&os.ModeSymlink != 0 {
|
||||
isSymlink = true
|
||||
if target, err := os.Readlink(entryPath); err == nil && vaultPath == "" {
|
||||
vaultPath = target
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vaults = append(vaults, &Vault{
|
||||
ID: id,
|
||||
Name: rv.Name,
|
||||
ProjectID: projectID,
|
||||
Description: rv.Description,
|
||||
Path: vaultPath,
|
||||
Symlink: isSymlink,
|
||||
Tags: rv.Tags,
|
||||
})
|
||||
}
|
||||
|
||||
return vaults, nil
|
||||
}
|
||||
|
||||
// bodySections holds the extracted sections from a .md body.
|
||||
type bodySections struct {
|
||||
example string // content under ## Ejemplo
|
||||
|
||||
Reference in New Issue
Block a user