@@ -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 {
0 commit comments