Skip to content

fix(wrapper-generator): fail loudly on cmdlet file collisions - #3713

Merged
Joywambui-maina merged 4 commits into
feature/wrapper-module-packagingfrom
fix/cmdlet-file-collisions
Aug 14, 2026
Merged

fix(wrapper-generator): fail loudly on cmdlet file collisions#3713
Joywambui-maina merged 4 commits into
feature/wrapper-module-packagingfrom
fix/cmdlet-file-collisions

Conversation

@Joywambui-maina

Copy link
Copy Markdown

Changes proposed in this pull request

  • Fail generation loudly when two operations resolve to the same cmdlet file, listing every collision with both operations' request-builder expressions, instead of letting the later one silently overwrite the earlier. This is the silent-drop failure mode AutoRest had, and nothing could detect it before: writes are not logged at console level, the summary counts surviving files and the parity gate only inspects files that exist.
  • Merge OData cast list/item pairs (owners/graph.user with owners/{id}/graph.user) into one Get-* dispatcher the way plain list/item pairs already merge, matching the single cmdlet the published SDK ships for them.
  • Resolve the collisions the full-inventory sweep surfaced as NamingOverrides data: the termStore and agreement-file stitches, the default-singleton renames (SubSite, DefaultDrive, DefaultCalendarEvent, GroupLifecyclePolicyByGroup) and the nested navigations the SDK never shipped. Every entry cites the AutoRest directive or the published command inventory that proves it.
  • Add PathMatch.Suffix to NamingOverrides so a navigation that recurs under many roots takes one entry instead of one per root.
  • Surface the generator's exception instead of its skip warnings when Build-WrapperModule.ps1 captures a failure, so a collision failure is readable in the build summary.
  • Document both collision classes in the edge-case catalog, and pin the renamed names, the suppression matching and the guard itself in the unit tests (115 passing).
  • Six modules now fail generation instead of building green: Calendar 3, Files 1, Groups 8, Identity.Governance 80, Notes 18 and Sites 102 collisions. That is the guard working rather than a regression, because those modules were already losing cmdlets silently.

Other links

A second operation resolving to an already-written cmdlet file now fails
generation with the full collision list instead of silently overwriting it,
which is the silent-drop failure mode AutoRest had.

OData cast list/item pairs (owners/graph.user) now merge like plain pairs,
and the sweep's collisions land as cited NamingOverrides entries: termStore
and agreement-file stitches, default-singleton renames (SubSite,
DefaultDrive, DefaultCalendarEvent), and nested navs the SDK never shipped.
Remaining families are tracked on #3704.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR hardens the WrapperGenerator to prevent silent cmdlet loss by detecting and failing on cmdlet file name collisions, and expands the naming-override data/model to encode published-SDK renames/suppressions (including broad “suffix” matching for recurring navigations). It also adjusts GET list/item pairing to merge OData cast list/item pairs into a single dispatcher cmdlet, and updates tests/docs accordingly.

Changes:

  • Detect cmdlet .g.cs file collisions during generation and fail with a consolidated, operation-identifying error message.
  • Extend NamingOverrides to support exact/prefix/suffix path matching and add many oracle-/directive-cited rename/suppression entries to resolve known collisions.
  • Merge cast list/item GET pairs (e.g., .../owners/graph.user with .../owners/{id}/graph.user) into one Get-* dispatcher; add regression/unit tests and documentation updates.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tools/WrapperGenerator/README.md Updates documentation to reflect collision-guard behavior and expanded overrides/testing counts.
tools/WrapperGenerator/PowerShellWrapperGenerationService.cs Tracks written files to detect collisions and throws a consolidated exception at end of generation.
tools/WrapperGenerator/NamingOverrides.cs Reworks override matching to Exact/Prefix/Suffix and adds many new rename/suppression entries with citations.
tools/WrapperGenerator/edge-cases/naming-edge-cases.md Documents collision families and how they’re handled/resolved.
tools/WrapperGenerator/CmdletNaming.cs Enhances list/item merge detection to support OData cast list/item pairing.
tools/WrapperGenerator.Tests/NamingTests.cs Adds unit coverage for new overrides and cast list/item pairing behavior.
tools/WrapperGenerator.Tests/GenerationServiceRegressionTests.cs Adds regression test ensuring collisions fail loudly and identify both operations.
tools/Build-WrapperModule.ps1 Improves failure capture to surface the generator exception text in build output summaries.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread tools/WrapperGenerator/PowerShellWrapperGenerationService.cs Outdated
Comment thread tools/Build-WrapperModule.ps1 Outdated
… data

Derive-CollisionResolutions.ps1 replays the checked-in collision inventory
(212 lines, 365 contested routes) against MgCommandMetadata and emits
exact-match resolution data: 191 suppressions (routes the published SDK
prunes) and 64 renames (published nouns), each entry carrying its oracle
evidence. The files embed into the generator and apply only when
UseCollisionData is set; -Validate fails on drift, and a new xunit test
runs it on every `dotnet test` so staleness fails the suite instead of
depending on someone remembering to run the script by hand. Derivation
itself fails on any unclassified or ambiguous route. Only 2 cross-path
variant merges exist in all of v1.0 (GroupPhoto, ShareListItem) - deferred
with the singleton side kept, cataloged in crosspath-merge-edge-cases.md.

Full 39-module v1.0 generation now produces zero collisions; 20 published
commands that lost filename races are recovered; exact-name matches rise
5,042 -> 5,098.

Also: cmdlets emit into a per-module namespace derived from the client
namespace instead of the leftover MgPoC placeholder; Build-WrapperModule's
generated csproj references Authentication by a relative path instead of
an absolute one; its -Configuration parameter now actually reaches the
wrapper generator's own build, not just the final module build; and a
pre-existing nullable warning in the list/item pairing check is fixed.
121 tests pass.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 22 changed files in this pull request and generated no new comments.

Suppressed comments (2)

tools/Build-WrapperModule.ps1:152

  • $lines is a PowerShell array; it does not reliably expose an instance IndexOf() method, so $lines.IndexOf($exception) can throw when the wrapper generator fails (the exact path this new error-surfacing logic is meant to improve). Use [Array]::IndexOf($lines, $exception) (or a manual loop) to compute the index safely.
            $lines = @($wrapperOut | ForEach-Object { "$_" })
            $exception = $lines | Where-Object { $_ -match 'Unhandled exception|Exception:' } | Select-Object -First 1
            $exceptionIndex = if ($exception) { $lines.IndexOf($exception) } else { -1 }
            $result.Error = if ($exceptionIndex -ge 0) {

tools/WrapperGenerator/DerivedCollisionResolutions.cs:46

  • Derived collision data lookup is currently API-version case-sensitive (StringComparer.Ordinal). If a caller supplies ApiVersion with different casing (e.g. "Beta" / "V1.0"), derived suppressions/renames will silently not apply and the generator may fail with avoidable collisions. Consider making the API-version dictionary case-insensitive (or normalizing ApiVersion).
        var result = new Dictionary<string, Tables>(StringComparer.Ordinal);
        var assembly = typeof(DerivedCollisionResolutions).Assembly;

@peombwa Peter Ombwa (peombwa) left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Joywambui-maina - great work here on detecting and including the collision inventory. I wanted to share some feedback on PR structure that'll help both you and the reviewers going forward.

This PR bundles several independently valuable changes into one 6,600-line diff, which makes it quite hard to review confidently. As a general practice, each PR should do one thing - a single, self-contained change that a reviewer can understand, evaluate, and approve (or request changes on) without needing to hold the entire context of unrelated changes in their head at the same time.

Looking at the description, I'd suggest this could have been split into roughly four focused PRs, merged in order to avoid conflicts:

  1. Collision guard - the writtenCmdletFiles tracking in PowerShellWrapperGenerationService.cs and its regression test. This is the core safety net and could land first with zero naming-data dependencies.

  2. OData cast list/item pairing - the IsListItemPair enhancement in CmdletNaming.cs, the TrailingCastMember helper, and its unit tests. A standalone behavior change with its own tests.

  3. Curated NamingOverrides expansion - the PathMatch.Suffix model change, the ~30 new curated entries (SubSite, DefaultDrive, termStore stitching, Security nested navs, etc.), their naming tests, and the edge-case docs. This is the domain-knowledge-heavy part reviewers need to scrutinize most carefully.

  4. Derived collision-resolution pipeline - Derive-CollisionResolutions.ps1, DerivedCollisionResolutions.cs, the embedded JSON data files, the drift-gate test, and the --api-version / --no-collision-data CLI flags. This is a whole new subsystem with its own derivation, validation, and embedding strategy.

Each of these tells a clear story, is independently testable, and is small enough that a reviewer can give it proper attention. When they're combined, it's easy for reviewers to rubber-stamp the data files (which are the bulk of the diff) instead of carefully evaluating whether each entry is correct - which defeats the purpose of code review.

No need to split this one retroactively - let's get it across the line as-is. But for future work, let's keep the PRs focused on just one thing.

Let's also address the copilot comments.

Joywambui-maina and others added 2 commits August 13, 2026 21:20
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@Joywambui-maina
Joywambui-maina merged commit 2ed1ab3 into feature/wrapper-module-packaging Aug 14, 2026
4 checks passed
@Joywambui-maina
Joywambui-maina deleted the fix/cmdlet-file-collisions branch August 14, 2026 05:15
@Joywambui-maina
Joywambui-maina restored the fix/cmdlet-file-collisions branch August 14, 2026 05:31
@Joywambui-maina
Joywambui-maina deleted the fix/cmdlet-file-collisions branch August 14, 2026 06:40
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.

3 participants