Skip to content

Comments

[ANE-2655] Expose yarn and npm workspace packages as individual build targets#1643

Open
jagonalez wants to merge 4 commits intomasterfrom
feat/yarn-workspace-build-targets
Open

[ANE-2655] Expose yarn and npm workspace packages as individual build targets#1643
jagonalez wants to merge 4 commits intomasterfrom
feat/yarn-workspace-build-targets

Conversation

@jagonalez
Copy link
Contributor

@jagonalez jagonalez commented Feb 17, 2026

Summary

  • Yarn and npm workspace packages (including the root) now appear as individual build targets (e.g. yarn@./:my-monorepo, yarn@./:app, npm@./:api)
  • Users can see workspace packages via fossa list-targets and filter with .fossa.yml
  • When specific targets are selected, only those packages' dependencies are included in the analysis
  • When no filtering is applied, all targets are selected and all dependencies from every workspace package are included
  • No dependencies are silently dropped — root deps are available on the root target

Changes

  • src/Strategy/Node.hs: mkProject populates projectBuildTargets for all workspace project types (yarn + npm). findWorkspaceBuildTargets includes root package name alongside workspace members. getDeps passes FoundTargets through to both analyzeYarn and analyzeNpmLock. extractDepListsForTargets scopes FlatDeps to selected packages.
  • test/Node/NodeSpec.hs: Tests for findWorkspaceBuildTargets (root + members, single-package) and extractDepListsForTargets (all deps, scoped, root-only, all targets selected)
  • docs/references/strategies/languages/nodejs/yarn.md: Documents workspace build target behavior
  • docs/references/strategies/languages/nodejs/npm-lockfile.md: Documents workspace build target behavior with lockfile version caveat
  • Changelog.md: Unreleased entry

Test plan

  • cabal build succeeds
  • cabal test unit-tests — all 8 Node tests pass
  • hlint and fourmolu clean
  • Manual test: fossa list-targets shows root + workspace member targets for both yarn and npm workspaces
  • Manual test: unfiltered fossa analyze -o dep counts match released fossa 3.15.7
  • Verified no dependencies are dropped when no filtering is applied

🤖 Generated with Claude Code

@jagonalez jagonalez requested a review from a team as a code owner February 17, 2026 20:47
@jagonalez jagonalez requested a review from spatten February 17, 2026 20:47
@jagonalez jagonalez changed the title Expose yarn/npm workspace members as build targets [ANE-2655] Expose yarn/npm workspace members as build targets Feb 17, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Workspace-aware build target support for Node.js projects was added. Yarn workspace members are now represented as individual build targets. New functions findWorkspaceBuildTargets and extractDepListsForTargets derive and filter build targets from package graphs. getDeps now accepts a FoundTargets argument and passes target information through analyzers; analyzeYarn and analyzeNpmLock were updated accordingly. The public API exports FoundTargets, ProjectWithoutTargets, BuildTarget, and unBuildTarget. Tests and Yarn documentation were updated to cover workspace target discovery and dependency filtering behavior.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Title check ✅ Passed The title clearly summarizes the main change: exposing yarn and npm workspace packages as individual build targets, which is the primary objective of this PR.
Description check ✅ Passed The PR description covers all required template sections: Overview (summary), Acceptance Criteria (workspace visibility/filtering), Testing Plan (build, tests, manual validation), Risks (none noted but appropriate), Metrics (none needed), References (ANE-2655), and Checklist items completed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/Strategy/Node.hs (1)

180-191: Targets are intentionally ignored for Pnpm and NPM analyzers.

The targets parameter is discarded for Pnpm and NPM cases. If per-workspace filtering should eventually apply to these analyzers too, consider adding a TODO comment or documenting this limitation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Strategy/Node.hs` around lines 180 - 191, The getDeps function currently
discards the targets parameter for the Pnpm and NPM branches (patterns Pnpm and
NPM) by calling analyzePnpmLock and analyzeNpm without passing targets; update
these branches to either accept and forward per-workspace filtering to
analyzePnpmLock/analyzeNpm or, if that behavior is not yet supported, add a
concise TODO comment documenting this limitation (e.g. "TODO: apply FoundTargets
filtering to Pnpm/NPM analyzers") next to the Pnpm and NPM clauses so future
maintainers know targets were intentionally ignored and where to extend
filtering.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/Strategy/Node.hs`:
- Around line 180-191: The getDeps function currently discards the targets
parameter for the Pnpm and NPM branches (patterns Pnpm and NPM) by calling
analyzePnpmLock and analyzeNpm without passing targets; update these branches to
either accept and forward per-workspace filtering to analyzePnpmLock/analyzeNpm
or, if that behavior is not yet supported, add a concise TODO comment
documenting this limitation (e.g. "TODO: apply FoundTargets filtering to
Pnpm/NPM analyzers") next to the Pnpm and NPM clauses so future maintainers know
targets were intentionally ignored and where to extend filtering.

@jagonalez jagonalez force-pushed the feat/yarn-workspace-build-targets branch from 300f95d to d7dcc8b Compare February 17, 2026 21:00
Workspace members now appear as separate build targets (e.g.
yarn@./:fossa-coder) instead of being merged into a single opaque
target. This follows the established Maven pattern: one DiscoveredProject
per workspace root with a BuildTarget per member.

Users can now see individual workspace packages in `fossa list-targets`
and filter them via `.fossa.yml`.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jagonalez jagonalez force-pushed the feat/yarn-workspace-build-targets branch from d7dcc8b to 3399748 Compare February 17, 2026 21:02
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
docs/references/strategies/languages/nodejs/npm-lockfile.md (1)

20-30: Clear documentation of workspace build targets.

The documentation effectively explains the new workspace member targeting feature, including:

  • Build target format and discovery (fossa list-targets)
  • Filtering behavior and backward compatibility
  • Version-specific limitations

The explanation is accurate and aligns with the implementation described in the PR objectives.

Optional: Minor style suggestion to improve scannability

Consider distinguishing the version limitation with a different label or separate blockquote to make it more prominent:

 > them in `.fossa.yml`. When no filtering is applied (or all targets are
 > selected), all dependencies are included — the same as previous behavior.
 > Filtering to a specific subset of targets scopes the analysis to only those
 > members' dependencies.
->
-> Note: Target-level dependency filtering is only supported for lockfile version
-> 1. Version 3 lockfiles will show workspace build targets in `fossa list-targets`,
-> but filtering to specific targets does not yet scope the dependency results.
+
+> **Limitation**: Target-level dependency filtering is only supported for lockfile
+> version 1. Version 3 lockfiles will show workspace build targets in
+> `fossa list-targets`, but filtering to specific targets does not yet scope the
+> dependency results.

This makes the version-specific limitation more discoverable for users.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/references/strategies/languages/nodejs/npm-lockfile.md` around lines 20
- 30, Separate the version-specific limitation into a more prominent, standalone
element to improve scannability: extract the sentence beginning "Note:
Target-level dependency filtering is only supported for lockfile version 1.
Version 3 lockfiles..." and render it as a distinct labeled note or blockquote
(e.g., "Note" or "Version-specific limitation") so it clearly stands apart from
the main explanation about fossa list-targets, `.fossa.yml`, and target
filtering behavior; keep the original wording but ensure the label is visible
and the block sits immediately after the paragraph describing filtering
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Strategy/Node.hs`:
- Line 41: The import qualifier "NonEmptySet" must be the full module path:
change the import to "import Data.Set.NonEmpty qualified as Data.Set.NonEmpty"
and update all call sites that currently use the alias NonEmptySet (e.g., usages
like NonEmptySet.fromList, NonEmptySet.member, NonEmptySet.insert,
NonEmptySet.toList, NonEmptySet.union) to use Data.Set.NonEmpty.fromList,
Data.Set.NonEmpty.member, etc.; apply these edits in the occurrences around the
current import and the referenced usages (near the ranges around lines 168-175
and 312).
- Around line 307-309: The function extractDepListsForTargets currently uses a
match guard on the pattern (FoundTargets targets) graph@PkgJsonGraph{..} to
branch when targetNames == allWorkspaceNames; replace that guarded equation with
a single equation using an if (or case) expression: in extractDepListsForTargets
bind the same parameters (FoundTargets targets) graph@PkgJsonGraph{..} and
implement "if targetNames == allWorkspaceNames then extractDepLists graph else
foldMap extractSingle selectedPackageJsons", keeping references to
extractDepLists, extractSingle, targetNames, allWorkspaceNames, and
selectedPackageJsons so the logic is identical but without a match guard.

In `@test/Node/NodeSpec.hs`:
- Around line 10-16: The new imports must be changed to qualified imports with
full module names and all call sites updated to use those qualifiers: import
Data.Set.NonEmpty as Data.Set.NonEmpty and replace nonEmpty →
Data.Set.NonEmpty.nonEmpty; import Data.Tagged as Data.Tagged and replace
applyTag → Data.Tagged.applyTag; qualify Graphing (e.g., Graphing) and prefix
its uses; qualify Path and Path.IO (e.g., Path, Path.IO) and update mkRelDir,
mkRelFile, (</>), getCurrentDir to Path.mkRelDir, Path.mkRelFile, Path.(</>),
Path.IO.getCurrentDir respectively; and qualify Strategy.Node (e.g.,
Strategy.Node) and update NPMLock, discover, extractDepListsForTargets,
findWorkspaceBuildTargets, getDeps to Strategy.Node.NPMLock,
Strategy.Node.discover, Strategy.Node.extractDepListsForTargets,
Strategy.Node.findWorkspaceBuildTargets, Strategy.Node.getDeps; also apply the
same qualification pattern to Test.Hspec and any other unqualified imports
mentioned in the review and update all their call sites accordingly.

---

Nitpick comments:
In `@docs/references/strategies/languages/nodejs/npm-lockfile.md`:
- Around line 20-30: Separate the version-specific limitation into a more
prominent, standalone element to improve scannability: extract the sentence
beginning "Note: Target-level dependency filtering is only supported for
lockfile version 1. Version 3 lockfiles..." and render it as a distinct labeled
note or blockquote (e.g., "Note" or "Version-specific limitation") so it clearly
stands apart from the main explanation about fossa list-targets, `.fossa.yml`,
and target filtering behavior; keep the original wording but ensure the label is
visible and the block sits immediately after the paragraph describing filtering
behavior.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/references/strategies/languages/nodejs/yarn.md`:
- Around line 47-51: The fenced code block containing the yarn workspace lines
(the block with "yarn@./:app", "yarn@./:lib-utils", "yarn@./:lib-core") needs a
language identifier to satisfy markdownlint MD040; update the opening fence to
include a language such as text or console (for example change ``` to ```text)
so the block is identified and the linter warning is resolved.

---

Duplicate comments:
In `@src/Strategy/Node.hs`:
- Around line 305-309: The function extractDepListsForTargets uses a guard on
the FoundTargets match; replace the guard with an explicit if/then/else (or
case) to eliminate match guards: inspect targetNames and allWorkspaceNames
inside the (FoundTargets targets) branch and call extractDepLists graph when
equal, otherwise compute foldMap extractSingle selectedPackageJsons; keep
existing names (extractDepListsForTargets, FoundTargets, targetNames,
allWorkspaceNames, extractDepLists, selectedPackageJsons, extractSingle) so the
logic is identical but without a match guard.
- Line 41: Change the qualified import alias to match the module name: replace
the current import "import Data.Set.NonEmpty qualified as NonEmptySet" with
"import qualified Data.Set.NonEmpty as NonEmpty", then update every call site
that uses the alias (references like NonEmptySet.someFunction or
NonEmptySet.someValue) to use the new alias (NonEmpty.someFunction,
NonEmpty.someValue) so the qualifier matches the module path.

In `@test/Node/NodeSpec.hs`:
- Around line 10-39: Change the imports so they are imported qualified with
their full module names (e.g. use "import qualified Data.Set" not an alias) and
then update all call sites to use the full module-qualified symbols (e.g.
Data.Set.nonEmpty, Data.Set.member, Path.mkRelDir, Path.IO.getCurrentDir,
Graphing.whatever, Strategy.Node.discover, Strategy.Node.getDeps,
Strategy.Node.NPMLock, Strategy.Node.extractDepListsForTargets,
Strategy.Node.findWorkspaceBuildTargets, Strategy.Node.PackageJson.FlatDeps,
Strategy.Node.PackageJson.Manifest, Strategy.Node.PackageJson.NodePackage,
Strategy.Node.PackageJson.PackageJson, Strategy.Node.PackageJson.PkgJsonGraph,
Strategy.Node.PackageJson.PkgJsonLicense,
Strategy.Node.PackageJson.PkgJsonWorkspaces,
Strategy.Node.PackageJson.Production, Test.Effect.it', Test.Hspec.it,
Types.<symbols>) so the code uses fully qualified module names consistently.

npm workspaces can legitimately have root production dependencies,
so we can't accurately determine what should be included or excluded.
Restrict workspace build target support to yarn, where root deps are
always workspace tooling (never published).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jagonalez jagonalez changed the title [ANE-2655] Expose yarn/npm workspace members as build targets Expose yarn workspace members as individual build targets Feb 17, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/Strategy/Node.hs (1)

302-327: Consider extracting shared helpers to reduce duplication.

mapToSet and extractSingle are duplicated between extractDepLists (lines 292-300) and extractDepListsForTargets (lines 319-327). This violates DRY.

♻️ Suggested refactor to share helpers
+mapToSet :: Map Text Text -> Set NodePackage
+mapToSet = Set.fromList . map (uncurry NodePackage) . Map.toList
+
+extractSingle :: Map Manifest PackageJson -> PackageJson -> FlatDeps
+extractSingle lookup' PackageJson{..} =
+  FlatDeps
+    (applyTag `@Production` $ mapToSet (packageDeps `Map.union` packagePeerDeps))
+    (applyTag `@Development` $ mapToSet packageDevDeps)
+    (Map.keysSet lookup')
+
 extractDepLists :: PkgJsonGraph -> FlatDeps
-extractDepLists PkgJsonGraph{..} = foldMap extractSingle $ Map.elems jsonLookup
-  where
-    mapToSet :: Map Text Text -> Set NodePackage
-    mapToSet = Set.fromList . map (uncurry NodePackage) . Map.toList
-
-    extractSingle :: PackageJson -> FlatDeps
-    extractSingle PackageJson{..} =
-      FlatDeps
-        (applyTag `@Production` $ mapToSet (packageDeps `Map.union` packagePeerDeps))
-        (applyTag `@Development` $ mapToSet packageDevDeps)
-        (Map.keysSet jsonLookup)
+extractDepLists PkgJsonGraph{..} = foldMap (extractSingle jsonLookup) $ Map.elems jsonLookup

 extractDepListsForTargets :: FoundTargets -> PkgJsonGraph -> FlatDeps
 extractDepListsForTargets ProjectWithoutTargets graph = extractDepLists graph
 extractDepListsForTargets (FoundTargets targets) PkgJsonGraph{..} =
-  foldMap extractSingle selectedPackageJsons
+  foldMap (extractSingle jsonLookup) selectedPackageJsons
   where
     targetNames :: Set Text
     targetNames = Set.map unBuildTarget (NonEmptySet.toSet targets)

     selectedPackageJsons :: [PackageJson]
     selectedPackageJsons =
       filter (maybe False (`Set.member` targetNames) . packageName) $
         Map.elems jsonLookup
-
-    mapToSet :: Map Text Text -> Set NodePackage
-    mapToSet = Set.fromList . map (uncurry NodePackage) . Map.toList
-
-    extractSingle :: PackageJson -> FlatDeps
-    extractSingle PackageJson{..} =
-      FlatDeps
-        (applyTag `@Production` $ mapToSet (packageDeps `Map.union` packagePeerDeps))
-        (applyTag `@Development` $ mapToSet packageDevDeps)
-        (Map.keysSet jsonLookup)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Strategy/Node.hs` around lines 302 - 327, The two functions
extractDepLists and extractDepListsForTargets duplicate mapToSet and
extractSingle; refactor by extracting a shared helper (e.g., mapToSet :: Map
Text Text -> Set NodePackage) and a shared package-to-FlatDeps helper (e.g.,
packageToFlatDeps :: Map Text PackageJson -> PackageJson -> FlatDeps or
packageToFlatDeps :: (Map Text PackageJson) -> PackageJson -> FlatDeps) so both
extractDepLists and extractDepListsForTargets call the same helpers; update
extractDepListsForTargets to reuse mapToSet and the new packageToFlatDeps helper
and remove the duplicated local definitions (references:
extractDepListsForTargets, extractDepLists, mapToSet, extractSingle,
PkgJsonGraph, PackageJson).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/Strategy/Node.hs`:
- Around line 302-327: The two functions extractDepLists and
extractDepListsForTargets duplicate mapToSet and extractSingle; refactor by
extracting a shared helper (e.g., mapToSet :: Map Text Text -> Set NodePackage)
and a shared package-to-FlatDeps helper (e.g., packageToFlatDeps :: Map Text
PackageJson -> PackageJson -> FlatDeps or packageToFlatDeps :: (Map Text
PackageJson) -> PackageJson -> FlatDeps) so both extractDepLists and
extractDepListsForTargets call the same helpers; update
extractDepListsForTargets to reuse mapToSet and the new packageToFlatDeps helper
and remove the duplicated local definitions (references:
extractDepListsForTargets, extractDepLists, mapToSet, extractSingle,
PkgJsonGraph, PackageJson).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jagonalez jagonalez changed the title Expose yarn workspace members as individual build targets [ANE-2655] Expose yarn workspace members as individual build targets Feb 17, 2026
The root package.json is now a build target alongside workspace members.
This ensures no dependencies are silently dropped — root deps (e.g.
husky) are available on the root target. Also re-enables workspace
build targets for npm lockfile projects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@jagonalez jagonalez changed the title [ANE-2655] Expose yarn workspace members as individual build targets [ANE-2655] Expose yarn and npm workspace packages as individual build targets Feb 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant