diff --git a/snapshot/snapshot.go b/snapshot/snapshot.go new file mode 100644 index 0000000..05c5125 --- /dev/null +++ b/snapshot/snapshot.go @@ -0,0 +1,62 @@ +// Package snapshot generates frozen core.json release manifests. +package snapshot + +import ( + "encoding/json" + "errors" + "time" + + "forge.lthn.ai/core/go-scm/manifest" +) + +// Snapshot is the frozen release manifest written as core.json. +type Snapshot struct { + Schema int `json:"schema"` + Code string `json:"code"` + Name string `json:"name"` + Version string `json:"version"` + Description string `json:"description,omitempty"` + Commit string `json:"commit"` + Tag string `json:"tag"` + Built string `json:"built"` + Daemons map[string]manifest.DaemonSpec `json:"daemons,omitempty"` + Layout string `json:"layout,omitempty"` + Slots map[string]string `json:"slots,omitempty"` + Permissions *manifest.Permissions `json:"permissions,omitempty"` + Modules []string `json:"modules,omitempty"` +} + +// Generate creates a core.json snapshot from a manifest. +// The built timestamp is set to the current time. +func Generate(m *manifest.Manifest, commit, tag string) ([]byte, error) { + return GenerateAt(m, commit, tag, time.Now().UTC()) +} + +// GenerateAt creates a core.json snapshot with an explicit build timestamp. +func GenerateAt(m *manifest.Manifest, commit, tag string, built time.Time) ([]byte, error) { + if m == nil { + return nil, errors.New("snapshot: manifest is nil") + } + + snap := Snapshot{ + Schema: 1, + Code: m.Code, + Name: m.Name, + Version: m.Version, + Description: m.Description, + Commit: commit, + Tag: tag, + Built: built.Format(time.RFC3339), + Daemons: m.Daemons, + Layout: m.Layout, + Slots: m.Slots, + Modules: m.Modules, + } + + if m.Permissions.Read != nil || m.Permissions.Write != nil || + m.Permissions.Net != nil || m.Permissions.Run != nil { + snap.Permissions = &m.Permissions + } + + return json.MarshalIndent(snap, "", " ") +} diff --git a/snapshot/snapshot_test.go b/snapshot/snapshot_test.go new file mode 100644 index 0000000..1468115 --- /dev/null +++ b/snapshot/snapshot_test.go @@ -0,0 +1,55 @@ +package snapshot + +import ( + "encoding/json" + "testing" + + "forge.lthn.ai/core/go-scm/manifest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGenerate_Good(t *testing.T) { + m := &manifest.Manifest{ + Code: "test-app", + Name: "Test App", + Version: "1.0.0", + Description: "A test application", + Daemons: map[string]manifest.DaemonSpec{ + "serve": {Binary: "core-php", Args: []string{"php", "serve"}, Default: true}, + }, + Modules: []string{"core/media"}, + } + + data, err := Generate(m, "abc123def456", "v1.0.0") + require.NoError(t, err) + + var snap Snapshot + require.NoError(t, json.Unmarshal(data, &snap)) + + assert.Equal(t, 1, snap.Schema) + assert.Equal(t, "test-app", snap.Code) + assert.Equal(t, "1.0.0", snap.Version) + assert.Equal(t, "abc123def456", snap.Commit) + assert.Equal(t, "v1.0.0", snap.Tag) + assert.NotEmpty(t, snap.Built) + assert.Len(t, snap.Daemons, 1) + assert.Equal(t, "core-php", snap.Daemons["serve"].Binary) +} + +func TestGenerate_Good_NoDaemons(t *testing.T) { + m := &manifest.Manifest{ + Code: "simple", + Name: "Simple", + Version: "0.1.0", + } + + data, err := Generate(m, "abc123", "v0.1.0") + require.NoError(t, err) + + var snap Snapshot + require.NoError(t, json.Unmarshal(data, &snap)) + + assert.Equal(t, "simple", snap.Code) + assert.Nil(t, snap.Daemons) +}