0
0
mirror of https://github.com/go-gitea/gitea.git synced 2025-12-10 19:44:33 +01:00
gitea/modules/renderplugin/manifest.go
2025-12-05 20:38:42 -08:00

107 lines
3.1 KiB
Go

// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package renderplugin
import (
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/util"
)
var identifierRegexp = regexp.MustCompile(`^[a-z0-9][a-z0-9\-_.]{1,63}$`)
// Manifest describes the metadata declared by a render plugin.
const SupportedManifestVersion = 1
type Manifest struct {
SchemaVersion int `json:"schemaVersion"`
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description"`
Entry string `json:"entry"`
FilePatterns []string `json:"filePatterns"`
}
// Normalize validates mandatory fields and normalizes values.
func (m *Manifest) Normalize() error {
if m.SchemaVersion == 0 {
return errors.New("manifest schemaVersion is required")
}
if m.SchemaVersion != SupportedManifestVersion {
return fmt.Errorf("manifest schemaVersion %d is not supported", m.SchemaVersion)
}
m.ID = strings.TrimSpace(strings.ToLower(m.ID))
if !identifierRegexp.MatchString(m.ID) {
return fmt.Errorf("manifest id %q is invalid; only lowercase letters, numbers, dash, underscore and dot are allowed", m.ID)
}
m.Name = strings.TrimSpace(m.Name)
if m.Name == "" {
return errors.New("manifest name is required")
}
m.Version = strings.TrimSpace(m.Version)
if m.Version == "" {
return errors.New("manifest version is required")
}
if m.Entry == "" {
m.Entry = "render.js"
}
m.Entry = util.PathJoinRelX(m.Entry)
if m.Entry == "" || strings.HasPrefix(m.Entry, "../") {
return fmt.Errorf("manifest entry %q is invalid", m.Entry)
}
cleanPatterns := make([]string, 0, len(m.FilePatterns))
for _, pattern := range m.FilePatterns {
pattern = strings.TrimSpace(pattern)
if pattern == "" {
continue
}
cleanPatterns = append(cleanPatterns, pattern)
}
if len(cleanPatterns) == 0 {
return errors.New("manifest must declare at least one file pattern")
}
sort.Strings(cleanPatterns)
m.FilePatterns = cleanPatterns
return nil
}
// LoadManifest reads and validates the manifest.json file located under dir.
func LoadManifest(dir string) (*Manifest, error) {
manifestPath := filepath.Join(dir, "manifest.json")
f, err := os.Open(manifestPath)
if err != nil {
return nil, err
}
defer f.Close()
var manifest Manifest
if err := json.NewDecoder(f).Decode(&manifest); err != nil {
return nil, fmt.Errorf("malformed manifest.json: %w", err)
}
if err := manifest.Normalize(); err != nil {
return nil, err
}
return &manifest, nil
}
// Metadata is the public information exposed to the frontend for an enabled plugin.
type Metadata struct {
ID string `json:"id"`
Name string `json:"name"`
Version string `json:"version"`
Description string `json:"description"`
Entry string `json:"entry"`
EntryURL string `json:"entryUrl"`
AssetsBase string `json:"assetsBaseUrl"`
FilePatterns []string `json:"filePatterns"`
SchemaVersion int `json:"schemaVersion"`
}