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.
157 lines
4.1 KiB
Go
157 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"testing"
|
|
)
|
|
|
|
func TestMergeOptions(t *testing.T) {
|
|
base := options{}
|
|
base.ESBuild.EntryPoints = []string{"a.js", "b.js"}
|
|
base.ESBuild.LogLevel = 3
|
|
base.ESBuild.Splitting = true
|
|
base.Copy = []struct {
|
|
Src string `yaml:"src"`
|
|
Dest string `yaml:"dest"`
|
|
}{{Src: "index.html", Dest: "dist"}}
|
|
|
|
overlay := options{}
|
|
overlay.ESBuild.EntryPoints = []string{"b.js", "c.js"} // b.js duplicates the base
|
|
overlay.ESBuild.LogLevel = 2 // scalar override
|
|
overlay.Copy = []struct {
|
|
Src string `yaml:"src"`
|
|
Dest string `yaml:"dest"`
|
|
}{{Src: "index.html", Dest: "dist"}} // exact duplicate of the base entry
|
|
|
|
merged := mergeOptions(base, overlay)
|
|
|
|
want := []string{"a.js", "b.js", "c.js"}
|
|
if !reflect.DeepEqual(merged.ESBuild.EntryPoints, want) {
|
|
t.Fatalf("entryPoints = %v, want %v", merged.ESBuild.EntryPoints, want)
|
|
}
|
|
|
|
if merged.ESBuild.LogLevel != 2 {
|
|
t.Fatalf("logLevel = %d, want overlay value 2", merged.ESBuild.LogLevel)
|
|
}
|
|
|
|
if !merged.ESBuild.Splitting {
|
|
t.Fatal("splitting = false, want inherited true")
|
|
}
|
|
|
|
if len(merged.Copy) != 1 {
|
|
t.Fatalf("copy = %d entries, want de-duplicated 1", len(merged.Copy))
|
|
}
|
|
|
|
if merged.Extends != "" {
|
|
t.Fatalf("extends = %q, want cleared after merge", merged.Extends)
|
|
}
|
|
}
|
|
|
|
func TestPatternToRegexp(t *testing.T) {
|
|
cases := []struct {
|
|
pattern string
|
|
path string
|
|
match bool
|
|
}{
|
|
// Production shape: processPaths absolutizes patterns and entries
|
|
// alike, so matching happens between absolute forward-slash paths.
|
|
{"/proj/apps/taxes/**", "/proj/apps/taxes/taxes-app.js", true},
|
|
{"/proj/apps/taxes/**", "/proj/apps/taxes/elements/tax-script.js", true},
|
|
{"/proj/apps/taxes/**", "/proj/apps/journal/journal-app.js", false},
|
|
{"/proj/apps/vault/**", "/proj/apps/vaultx/vaultx-app.js", false},
|
|
{"/proj/src/*.js", "/proj/src/the-app.js", true},
|
|
{"/proj/src/*.js", "/proj/src/admin/the-admin.js", false},
|
|
{"/proj/src/the-?.js", "/proj/src/the-a.js", true},
|
|
{"/proj/src/the-?.js", "/proj/src/the-ab.js", false},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
re := regexp.MustCompile(patternToRegexp(c.pattern))
|
|
if re.MatchString(c.path) != c.match {
|
|
t.Errorf("pattern %q vs %q: got %v, want %v", c.pattern, c.path, re.MatchString(c.path), c.match)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFilterEntryPoints(t *testing.T) {
|
|
opts := options{}
|
|
opts.ESBuild.EntryPoints = []string{
|
|
"/proj/apps/taxes/taxes-app.js",
|
|
"/proj/apps/chat/chat-app.js",
|
|
"/proj/the-terminal.js",
|
|
}
|
|
opts.ESBuild.ExcludeEntryPoints = []string{"/proj/apps/taxes/**"}
|
|
|
|
filterEntryPoints(&opts)
|
|
|
|
want := []string{"/proj/apps/chat/chat-app.js", "/proj/the-terminal.js"}
|
|
if !reflect.DeepEqual(opts.ESBuild.EntryPoints, want) {
|
|
t.Fatalf("entryPoints = %v, want %v", opts.ESBuild.EntryPoints, want)
|
|
}
|
|
}
|
|
|
|
func TestReadCfgExtends(t *testing.T) {
|
|
dir := t.TempDir()
|
|
prevWd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { os.Chdir(prevWd) })
|
|
|
|
writeFile := func(name, content string) {
|
|
if err := os.WriteFile(filepath.Join(dir, name), []byte(content), 0644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
writeFile("base.yaml", `- esbuild:
|
|
entryPoints:
|
|
- ./src/base-one.js
|
|
- ./src/base-two.js
|
|
outdir: ./dist
|
|
logLevel: 3
|
|
copy:
|
|
- src: ./index.html
|
|
dest: ./dist
|
|
`)
|
|
|
|
writeFile("overlay.yaml", `- extends: ./base.yaml
|
|
esbuild:
|
|
entryPoints:
|
|
- ./src/base-two.js
|
|
- ./src/overlay.js
|
|
excludeEntryPoints:
|
|
- ./src/base-one.js
|
|
`)
|
|
|
|
if err := os.Chdir(dir); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
profiles := readCfg("./overlay.yaml")
|
|
if len(profiles) != 1 {
|
|
t.Fatalf("profiles = %d, want 1", len(profiles))
|
|
}
|
|
|
|
p := profiles[0]
|
|
want := []string{
|
|
filepath.Join(dir, "src/base-two.js"),
|
|
filepath.Join(dir, "src/overlay.js"),
|
|
}
|
|
|
|
if !reflect.DeepEqual(p.ESBuild.EntryPoints, want) {
|
|
t.Fatalf("entryPoints = %v, want %v", p.ESBuild.EntryPoints, want)
|
|
}
|
|
|
|
if p.ESBuild.LogLevel != 3 {
|
|
t.Fatalf("logLevel = %d, want inherited 3", p.ESBuild.LogLevel)
|
|
}
|
|
|
|
if len(p.Copy) != 1 {
|
|
t.Fatalf("copy = %d entries, want inherited 1", len(p.Copy))
|
|
}
|
|
}
|