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() }