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:
@@ -38,20 +38,22 @@ func cfgToESBuildCfg(cfg options) api.BuildOptions {
|
||||
}
|
||||
|
||||
type options struct {
|
||||
Extends string `yaml:"extends"` // inherit from the first profile of the referenced config file
|
||||
ESBuild struct {
|
||||
EntryPoints []string `yaml:"entryPoints"`
|
||||
Outdir string `yaml:"outdir"`
|
||||
Outbase string `yaml:"outbase"`
|
||||
Outfile string `yaml:"outfile"`
|
||||
Sourcemap int `yaml:"sourcemap"`
|
||||
Format int `yaml:"format"`
|
||||
Splitting bool `yaml:"splitting"`
|
||||
Platform int `yaml:"platform"`
|
||||
Bundle bool `yaml:"bundle"`
|
||||
Write bool `yaml:"write"`
|
||||
LogLevel int `yaml:"logLevel"`
|
||||
Target int `yaml:"target"`
|
||||
PurgeBeforeBuild bool `yaml:"purgeBeforeBuild"`
|
||||
EntryPoints []string `yaml:"entryPoints"`
|
||||
ExcludeEntryPoints []string `yaml:"excludeEntryPoints"`
|
||||
Outdir string `yaml:"outdir"`
|
||||
Outbase string `yaml:"outbase"`
|
||||
Outfile string `yaml:"outfile"`
|
||||
Sourcemap int `yaml:"sourcemap"`
|
||||
Format int `yaml:"format"`
|
||||
Splitting bool `yaml:"splitting"`
|
||||
Platform int `yaml:"platform"`
|
||||
Bundle bool `yaml:"bundle"`
|
||||
Write bool `yaml:"write"`
|
||||
LogLevel int `yaml:"logLevel"`
|
||||
Target int `yaml:"target"`
|
||||
PurgeBeforeBuild bool `yaml:"purgeBeforeBuild"`
|
||||
} `yaml:"esbuild"`
|
||||
Watch struct {
|
||||
Paths []string `yaml:"paths"`
|
||||
@@ -119,6 +121,38 @@ func readCfg(cfgPath string) []options {
|
||||
cfgPath = yamlPath
|
||||
}
|
||||
|
||||
profiles := readCfgFile(cfgPath, map[string]bool{})
|
||||
|
||||
// Path resolution and entry point filtering run once, after all extends
|
||||
// chains are merged, so de-duplication compares like with like and
|
||||
// patterns match the same shape as the entries.
|
||||
for i := range profiles {
|
||||
processPaths(&profiles[i])
|
||||
filterEntryPoints(&profiles[i])
|
||||
}
|
||||
|
||||
return profiles
|
||||
}
|
||||
|
||||
// readCfgFile loads a config file and resolves `extends` chains. A profile
|
||||
// may declare `extends: <path>`; the first profile of the referenced file is
|
||||
// merged underneath it (scalars: overlay wins when set, slices: concatenated
|
||||
// base-first and de-duplicated). Chains are resolved recursively; visiting
|
||||
// guards against inheritance cycles.
|
||||
func readCfgFile(cfgPath string, visiting map[string]bool) []options {
|
||||
absPath, err := filepath.Abs(cfgPath)
|
||||
if err != nil {
|
||||
fmt.Printf("%+v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if visiting[absPath] {
|
||||
fmt.Printf("extends cycle detected at %s\n", absPath)
|
||||
os.Exit(1)
|
||||
}
|
||||
visiting[absPath] = true
|
||||
defer delete(visiting, absPath)
|
||||
|
||||
cfgContent, err := os.ReadFile(cfgPath)
|
||||
|
||||
if err != nil {
|
||||
@@ -140,9 +174,27 @@ func readCfg(cfgPath string) []options {
|
||||
optsSetups = append(optsSetups, opt)
|
||||
}
|
||||
|
||||
// Process all paths in each options setup
|
||||
cfgDir := filepath.Dir(cfgPath)
|
||||
|
||||
for i := range optsSetups {
|
||||
processPaths(&optsSetups[i])
|
||||
base := strings.TrimSpace(optsSetups[i].Extends)
|
||||
|
||||
if base == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if !filepath.IsAbs(base) {
|
||||
base = filepath.Join(cfgDir, base)
|
||||
}
|
||||
|
||||
baseProfiles := readCfgFile(base, visiting)
|
||||
|
||||
if len(baseProfiles) == 0 {
|
||||
fmt.Printf("extends target %s contains no profiles\n", base)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
optsSetups[i] = mergeOptions(baseProfiles[0], optsSetups[i])
|
||||
}
|
||||
|
||||
return optsSetups
|
||||
@@ -178,6 +230,10 @@ func processPaths(opts *options) {
|
||||
for i, entry := range opts.ESBuild.EntryPoints {
|
||||
opts.ESBuild.EntryPoints[i] = fsutils.ResolvePath(entry)
|
||||
}
|
||||
|
||||
for i, entry := range opts.ESBuild.ExcludeEntryPoints {
|
||||
opts.ESBuild.ExcludeEntryPoints[i] = fsutils.ResolvePath(entry)
|
||||
}
|
||||
opts.ESBuild.Outdir = fsutils.ResolvePath(opts.ESBuild.Outdir)
|
||||
opts.ESBuild.Outfile = fsutils.ResolvePath(opts.ESBuild.Outfile)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user