refactor(marketplace): inject mediums into SCM helpers
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
905889a9f8
commit
2f599eb6d5
4 changed files with 116 additions and 8 deletions
|
|
@ -22,6 +22,10 @@ const IndexVersion = 1
|
|||
// Builder constructs a marketplace Index by crawling directories for
|
||||
// core.json (compiled manifests) or .core/manifest.yaml files.
|
||||
type Builder struct {
|
||||
// Medium is the filesystem abstraction used for manifest reads.
|
||||
// If nil, io.Local is used.
|
||||
Medium coreio.Medium
|
||||
|
||||
// BaseURL is the prefix for constructing repository URLs, e.g.
|
||||
// "https://forge.lthn.ai". When set, module Repo is derived as
|
||||
// BaseURL + "/" + org + "/" + code + ".git".
|
||||
|
|
@ -147,9 +151,14 @@ func WriteIndex(m coreio.Medium, path string, idx *Index) error {
|
|||
|
||||
// loadFromDir tries core.json first, then falls back to .core/manifest.yaml.
|
||||
func (b *Builder) loadFromDir(dir string) (*manifest.Manifest, error) {
|
||||
medium := b.Medium
|
||||
if medium == nil {
|
||||
medium = coreio.Local
|
||||
}
|
||||
|
||||
// Prefer compiled manifest (core.json).
|
||||
coreJSON := filepath.Join(dir, "core.json")
|
||||
if raw, err := coreio.Local.Read(coreJSON); err == nil {
|
||||
if raw, err := medium.Read(coreJSON); err == nil {
|
||||
cm, err := manifest.ParseCompiled([]byte(raw))
|
||||
if err != nil {
|
||||
return nil, coreerr.E("marketplace.Builder.loadFromDir", "parse core.json", err)
|
||||
|
|
@ -159,7 +168,7 @@ func (b *Builder) loadFromDir(dir string) (*manifest.Manifest, error) {
|
|||
|
||||
// Fall back to source manifest.
|
||||
manifestYAML := filepath.Join(dir, ".core", "manifest.yaml")
|
||||
raw, err := coreio.Local.Read(manifestYAML)
|
||||
raw, err := medium.Read(manifestYAML)
|
||||
if err != nil {
|
||||
return nil, nil // No manifest — skip silently.
|
||||
}
|
||||
|
|
|
|||
|
|
@ -108,6 +108,34 @@ func TestBuildFromDirs_Good_CoreJSON_Good(t *testing.T) {
|
|||
assert.Equal(t, "Compiled Module", idx.Modules[0].Name)
|
||||
}
|
||||
|
||||
func TestBuildFromDirs_Good_UsesInjectedMedium_Good(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
modDir := filepath.Join(root, "virtual-mod")
|
||||
require.NoError(t, os.MkdirAll(modDir, 0755))
|
||||
|
||||
medium := io.NewMockMedium()
|
||||
cm := manifest.CompiledManifest{
|
||||
Manifest: manifest.Manifest{
|
||||
Code: "virtual-mod",
|
||||
Name: "Virtual Module",
|
||||
Version: "9.9.9",
|
||||
Sign: "sig-virtual",
|
||||
},
|
||||
Commit: "commit-virtual",
|
||||
}
|
||||
data, err := json.Marshal(cm)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, medium.Write(filepath.Join(modDir, "core.json"), string(data)))
|
||||
|
||||
b := &Builder{Medium: medium}
|
||||
idx, err := b.BuildFromDirs(root)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, idx.Modules, 1)
|
||||
assert.Equal(t, "virtual-mod", idx.Modules[0].Code)
|
||||
assert.Equal(t, "sig-virtual", idx.Modules[0].SignKey)
|
||||
}
|
||||
|
||||
func TestBuildFromDirs_Good_PrefersCompiledOverSource_Good(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
modDir := filepath.Join(root, "dual-mod")
|
||||
|
|
|
|||
|
|
@ -29,9 +29,16 @@ type DiscoveredProvider struct {
|
|||
// Only manifests with provider fields (namespace + binary) are returned.
|
||||
// Usage: DiscoverProviders(...)
|
||||
func DiscoverProviders(dir string) ([]DiscoveredProvider, error) {
|
||||
entries, err := os.ReadDir(dir)
|
||||
return DiscoverProvidersWithMedium(coreio.Local, dir)
|
||||
}
|
||||
|
||||
// DiscoverProvidersWithMedium scans the given directory for runtime provider
|
||||
// manifests using the supplied filesystem medium.
|
||||
// Usage: DiscoverProvidersWithMedium(...)
|
||||
func DiscoverProvidersWithMedium(medium coreio.Medium, dir string) ([]DiscoveredProvider, error) {
|
||||
entries, err := medium.List(dir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if !medium.Exists(dir) {
|
||||
return nil, nil // No providers directory — not an error.
|
||||
}
|
||||
return nil, coreerr.E("marketplace.DiscoverProviders", "read directory", err)
|
||||
|
|
@ -46,7 +53,7 @@ func DiscoverProviders(dir string) ([]DiscoveredProvider, error) {
|
|||
providerDir := filepath.Join(dir, e.Name())
|
||||
manifestPath := filepath.Join(providerDir, ".core", "manifest.yaml")
|
||||
|
||||
raw, err := coreio.Local.Read(manifestPath)
|
||||
raw, err := medium.Read(manifestPath)
|
||||
if err != nil {
|
||||
core.Warn(core.Sprintf("marketplace: skipping %s: %v", e.Name(), err))
|
||||
continue
|
||||
|
|
@ -90,7 +97,14 @@ type ProviderRegistryFile struct {
|
|||
// Returns an empty registry if the file does not exist.
|
||||
// Usage: LoadProviderRegistry(...)
|
||||
func LoadProviderRegistry(path string) (*ProviderRegistryFile, error) {
|
||||
raw, err := coreio.Local.Read(path)
|
||||
return LoadProviderRegistryWithMedium(coreio.Local, path)
|
||||
}
|
||||
|
||||
// LoadProviderRegistryWithMedium reads a registry.yaml file using the supplied
|
||||
// filesystem medium.
|
||||
// Usage: LoadProviderRegistryWithMedium(...)
|
||||
func LoadProviderRegistryWithMedium(medium coreio.Medium, path string) (*ProviderRegistryFile, error) {
|
||||
raw, err := medium.Read(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return &ProviderRegistryFile{
|
||||
|
|
@ -116,7 +130,14 @@ func LoadProviderRegistry(path string) (*ProviderRegistryFile, error) {
|
|||
// SaveProviderRegistry writes the registry to the given path.
|
||||
// Usage: SaveProviderRegistry(...)
|
||||
func SaveProviderRegistry(path string, reg *ProviderRegistryFile) error {
|
||||
if err := coreio.Local.EnsureDir(filepath.Dir(path)); err != nil {
|
||||
return SaveProviderRegistryWithMedium(coreio.Local, path, reg)
|
||||
}
|
||||
|
||||
// SaveProviderRegistryWithMedium writes the registry using the supplied
|
||||
// filesystem medium.
|
||||
// Usage: SaveProviderRegistryWithMedium(...)
|
||||
func SaveProviderRegistryWithMedium(medium coreio.Medium, path string, reg *ProviderRegistryFile) error {
|
||||
if err := medium.EnsureDir(filepath.Dir(path)); err != nil {
|
||||
return coreerr.E("marketplace.SaveProviderRegistry", "ensure directory", err)
|
||||
}
|
||||
|
||||
|
|
@ -125,7 +146,7 @@ func SaveProviderRegistry(path string, reg *ProviderRegistryFile) error {
|
|||
return coreerr.E("marketplace.SaveProviderRegistry", "marshal failed", err)
|
||||
}
|
||||
|
||||
return coreio.Local.Write(path, string(data))
|
||||
return medium.Write(path, string(data))
|
||||
}
|
||||
|
||||
// Add adds or updates a provider entry in the registry.
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
os "dappco.re/go/core/scm/internal/ax/osx"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/io"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
|
@ -163,6 +164,31 @@ binary: ./test-prov
|
|||
assert.Equal(t, filepath.Join(dir, "test-prov"), providers[0].Dir)
|
||||
}
|
||||
|
||||
func TestDiscoverProvidersWithMedium_Good(t *testing.T) {
|
||||
medium := io.NewMockMedium()
|
||||
medium.Dirs["/providers"] = true
|
||||
medium.Dirs["/providers/cool-widget"] = true
|
||||
medium.Dirs["/providers/data-viz"] = true
|
||||
medium.Files["/providers/cool-widget/.core/manifest.yaml"] = `
|
||||
code: cool-widget
|
||||
name: Cool Widget
|
||||
version: 1.0.0
|
||||
namespace: /api/v1/cool-widget
|
||||
binary: ./cool-widget
|
||||
`
|
||||
medium.Files["/providers/data-viz/.core/manifest.yaml"] = `
|
||||
code: data-viz
|
||||
name: Data Visualiser
|
||||
version: 0.2.0
|
||||
namespace: /api/v1/data-viz
|
||||
binary: ./data-viz
|
||||
`
|
||||
|
||||
providers, err := DiscoverProvidersWithMedium(medium, "/providers")
|
||||
require.NoError(t, err)
|
||||
assert.Len(t, providers, 2)
|
||||
}
|
||||
|
||||
// -- ProviderRegistryFile tests -----------------------------------------------
|
||||
|
||||
func TestProviderRegistry_LoadSave_Good(t *testing.T) {
|
||||
|
|
@ -201,6 +227,30 @@ func TestProviderRegistry_Load_Good_NonexistentFile_Good(t *testing.T) {
|
|||
assert.Empty(t, reg.Providers)
|
||||
}
|
||||
|
||||
func TestProviderRegistry_LoadSave_WithMedium_Good(t *testing.T) {
|
||||
medium := io.NewMockMedium()
|
||||
path := "/registry/registry.yaml"
|
||||
|
||||
reg := &ProviderRegistryFile{
|
||||
Version: 1,
|
||||
Providers: map[string]ProviderRegistryEntry{},
|
||||
}
|
||||
reg.Add("cool-widget", ProviderRegistryEntry{
|
||||
Installed: "2026-03-14T12:00:00Z",
|
||||
Version: "1.0.0",
|
||||
Source: "forge.lthn.ai/someone/cool-widget",
|
||||
AutoStart: true,
|
||||
})
|
||||
|
||||
err := SaveProviderRegistryWithMedium(medium, path, reg)
|
||||
require.NoError(t, err)
|
||||
|
||||
loaded, err := LoadProviderRegistryWithMedium(medium, path)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, loaded.Version)
|
||||
assert.Len(t, loaded.Providers, 1)
|
||||
}
|
||||
|
||||
func TestProviderRegistry_Add_Good(t *testing.T) {
|
||||
reg := &ProviderRegistryFile{
|
||||
Version: 1,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue