build.go

v1.3.4
Doc Versions Source
1
package build
2
3
import (
4
	"bytes"
5
	"encoding/json"
6
	"fmt"
7
	"io"
8
	"io/fs"
9
	"time"
10
11
	"golang.org/x/mod/module"
12
	"golang.org/x/mod/zip"
13
14
	"go.bigb.es/curator/internal/git"
15
)
16
17
// RevInfo is the JSON structure returned by .info and @latest endpoints.
18
type RevInfo struct {
19
	Version string    `json:"Version"`
20
	Time    time.Time `json:"Time"`
21
	Origin  *Origin   `json:"Origin,omitempty"`
22
}
23
24
// Origin describes the provenance of a module version.
25
type Origin struct {
26
	VCS  string `json:"VCS,omitempty"`
27
	URL  string `json:"URL,omitempty"`
28
	Hash string `json:"Hash,omitempty"`
29
	Ref  string `json:"Ref,omitempty"`
30
}
31
32
// Info builds a JSON .info response for the given version.
33
func Info(repoPath string, rv *git.ResolvedVersion, modulePath string) ([]byte, string, error) {
34
	hash, t, err := git.CommitInfo(repoPath, rv.GitRev)
35
	if err != nil {
36
		return nil, "", err
37
	}
38
39
	info := RevInfo{
40
		Version: rv.Version,
41
		Time:    t.UTC(),
42
		Origin: &Origin{
43
			VCS:  "git",
44
			URL:  modulePath,
45
			Hash: hash,
46
			Ref:  rv.GitRev,
47
		},
48
	}
49
50
	data, err := json.Marshal(info)
51
	return data, "application/json", err
52
}
53
54
// Mod builds a go.mod response. Falls back to a synthetic go.mod if none exists.
55
func Mod(repoPath string, rv *git.ResolvedVersion, modulePath string) ([]byte, string, error) {
56
	data, err := git.ReadFile(repoPath, rv.GitRev, "go.mod")
57
	if err != nil {
58
		data = []byte("module " + modulePath + "\n")
59
	}
60
61
	return data, "text/plain; charset=utf-8", nil
62
}
63
64
// Zip builds a module zip archive. Works with both bare and non-bare git repos
65
// by reading files directly via git commands.
66
func Zip(repoPath string, rv *git.ResolvedVersion, modulePath string) ([]byte, string, error) {
67
	var buf bytes.Buffer
68
	mv := module.Version{Path: modulePath, Version: rv.Version}
69
70
	files, err := git.ListFiles(repoPath, rv.GitRev)
71
	if err != nil {
72
		return nil, "", fmt.Errorf("list files: %w", err)
73
	}
74
75
	var zipFiles []zip.File
76
	for _, path := range files {
77
		zipFiles = append(zipFiles, &gitFile{
78
			repoPath: repoPath,
79
			rev:      rv.GitRev,
80
			path:     path,
81
		})
82
	}
83
84
	if err := zip.Create(&buf, mv, zipFiles); err != nil {
85
		return nil, "", err
86
	}
87
88
	return buf.Bytes(), "application/zip", nil
89
}
90
91
92
// gitFile implements zip.File by reading from a bare git repo.
93
type gitFile struct {
94
	repoPath string
95
	rev      string
96
	path     string
97
}
98
99
func (f *gitFile) Path() string { return f.path }
100
101
func (f *gitFile) Lstat() (fs.FileInfo, error) {
102
	data, err := f.readContent()
103
	if err != nil {
104
		return nil, err
105
	}
106
	return &gitFileInfo{name: f.path, size: int64(len(data))}, nil
107
}
108
109
func (f *gitFile) Open() (io.ReadCloser, error) {
110
	data, err := f.readContent()
111
	if err != nil {
112
		return nil, err
113
	}
114
	return io.NopCloser(bytes.NewReader(data)), nil
115
}
116
117
func (f *gitFile) readContent() ([]byte, error) {
118
	return git.ReadFile(f.repoPath, f.rev, f.path)
119
}
120
121
// gitFileInfo implements fs.FileInfo for git files.
122
type gitFileInfo struct {
123
	name string
124
	size int64
125
}
126
127
func (fi *gitFileInfo) Name() string      { return fi.name }
128
func (fi *gitFileInfo) Size() int64       { return fi.size }
129
func (fi *gitFileInfo) Mode() fs.FileMode { return 0o644 }
130
func (fi *gitFileInfo) ModTime() time.Time { return time.Time{} }
131
func (fi *gitFileInfo) IsDir() bool       { return false }
132
func (fi *gitFileInfo) Sys() any          { return nil }
133

Source Files