diff --git a/CHANGELOG.md b/CHANGELOG.md index e9f7ba6..8cefc3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.21.0] - 2026-05-02 + +### Added +- Top-level `type` field in `.goodchangesrc.json` (`"library"` or `"app"`). When set, overrides the automatic library-vs-app inference from `package.json`. Invalid values cause a fatal error at startup. + ## [0.20.0] - 2026-05-01 ### Changed @@ -287,6 +292,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Multi-stage Docker build - Automated vendor upgrade workflow +[0.21.0]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.20.0...v0.21.0 [0.20.0]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.19.4...v0.20.0 [0.19.4]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.19.3...v0.19.4 [0.19.3]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.19.2...v0.19.3 diff --git a/README.md b/README.md index ec7c48a..8ed32b6 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,9 @@ JSON array of target objects: ## Library vs app detection -A package is classified as a **library** if its `package.json` contains any of: +Detection can be overridden by setting the top-level `type` field in `.goodchangesrc.json` to `"library"` or `"app"` (see [Configuration](#configuration)). When unset, classification is inferred. + +A package is inferred as a **library** if its `package.json` contains any of: - `types` (TypeScript type declarations) - `exports` (modern package exports field) @@ -77,7 +79,7 @@ A package is classified as a **library** if its `package.json` contains any of: Libraries get full AST-level analysis: entrypoint resolution, symbol diffing, and taint propagation through their internal import graph. -Everything else is an **app** (bundled). Apps are not analyzed for granular exports -- if any file in an app changes, the app is considered fully tainted. +Everything else is inferred as an **app** (bundled). Apps are not analyzed for granular exports -- if any file in an app changes, the app is considered fully tainted. ## Configuration @@ -152,11 +154,12 @@ Each `changeDirs` entry is an object with: **Top-level fields:** -| Field | Type | Description | -|--------------|---------------|---------------------------------------------------------------------------------------------------------| -| `targets` | `TargetDef[]` | Array of target definitions (see below) | -| `ignores` | `string[]` | Glob patterns for files to exclude from change detection | -| `changeDirs` | `ChangeDir[]` | Global changeDirs. When triggered, taints all library exports and triggers all targets in this package. | +| Field | Type | Description | +|--------------|----------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `type` | `"library" \| "app"` | Optional. Forces this package's classification, skipping the inference described in [Library vs app detection](#library-vs-app-detection). Invalid values cause a fatal error. | +| `targets` | `TargetDef[]` | Array of target definitions (see below) | +| `ignores` | `string[]` | Glob patterns for files to exclude from change detection | +| `changeDirs` | `ChangeDir[]` | Global changeDirs. When triggered, taints all library exports and triggers all targets in this package. | **TargetDef fields (each entry in `targets`):** diff --git a/VERSION b/VERSION index a881cf7..1db0ede 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.20.0 \ No newline at end of file +0.21.0 \ No newline at end of file diff --git a/internal/analyzer/analyzer.go b/internal/analyzer/analyzer.go index 1f990fd..bdd5ff0 100644 --- a/internal/analyzer/analyzer.go +++ b/internal/analyzer/analyzer.go @@ -32,7 +32,12 @@ type AffectedExport struct { } // IsLibrary determines if a package is a library (transpiled) vs a bundled app. -func IsLibrary(pkg rush.PackageJSON) bool { +// When the project config sets an explicit `type`, that value wins; otherwise +// the result is inferred from package.json fields. +func IsLibrary(pc *rush.ProjectConfig, pkg rush.PackageJSON) bool { + if pc != nil && pc.Type != nil { + return *pc.Type == "library" + } if pkg.Types != "" { return true } diff --git a/internal/rush/rush.go b/internal/rush/rush.go index 0558380..a6e2bca 100644 --- a/internal/rush/rush.go +++ b/internal/rush/rush.go @@ -134,6 +134,7 @@ func (td TargetDef) OutputName(packageName string) string { } type ProjectConfig struct { + Type *string `json:"type,omitempty"` // "library" or "app". When set, overrides automatic inference. Targets []TargetDef `json:"targets,omitempty"` Ignores []string `json:"ignores,omitempty"` ChangeDirs []ChangeDir `json:"changeDirs,omitempty"` // global changeDirs: triggers all exports (library) or all targets (app) diff --git a/main.go b/main.go index 186606c..ff53c8a 100644 --- a/main.go +++ b/main.go @@ -104,6 +104,16 @@ func main() { projectMap := rush.BuildProjectMap(rushConfig) configMap := rush.LoadAllProjectConfigs(rushConfig) + for projectFolder, cfg := range configMap { + if cfg == nil || cfg.Type == nil { + continue + } + if *cfg.Type != "library" && *cfg.Type != "app" { + fmt.Fprintf(os.Stderr, "Invalid type %q in %s/.goodchangesrc.json: must be \"library\" or \"app\"\n", *cfg.Type, projectFolder) + os.Exit(1) + } + } + // Parse TARGETS filter early to skip expensive detection for non-matching targets var targetPatterns []string if targetsEnv := os.Getenv("TARGETS"); targetsEnv != "" { @@ -208,7 +218,7 @@ func main() { if info == nil { continue } - if analyzer.IsLibrary(info.Package) { + if analyzer.IsLibrary(configMap[rp.ProjectFolder], info.Package) { if allUpstreamTaint[rp.PackageName] == nil { allUpstreamTaint[rp.PackageName] = make(map[string]bool) } @@ -233,7 +243,7 @@ func main() { continue } pkg := info.Package - lib := analyzer.IsLibrary(pkg) + lib := analyzer.IsLibrary(configMap[info.ProjectFolder], pkg) directlyChanged := changedProjects[pkgName] != nil changedDeps := depChangedDeps[info.ProjectFolder] isDepAffected := len(changedDeps) > 0