Skip to content

Commit 78c1559

Browse files
authored
chore: Merge target and virtual-target into unified target definition
2 parents d862b4d + 9b3a817 commit 78c1559

6 files changed

Lines changed: 145 additions & 148 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.16.0] - 2026-04-04
9+
10+
### Changed
11+
- **Breaking:** Merged `target` and `virtual-target` types into a unified target definition. The `type` field is removed. All targets now support `app`, `targetName`, `changeDirs`, lockfile detection, and fine-grained mode. `targetName` defaults to the package name when not set. `changeDirs` defaults to `**/*` when not set.
12+
813
## [0.15.3] - 2026-02-23
914

1015
### Fixed
@@ -197,6 +202,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
197202
- Multi-stage Docker build
198203
- Automated vendor upgrade workflow
199204

205+
[0.16.0]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.15.3...v0.16.0
200206
[0.15.3]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.15.2...v0.15.3
201207
[0.15.2]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.15.1...v0.15.2
202208
[0.15.1]: https://github.com/gooddata/gooddata-goodchanges/compare/v0.15.0...v0.15.1

README.md

Lines changed: 15 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,9 @@ Each project can optionally have a `.goodchangesrc.json` file in its root direct
8686
{
8787
"targets": [
8888
{
89-
"type": "target",
9089
"app": "@gooddata/gdc-dashboards"
9190
},
9291
{
93-
"type": "virtual-target",
9492
"targetName": "neobackstop",
9593
"changeDirs": [
9694
{ "glob": "src/**/*" },
@@ -104,20 +102,16 @@ Each project can optionally have a `.goodchangesrc.json` file in its root direct
104102
}
105103
```
106104

107-
### Target
105+
### Trigger conditions
108106

109-
Marks a project as an e2e test package. The package name is included in the output when any of the 4 trigger conditions are met.
107+
Each target is triggered by any of these conditions:
110108

111-
**Trigger conditions:**
112-
113-
1. **Direct file changes** -- files changed in the project folder (excluding ignored paths)
109+
1. **Direct file changes** -- files matching `changeDirs` globs changed (excluding ignored paths). Defaults to `**/*` (entire project) when `changeDirs` is not set.
114110
2. **External dependency changes** -- a dependency version changed in `pnpm-lock.yaml`
115-
3. **Tainted workspace imports** -- the target imports a tainted symbol from a workspace library
111+
3. **Tainted workspace imports** -- a file matching `changeDirs` globs imports a tainted symbol from a workspace library
116112
4. **Corresponding app is tainted** -- the app specified by `app` is affected (any of the above conditions)
117113

118-
### Virtual target
119-
120-
An aggregated target that uses glob patterns to match files across a project. Does not correspond to a real package name in the output -- uses `targetName` instead.
114+
### changeDirs
121115

122116
Each `changeDirs` entry is an object with:
123117

@@ -142,20 +136,19 @@ Each `changeDirs` entry is an object with:
142136

143137
**Top-level fields:**
144138

145-
| Field | Type | Description |
146-
|-----------|---------------|------------------------------------------------------------|
147-
| `targets` | `TargetDef[]` | Array of target definitions (see below) |
148-
| `ignores` | `string[]` | Glob patterns for files to exclude from change detection |
139+
| Field | Type | Description |
140+
|-----------|---------------|----------------------------------------------------------|
141+
| `targets` | `TargetDef[]` | Array of target definitions (see below) |
142+
| `ignores` | `string[]` | Glob patterns for files to exclude from change detection |
149143

150144
**TargetDef fields (each entry in `targets`):**
151145

152-
| Field | Type | Used by | Description |
153-
|--------------|----------------------------------|----------------|--------------------------------------------------------------------------------------------------------|
154-
| `type` | `"target"` \| `"virtual-target"` | Both | Declares what kind of target this is |
155-
| `app` | `string` | Target | Package name of the corresponding app this e2e package tests |
156-
| `targetName` | `string` | Virtual target | Output name emitted when the virtual target is triggered |
157-
| `changeDirs` | `ChangeDir[]` | Virtual target | Glob patterns to match files. Each entry: `{"glob": "...", "filter?": "...", "type?": "fine-grained"}` |
158-
| `ignores` | `string[]` | Both | Per-target ignore globs. Additive with the global `ignores` -- only applies to this target's detection |
146+
| Field | Type | Description |
147+
|--------------|----------------|--------------------------------------------------------------------------------------------------------|
148+
| `app` | `string` | Package name of the corresponding app this target tests |
149+
| `targetName` | `string` | Custom output name (defaults to the package name when not set) |
150+
| `changeDirs` | `ChangeDir[]` | Glob patterns to match files. Defaults to `**/*` (entire project). Each entry: `{"glob": "...", "filter?": "...", "type?": "fine-grained"}` |
151+
| `ignores` | `string[]` | Per-target ignore globs. Additive with the global `ignores` -- only applies to this target's detection |
159152

160153
The `.goodchangesrc.json` file itself is always ignored.
161154

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.15.3
1+
0.16.0

internal/analyzer/analyzer.go

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1323,20 +1323,24 @@ func FindAffectedFiles(globPattern string, filterPattern string, upstreamTaint m
13231323
}
13241324
changedSymbols := findAffectedSymbolsByASTDiff(oldAnalysis, analysis, oldContent, includeTypes)
13251325
debugf(" %s: affected symbols (AST diff): %v", stem, changedSymbols)
1326-
if len(changedSymbols) > 0 || oldAnalysis == nil {
1327-
if tainted[stem] == nil {
1328-
tainted[stem] = make(map[string]bool)
1326+
if tainted[stem] == nil {
1327+
tainted[stem] = make(map[string]bool)
1328+
}
1329+
if oldAnalysis == nil {
1330+
// New file: taint all symbols
1331+
debugf(" %s: new file — tainting all symbols", stem)
1332+
for _, sym := range analysis.Symbols {
1333+
tainted[stem][sym.Name] = true
13291334
}
1335+
tainted[stem]["*"] = true
1336+
} else if len(changedSymbols) > 0 {
13301337
for _, s := range changedSymbols {
13311338
tainted[stem][s] = true
13321339
}
1333-
// New file: taint all symbols
1334-
if oldAnalysis == nil {
1335-
debugf(" %s: new file — tainting all symbols", stem)
1336-
for _, sym := range analysis.Symbols {
1337-
tainted[stem][sym.Name] = true
1338-
}
1339-
}
1340+
} else {
1341+
// File changed but no symbol-level diff detected (e.g. changes in
1342+
// test()/describe() blocks or other non-declaration code).
1343+
tainted[stem]["*"] = true
13401344
}
13411345
}
13421346

@@ -1699,6 +1703,38 @@ func FindAffectedFiles(globPattern string, filterPattern string, upstreamTaint m
16991703
}
17001704
}
17011705

1706+
// Mark leaf files (no tainted symbols yet) that import from tainted files.
1707+
// Covers spec/test files that use imports in test()/describe() blocks
1708+
// without declaring top-level symbols the usage checker can find.
1709+
for stem := range fileAnalyses {
1710+
if tainted[stem] != nil {
1711+
continue
1712+
}
1713+
for _, edge := range localImportGraph[stem] {
1714+
src := tainted[edge.fromStem]
1715+
if src == nil {
1716+
continue
1717+
}
1718+
if edge.isSideEffect {
1719+
tainted[stem] = map[string]bool{"*": true}
1720+
break
1721+
}
1722+
for _, origName := range edge.origNames {
1723+
if origName == "*" && len(src) > 0 {
1724+
tainted[stem] = map[string]bool{"*": true}
1725+
break
1726+
}
1727+
if src[origName] || src["*"] {
1728+
tainted[stem] = map[string]bool{"*": true}
1729+
break
1730+
}
1731+
}
1732+
if tainted[stem] != nil {
1733+
break
1734+
}
1735+
}
1736+
}
1737+
17021738
debugf("=== Final taint map (FindAffectedFiles) ===")
17031739
for stem, names := range tainted {
17041740
debugf(" %s: %v", stem, mapKeys(names))

internal/rush/rush.go

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,21 +119,18 @@ func (cd ChangeDir) IsFineGrained() bool {
119119
}
120120

121121
type TargetDef struct {
122-
Type string `json:"type"` // "target", "virtual-target"
123122
App *string `json:"app,omitempty"` // rush project name of corresponding app
124-
TargetName *string `json:"targetName,omitempty"` // output name for virtual targets
125-
ChangeDirs []ChangeDir `json:"changeDirs,omitempty"` // globs to watch for virtual targets
123+
TargetName *string `json:"targetName,omitempty"` // custom output name (defaults to package name)
124+
ChangeDirs []ChangeDir `json:"changeDirs,omitempty"` // globs to watch (defaults to **/* if empty)
126125
Ignores []string `json:"ignores,omitempty"` // per-target ignore globs (additive with global)
127126
}
128127

129-
// IsTarget returns true if this target definition is a regular target.
130-
func (td TargetDef) IsTarget() bool {
131-
return td.Type == "target"
132-
}
133-
134-
// IsVirtualTarget returns true if this target definition is a virtual target.
135-
func (td TargetDef) IsVirtualTarget() bool {
136-
return td.Type == "virtual-target"
128+
// OutputName returns the target's output name: targetName if set, otherwise the package name.
129+
func (td TargetDef) OutputName(packageName string) string {
130+
if td.TargetName != nil {
131+
return *td.TargetName
132+
}
133+
return packageName
137134
}
138135

139136
type ProjectConfig struct {

0 commit comments

Comments
 (0)