Skip to content

Commit 9c0ca31

Browse files
authored
Add Font Package "New" Support and ManifestRootType (#645)
* Add font support * Feedback update
1 parent 75fd379 commit 9c0ca31

File tree

15 files changed

+274
-51
lines changed

15 files changed

+274
-51
lines changed

src/WingetCreateCLI/Commands/BaseCommand.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,12 @@ public async Task<bool> LoadGitHubClient(bool requireToken = false)
166166
/// Creates a formatted directory of manifest files from the manifest object models and saves the directory to a local path.
167167
/// </summary>
168168
/// <param name="manifests">Wrapper object for manifest object models.</param>
169+
/// <param name="manifestRoot">Manifest root name.</param>
169170
/// <param name="outputDir">Output directory where the manifests are saved locally.</param>
170171
/// <returns>Path to manifest directory.</returns>
171172
protected static string SaveManifestDirToLocalPath(
172173
Manifests manifests,
174+
string manifestRoot,
173175
string outputDir)
174176
{
175177
VersionManifest versionManifest = manifests.VersionManifest;
@@ -180,7 +182,7 @@ protected static string SaveManifestDirToLocalPath(
180182
outputDir = Environment.ExpandEnvironmentVariables(outputDir);
181183
string version = versionManifest.PackageVersion;
182184
string packageId = versionManifest.PackageIdentifier;
183-
string manifestDir = Utils.GetAppManifestDirPath(packageId, version);
185+
string manifestDir = Utils.GetAppManifestDirPath(packageId, version, manifestRoot);
184186
string fullDirPath = Path.GetFullPath(Path.Combine(outputDir, manifestDir));
185187

186188
try
@@ -739,11 +741,12 @@ protected async Task<bool> CheckGitHubTokenAndSetClient()
739741
/// Submits a pull request with multifile manifests using the user's GitHub access token.
740742
/// </summary>
741743
/// <param name="manifests">Wrapper object for manifest object models to be submitted.</param>
744+
/// <param name="manifestRoot">Manifest root name.</param>
742745
/// <param name="prTitle">Optional parameter specifying the title for the pull request.</param>
743746
/// <param name="shouldReplace">Optional parameter specifying whether the new submission should replace an existing manifest.</param>
744747
/// <param name="replaceVersion">Optional parameter specifying the version of the manifest to be replaced.</param>
745748
/// <returns>A <see cref="Task"/> representing the success of the asynchronous operation.</returns>
746-
protected async Task<bool> GitHubSubmitManifests(Manifests manifests, string prTitle = null, bool shouldReplace = false, string replaceVersion = null)
749+
protected async Task<bool> GitHubSubmitManifests(Manifests manifests, string manifestRoot = Constants.WingetManifestRoot, string prTitle = null, bool shouldReplace = false, string replaceVersion = null)
747750
{
748751
// Community repo only supports yaml submissions.
749752
if (this.WingetRepo == DefaultWingetRepo &&
@@ -765,7 +768,7 @@ protected async Task<bool> GitHubSubmitManifests(Manifests manifests, string prT
765768

766769
try
767770
{
768-
Octokit.PullRequest pullRequest = await this.GitHubClient.SubmitPullRequestAsync(manifests, this.SubmitPRToFork, prTitle, shouldReplace, replaceVersion);
771+
Octokit.PullRequest pullRequest = await this.GitHubClient.SubmitPullRequestAsync(manifests, this.SubmitPRToFork, manifestRoot, prTitle, shouldReplace, replaceVersion);
769772
this.PullRequestNumber = pullRequest.Number;
770773
PullRequestEvent pullRequestEvent = new PullRequestEvent { IsSuccessful = true, PullRequestNumber = pullRequest.Number };
771774
TelemetryManager.Log.WriteEvent(pullRequestEvent);

src/WingetCreateCLI/Commands/NewCommand.cs

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ namespace Microsoft.WingetCreateCLI.Commands
2525
using Microsoft.WingetCreateCore.Models.DefaultLocale;
2626
using Microsoft.WingetCreateCore.Models.Installer;
2727
using Microsoft.WingetCreateCore.Models.Version;
28+
using Newtonsoft.Json.Schema.Generation;
2829
using Sharprompt;
2930

3031
/// <summary>
@@ -41,7 +42,20 @@ public class NewCommand : BaseCommand
4142
/// <summary>
4243
/// Installer type file extensions that are supported.
4344
/// </summary>
44-
private static readonly string[] SupportedInstallerTypeExtensions = new[] { ".msix", ".msi", ".exe", ".msixbundle", ".appx", ".appxbundle" };
45+
private static readonly string[] SupportedInstallerTypeExtensions =
46+
[
47+
".msix",
48+
".msi",
49+
".exe",
50+
".msixbundle",
51+
".appx",
52+
".appxbundle",
53+
".otf", // OpenType Font
54+
".ttf", // TrueType Font
55+
".fnt", // Font
56+
".ttc", // TrueType Font Collection
57+
".otc", // OpenType Font Collection
58+
];
4559

4660
/// <summary>
4761
/// Gets the usage examples for the New command.
@@ -117,6 +131,7 @@ public override async Task<bool> Execute()
117131
Prompt.Symbols.Prompt = new Symbol(string.Empty, string.Empty);
118132

119133
Manifests manifests = new Manifests();
134+
PackageParser.ManifestRootType manifestRootType = PackageParser.ManifestRootType.Unknown;
120135

121136
if (!this.InstallerUrls.Any())
122137
{
@@ -169,6 +184,29 @@ public override async Task<bool> Execute()
169184
.ToList();
170185

171186
int extractedFilesCount = extractedFiles.Count();
187+
188+
var rootTypeForFiles = PackageParser.GetManifestRootTypeForInstallerPaths(extractedFiles);
189+
var isFontPackage = rootTypeForFiles == PackageParser.ManifestRootType.Fonts;
190+
191+
// Set the root type if it is unknown.
192+
if (manifestRootType == PackageParser.ManifestRootType.Unknown)
193+
{
194+
manifestRootType = rootTypeForFiles;
195+
}
196+
197+
// Check for installer type mismatches or mixed installers.
198+
if ((manifestRootType != rootTypeForFiles) || (manifestRootType == PackageParser.ManifestRootType.Unknown))
199+
{
200+
// Mismatched root and installer types is a WinGet-Pkgs policy, not an invalid manifest.
201+
Logger.WarnLocalized(nameof(Resources.MixedInstallerRootTypes_ErrorMessage));
202+
}
203+
204+
// If the root type is still unknown, it means a mixed package. We will assume manifests.
205+
if (manifestRootType == PackageParser.ManifestRootType.Unknown)
206+
{
207+
manifestRootType = PackageParser.ManifestRootType.Manifests;
208+
}
209+
172210
List<string> selectedInstallers;
173211

174212
if (extractedFilesCount == 0)
@@ -180,26 +218,70 @@ public override async Task<bool> Execute()
180218
{
181219
selectedInstallers = extractedFiles;
182220
}
221+
else if (isFontPackage)
222+
{
223+
// If this is a font package, every installer is intended to be installed.
224+
selectedInstallers = extractedFiles;
225+
}
183226
else
184227
{
185228
selectedInstallers = Prompt.MultiSelect(Resources.SelectInstallersFromZip_Message, extractedFiles, minimum: 1).ToList();
186229
}
187230

188-
foreach (var installer in selectedInstallers)
231+
if (isFontPackage)
189232
{
233+
// If every installer is a single font package, we can convert the entire package into a single font nested installer.
234+
List<NestedInstallerFile> nestedInstallerFiles = [];
235+
foreach (var fontFile in selectedInstallers)
236+
{
237+
nestedInstallerFiles.Add(new NestedInstallerFile { RelativeFilePath = fontFile });
238+
}
239+
190240
installerUpdateList.Add(
191241
new InstallerMetadata
192242
{
193243
InstallerUrl = installerUrl,
194244
PackageFile = packageFile,
195-
NestedInstallerFiles = new List<NestedInstallerFile> { new NestedInstallerFile { RelativeFilePath = installer } },
245+
NestedInstallerFiles = nestedInstallerFiles,
246+
OverrideArchitecture = Architecture.Neutral,
196247
IsZipFile = true,
197248
ExtractedDirectory = extractDirectory,
198249
});
199250
}
251+
else
252+
{
253+
foreach (var installer in selectedInstallers)
254+
{
255+
installerUpdateList.Add(
256+
new InstallerMetadata
257+
{
258+
InstallerUrl = installerUrl,
259+
PackageFile = packageFile,
260+
NestedInstallerFiles = new List<NestedInstallerFile> { new NestedInstallerFile { RelativeFilePath = installer } },
261+
IsZipFile = true,
262+
ExtractedDirectory = extractDirectory,
263+
});
264+
}
265+
}
200266
}
201-
else
267+
else // Not a Zip package archive.
202268
{
269+
// Set the manifest root type. There is only one file here so it can only be Font or Manifests.
270+
var rootTypeForInstaller = PackageParser.GetManifestRootTypeForInstallerPaths([packageFile]);
271+
272+
// Set the root type if it is unknown.
273+
if (manifestRootType == PackageParser.ManifestRootType.Unknown)
274+
{
275+
manifestRootType = rootTypeForInstaller;
276+
}
277+
278+
// Check for root type mismatches.
279+
if (manifestRootType != rootTypeForInstaller)
280+
{
281+
// Mismatched root and installer types is a WinGet-Pkgs policy, not an invalid manifest.
282+
Logger.WarnLocalized(nameof(Resources.MixedInstallerRootTypes_ErrorMessage));
283+
}
284+
203285
installerUpdateList.Add(new InstallerMetadata { InstallerUrl = installerUrl, PackageFile = packageFile });
204286
}
205287
}
@@ -282,7 +364,8 @@ public override async Task<bool> Execute()
282364
this.OutputDir = Directory.GetCurrentDirectory();
283365
}
284366

285-
SaveManifestDirToLocalPath(manifests, this.OutputDir);
367+
var manifestRoot = manifestRootType == PackageParser.ManifestRootType.Fonts ? Constants.WingetFontRoot : Constants.WingetManifestRoot;
368+
SaveManifestDirToLocalPath(manifests, manifestRoot, this.OutputDir);
286369

287370
if (isManifestValid && Prompt.Confirm(Resources.ConfirmGitHubSubmitManifest_Message))
288371
{
@@ -494,13 +577,13 @@ private static void PromptForPortableFieldsIfApplicable(Installer installer)
494577

495578
/// <summary>
496579
/// Merge nested installer files into a single installer if:
497-
/// 1. Matching installers have NestedInstallerType: portable.
580+
/// 1. Matching installers have NestedInstallerType: portable
498581
/// 2. Matching installers have the same architecture.
499582
/// 3. Matching installers have the same hash.
500583
/// </summary>
501584
private static void MergeNestedInstallerFilesIfApplicable(InstallerManifest installerManifest)
502585
{
503-
var nestedPortableInstallers = installerManifest.Installers.Where(i => i.NestedInstallerType == NestedInstallerType.Portable).ToList();
586+
var nestedPortableInstallers = installerManifest.Installers.Where(i => (i.NestedInstallerType == NestedInstallerType.Portable)).ToList();
504587
var mergeableInstallersList = nestedPortableInstallers.GroupBy(i => i.Architecture + i.InstallerSha256).ToList();
505588
foreach (var installers in mergeableInstallersList)
506589
{

src/WingetCreateCLI/Commands/NewLocaleCommand.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,10 @@ public override async Task<bool> Execute()
245245
this.OutputDir = Directory.GetCurrentDirectory();
246246
}
247247

248-
string manifestDirectoryPath = SaveManifestDirToLocalPath(originalManifests, this.OutputDir);
248+
// TODO: Need to discern font root manifests. Assume default root for now.
249+
// All of this locale code is generated assuming 'manifests' root, and updating it to support fonts
250+
// would require significant restructuring. See issue https://github.com/microsoft/winget-create/issues/647
251+
string manifestDirectoryPath = SaveManifestDirToLocalPath(originalManifests, Constants.WingetManifestRoot, this.OutputDir);
249252

250253
if (ValidateManifest(manifestDirectoryPath, this.Format))
251254
{

src/WingetCreateCLI/Commands/SubmitCommand.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ namespace Microsoft.WingetCreateCLI.Commands
99
using System.Threading.Tasks;
1010
using CommandLine;
1111
using CommandLine.Text;
12+
using Microsoft.VisualBasic;
1213
using Microsoft.WingetCreateCLI.Logging;
1314
using Microsoft.WingetCreateCLI.Properties;
1415
using Microsoft.WingetCreateCLI.Telemetry;
@@ -128,7 +129,9 @@ private async Task<bool> SubmitManifest()
128129
return false;
129130
}
130131

131-
return await this.GitHubSubmitManifests(manifests, this.PRTitle, this.Replace, this.ReplaceVersion);
132+
// TODO: Add Font Root Support
133+
// See issue: See issue https://github.com/microsoft/winget-create/issues/647
134+
return await this.GitHubSubmitManifests(manifests, this.PRTitle, WingetCreateCore.Common.Constants.WingetManifestRoot, this.Replace, this.ReplaceVersion);
132135
}
133136
else if (Directory.Exists(expandedPath) && ValidateManifest(expandedPath, this.Format))
134137
{
@@ -140,7 +143,9 @@ private async Task<bool> SubmitManifest()
140143
return false;
141144
}
142145

143-
return await this.GitHubSubmitManifests(manifests, this.PRTitle, this.Replace, this.ReplaceVersion);
146+
// TODO: Add Font Root Support
147+
// See issue: See issue https://github.com/microsoft/winget-create/issues/647
148+
return await this.GitHubSubmitManifests(manifests, this.PRTitle, WingetCreateCore.Common.Constants.WingetManifestRoot, this.Replace, this.ReplaceVersion);
144149
}
145150
else
146151
{

src/WingetCreateCLI/Commands/UpdateCommand.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,9 @@ await this.UpdateManifestsInteractively(initialManifests) :
287287
this.OutputDir = Directory.GetCurrentDirectory();
288288
}
289289

290-
string manifestDirectoryPath = SaveManifestDirToLocalPath(updatedManifests, this.OutputDir);
290+
// TODO: Font root support.
291+
// See issue: See issue https://github.com/microsoft/winget-create/issues/647
292+
string manifestDirectoryPath = SaveManifestDirToLocalPath(updatedManifests, Constants.WingetManifestRoot, this.OutputDir);
291293

292294
if (ValidateManifest(manifestDirectoryPath, this.Format))
293295
{
@@ -314,9 +316,12 @@ await this.UpdateManifestsInteractively(initialManifests) :
314316
}
315317
}
316318

319+
// TODO: Font root support.
320+
// See issue: See issue https://github.com/microsoft/winget-create/issues/647
317321
return await this.LoadGitHubClient(true)
318322
? (commandEvent.IsSuccessful = await this.GitHubSubmitManifests(
319323
updatedManifests,
324+
Constants.WingetManifestRoot,
320325
this.GetPRTitle(updatedManifests, originalManifests),
321326
this.Replace,
322327
this.ReplaceVersion))

src/WingetCreateCLI/Commands/UpdateLocaleCommand.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,9 @@ public override async Task<bool> Execute()
176176
this.OutputDir = Directory.GetCurrentDirectory();
177177
}
178178

179-
string manifestDirectoryPath = SaveManifestDirToLocalPath(originalManifests, this.OutputDir);
179+
// TODO: Font root support.
180+
// See issue: See issue https://github.com/microsoft/winget-create/issues/647
181+
string manifestDirectoryPath = SaveManifestDirToLocalPath(originalManifests, WingetCreateCore.Common.Constants.WingetManifestRoot, this.OutputDir);
180182

181183
if (ValidateManifest(manifestDirectoryPath, this.Format))
182184
{

src/WingetCreateCLI/Properties/Resources.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/WingetCreateCLI/Properties/Resources.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1483,4 +1483,7 @@ Warning: Using this argument may result in the token being logged. Consider an a
14831483
<data name="NoOpenPRInBrowser_HelpText" xml:space="preserve">
14841484
<value>Boolean value that controls whether the pull request should not be open in the browser on submission. Default is false.</value>
14851485
</data>
1486+
<data name="MixedInstallerRootTypes_ErrorMessage" xml:space="preserve">
1487+
<value>Found mixed font and non-font installer types in the manifest. This is not a supported configuration and the manifest should be edited to contain only font or non-font installers before submission.</value>
1488+
</data>
14861489
</root>

0 commit comments

Comments
 (0)