feat: add DaemonSpec to manifest, rename path to .core/manifest.yaml
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
68379b6b58
commit
849221eb80
3 changed files with 134 additions and 15 deletions
|
|
@ -9,14 +9,14 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const manifestPath = ".core/view.yml"
|
||||
const manifestPath = ".core/manifest.yaml"
|
||||
|
||||
// MarshalYAML serializes a manifest to YAML bytes.
|
||||
func MarshalYAML(m *Manifest) ([]byte, error) {
|
||||
return yaml.Marshal(m)
|
||||
}
|
||||
|
||||
// Load reads and parses a .core/view.yml from the given root directory.
|
||||
// Load reads and parses a .core/manifest.yaml from the given root directory.
|
||||
func Load(medium io.Medium, root string) (*Manifest, error) {
|
||||
path := filepath.Join(root, manifestPath)
|
||||
data, err := medium.Read(path)
|
||||
|
|
|
|||
|
|
@ -6,25 +6,35 @@ import (
|
|||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// Manifest represents a .core/view.yml application manifest.
|
||||
// Manifest represents a .core/manifest.yaml application manifest.
|
||||
type Manifest struct {
|
||||
Code string `yaml:"code"`
|
||||
Name string `yaml:"name"`
|
||||
Version string `yaml:"version"`
|
||||
Sign string `yaml:"sign"`
|
||||
Layout string `yaml:"layout"`
|
||||
Slots map[string]string `yaml:"slots"`
|
||||
Code string `yaml:"code" json:"code"`
|
||||
Name string `yaml:"name" json:"name"`
|
||||
Description string `yaml:"description,omitempty" json:"description,omitempty"`
|
||||
Version string `yaml:"version" json:"version"`
|
||||
Sign string `yaml:"sign,omitempty" json:"sign,omitempty"`
|
||||
Layout string `yaml:"layout,omitempty" json:"layout,omitempty"`
|
||||
Slots map[string]string `yaml:"slots,omitempty" json:"slots,omitempty"`
|
||||
|
||||
Permissions Permissions `yaml:"permissions"`
|
||||
Modules []string `yaml:"modules"`
|
||||
Permissions Permissions `yaml:"permissions,omitempty" json:"permissions,omitempty"`
|
||||
Modules []string `yaml:"modules,omitempty" json:"modules,omitempty"`
|
||||
Daemons map[string]DaemonSpec `yaml:"daemons,omitempty" json:"daemons,omitempty"`
|
||||
}
|
||||
|
||||
// Permissions declares the I/O capabilities a module requires.
|
||||
type Permissions struct {
|
||||
Read []string `yaml:"read"`
|
||||
Write []string `yaml:"write"`
|
||||
Net []string `yaml:"net"`
|
||||
Run []string `yaml:"run"`
|
||||
Read []string `yaml:"read" json:"read"`
|
||||
Write []string `yaml:"write" json:"write"`
|
||||
Net []string `yaml:"net" json:"net"`
|
||||
Run []string `yaml:"run" json:"run"`
|
||||
}
|
||||
|
||||
// DaemonSpec describes a long-running process managed by the runtime.
|
||||
type DaemonSpec struct {
|
||||
Binary string `yaml:"binary,omitempty" json:"binary,omitempty"`
|
||||
Args []string `yaml:"args,omitempty" json:"args,omitempty"`
|
||||
Health string `yaml:"health,omitempty" json:"health,omitempty"`
|
||||
Default bool `yaml:"default,omitempty" json:"default,omitempty"`
|
||||
}
|
||||
|
||||
// Parse decodes YAML bytes into a Manifest.
|
||||
|
|
@ -48,3 +58,28 @@ func (m *Manifest) SlotNames() []string {
|
|||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// DefaultDaemon returns the name, spec, and true for the default daemon.
|
||||
// A daemon is the default if it has Default:true, or if it is the only daemon
|
||||
// in the map. Returns empty values and false if no default can be determined.
|
||||
func (m *Manifest) DefaultDaemon() (string, DaemonSpec, bool) {
|
||||
if len(m.Daemons) == 0 {
|
||||
return "", DaemonSpec{}, false
|
||||
}
|
||||
|
||||
// Look for an explicit default.
|
||||
for name, spec := range m.Daemons {
|
||||
if spec.Default {
|
||||
return name, spec, true
|
||||
}
|
||||
}
|
||||
|
||||
// If exactly one daemon exists, treat it as the implicit default.
|
||||
if len(m.Daemons) == 1 {
|
||||
for name, spec := range m.Daemons {
|
||||
return name, spec, true
|
||||
}
|
||||
}
|
||||
|
||||
return "", DaemonSpec{}, false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,3 +63,87 @@ func TestManifest_SlotNames_Good(t *testing.T) {
|
|||
assert.Contains(t, names, "main-content")
|
||||
assert.Len(t, names, 2)
|
||||
}
|
||||
|
||||
func TestParse_Good_WithDaemons(t *testing.T) {
|
||||
raw := `
|
||||
code: my-service
|
||||
name: My Service
|
||||
description: A test service with daemons
|
||||
version: 1.0.0
|
||||
|
||||
daemons:
|
||||
api:
|
||||
binary: ./bin/api
|
||||
args: ["--port", "8080"]
|
||||
health: /healthz
|
||||
default: true
|
||||
worker:
|
||||
binary: ./bin/worker
|
||||
args: ["--concurrency", "4"]
|
||||
health: /ready
|
||||
`
|
||||
m, err := Parse([]byte(raw))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "my-service", m.Code)
|
||||
assert.Equal(t, "My Service", m.Name)
|
||||
assert.Equal(t, "A test service with daemons", m.Description)
|
||||
assert.Equal(t, "1.0.0", m.Version)
|
||||
assert.Len(t, m.Daemons, 2)
|
||||
|
||||
api, ok := m.Daemons["api"]
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "./bin/api", api.Binary)
|
||||
assert.Equal(t, []string{"--port", "8080"}, api.Args)
|
||||
assert.Equal(t, "/healthz", api.Health)
|
||||
assert.True(t, api.Default)
|
||||
|
||||
worker, ok := m.Daemons["worker"]
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, "./bin/worker", worker.Binary)
|
||||
assert.Equal(t, []string{"--concurrency", "4"}, worker.Args)
|
||||
assert.Equal(t, "/ready", worker.Health)
|
||||
assert.False(t, worker.Default)
|
||||
}
|
||||
|
||||
func TestManifest_DefaultDaemon_Good(t *testing.T) {
|
||||
m := Manifest{
|
||||
Daemons: map[string]DaemonSpec{
|
||||
"api": {
|
||||
Binary: "./bin/api",
|
||||
Default: true,
|
||||
},
|
||||
"worker": {
|
||||
Binary: "./bin/worker",
|
||||
},
|
||||
},
|
||||
}
|
||||
name, spec, ok := m.DefaultDaemon()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "api", name)
|
||||
assert.Equal(t, "./bin/api", spec.Binary)
|
||||
assert.True(t, spec.Default)
|
||||
}
|
||||
|
||||
func TestManifest_DefaultDaemon_Bad_NoDaemons(t *testing.T) {
|
||||
m := Manifest{}
|
||||
name, spec, ok := m.DefaultDaemon()
|
||||
assert.False(t, ok)
|
||||
assert.Empty(t, name)
|
||||
assert.Empty(t, spec.Binary)
|
||||
}
|
||||
|
||||
func TestManifest_DefaultDaemon_Good_SingleImplicit(t *testing.T) {
|
||||
m := Manifest{
|
||||
Daemons: map[string]DaemonSpec{
|
||||
"server": {
|
||||
Binary: "./bin/server",
|
||||
Args: []string{"--port", "3000"},
|
||||
},
|
||||
},
|
||||
}
|
||||
name, spec, ok := m.DefaultDaemon()
|
||||
assert.True(t, ok)
|
||||
assert.Equal(t, "server", name)
|
||||
assert.Equal(t, "./bin/server", spec.Binary)
|
||||
assert.False(t, spec.Default)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue