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:
+156
@@ -0,0 +1,156 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user