Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions boot/deps/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,31 @@ const (

type ModuleConfig struct {
ExcludeMeta map[string][]string `yaml:"exclude_meta,omitempty"`
Organization string `yaml:"organization"`
ModuleName string `yaml:"module"`
Version string `yaml:"version"`
Metadata map[string]any `yaml:"metadata,omitempty"`
Publish PublishConfig `yaml:"publish,omitempty"`
Repository string `yaml:"repository,omitempty"`
Description string `yaml:"description,omitempty"`
License string `yaml:"license,omitempty"`
Repository string `yaml:"repository,omitempty"`
Version string `yaml:"version"`
Homepage string `yaml:"homepage,omitempty"`
ModuleName string `yaml:"module"`
Organization string `yaml:"organization"`
Keywords []string `yaml:"keywords,omitempty"`
Authors []string `yaml:"authors,omitempty"`
Include []string `yaml:"include,omitempty"`
Exclude []string `yaml:"exclude,omitempty"`
Metadata map[string]any `yaml:"metadata,omitempty"`
Embed []string `yaml:"embed,omitempty"`
}

type PublishConfig struct {
Profiles PublishProfilesConfig `yaml:"profiles,omitempty"`
}

type PublishProfilesConfig struct {
Enabled *bool `yaml:"enabled,omitempty"`
Source string `yaml:"source,omitempty"`
}

func Load(dir string) (*ModuleConfig, error) {
path := filepath.Join(dir, DefaultConfigFile)
return LoadFrom(path)
Expand Down
19 changes: 19 additions & 0 deletions boot/deps/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,25 @@ metadata:
assert.Equal(t, "debug", loggerMap["level"])
}

func TestLoad_WithPublishProfileConfig(t *testing.T) {
dir := t.TempDir()
content := `
organization: myorg
module: mymod
publish:
profiles:
enabled: false
source: config/profiles.yaml
`
require.NoError(t, os.WriteFile(filepath.Join(dir, DefaultConfigFile), []byte(content), 0644))

cfg, err := Load(dir)
require.NoError(t, err)
require.NotNil(t, cfg.Publish.Profiles.Enabled)
assert.False(t, *cfg.Publish.Profiles.Enabled)
assert.Equal(t, "config/profiles.yaml", cfg.Publish.Profiles.Source)
}

// --- EntryExcludes ---

func TestEntryExcludes(t *testing.T) {
Expand Down
3 changes: 3 additions & 0 deletions cmd/wippy/cmd/publish.go
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,9 @@ func packModule(ctx context.Context, app *appinit.Context, cfg *config.ModuleCon
}
metadata[trimmed] = value
}
if err := addPublishedRuntimeProfileMetadata(metadata, srcDir, cfg.Publish.Profiles); err != nil {
return nil, NewPublishConfigError(err)
}

packWriter := wapp.NewWriter()

Expand Down
180 changes: 180 additions & 0 deletions cmd/wippy/cmd/publish_profiles.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: MPL-2.0

package cmd

import (
"fmt"
"path/filepath"
"strings"

"github.com/wippyai/runtime/api/attrs"
"github.com/wippyai/runtime/api/boot"
"github.com/wippyai/runtime/boot/deps/config"
"github.com/wippyai/runtime/cmd/internal/bootconfig"
)

const (
publishRuntimeMetadataKey = "runtime"
publishRuntimeProfilesMetadataKey = "profiles"
publishRuntimeVarsMetadataKey = "vars"
)

func addPublishedRuntimeProfileMetadata(metadata attrs.Bag, configDir string, profileCfg config.PublishProfilesConfig) error {
if hasNestedRuntimeMetadata(metadata, publishRuntimeProfilesMetadataKey) {
return fmt.Errorf("wippy.yaml metadata.runtime.profiles is not supported; declare publishable profiles in .wippy.yaml or publish.profiles.source")
}

if profileCfg.Enabled != nil && !*profileCfg.Enabled {
return nil
}

source := strings.TrimSpace(profileCfg.Source)
if source == "" {
source = defaultConfigFile
}
if !filepath.IsAbs(source) {
source = filepath.Join(configDir, source)
}

cfg, err := bootconfig.Load(source)
if err != nil {
return fmt.Errorf("load runtime profile source %s: %w", source, err)
}
if cfg == nil {
return nil
}

profiles, err := runtimeProfilesFromConfig(cfg)
if err != nil {
return err
}
if len(profiles) == 0 {
return nil
}

if hasNestedRuntimeMetadata(metadata, publishRuntimeVarsMetadataKey) {
return fmt.Errorf("runtime vars are defined in both %s and wippy.yaml metadata; keep profile variables in the profile source", source)
}

runtime, err := runtimeMetadataMap(metadata)
if err != nil {
return err
}
runtime[publishRuntimeProfilesMetadataKey] = profiles

vars := runtimeSectionFromConfig(cfg, publishRuntimeVarsMetadataKey)
if len(vars) > 0 {
runtime[publishRuntimeVarsMetadataKey] = vars
}

return nil
}

func runtimeProfilesFromConfig(cfg boot.Config) (map[string]any, error) {
profiles := make(map[string]any)
if cfg == nil {
return profiles, nil
}

for _, key := range cfg.Keys() {
if !strings.HasPrefix(key, "profiles.") {
continue
}

rest := strings.TrimPrefix(key, "profiles.")
profileName, rest, ok := strings.Cut(rest, ".")
if !ok || profileName == "" || rest == "" {
return nil, fmt.Errorf("invalid runtime profile key %q", key)
}
section, subkey, ok := strings.Cut(rest, ".")
if !ok || section == "" || subkey == "" {
return nil, fmt.Errorf("invalid runtime profile key %q", key)
}

profileMap, ok := profiles[profileName].(map[string]any)
if !ok {
profileMap = make(map[string]any)
profiles[profileName] = profileMap
}

sectionMap, ok := profileMap[section].(map[string]any)
if !ok {
sectionMap = make(map[string]any)
profileMap[section] = sectionMap
}

value, _ := cfg.Get(key)
sectionMap[subkey] = value
}

return profiles, nil
}

func runtimeSectionFromConfig(cfg boot.Config, section string) map[string]any {
values := make(map[string]any)
if cfg == nil {
return values
}

prefix := section + "."
for _, key := range cfg.Keys() {
if !strings.HasPrefix(key, prefix) {
continue
}
value, _ := cfg.Get(key)
values[strings.TrimPrefix(key, prefix)] = value
}
return values
}

func runtimeMetadataMap(metadata attrs.Bag) (map[string]any, error) {
if metadata == nil {
return nil, fmt.Errorf("metadata bag is nil")
}

raw, exists := metadata[publishRuntimeMetadataKey]
if !exists {
runtime := make(map[string]any)
metadata[publishRuntimeMetadataKey] = runtime
return runtime, nil
}

switch typed := raw.(type) {
case map[string]any:
return typed, nil
case attrs.Bag:
runtime := map[string]any(typed)
metadata[publishRuntimeMetadataKey] = runtime
return runtime, nil
default:
return nil, fmt.Errorf("wippy.yaml metadata.runtime must be a map when publishing runtime profiles")
}
}

func hasNestedRuntimeMetadata(metadata attrs.Bag, nestedKey string) bool {
if metadata == nil {
return false
}

dotted := publishRuntimeMetadataKey + "." + nestedKey
for key := range metadata {
if key == dotted || strings.HasPrefix(key, dotted+".") {
return true
}
}

raw, exists := metadata[publishRuntimeMetadataKey]
if !exists {
return false
}
switch typed := raw.(type) {
case map[string]any:
_, exists = typed[nestedKey]
return exists
case attrs.Bag:
_, exists = typed[nestedKey]
return exists
default:
return false
}
}
Loading