Skip to content

Fix: bare dotnet restore doesn't hydrate Node deps for Pcf/ScriptLibrary/CodeApp - #104

Open
TomProkop wants to merge 4 commits into
masterfrom
fix/noderestore-sdk-resolver-anchor
Open

Fix: bare dotnet restore doesn't hydrate Node deps for Pcf/ScriptLibrary/CodeApp#104
TomProkop wants to merge 4 commits into
masterfrom
fix/noderestore-sdk-resolver-anchor

Conversation

@TomProkop

Copy link
Copy Markdown
Member

Problem

A standalone dotnet restore sets ExcludeRestorePackageImports=true, which makes NuGet skip importing ordinary PackageReference/buildTransitive-delivered .targets files entirely - not just skip running their targets, the files aren't even parsed. The per-project-type NodeRestore anchors (_PcfNodeRestore, _ScriptLibraryNodeRestore, _CodeAppNodeRestore) lived in exactly those excluded files, so a bare dotnet restore silently never hydrated Node/rush dependencies for PCF/ScriptLibrary/CodeApp projects - see build https://dev.azure.com/thenetworg/INT0015/_build/results?buildId=64755, where the restore step completed in 23 seconds without ever touching common/temp/node_modules.

Fix

Moves the anchor into Sdk.targets, which is resolved through the MSBuild SDK-resolver mechanism and is therefore not subject to ExcludeRestorePackageImports. Scoped to Pcf/ScriptLibrary/CodeApp only - every other project type (Plugin, WorkflowActivity, Solution, PDPackage, GenPage, ...) also references this SDK for unrelated shared targets (e.g. GenerateVersionNumber), but must never attempt Node/rush restore. RunNodeBuild=false still opts a project out explicitly.

Two additional issues surfaced during real restore testing against a live PCF control, both fixed here:

  • $(NuGetPackageRoot) is not reliably populated in the nested MSBuild evaluation NuGet uses internally to build the restore graph (_GenerateRestoreGraphProjectEntry), silently preventing the anchor's cross-package import from ever resolving. Sdk.props now derives the NuGet package cache root from its own resolved file location instead (the same technique already used for TALXISDevKitDataversePackageVersion).
  • Brand-new machine/cache gap: the Tasks package isn't downloaded yet when the anchor's nested evaluation runs on a fresh cache, so Node deps were never hydrated by that restore at all - not even by a later build/publish, since ExcludeRestorePackageImports only affects the restore operation. A second hook, AfterTargets="Restore", re-invokes the anchor in a freshly re-evaluated nested build once the outer restore has finished downloading packages.
  • A duplicate-import warning (MSB4011) that would otherwise appear on every normal dotnet build is avoided via a marker property set by the Tasks package's own buildTransitive proxy.

Validation

Tested end-to-end against FileExplorer (a real, complex PCF control) using disposable local test packages, with NuGet cache fully cleared between runs:

  • Single cold-cache dotnet restore now hydrates node_modules via rush update.
  • Warm-cache restore is idempotent and fast (rush update no-ops in ~0.2s).
  • A normal dotnet build/publish afterward succeeds with 0 warnings, 0 errors (no duplicate-import noise).
  • Negative test: restoring a Solution-type project triggers zero Node/rush activity for itself (only for its Node-type project references, as expected).

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

TomProkop and others added 4 commits July 31, 2026 10:39
…ary/CodeApp

A standalone "dotnet restore" sets ExcludeRestorePackageImports=true, so NuGet
never imports ordinary PackageReference/buildTransitive-delivered .targets
files - including the Tasks package's NodeRestore.targets - during that
operation. The per-project-type NodeRestore anchors (_PcfNodeRestore,
_ScriptLibraryNodeRestore, _CodeAppNodeRestore) lived in exactly those excluded
files, so a bare restore silently skipped Node/rush hydration entirely.

Moves the anchor into Sdk.targets, which is imported through the MSBuild
SDK-resolver mechanism and is therefore not subject to
ExcludeRestorePackageImports. Scoped to Pcf/ScriptLibrary/CodeApp only, since
every other project type also references this SDK for unrelated shared targets
(e.g. GenerateVersionNumber) but must never attempt Node/rush restore.

Two additional issues surfaced during real restore testing against a live PCF
control, both fixed here:
- $(NuGetPackageRoot) is not reliably populated in the nested MSBuild
  evaluation NuGet uses internally to build the restore graph, silently
  preventing the anchor from ever firing. Sdk.props now derives the NuGet
  package cache root from its own resolved file location instead.
- On a brand new machine/cache, the Tasks package isn't downloaded yet when
  the anchor's nested evaluation runs, so Node deps were never hydrated by
  that restore at all. A second hook, AfterTargets="Restore", re-invokes the
  anchor in a freshly re-evaluated nested build once the outer restore has
  finished downloading packages.

Validated end-to-end against FileExplorer (a real, complex PCF control) with
disposable local test packages: a single cold-cache "dotnet restore" now
hydrates node_modules via rush, a warm-cache restore is idempotent, a normal
dotnet build has no duplicate-import warnings, and a Solution-type project
restore correctly triggers zero Node/rush activity for itself.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sdk.targets previously mixed four unrelated concerns in one file:
Microsoft.NET.Sdk passthrough, per-project-type PackageReference wiring,
Git-versioning defaults, and the NodeRestore restore-time anchor. Split
into:
- Sdk.targets: thin entry point, imports the three files below.
- Sdk.PackageReference.targets: wires in the per-project-type Dataverse
  package based on $(ProjectType).
- Sdk.GitVersioning.targets: Git-based version-number defaults +
  ResolveGitBranch.
- Sdk.NodeRestore.targets: the restore-time Node dependency hydration
  anchor, with a top-of-file note clarifying it's restore-time only -
  build-time Node delegation lives in each project type's own .targets
  file and the Tasks package's NodeBuild targets.

Pure reorganization, no logic changes. Validated: dotnet build 0
warnings/0 errors; cold-cache restore against a disposable FileExplorer
test project hydrates node_modules via rush; warm restore is idempotent
(~2s, no-op); full dotnet build succeeds with 0 warnings (no MSB4011
duplicate-import regression); Solution-type negative test confirms
NodeRestore never fires for non-Node project types.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Follow-up to the Sdk.targets split - the one-line comments in
Pcf/ScriptLibrary/CodeApp's own .targets files pointed at Sdk.targets,
which no longer directly contains _NodeRestoreAnchor after the split.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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