feat(marketplace): add category-aware index builder
Propagate category metadata while building marketplace indexes and deduplicate the category list for consumers such as the UI. Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
82c25469e8
commit
1f98d7ab8a
2 changed files with 145 additions and 0 deletions
77
marketplace/indexer.go
Normal file
77
marketplace/indexer.go
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package marketplace
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"dappco.re/go/core/io"
|
||||
"dappco.re/go/core/scm/manifest"
|
||||
)
|
||||
|
||||
// IndexOptions controls how BuildIndex populates marketplace metadata.
|
||||
// Usage: IndexOptions{...}
|
||||
type IndexOptions struct {
|
||||
// Org is the default organisation used when constructing repo URLs.
|
||||
Org string
|
||||
|
||||
// ForgeURL is the base URL used when constructing repo URLs.
|
||||
ForgeURL string
|
||||
|
||||
// CategoryFn assigns a category to a module code.
|
||||
CategoryFn func(code string) string
|
||||
}
|
||||
|
||||
// BuildIndex reads .core/manifest.yaml from each repository root and produces
|
||||
// a marketplace index. Repositories without a manifest are skipped silently.
|
||||
// Categories are deduplicated and sorted.
|
||||
// Usage: BuildIndex(...)
|
||||
func BuildIndex(medium io.Medium, repoPaths []string, opts IndexOptions) (*Index, error) {
|
||||
idx := &Index{
|
||||
Version: IndexVersion,
|
||||
}
|
||||
|
||||
seen := make(map[string]bool)
|
||||
categories := make(map[string]bool)
|
||||
|
||||
for _, repoPath := range repoPaths {
|
||||
m, err := manifest.Load(medium, repoPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if m == nil || m.Code == "" {
|
||||
continue
|
||||
}
|
||||
if seen[m.Code] {
|
||||
continue
|
||||
}
|
||||
seen[m.Code] = true
|
||||
|
||||
module := Module{
|
||||
Code: m.Code,
|
||||
Name: m.Name,
|
||||
}
|
||||
if opts.ForgeURL != "" && opts.Org != "" {
|
||||
module.Repo = opts.ForgeURL + "/" + opts.Org + "/" + m.Code
|
||||
}
|
||||
if opts.CategoryFn != nil {
|
||||
module.Category = opts.CategoryFn(m.Code)
|
||||
}
|
||||
if module.Category != "" {
|
||||
categories[module.Category] = true
|
||||
}
|
||||
|
||||
idx.Modules = append(idx.Modules, module)
|
||||
}
|
||||
|
||||
sort.Slice(idx.Modules, func(i, j int) bool {
|
||||
return idx.Modules[i].Code < idx.Modules[j].Code
|
||||
})
|
||||
|
||||
for category := range categories {
|
||||
idx.Categories = append(idx.Categories, category)
|
||||
}
|
||||
sort.Strings(idx.Categories)
|
||||
|
||||
return idx, nil
|
||||
}
|
||||
68
marketplace/indexer_test.go
Normal file
68
marketplace/indexer_test.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
// SPDX-License-Identifier: EUPL-1.2
|
||||
|
||||
package marketplace
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestBuildIndex_Good_CategoriesAndRepoURLs_Good(t *testing.T) {
|
||||
medium := io.NewMockMedium()
|
||||
|
||||
require.NoError(t, medium.Write("/repos/a/.core/manifest.yaml", `
|
||||
code: a
|
||||
name: Alpha
|
||||
version: 1.0.0
|
||||
`))
|
||||
require.NoError(t, medium.Write("/repos/b/.core/manifest.yaml", `
|
||||
code: b
|
||||
name: Beta
|
||||
version: 1.0.0
|
||||
`))
|
||||
require.NoError(t, medium.Write("/repos/c/.core/manifest.yaml", `
|
||||
code: c
|
||||
name: Gamma
|
||||
version: 1.0.0
|
||||
`))
|
||||
|
||||
idx, err := BuildIndex(medium, []string{"/repos/a", "/repos/b", "/repos/c"}, IndexOptions{
|
||||
ForgeURL: "https://forge.example.com",
|
||||
Org: "core",
|
||||
CategoryFn: func(code string) string {
|
||||
switch code {
|
||||
case "a", "b":
|
||||
return "tools"
|
||||
default:
|
||||
return "products"
|
||||
}
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, idx.Modules, 3)
|
||||
assert.Equal(t, "a", idx.Modules[0].Code)
|
||||
assert.Equal(t, "https://forge.example.com/core/a", idx.Modules[0].Repo)
|
||||
assert.Equal(t, "tools", idx.Modules[0].Category)
|
||||
assert.Equal(t, []string{"products", "tools"}, idx.Categories)
|
||||
}
|
||||
|
||||
func TestBuildIndex_Good_SkipsMissingManifest_Good(t *testing.T) {
|
||||
medium := io.NewMockMedium()
|
||||
|
||||
require.NoError(t, medium.Write("/repos/one/.core/manifest.yaml", `
|
||||
code: one
|
||||
name: One
|
||||
version: 1.0.0
|
||||
`))
|
||||
|
||||
idx, err := BuildIndex(medium, []string{"/repos/one", "/repos/missing"}, IndexOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, idx.Modules, 1)
|
||||
assert.Equal(t, "one", idx.Modules[0].Code)
|
||||
assert.Empty(t, idx.Categories)
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue