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
|
// Builder constructs a marketplace Index by crawling directories for
|
||||||
// core.json (compiled manifests) or .core/manifest.yaml files.
|
// core.json (compiled manifests) or .core/manifest.yaml files.
|
||||||
type Builder struct {
|
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.
|
// BaseURL is the prefix for constructing repository URLs, e.g.
|
||||||
// "https://forge.lthn.ai". When set, module Repo is derived as
|
// "https://forge.lthn.ai". When set, module Repo is derived as
|
||||||
// BaseURL + "/" + org + "/" + code + ".git".
|
// 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.
|
// loadFromDir tries core.json first, then falls back to .core/manifest.yaml.
|
||||||
func (b *Builder) loadFromDir(dir string) (*manifest.Manifest, error) {
|
func (b *Builder) loadFromDir(dir string) (*manifest.Manifest, error) {
|
||||||
|
medium := b.Medium
|
||||||
|
if medium == nil {
|
||||||
|
medium = coreio.Local
|
||||||
|
}
|
||||||
|
|
||||||
// Prefer compiled manifest (core.json).
|
// Prefer compiled manifest (core.json).
|
||||||
coreJSON := filepath.Join(dir, "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))
|
cm, err := manifest.ParseCompiled([]byte(raw))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, coreerr.E("marketplace.Builder.loadFromDir", "parse core.json", err)
|
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.
|
// Fall back to source manifest.
|
||||||
manifestYAML := filepath.Join(dir, ".core", "manifest.yaml")
|
manifestYAML := filepath.Join(dir, ".core", "manifest.yaml")
|
||||||
raw, err := coreio.Local.Read(manifestYAML)
|
raw, err := medium.Read(manifestYAML)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil // No manifest — skip silently.
|
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)
|
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) {
|
func TestBuildFromDirs_Good_PrefersCompiledOverSource_Good(t *testing.T) {
|
||||||
root := t.TempDir()
|
root := t.TempDir()
|
||||||
modDir := filepath.Join(root, "dual-mod")
|
modDir := filepath.Join(root, "dual-mod")
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,16 @@ type DiscoveredProvider struct {
|
||||||
// Only manifests with provider fields (namespace + binary) are returned.
|
// Only manifests with provider fields (namespace + binary) are returned.
|
||||||
// Usage: DiscoverProviders(...)
|
// Usage: DiscoverProviders(...)
|
||||||
func DiscoverProviders(dir string) ([]DiscoveredProvider, error) {
|
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 err != nil {
|
||||||
if os.IsNotExist(err) {
|
if !medium.Exists(dir) {
|
||||||
return nil, nil // No providers directory — not an error.
|
return nil, nil // No providers directory — not an error.
|
||||||
}
|
}
|
||||||
return nil, coreerr.E("marketplace.DiscoverProviders", "read directory", err)
|
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())
|
providerDir := filepath.Join(dir, e.Name())
|
||||||
manifestPath := filepath.Join(providerDir, ".core", "manifest.yaml")
|
manifestPath := filepath.Join(providerDir, ".core", "manifest.yaml")
|
||||||
|
|
||||||
raw, err := coreio.Local.Read(manifestPath)
|
raw, err := medium.Read(manifestPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
core.Warn(core.Sprintf("marketplace: skipping %s: %v", e.Name(), err))
|
core.Warn(core.Sprintf("marketplace: skipping %s: %v", e.Name(), err))
|
||||||
continue
|
continue
|
||||||
|
|
@ -90,7 +97,14 @@ type ProviderRegistryFile struct {
|
||||||
// Returns an empty registry if the file does not exist.
|
// Returns an empty registry if the file does not exist.
|
||||||
// Usage: LoadProviderRegistry(...)
|
// Usage: LoadProviderRegistry(...)
|
||||||
func LoadProviderRegistry(path string) (*ProviderRegistryFile, error) {
|
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 err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return &ProviderRegistryFile{
|
return &ProviderRegistryFile{
|
||||||
|
|
@ -116,7 +130,14 @@ func LoadProviderRegistry(path string) (*ProviderRegistryFile, error) {
|
||||||
// SaveProviderRegistry writes the registry to the given path.
|
// SaveProviderRegistry writes the registry to the given path.
|
||||||
// Usage: SaveProviderRegistry(...)
|
// Usage: SaveProviderRegistry(...)
|
||||||
func SaveProviderRegistry(path string, reg *ProviderRegistryFile) error {
|
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)
|
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 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.
|
// Add adds or updates a provider entry in the registry.
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
os "dappco.re/go/core/scm/internal/ax/osx"
|
os "dappco.re/go/core/scm/internal/ax/osx"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"dappco.re/go/core/io"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
@ -163,6 +164,31 @@ binary: ./test-prov
|
||||||
assert.Equal(t, filepath.Join(dir, "test-prov"), providers[0].Dir)
|
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 -----------------------------------------------
|
// -- ProviderRegistryFile tests -----------------------------------------------
|
||||||
|
|
||||||
func TestProviderRegistry_LoadSave_Good(t *testing.T) {
|
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)
|
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) {
|
func TestProviderRegistry_Add_Good(t *testing.T) {
|
||||||
reg := &ProviderRegistryFile{
|
reg := &ProviderRegistryFile{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue