go-scm/manifest/compile.go

111 lines
3.5 KiB
Go
Raw Permalink Normal View History

// SPDX-License-Identifier: EUPL-1.2
package manifest
import (
"crypto/ed25519"
filepath "dappco.re/go/core/scm/internal/ax/filepathx"
json "dappco.re/go/core/scm/internal/ax/jsonx"
"time"
"dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
)
// CompiledManifest is the distribution-ready form of a manifest, written as
// core.json at the repository root (not inside .core/). It embeds the
// original Manifest and adds build metadata stapled at compile time.
type CompiledManifest struct {
Manifest `json:",inline" yaml:",inline"`
// Build metadata — populated by Compile.
Commit string `json:"commit,omitempty" yaml:"commit,omitempty"`
Tag string `json:"tag,omitempty" yaml:"tag,omitempty"`
BuiltAt string `json:"built_at,omitempty" yaml:"built_at,omitempty"`
BuiltBy string `json:"built_by,omitempty" yaml:"built_by,omitempty"`
}
// CompileOptions controls how Compile populates the build metadata.
type CompileOptions struct {
Version string // Optional override for the manifest version
Commit string // Git commit hash
Tag string // Git tag (e.g. v1.0.0)
BuiltBy string // Builder identity (e.g. "core build")
SignKey ed25519.PrivateKey // Optional — signs before compiling
}
// Compile produces a CompiledManifest from a source manifest and build
// options. If opts.SignKey is provided the manifest is signed first.
// Usage: Compile(...)
func Compile(m *Manifest, opts CompileOptions) (*CompiledManifest, error) {
if m == nil {
return nil, coreerr.E("manifest.Compile", "nil manifest", nil)
}
if m.Code == "" {
return nil, coreerr.E("manifest.Compile", "missing code", nil)
}
if m.Version == "" {
return nil, coreerr.E("manifest.Compile", "missing version", nil)
}
if opts.Version != "" {
m.Version = opts.Version
}
// Sign if a key is supplied.
if opts.SignKey != nil {
if err := Sign(m, opts.SignKey); err != nil {
return nil, coreerr.E("manifest.Compile", "sign failed", err)
}
}
return &CompiledManifest{
Manifest: *m,
Commit: opts.Commit,
Tag: opts.Tag,
BuiltAt: time.Now().UTC().Format(time.RFC3339),
BuiltBy: opts.BuiltBy,
}, nil
}
// MarshalJSON serialises a CompiledManifest to JSON bytes.
// Usage: MarshalJSON(...)
func MarshalJSON(cm *CompiledManifest) ([]byte, error) {
return json.MarshalIndent(cm, "", " ")
}
// ParseCompiled decodes a core.json into a CompiledManifest.
// Usage: ParseCompiled(...)
func ParseCompiled(data []byte) (*CompiledManifest, error) {
var cm CompiledManifest
if err := json.Unmarshal(data, &cm); err != nil {
return nil, coreerr.E("manifest.ParseCompiled", "unmarshal failed", err)
}
return &cm, nil
}
const compiledPath = "core.json"
// WriteCompiled writes a CompiledManifest as core.json to the given root
// directory. The file lives at the distribution root, not inside .core/.
// Usage: WriteCompiled(...)
func WriteCompiled(medium io.Medium, root string, cm *CompiledManifest) error {
data, err := MarshalJSON(cm)
if err != nil {
return coreerr.E("manifest.WriteCompiled", "marshal failed", err)
}
path := filepath.Join(root, compiledPath)
return medium.Write(path, string(data))
}
// LoadCompiled reads and parses a core.json from the given root directory.
// Usage: LoadCompiled(...)
func LoadCompiled(medium io.Medium, root string) (*CompiledManifest, error) {
path := filepath.Join(root, compiledPath)
data, err := medium.Read(path)
if err != nil {
return nil, coreerr.E("manifest.LoadCompiled", "read failed", err)
}
return ParseCompiled([]byte(data))
}