feat(182): Visual Studio extension for JD.Efcpt.Build#207
Merged
Conversation
Adds ide/vs/ with a shared, IDE-agnostic JD.Efcpt.Ide.Core (netstandard2.0, ubuntu-CI-tested: JDxxxx diagnostic parser, build-profile.json reader matched against the real BuildRunOutput.cs schema, project discovery, secret redaction) plus the Windows-only JD.Efcpt.VsExtension VSIX (net48, Community.VisualStudio.Toolkit.17): Tools > Entity Framework > JD.Efcpt: Regenerate Models and JD.Efcpt: Show Build Status, backed by a build-status tool window with a FileSystemWatcher on build-profile.json. Regenerate Models shells out to `dotnet build -p:EfcptForceRegenerate=true -p:EfcptEnableProfiling=true -p:EfcptLogVerbosity=minimal`, streaming redacted output into a dedicated "JD.Efcpt" Output Window pane and parsing JDxxxx diagnostics as they arrive. The VSIX uses its own command-set/tool-window/package GUIDs and its own "Entity Framework" submenu under Tools, distinct from EF Core Power Tools, so it cannot collide with it. JD.Efcpt.Ide.Core + JD.Efcpt.Ide.Core.Tests are added to JD.Efcpt.Build.sln (ubuntu CI builds and tests them); JD.Efcpt.VsExtension is deliberately kept out of that sln (its VSSDK/net48 chain would break the ubuntu build) and lives only in the new ide/vs/JD.Efcpt.Vs.sln. A new windows-only .github/workflows/vsix.yml builds and packages the VSIX via msbuild and uploads it as an artifact; marketplace publish is secret-gated on VS_MARKETPLACE_PAT and skips cleanly when absent. Closes #182. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #207 +/- ##
==========================================
+ Coverage 84.93% 92.19% +7.26%
==========================================
Files 93 93
Lines 4832 4832
Branches 746 620 -126
==========================================
+ Hits 4104 4455 +351
+ Misses 429 377 -52
+ Partials 299 0 -299
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…e correlation, error surfacing, watcher recovery Applies all findings from two adversarial reviews of PR #207: 1. [HIGH data race] RegenerateModelsService: diagnostics.Add ran outside the writeLock and was called from BOTH concurrent stdout/stderr pump loops (List<T>.Add is not thread-safe → could throw or drop diagnostics). Moved the Add inside the same writeLock critical section as the pane write, so all mutation of the list and pane is serialized. No diagnostics lost. 2. [CRITICAL stale-profile] The build-status tool window showed the prior green run after a FAILED regenerate (which never rewrites build-profile.json, so the watcher never fired). Added: - A new testable BuildStatusEvaluator + RegenerateAttempt in Ide.Core that, given the last regenerate the extension initiated (start time + succeeded + exit code) and the loaded profile, classifies Current / StaleFromEarlierRun / StaleAfterFailedRegenerate / NoProfile and produces a user-facing banner. Compared in UTC; a profile predating the attempt (or missing EndTime) is treated as stale conservatively. 8 new Ide.Core.Tests (ubuntu CI). - A windows-only RegenerateNotifier: RegenerateModelsCommand captures the run start timestamp, then pushes the outcome directly to any open tool window, independent of the FileSystemWatcher. The tool window subscribes, records the attempt, and re-renders via the evaluator so a failed/superseded run is shown with a banner instead of the stale success. A banner element was added to the tool-window XAML. 3. [HIGH swallowed errors] a. ProjectDiscovery: narrowed the bare `catch {}` to `catch (IOException | UnauthorizedAccessException)` and now returns a ProjectDiscoveryResult carrying Skipped { Path, Reason } so a lock/perms error is distinguishable from "no JD.Efcpt project". Unexpected exception types propagate. New Ide.Core.Tests cover skip-with-reason and no-swallow. b. ProjectResolver: the active-project fast-path File.ReadAllText is now guarded the same way as the fallback scan (routed through the same DiscoverJdEfcptProjects try/catch) and surfaces skipped candidates. c. RegenerateModelsCommand.ExecuteAsync now wraps the whole flow in try/catch that reports via the output pane + VS.MessageBox.ShowErrorAsync. d. The tool-window async-void handlers now ALSO set the viewmodel to a visible error state (SetUnavailable) in their catch blocks, not just ex.LogAsync(). 4. [HIGH FileSystemWatcher] EnsureWatcher wraps construction/arming in try/catch, subscribes to _watcher.Error, and on error logs, sets "Live updates stopped — click Refresh," and re-arms on the next reload (handles dotnet clean / obj deletion + buffer-overflow instead of freezing permanently). 5. [LOW doc drift] ProjectDiscovery XML doc no longer claims to match PackageVersion (the regex matches PackageReference). Validation: dotnet build JD.Efcpt.Build.sln -c Debug → 0 errors (VSIX still excluded); dotnet test Ide.Core.Tests → 54/54 passing; VSIX Release compile → 0 errors/0 warnings locally (full .vsix packaging remains CI-only). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diagnosed from failed run 29167011617: msbuild compiled the extension DLL with 0 errors but the "Locate built .vsix" step reported "No .vsix was produced". The locate step was already recursive/correct — this was CASE B: the .vsix was genuinely never produced because the CreateVsixContainer target chain never ran (the msbuild target flow was PrepareForBuild → CoreCompile → CopyFilesToOutputDirectory → Done, with no GeneratePkgDefFile / CreateVsixContainer). Root cause: CreateVsixContainer lives in Microsoft.VsSDK.targets and is appended to $(PrepareForRunDependsOn) (which Microsoft.Common.targets runs during a normal Build). Microsoft.VSSDK.BuildTools only auto-imports its path/env setup props+targets — it does NOT import Microsoft.VsSDK.targets. The SDK-style project therefore never had the packaging targets at all (the classic VSIX template imports them explicitly). Fix: - Convert the project to the explicit SDK import form (Import Sdk.props …; body; Import Sdk.targets …) and then import $(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets AFTER the SDK targets, guarded by '$(MSBuildRuntimeType)' == 'Full'. Under full MSBuild (Visual Studio / windows CI) the packaging targets run and produce the .vsix; under `dotnet build` (Core) the import is skipped so the net472-only VSIX tasks never load and the cross-platform compile stays 0 errors. - Fix VSSDK1310: the manifest's <License>..\..\..\LICENSE</License> escaped the package root. Ship the repo LICENSE inside the .vsix at the package root (Content Include with Link=LICENSE + IncludeInVSIX) and reference it as <License>LICENSE</License>. Verified locally with full VS18 MSBuild (same Full runtime as CI): produces ide/vs/JD.Efcpt.VsExtension/bin/Release/net48/JD.Efcpt.VsExtension.vsix containing extension.vsixmanifest, JD.Efcpt.VsExtension.dll, Community.VisualStudio.Toolkit.dll, JD.Efcpt.Ide.Core.dll, JD.Efcpt.VsExtension.pkgdef and LICENSE — exactly where the recursive locate step looks. `dotnet build ide/vs/JD.Efcpt.VsExtension -c Release` and `dotnet build JD.Efcpt.Build.sln -c Debug` both remain 0 errors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Contributor
Code Coverage |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ide/vs/with a shared, IDE-agnostic JD.Efcpt.Ide.Core (netstandard2.0, ubuntu-CI-tested):JdDiagnosticParser(JDxxxx warning/error parsing, mirrorside/vscode/src/jdDiagnostics.ts),BuildProfileReader(deserializesobj/efcpt/build-profile.jsonagainst the real schema insrc/JD.Efcpt.Build.Tasks/Profiling/BuildRunOutput.cs),ProjectDiscovery(finds.csprojfiles referencingJD.Efcpt.Build), andSecretRedaction(a documented, intentional local copy ofJD.Efcpt.Build.Core.Diagnostics.SecretRedaction- that project multi-targetsnet472;net8.0;net9.0;net10.0and has nonetstandard2.0target, so it isn't safely project-referenceable from this assembly without broadening its TFM list).net48,Community.VisualStudio.Toolkit.17+Microsoft.VisualStudio.SDK+Microsoft.VSSDK.BuildTools,[17.0,19.0)):Tools > Entity Framework > JD.Efcpt: Regenerate ModelsandJD.Efcpt: Show Build Status, anAsyncPackagethat background-loads onSolutionExists.JD.Efcpt.Build, else the first match anywhere in the solution) and shells out todotnet build <proj> -p:EfcptForceRegenerate=true -p:EfcptEnableProfiling=true -p:EfcptLogVerbosity=minimal, streaming redacted output into a dedicated"JD.Efcpt"Output Window pane and parsingJDxxxxdiagnostics as they arrive. Shelling out (not calling MSBuild APIs in-process) avoids design-time-build entanglement and matches CLI/CI behavior exactly.BuildProfileReader(model count, status, timestamps, duration, warnings/errors), with aFileSystemWatcheronobj/efcpt/build-profile.jsonso it also refreshes after CLI/IDE builds, not just extension-triggered ones."Entity Framework"submenu group underTools(JdEfcptCommandTable.vsct) - none shared with EF Core Power Tools, so it cannot collide with it.JD.Efcpt.Ide.Core+JD.Efcpt.Ide.Core.Testsare added toJD.Efcpt.Build.sln(45 xUnit tests, ubuntu CI builds+tests them).JD.Efcpt.VsExtensionis deliberately excluded from that sln (its VSSDK/net48 chain would break the ubuntudotnet build) and lives only in the newide/vs/JD.Efcpt.Vs.sln..github/workflows/vsix.yml: builds+packages the VSIX viamsbuild(microsoft/setup-msbuild) onwindows-latest, uploads the.vsixartifact; marketplace publish is gated on theVS_MARKETPLACE_PATsecret and skips cleanly (mirroring the NuGet-key guard inci.yml) rather than failing when it's absent.ci.ymlitself is untouched.docs/user-guide/visual-studio.md(+toc.ymlentry): install, commands, tool window, redaction, EF Core Power Tools conflict-avoidance, architecture, CI, and local-build notes.Validation
dotnet build JD.Efcpt.Build.sln -c Debug- 0 errors (26 projects, 6 pre-existing unrelated warnings).dotnet test ide/vs/JD.Efcpt.Ide.Core.Tests- 45/45 passing.JD.Efcpt.VsExtensionwas not expected to build on this dev machine (no VS SDK) but, after fixing thenet472→net48target (Community.VisualStudio.Toolkit.17's NuGet dependency group is declared for.NETFramework4.8specifically) and a couple of vs-threading analyzer fixups,dotnet build ide/vs/JD.Efcpt.VsExtension/JD.Efcpt.VsExtension.csprojactually compiles with 0 errors/0 warnings here. Full.vsixpackaging (VSSDK'sCreateVsixContainertarget chain) did not run under plaindotnet buildin this environment - that step needs the windows-latest CI runner's full MSBuild/VSSDK tooling, which is whatvsix.ymlexercises.Test plan
vsix.ymlruns green onwindows-latestand uploads a.vsixartifact.vsixlocally in VS 2022 and confirmTools > Entity Framework > JD.Efcpt: Regenerate Models/JD.Efcpt: Show Build Statusappear and don't collide with EF Core Power ToolsRegenerate Modelson a realJD.Efcpt.Build-referencing project produces output in theJD.EfcptOutput pane and regenerates modelsShow Build Statustool window populates fromobj/efcpt/build-profile.jsonand auto-refreshes after a buildCloses #182.