Add config extends and entry point exclusion
Profiles may inherit from another config file (extends: <path>): the first profile of the referenced file is merged underneath (scalars from the overlay win, slices concatenate base-first and de-duplicate, chains resolve recursively, cycles are detected). esbuild.excludeEntryPoints removes merged entry points by glob (** spans path separators, * stays within a segment), applied after path resolution so patterns and entries match in the same shape. Lets product editions/flavors (e.g. the wmd4x Range Trader Pro edition) build only their own entry set on top of a shared, merge-clean base config. Includes unit tests and template docs.
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// mergeOptions layers an extending profile on top of the profile it extends:
|
||||
// scalar fields from the overlay win when set, slices are concatenated
|
||||
// (base first, de-duplicated), nested structs merge recursively.
|
||||
func mergeOptions(base, overlay options) options {
|
||||
merged := mergeValue(reflect.ValueOf(base), reflect.ValueOf(overlay)).Interface().(options)
|
||||
merged.Extends = "" // already resolved; must not re-trigger
|
||||
return merged
|
||||
}
|
||||
|
||||
func mergeValue(base, overlay reflect.Value) reflect.Value {
|
||||
switch base.Kind() {
|
||||
case reflect.Struct:
|
||||
out := reflect.New(base.Type()).Elem()
|
||||
for i := range base.NumField() {
|
||||
out.Field(i).Set(mergeValue(base.Field(i), overlay.Field(i)))
|
||||
}
|
||||
return out
|
||||
case reflect.Slice:
|
||||
if base.Len() == 0 {
|
||||
return overlay
|
||||
}
|
||||
if overlay.Len() == 0 {
|
||||
return base
|
||||
}
|
||||
return concatDedupe(base, overlay)
|
||||
default:
|
||||
if overlay.IsZero() {
|
||||
return base
|
||||
}
|
||||
return overlay
|
||||
}
|
||||
}
|
||||
|
||||
func concatDedupe(base, overlay reflect.Value) reflect.Value {
|
||||
out := reflect.MakeSlice(base.Type(), 0, base.Len()+overlay.Len())
|
||||
|
||||
for _, src := range []reflect.Value{base, overlay} {
|
||||
for i := range src.Len() {
|
||||
v := src.Index(i)
|
||||
|
||||
dup := false
|
||||
for j := range out.Len() {
|
||||
if reflect.DeepEqual(out.Index(j).Interface(), v.Interface()) {
|
||||
dup = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !dup {
|
||||
out = reflect.Append(out, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// filterEntryPoints removes entry points matched by any excludeEntryPoints
|
||||
// pattern. Patterns are matched against the resolved (absolute, forward-slash)
|
||||
// entry paths and support ** (any characters, including path separators),
|
||||
// * (anything except a path separator) and ? (a single character).
|
||||
func filterEntryPoints(opts *options) {
|
||||
if len(opts.ESBuild.ExcludeEntryPoints) == 0 || len(opts.ESBuild.EntryPoints) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
patterns := make([]*regexp.Regexp, 0, len(opts.ESBuild.ExcludeEntryPoints))
|
||||
for _, p := range opts.ESBuild.ExcludeEntryPoints {
|
||||
re, err := regexp.Compile(patternToRegexp(filepath.ToSlash(p)))
|
||||
if err != nil {
|
||||
fmt.Printf("invalid excludeEntryPoints pattern %q: %+v\n", p, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
patterns = append(patterns, re)
|
||||
}
|
||||
|
||||
kept := make([]string, 0, len(opts.ESBuild.EntryPoints))
|
||||
for _, e := range opts.ESBuild.EntryPoints {
|
||||
excluded := false
|
||||
for _, re := range patterns {
|
||||
if re.MatchString(filepath.ToSlash(e)) {
|
||||
excluded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !excluded {
|
||||
kept = append(kept, e)
|
||||
}
|
||||
}
|
||||
|
||||
opts.ESBuild.EntryPoints = kept
|
||||
}
|
||||
|
||||
func patternToRegexp(pattern string) string {
|
||||
var b strings.Builder
|
||||
b.WriteString("^")
|
||||
|
||||
// Classic loop: the body advances i to consume the second '*' of '**'.
|
||||
for i := 0; i < len(pattern); i++ {
|
||||
switch c := pattern[i]; c {
|
||||
case '*':
|
||||
if i+1 < len(pattern) && pattern[i+1] == '*' {
|
||||
b.WriteString(".*")
|
||||
i++
|
||||
} else {
|
||||
b.WriteString("[^/]*")
|
||||
}
|
||||
case '?':
|
||||
b.WriteString("[^/]")
|
||||
default:
|
||||
b.WriteString(regexp.QuoteMeta(string(c)))
|
||||
}
|
||||
}
|
||||
|
||||
b.WriteString("$")
|
||||
return b.String()
|
||||
}
|
||||
Reference in New Issue
Block a user