Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.24.1] - 2026-07-21

### Fixed
- Affected **apps** now propagate taint to packages that import them. When a package is not a library, its per-symbol export analysis is skipped — but it was also seeding no taint at all, so the taint chain died at the app boundary. A consumer importing from an affected app (e.g. a thin harness that dynamically imports its app package: `() => import("gdc-analytical-designer-module")`) saw a clean upstream-taint map and was never flagged, so app/harness targets sitting behind an intermediate app went undetected. Now an affected app is tainted **wholesale**: all of its entrypoint exports are seeded into the upstream-taint map (mirroring the existing global-`changeDirs` full-taint seeding for libraries), so downstream importers match via the normal import graph — including bare/dynamic side-effect imports (empty import names), which match on any non-empty symbol set for the package. Example: a change in `gdc-analytical-designer-runtime` now correctly reaches `gdc-analytical-designer-harness` through the intermediate `gdc-analytical-designer-module` app.

## [0.24.0] - 2026-06-08

### Added
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.24.0
0.24.1
37 changes: 36 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,42 @@ func main() {
}

if !lib {
log.Basicf(" Type: app (not a library) — skipping export analysis\n")
log.Basicf(" Type: app (not a library) — skipping export analysis")
// Every package reaching this loop is affected (directly, via a
// lockfile dep, or transitively via the workspace graph). An app gets
// no per-symbol export diffing, but it is tainted wholesale: anything
// importing from it must be treated as tainted too (e.g. an app
// mounted by a thin harness that dynamically imports it). Seed ALL of
// the app's entrypoint exports, mirroring the global-changeDirs
// full-taint seeding used for libraries below. Downstream importers
// then match these in HasTaintedImportsForGlob / FindAffectedFiles —
// including bare/dynamic side-effect imports, which match on any
// non-empty symbol set for the package.
entrypoints := analyzer.FindEntrypoints(info.ProjectFolder, pkg)
totalExports := 0
for _, ep := range entrypoints {
specifier := pkgName
if ep.ExportPath != "." {
specifier = pkgName + strings.TrimPrefix(ep.ExportPath, ".")
}
exports := analyzer.CollectEntrypointExports(info.ProjectFolder, ep)
if allUpstreamTaint[specifier] == nil {
allUpstreamTaint[specifier] = make(map[string]bool)
}
for _, name := range exports {
allUpstreamTaint[specifier][name] = true
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
// A side-effect-only entrypoint enumerates zero exports, which would
// leave an empty (== untainted) set and stop propagation to bare/dynamic
// importers. Fall back to the "*" whole-package marker (as used for
// version-changed and CSS-tainted packages) so the app still taints
// wholesale.
if len(exports) == 0 {
allUpstreamTaint[specifier]["*"] = true
}
totalExports += len(exports)
}
log.Basicf(" App is affected — tainting all %d exports across %d entrypoint(s) (whole-app taint)\n", totalExports, len(entrypoints))
continue
}

Expand Down
Loading