Skip to content

Commit fe5e1f9

Browse files
committed
allow recovering of already exported extension
allow recovering of already exported extension in manage extensions of a specific solution. Otherwise every export is destructive, with existing vsext.
1 parent e12d66f commit fe5e1f9

10 files changed

Lines changed: 145 additions & 30 deletions

File tree

src/ExtensionManager.Manifest/ExtensionManager.Manifest.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<PackageReference Include="Nullable" Version="1.3.1" PrivateAssets="all" />
55
<PackageReference Include="IsExternalInit" Version="1.0.3" PrivateAssets="all" />
66
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
7-
<PackageReference Include="System.Text.Json" Version="8.0.5" />
7+
<PackageReference Include="System.Text.Json" Version="8.0.6" />
88
</ItemGroup>
99

1010
<ItemGroup>

src/ExtensionManager.UI/DialogService.cs

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,23 @@ public DialogService(IVSThreads threads)
2020
_threads = threads;
2121
}
2222

23-
public Task<string?> ShowSaveVsextFileDialogAsync() => ShowVsextFileDialogAsync<SaveFileDialog>();
24-
public Task<string?> ShowOpenVsextFileDialogAsync() => ShowVsextFileDialogAsync<OpenFileDialog>();
23+
public Task<string?> ShowSaveVsextFileDialogAsync()
24+
{
25+
return ShowVsextFileDialogAsync<SaveFileDialog>();
26+
}
27+
28+
public Task<string?> ShowOpenVsextFileDialogAsync()
29+
{
30+
return ShowVsextFileDialogAsync<OpenFileDialog>();
31+
}
32+
2533
private Task<string?> ShowVsextFileDialogAsync<TFileDialog>()
2634
where TFileDialog : FileDialog, new()
2735
{
2836
if (_threads.CheckUIThreadAccess())
37+
{
2938
return Task.FromResult(OnUIThread());
39+
}
3040

3141
return _threads.RunOnUIThreadAsync(OnUIThread);
3242

@@ -40,30 +50,46 @@ public DialogService(IVSThreads threads)
4050
};
4151

4252
if (dialog.ShowDialog() == true)
53+
{
4354
return dialog.FileName;
55+
}
4456

4557
return null;
4658
}
4759
}
4860

49-
public Task ShowExportDialogAsync(IExportWorker worker, IManifest manifest, IReadOnlyCollection<IVSExtension> installedExtensions)
50-
=> ShowExportDialogAsync(worker, manifest, installedExtensions, forSolution: false);
51-
public Task ShowExportForSolutionDialogAsync(IExportWorker worker, IManifest manifest, IReadOnlyCollection<IVSExtension> installedExtensions)
52-
=> ShowExportDialogAsync(worker, manifest, installedExtensions, forSolution: true);
53-
private async Task ShowExportDialogAsync(IExportWorker worker, IManifest manifest, IReadOnlyCollection<IVSExtension> installedExtensions, bool forSolution)
61+
public Task ShowExportDialogAsync(IExportWorker worker, IManifest manifest, IReadOnlyCollection<IVSExtension> installedExtensions, IReadOnlyCollection<IVSExtension> selectedExtensions)
62+
{
63+
return ShowExportDialogAsync(worker, manifest, installedExtensions, selectedExtensions, forSolution: false);
64+
}
65+
66+
public Task ShowExportForSolutionDialogAsync(IExportWorker worker, IManifest manifest, IReadOnlyCollection<IVSExtension> installedExtensions, IReadOnlyCollection<IVSExtension> selectedExtensions)
67+
{
68+
return ShowExportDialogAsync(worker, manifest, installedExtensions, selectedExtensions, forSolution: true);
69+
}
70+
71+
private async Task ShowExportDialogAsync(IExportWorker worker, IManifest manifest, IReadOnlyCollection<IVSExtension> installedExtensions, IReadOnlyCollection<IVSExtension> selectedExtensions, bool forSolution)
5472
{
5573
var vm = new ExportDialogViewModel(worker, manifest, forSolution);
5674

5775
foreach (var ext in installedExtensions)
58-
vm.Extensions.Add(new(ext));
76+
{
77+
vm.Extensions.Add(new(ext) { IsSelected = selectedExtensions.Any(se => se.Id == ext.Id) });
78+
}
5979

6080
await ShowInstallExportDialogAsync(vm);
6181
}
6282

6383
public Task ShowInstallDialogAsync(IInstallWorker worker, IManifest manifest, IReadOnlyCollection<VSExtensionToInstall> extensions)
64-
=> ShowInstallForSolutionDialogAsync(worker, manifest, extensions, forSolution: false);
84+
{
85+
return ShowInstallForSolutionDialogAsync(worker, manifest, extensions, forSolution: false);
86+
}
87+
6588
public Task ShowInstallForSolutionDialogAsync(IInstallWorker worker, IManifest manifest, IReadOnlyCollection<VSExtensionToInstall> extensions)
66-
=> ShowInstallForSolutionDialogAsync(worker, manifest, extensions, forSolution: true);
89+
{
90+
return ShowInstallForSolutionDialogAsync(worker, manifest, extensions, forSolution: true);
91+
}
92+
6793
private async Task ShowInstallForSolutionDialogAsync(IInstallWorker worker, IManifest manifest, IReadOnlyCollection<VSExtensionToInstall> extensions, bool forSolution)
6894
{
6995
var vm = new InstallDialogViewModel(worker, manifest, forSolution);

src/ExtensionManager.UI/IDialogService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ public interface IDialogService
99
Task<string?> ShowSaveVsextFileDialogAsync();
1010
Task<string?> ShowOpenVsextFileDialogAsync();
1111

12-
Task ShowExportDialogAsync(IExportWorker worker, IManifest manifest, IReadOnlyCollection<IVSExtension> installedExtensions);
13-
Task ShowExportForSolutionDialogAsync(IExportWorker worker, IManifest manifest, IReadOnlyCollection<IVSExtension> installedExtensions);
12+
Task ShowExportDialogAsync(IExportWorker worker, IManifest manifest, IReadOnlyCollection<IVSExtension> installedExtensions, IReadOnlyCollection<IVSExtension> selectedExtensions);
13+
Task ShowExportForSolutionDialogAsync(IExportWorker worker, IManifest manifest, IReadOnlyCollection<IVSExtension> installedExtensions, IReadOnlyCollection<IVSExtension> selectedExtensions);
1414
Task ShowInstallDialogAsync(IInstallWorker worker, IManifest manifest, IReadOnlyCollection<VSExtensionToInstall> extensions);
1515
Task ShowInstallForSolutionDialogAsync(IInstallWorker worker, IManifest manifest, IReadOnlyCollection<VSExtensionToInstall> extensions);
1616
}

src/ExtensionManager.VisualStudio.Shared/Extensions/VSExtensions.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,14 @@ public async Task StartInstallerAsync(IEnumerable<string> vsixFiles, bool system
5454
var arguments = $"{string.Join(" ", vsixFiles)} /instanceIds:{GetInstallationId()}";
5555

5656
if (systemWide)
57+
{
5758
arguments += $" /admin";
59+
}
5860

5961
if (!string.IsNullOrEmpty(rootSuffix))
62+
{
6063
arguments += $" /rootSuffix:{rootSuffix}";
64+
}
6165

6266
Process.Start(new ProcessStartInfo
6367
{

src/ExtensionManager.Vsix.VS2022/ExtensionManager.Vsix.VS2022.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,20 @@
9999
</ItemGroup>
100100
<ItemGroup>
101101
<PackageReference Include="Community.VisualStudio.Toolkit.17">
102-
<Version>17.0.507</Version>
102+
<Version>17.0.549</Version>
103103
</PackageReference>
104104
<PackageReference Include="Microsoft.VisualStudio.SDK">
105105
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
106106
<ExcludeAssets>runtime</ExcludeAssets>
107107
<Version>17.6.36389</Version>
108108
</PackageReference>
109109
<PackageReference Include="Microsoft.VisualStudio.Setup.Configuration.Interop">
110-
<Version>3.6.2115</Version>
110+
<Version>3.14.2075</Version>
111111
</PackageReference>
112112
<PackageReference Include="Microsoft.VSSDK.BuildTools">
113113
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
114114
<PrivateAssets>all</PrivateAssets>
115-
<Version>17.6.2164</Version>
115+
<Version>17.14.2120</Version>
116116
</PackageReference>
117117
<PackageReference Include="System.Collections.Immutable">
118118
<Version>8.0.0</Version>
@@ -149,4 +149,4 @@
149149
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
150150
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="'$(VSToolsPath)' != ''" />
151151
<Import Project="..\ExtensionManager.Vsix.props" />
152-
</Project>
152+
</Project>

src/ExtensionManager.Vsix.VS2022/source.extension.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// ------------------------------------------------------------------------------
22
// <auto-generated>
3-
// This file was generated by VSIX Synchronizer
3+
// This file was generated by VSIX Synchronizer 1.0.45
4+
// Available from https://marketplace.visualstudio.com/items?itemName=MadsKristensen.VsixSynchronizer64
45
// </auto-generated>
56
// ------------------------------------------------------------------------------
67
namespace ExtensionManager
@@ -14,5 +15,6 @@ internal sealed partial class Vsix
1415
public const string Version = "9.9.9999";
1516
public const string Author = "Loop8ack";
1617
public const string Tags = "extension pack, vsix";
18+
public const bool IsPreview = false;
1719
}
1820
}

src/ExtensionManager.Vsix.VS2022/source.extension.vsixmanifest

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<PackageManifest Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vsx-schema/2011" xmlns:d="http://schemas.microsoft.com/developer/vsx-schema-design/2011">
33
<Metadata>
44
<Identity Id="3d183c28-64c6-4efb-a201-50310d65e675" Version="9.9.9999" Language="en-US" Publisher="Loop8ack" />
@@ -11,7 +11,7 @@
1111
<Tags>extension pack, vsix</Tags>
1212
</Metadata>
1313
<Installation AllUsers="true">
14-
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[17.0,18.0)">
14+
<InstallationTarget Id="Microsoft.VisualStudio.Community" Version="[17.0,)">
1515
<ProductArchitecture>amd64</ProductArchitecture>
1616
</InstallationTarget>
1717
</Installation>

src/ExtensionManager/Features/Export/ExportFeature.cs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,17 @@ public ExportFeature(Args args)
1212
}
1313

1414
protected override async Task<string?> GetFilePathAsync()
15-
=> await DialogService.ShowSaveVsextFileDialogAsync();
15+
{
16+
return await DialogService.ShowSaveVsextFileDialogAsync();
17+
}
1618

17-
protected override async Task ShowExportDialogAsync(IManifest manifest, IExportWorker worker, IReadOnlyCollection<IVSExtension> extensions)
18-
=> await DialogService.ShowExportDialogAsync(worker, manifest, extensions);
19+
protected override async Task ShowExportDialogAsync(IManifest manifest, IExportWorker worker, IReadOnlyCollection<IVSExtension> extensions, IReadOnlyCollection<IVSExtension> selectedExtensions)
20+
{
21+
await DialogService.ShowExportDialogAsync(worker, manifest, extensions, selectedExtensions);
22+
}
1923

2024
protected override async Task OnManifestWrittenAsync(string filePath)
21-
=> await Documents.OpenAsync(filePath);
25+
{
26+
await Documents.OpenAsync(filePath);
27+
}
2228
}

src/ExtensionManager/Features/Export/ExportFeatureBase.cs

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using System.Collections.ObjectModel;
2+
13
using ExtensionManager.Manifest;
24
using ExtensionManager.UI;
35
using ExtensionManager.UI.Worker;
@@ -45,23 +47,61 @@ protected ExportFeatureBase(Args args)
4547

4648
public async Task ExecuteAsync()
4749
{
48-
var manifest = ManifestService.CreateNew();
50+
IManifest manifest;
51+
52+
try
53+
{
54+
// Find solution directory (or fallback to current)
55+
var solutionDir = FindSolutionDirectory();
56+
57+
// Look for a .vsext file next to the solution
58+
var vsextFile = Directory.EnumerateFiles(solutionDir, "*.vsext", SearchOption.TopDirectoryOnly).FirstOrDefault();
59+
60+
if (!string.IsNullOrEmpty(vsextFile))
61+
{
62+
try
63+
{
64+
// Attempt to read manifest from the found .vsext file
65+
manifest = await ManifestService.ReadAsync(vsextFile).ConfigureAwait(false);
66+
}
67+
catch
68+
{
69+
// If reading fails for any reason, fallback to creating a new manifest
70+
manifest = ManifestService.CreateNew();
71+
}
72+
}
73+
else
74+
{
75+
// No .vsext found: create new manifest
76+
manifest = ManifestService.CreateNew();
77+
}
78+
}
79+
catch
80+
{
81+
// On any unexpected error, ensure we still have a manifest
82+
manifest = ManifestService.CreateNew();
83+
}
84+
4985
var installedExtensions = await Extensions.GetInstalledExtensionsAsync().ConfigureAwait(false);
5086

5187
var installedExtensionsList = installedExtensions as List<IVSExtension>
5288
?? installedExtensions.ToList();
5389

5490
installedExtensionsList.RemoveAll(vsix => vsix.Id == VsixInfo.Id);
5591

56-
await ShowExportDialogAsync(manifest, this, installedExtensions);
92+
var selectedExtensions = manifest.Extensions;
93+
94+
await ShowExportDialogAsync(manifest, this, installedExtensions, new ReadOnlyCollection<IVSExtension>(selectedExtensions));
5795
}
5896

5997
async Task IExportWorker.ExportAsync(IManifest manifest, IProgress<ProgressStep<ExportStep>> progress, CancellationToken cancellationToken)
6098
{
6199
var filePath = await GetFilePathAsync().ConfigureAwait(false);
62100

63101
if (filePath is null or { Length: 0 })
102+
{
64103
return;
104+
}
65105

66106
progress.Report(null, ExportStep.SaveManifest);
67107
await ManifestService.WriteAsync(filePath, manifest, cancellationToken).ConfigureAwait(false);
@@ -71,6 +111,37 @@ async Task IExportWorker.ExportAsync(IManifest manifest, IProgress<ProgressStep<
71111
}
72112

73113
protected abstract Task<string?> GetFilePathAsync();
74-
protected abstract Task ShowExportDialogAsync(IManifest manifest, IExportWorker worker, IReadOnlyCollection<IVSExtension> installedExtensions);
114+
protected abstract Task ShowExportDialogAsync(IManifest manifest, IExportWorker worker, IReadOnlyCollection<IVSExtension> installedExtensions, IReadOnlyCollection<IVSExtension> selectedExtensions);
75115
protected abstract Task OnManifestWrittenAsync(string filePath);
116+
117+
private static string FindSolutionDirectory()
118+
{
119+
var dir = Directory.GetCurrentDirectory();
120+
121+
while (true)
122+
{
123+
try
124+
{
125+
if (Directory.EnumerateFiles(dir, "*.sln", SearchOption.TopDirectoryOnly).Any()
126+
|| Directory.EnumerateFiles(dir, "*.slnx", SearchOption.TopDirectoryOnly).Any())
127+
{
128+
return dir;
129+
}
130+
}
131+
catch
132+
{
133+
// ignore and try parent
134+
}
135+
136+
var parent = Directory.GetParent(dir);
137+
if (parent == null)
138+
{
139+
// No parent left, return original current directory as fallback
140+
return Directory.GetCurrentDirectory();
141+
}
142+
143+
dir = parent.FullName;
144+
}
145+
}
76146
}
147+

src/ExtensionManager/Features/Export/ExportSolutionFeature.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@ public ExportSolutionFeature(Args args, IVSSolutions solutions)
1616
}
1717

1818
protected override async Task<string?> GetFilePathAsync()
19-
=> await _solutions.GetCurrentSolutionExtensionsManifestFilePathAsync(MessageBox);
19+
{
20+
return await _solutions.GetCurrentSolutionExtensionsManifestFilePathAsync(MessageBox);
21+
}
2022

21-
protected override async Task ShowExportDialogAsync(IManifest manifest, IExportWorker worker, IReadOnlyCollection<IVSExtension> installedExtensions)
22-
=> await DialogService.ShowExportForSolutionDialogAsync(worker, manifest, installedExtensions);
23+
protected override async Task ShowExportDialogAsync(IManifest manifest, IExportWorker worker, IReadOnlyCollection<IVSExtension> installedExtensions, IReadOnlyCollection<IVSExtension> selectedExtensions)
24+
{
25+
await DialogService.ShowExportForSolutionDialogAsync(worker, manifest, installedExtensions, selectedExtensions);
26+
}
2327

2428
protected override async Task OnManifestWrittenAsync(string filePath)
2529
{
@@ -32,7 +36,9 @@ protected override async Task OnManifestWrittenAsync(string filePath)
3236
?? await solution.AddSolutionFolderAsync(folderName);
3337

3438
if (folder is null)
39+
{
3540
throw new InvalidOperationException("Could not add solution folder");
41+
}
3642

3743
await folder.AddExistingFilesAsync(filePath);
3844
await Documents.OpenAsync(filePath);

0 commit comments

Comments
 (0)