147 lines
3.5 KiB
Go
147 lines
3.5 KiB
Go
package fetcher
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"time"
|
|
)
|
|
|
|
// Fetcher allows pulling from an upstream scaffold registry. This is hard coded to the lunarway/scaffold registry, it can also be provided by a path which in that case, will not do anything
|
|
type Fetcher struct{}
|
|
|
|
func NewFetcher() *Fetcher {
|
|
return &Fetcher{}
|
|
}
|
|
|
|
const readWriteExec = 0o744
|
|
|
|
const githubProject = "kjuulh/scaffold"
|
|
|
|
var (
|
|
scaffoldFolder = os.ExpandEnv("$HOME/.scaffold")
|
|
scaffoldClone = path.Join(scaffoldFolder, "upstream")
|
|
scaffoldRegistry = path.Join(scaffoldClone, "registry")
|
|
scaffoldCache = path.Join(scaffoldFolder, "scaffold.updates.json")
|
|
)
|
|
|
|
func (f *Fetcher) Available(registryPath *string) bool {
|
|
if *registryPath == "" {
|
|
if _, err := os.Stat(scaffoldClone); err != nil {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (f *Fetcher) CloneRepository(ctx context.Context, registryPath *string, ui *slog.Logger) error {
|
|
if err := os.MkdirAll(scaffoldFolder, readWriteExec); err != nil {
|
|
return fmt.Errorf("failed to create scaffold folder: %w", err)
|
|
}
|
|
|
|
if *registryPath == "" {
|
|
// update the registry path as it is shared
|
|
*registryPath = scaffoldRegistry
|
|
if _, err := os.Stat(scaffoldClone); err != nil {
|
|
if !errors.Is(err, os.ErrNotExist) {
|
|
return fmt.Errorf("failed to find the upstream folder: %s, %w", scaffoldClone, err)
|
|
}
|
|
|
|
ui.Info("cloning upstream templates")
|
|
if err := cloneUpstream(ctx); err != nil {
|
|
return fmt.Errorf("failed to clone upstream registry: %w", err)
|
|
}
|
|
} else {
|
|
now := time.Now()
|
|
lastUpdatedUnix := getCacheUpdate(ui, ctx)
|
|
lastUpdated := time.Unix(lastUpdatedUnix, 0)
|
|
|
|
// Cache for 7 days
|
|
if lastUpdated.Before(now.Add(-time.Hour * 24 * 7)) {
|
|
ui.Info("update templates folder")
|
|
if err := f.UpdateUpstream(ctx); err != nil {
|
|
return fmt.Errorf("failed to update upstream scaffold folder: %w", err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (f *Fetcher) UpdateUpstream(ctx context.Context) error {
|
|
cmd := exec.CommandContext(ctx, "git", "pull", "--rebase")
|
|
cmd.Dir = scaffoldClone
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
fmt.Printf("git pull failed with output: %s\n\n", string(output))
|
|
return fmt.Errorf("git pull failed: %w", err)
|
|
}
|
|
|
|
if err := createCacheUpdate(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func cloneUpstream(ctx context.Context) error {
|
|
cmd := exec.CommandContext(ctx, "coffee", "repo", "clone", githubProject, scaffoldClone)
|
|
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
fmt.Printf("git clone failed with output: %s\n\n", string(output))
|
|
return fmt.Errorf("git clone failed: %w", err)
|
|
}
|
|
|
|
if err := createCacheUpdate(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type CacheUpdate struct {
|
|
LastUpdated int64 `json:"lastUpdated"`
|
|
}
|
|
|
|
func createCacheUpdate(_ context.Context) error {
|
|
content, err := json.Marshal(CacheUpdate{
|
|
LastUpdated: time.Now().Unix(),
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("failed to prepare cache update: %w", err)
|
|
}
|
|
|
|
if err := os.WriteFile(scaffoldCache, content, readWriteExec); err != nil {
|
|
return fmt.Errorf("failed to write cache update: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getCacheUpdate(ui *slog.Logger, _ context.Context) int64 {
|
|
content, err := os.ReadFile(scaffoldCache)
|
|
if err != nil {
|
|
return 0
|
|
}
|
|
|
|
var cacheUpdate CacheUpdate
|
|
if err := json.Unmarshal(content, &cacheUpdate); err != nil {
|
|
ui.Warn("failed to read cache, it might be invalid", "error", err)
|
|
return 0
|
|
}
|
|
|
|
return cacheUpdate.LastUpdated
|
|
}
|