107 lines
2.5 KiB
Go
107 lines
2.5 KiB
Go
package sourcemaps
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
sm "github.com/go-sourcemap/sourcemap"
|
|
)
|
|
|
|
var frameRE = regexp.MustCompile(`(.*?)(https?://[^ )]+|[^ )]+):(\d+):(\d+)(\)?\s*)$`)
|
|
|
|
type Resolver struct {
|
|
dir string
|
|
mu sync.RWMutex
|
|
maps map[string]*sm.Consumer
|
|
}
|
|
|
|
func New(dir string) (*Resolver, error) {
|
|
if dir == "" {
|
|
dir = "./sourcemaps"
|
|
}
|
|
if err := os.MkdirAll(dir, 0750); err != nil {
|
|
return nil, err
|
|
}
|
|
return &Resolver{dir: dir, maps: make(map[string]*sm.Consumer)}, nil
|
|
}
|
|
|
|
func key(release, source string) string { return release + "\x00" + normalize(source) }
|
|
|
|
func normalize(source string) string {
|
|
source = strings.Split(source, "?")[0]
|
|
if i := strings.Index(source, "/assets/"); i >= 0 {
|
|
return source[i:]
|
|
}
|
|
return strings.TrimPrefix(source, "./")
|
|
}
|
|
|
|
func (r *Resolver) Upload(release, source string, body io.Reader) error {
|
|
if strings.TrimSpace(release) == "" || strings.TrimSpace(source) == "" {
|
|
return fmt.Errorf("release and source are required")
|
|
}
|
|
b, err := io.ReadAll(io.LimitReader(body, 50<<20))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
consumer, err := sm.Parse(source+".map", b)
|
|
if err != nil {
|
|
return fmt.Errorf("invalid source map: %w", err)
|
|
}
|
|
name := filepath.Join(r.dir, safe(release)+"-"+safe(normalize(source))+".map")
|
|
if err := os.WriteFile(name, b, 0600); err != nil {
|
|
return err
|
|
}
|
|
r.mu.Lock()
|
|
r.maps[key(release, source)] = consumer
|
|
r.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
func safe(s string) string {
|
|
s = strings.ReplaceAll(s, "/", "_")
|
|
s = strings.ReplaceAll(s, "\\", "_")
|
|
return strings.Map(func(r rune) rune {
|
|
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || strings.ContainsRune("._-", r) {
|
|
return r
|
|
}
|
|
return '_'
|
|
}, s)
|
|
}
|
|
|
|
func (r *Resolver) ResolveStack(release, stack string) string {
|
|
if release == "" || stack == "" {
|
|
return stack
|
|
}
|
|
r.mu.RLock()
|
|
defer r.mu.RUnlock()
|
|
lines := strings.Split(stack, "\n")
|
|
for i, line := range lines {
|
|
m := frameRE.FindStringSubmatch(line)
|
|
if m == nil {
|
|
continue
|
|
}
|
|
genLine, _ := strconv.Atoi(m[3])
|
|
genCol, _ := strconv.Atoi(m[4])
|
|
consumer := r.maps[key(release, m[2])]
|
|
if consumer == nil {
|
|
continue
|
|
}
|
|
source, name, srcLine, srcCol, ok := consumer.Source(genLine, genCol)
|
|
if !ok {
|
|
continue
|
|
}
|
|
label := source
|
|
if name != "" {
|
|
label += " (" + name + ")"
|
|
}
|
|
lines[i] = fmt.Sprintf("%s%s:%d:%d%s [mapped from %s:%d:%d]", m[1], label, srcLine, srcCol, m[5], m[2], genLine, genCol)
|
|
}
|
|
return strings.Join(lines, "\n")
|
|
}
|