diff --git a/Tools/YamlCreate.ps1 b/Tools/YamlCreate.ps1 index 9b05b821bd5a2b..4de4af97e41080 100644 --- a/Tools/YamlCreate.ps1 +++ b/Tools/YamlCreate.ps1 @@ -23,6 +23,22 @@ if ($help) { exit } +# Installs `powershell-yaml` as a dependency for parsing yaml content +if (-not(Get-Module -ListAvailable -Name powershell-yaml)) { + try { + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force + Install-Module -Name powershell-yaml -Force -Repository PSGallery -Scope CurrentUser + } catch { + # If there was an exception while installing powershell-yaml, pass it as an InternalException for further debugging + throw [UnmetDependencyException]::new("'powershell-yaml' unable to be installed successfully", $_.Exception) + } finally { + # Double check that it was installed properly + if (-not(Get-Module -ListAvailable -Name powershell-yaml)) { + throw [UnmetDependencyException]::new("'powershell-yaml' is not found") + } + } +} + # Set settings directory on basis of Operating System $script:SettingsPath = Join-Path $(if ([System.Environment]::OSVersion.Platform -match 'Win') { $env:LOCALAPPDATA } else { $env:HOME + '/.config' } ) -ChildPath 'YamlCreate' # Check for settings directory and create it if none exists @@ -38,7 +54,7 @@ if ($Settings) { exit } -$ScriptHeader = '# Created with YamlCreate.ps1 v2.0.4' +$ScriptHeader = '# Created with YamlCreate.ps1 v2.0.5' $ManifestVersion = '1.1.0' $PSDefaultParameterValues = @{ '*:Encoding' = 'UTF8' } $Utf8NoBomEncoding = New-Object System.Text.UTF8Encoding $False @@ -67,22 +83,6 @@ $ofs = ', ' https://github.com/microsoft/winget-pkgs/blob/master/Tools/YamlCreate.ps1 #> -# Installs `powershell-yaml` as a dependency for parsing yaml content -if (-not(Get-Module -ListAvailable -Name powershell-yaml)) { - try { - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force - Install-Module -Name powershell-yaml -Force -Repository PSGallery -Scope CurrentUser - } catch { - # If there was an exception while installing powershell-yaml, pass it as an InternalException for further debugging - throw [UnmetDependencyException]::new("'powershell-yaml' unable to be installed successfully", $_.Exception) - } finally { - # Double check that it was installed properly - if (-not(Get-Module -ListAvailable -Name powershell-yaml)) { - throw [UnmetDependencyException]::new("'powershell-yaml' is not found") - } - } -} - # Fetch Schema data from github for entry validation, key ordering, and automatic commenting try { $ProgressPreference = 'SilentlyContinue' @@ -162,6 +162,8 @@ $Patterns = @{ ValidInstallModes = $InstallerSchema.definitions.InstallModes.items.enum FileExtension = $InstallerSchema.definitions.FileExtensions.items.pattern FileExtensionMaxLength = $InstallerSchema.definitions.FileExtensions.items.maxLength + ReleaseNotesMinLength = $LocaleSchema.properties.ReleaseNotes.MinLength + ReleaseNotesMaxLength = $LocaleSchema.properties.ReleaseNotes.MaxLength } # This function validates whether a string matches Minimum Length, Maximum Length, and Regex pattern @@ -458,7 +460,9 @@ Function Read-InstallerEntry { if ($_) { $_Installer['InstallerType'] = $_ | Select-Object -First 1 } Get-UriArchitecture -URI $_Installer['InstallerUrl'] -OutVariable _ | Out-Null if ($_) { $_Installer['Architecture'] = $_ | Select-Object -First 1 } - $ProductCode = $(Get-AppLockerFileInformation -Path $script:dest | Select-Object Publisher | Select-String -Pattern '{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}').Matches + if ([System.Environment]::OSVersion.Platform -match 'Win') { + $MSIProductCode = [string]$(Get-AppLockerFileInformation -Path $script:dest | Select-Object Publisher | Select-String -Pattern '{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}').Matches + } if (Test-String -Not "$ProductCode" -IsNull) { $_Installer['ProductCode'] = "$ProductCode" } } } @@ -699,6 +703,26 @@ Function Read-InstallerEntry { 'U' { $_Installer['UpgradeBehavior'] = 'uninstallPrevious' } default { $_Installer['UpgradeBehavior'] = 'install' } } + Write-Host + + # Request release date + $script:ReleaseDatePrompted = $true + do { + Write-Host -ForegroundColor 'Red' $script:_returnValue.ErrorString() + Write-Host -ForegroundColor 'Yellow' -Object '[Optional] Enter the application release date. Example: 2021-11-17' + Read-Host -Prompt 'ReleaseDate' -OutVariable ReleaseDate | Out-Null + try { + Get-Date([datetime]$($ReleaseDate | TrimString)) -f 'yyyy-MM-dd' -OutVariable _ValidDate | Out-Null + if ($_ValidDate) { $_Installer['ReleaseDate'] = $_ValidDate | TrimString } + $script:_returnValue = [ReturnValue]::Success() + } catch { + if (Test-String $ReleaseDate -IsNull) { + $script:_returnValue = [ReturnValue]::Success() + } else { + $script:_returnValue = [ReturnValue]::new(400, 'Invalid Date', 'Input could not be resolved to a date', 2) + } + } + } until ($script:_returnValue.StatusCode -eq [ReturnValue]::Success().StatusCode) if ($script:SaveOption -eq '1' -and (Test-Path -Path $script:dest)) { Remove-Item -Path $script:dest } @@ -785,10 +809,12 @@ Function Read-QuickInstallerEntry { $_NewInstaller['InstallerSha256'] = (Get-FileHash -Path $script:dest -Algorithm SHA256).Hash # Update the product code, if a new one exists # If a new product code doesn't exist, and the installer isn't an `.exe` file, remove the product code if it exists - $MSIProductCode = [string]$(Get-AppLockerFileInformation -Path $script:dest | Select-Object Publisher | Select-String -Pattern '{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}').Matches + if ([System.Environment]::OSVersion.Platform -match 'Win') { + $MSIProductCode = [string]$(Get-AppLockerFileInformation -Path $script:dest | Select-Object Publisher | Select-String -Pattern '{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}').Matches + } if (Test-String -not $MSIProductCode -IsNull) { $_NewInstaller['ProductCode'] = $MSIProductCode - } elseif ( ($_NewInstaller.Keys -contains 'ProductCode') -and ($_NewInstaller.InstallerType -in @('appx';'msi';'msix';'appxbundle';'msixbundle'))) { + } elseif ( ($_NewInstaller.Keys -contains 'ProductCode') -and ($_NewInstaller.InstallerType -in @('appx'; 'msi'; 'msix'; 'appxbundle'; 'msixbundle'))) { $_NewInstaller.Remove('ProductCode') } # If the installer is msix or appx, try getting the new SignatureSha256 @@ -1279,6 +1305,36 @@ Function Read-LocaleMetadata { $script:_returnValue = [ReturnValue]::LengthError($Patterns.DescriptionMinLength, $Patterns.DescriptionMaxLength) } } until ($script:_returnValue.StatusCode -eq [ReturnValue]::Success().StatusCode) + + # Request ReleaseNotes and Validate + do { + Write-Host -ForegroundColor 'Red' $script:_returnValue.ErrorString() + Write-Host -ForegroundColor 'Yellow' -Object '[Optional] Enter release notes for this version of the package.' + $script:ReleaseNotes = Read-Host -Prompt 'ReleaseNotes' | TrimString + if (Test-String $script:ReleaseNotes -MinLength $Patterns.ReleaseNotesMinLength -MaxLength $Patterns.ReleaseNotesMaxLength -AllowNull) { + $script:_returnValue = [ReturnValue]::Success() + } else { + $script:_returnValue = [ReturnValue]::LengthError($Patterns.ReleaseNotesMinLength, $Patterns.ReleaseNotesMaxLength) + } + } until ($script:_returnValue.StatusCode -eq [ReturnValue]::Success().StatusCode) + + # Request ReleaseNotes URL and Validate + do { + Write-Host -ForegroundColor 'Red' $script:_returnValue.ErrorString() + Write-Host -ForegroundColor 'Yellow' -Object '[Optional] Enter the release notes URL for this version of the package.' + $script:ReleaseNotesUrl = Read-Host -Prompt 'ReleaseNotesUrl' | TrimString + if (Test-String $script:ReleaseNotesUrl -MaxLength $Patterns.GenericUrlMaxLength -MatchPattern $Patterns.GenericUrl -AllowNull) { + $script:_returnValue = [ReturnValue]::Success() + } else { + if (Test-String -not $script:ReleaseNotesUrl -MaxLength $Patterns.GenericUrlMaxLength -AllowNull) { + $script:_returnValue = [ReturnValue]::LengthError(1, $Patterns.GenericUrlMaxLength) + } elseif (Test-String -not $script:ReleaseNotesUrl -MatchPattern $Patterns.GenericUrl) { + $script:_returnValue = [ReturnValue]::PatternError() + } else { + $script:_returnValue = [ReturnValue]::GenericError() + } + } + } until ($script:_returnValue.StatusCode -eq [ReturnValue]::Success().StatusCode) } # Requests the user to answer the prompts found in the winget-pkgs pull request template @@ -1564,6 +1620,10 @@ Function Write-InstallerManifest { $InstallerManifest['Installers'] = $script:OldVersionManifest['Installers'] } + foreach ($_Installer in $InstallerManifest.Installers){ + if ($_Installer['ReleaseDate'] -and !$script:ReleaseDatePrompted) { $_Installer.Remove('ReleaseDate') } + } + Add-YamlParameter -Object $InstallerManifest -Parameter 'ManifestType' -Value 'installer' Add-YamlParameter -Object $InstallerManifest -Parameter 'ManifestVersion' -Value $ManifestVersion If ($InstallerManifest['Dependencies']) { @@ -1633,7 +1693,7 @@ Function Write-InstallerManifest { # Create the folder for the file if it doesn't exist New-Item -ItemType 'Directory' -Force -Path $AppFolder | Out-Null - $InstallerManifestPath = $AppFolder + "\$PackageIdentifier" + '.installer' + '.yaml' + $script:InstallerManifestPath = $AppFolder + "\$PackageIdentifier" + '.installer' + '.yaml' # Write the manifest to the file $ScriptHeader + "$(Get-DebugString)`n# yaml-language-server: `$schema=https://aka.ms/winget-manifest.installer.$ManifestVersion.schema.json`n" > $InstallerManifestPath @@ -1655,9 +1715,6 @@ Function Write-LocaleManifest { } if (!$LocaleManifest) { [PSCustomObject]$LocaleManifest = [ordered]@{} } - # Set the appropriate langage server depending on if it is a default locale file or generic locale file - if ($LocaleManifest.ManifestType -eq 'defaultLocale') { $yamlServer = "# yaml-language-server: `$schema=https://aka.ms/winget-manifest.defaultLocale.$ManifestVersion.schema.json" } else { $yamlServer = "# yaml-language-server: `$schema=https://aka.ms/winget-manifest.locale.$ManifestVersion.schema.json" } - # Add the properties to the manifest $_Singletons = [ordered]@{ 'PackageIdentifier' = $PackageIdentifier @@ -1676,6 +1733,8 @@ Function Write-LocaleManifest { 'CopyrightUrl' = $CopyrightUrl 'ShortDescription' = $ShortDescription 'Description' = $Description + 'ReleaseNotes' = $ReleaseNotes + 'ReleaseNotesUrl' = $ReleaseNotesUrl } foreach ($_Item in $_Singletons.GetEnumerator()) { If ($_Item.Value) { Add-YamlParameter -Object $LocaleManifest -Parameter $_Item.Name -Value $_Item.Value } @@ -1690,8 +1749,15 @@ Function Write-LocaleManifest { if ($LocaleManifest['Tags']) { $LocaleManifest['Tags'] = @($LocaleManifest['Tags'] | ToLower | UniqueItems | NoWhitespace | Sort-Object) } if ($LocaleManifest['Moniker']) { $LocaleManifest['Moniker'] = $LocaleManifest['Moniker'] | ToLower | NoWhitespace } + # Clean up the volatile fields + if ($LocaleManifest['ReleaseNotes'] -and (Test-String $script:ReleaseNotes -IsNull)) { $LocaleManifest.Remove('ReleaseNotes') } + if ($LocaleManifest['ReleaseNotesUrl'] -and (Test-String $script:ReleaseNotes -IsNull)) { $LocaleManifest.Remove('ReleaseNotesUrl') } + $LocaleManifest = Restore-YamlKeyOrder $LocaleManifest $LocaleProperties + # Set the appropriate langage server depending on if it is a default locale file or generic locale file + if ($LocaleManifest.ManifestType -eq 'defaultLocale') { $yamlServer = "# yaml-language-server: `$schema=https://aka.ms/winget-manifest.defaultLocale.$ManifestVersion.schema.json" } else { $yamlServer = "# yaml-language-server: `$schema=https://aka.ms/winget-manifest.locale.$ManifestVersion.schema.json" } + # Create the folder for the file if it doesn't exist New-Item -ItemType 'Directory' -Force -Path $AppFolder | Out-Null $script:LocaleManifestPath = $AppFolder + "\$PackageIdentifier" + '.locale.' + "$PackageLocale" + '.yaml' @@ -1714,6 +1780,11 @@ Function Write-LocaleManifest { $script:OldLocaleManifest['ManifestVersion'] = $ManifestVersion # Clean up the existing files just in case if ($script:OldLocaleManifest['Tags']) { $script:OldLocaleManifest['Tags'] = @($script:OldLocaleManifest['Tags'] | ToLower | UniqueItems | NoWhitespace | Sort-Object) } + + # Clean up the volatile fields + if ($OldLocaleManifest['ReleaseNotes'] -and (Test-String $script:ReleaseNotes -IsNull)) { $OldLocaleManifest.Remove('ReleaseNotes') } + if ($OldLocaleManifest['ReleaseNotesUrl'] -and (Test-String $script:ReleaseNotes -IsNull)) { $OldLocaleManifest.Remove('ReleaseNotesUrl') } + $script:OldLocaleManifest = Restore-YamlKeyOrder $script:OldLocaleManifest $LocaleProperties $yamlServer = "# yaml-language-server: `$schema=https://aka.ms/winget-manifest.locale.$ManifestVersion.schema.json" @@ -2142,10 +2213,12 @@ Switch ($script:Option) { $_Installer['InstallerSha256'] = (Get-FileHash -Path $script:dest -Algorithm SHA256).Hash # Update the product code, if a new one exists # If a new product code doesn't exist, and the installer isn't an `.exe` file, remove the product code if it exists - $MSIProductCode = [string]$(Get-AppLockerFileInformation -Path $script:dest | Select-Object Publisher | Select-String -Pattern '{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}').Matches + if ([System.Environment]::OSVersion.Platform -match 'Win') { + $MSIProductCode = [string]$(Get-AppLockerFileInformation -Path $script:dest | Select-Object Publisher | Select-String -Pattern '{[A-Z0-9]{8}-([A-Z0-9]{4}-){3}[A-Z0-9]{12}}').Matches + } if (Test-String -not $MSIProductCode -IsNull) { $_Installer['ProductCode'] = $MSIProductCode - } elseif ( ($_Installer.Keys -contains 'ProductCode') -and ($_Installer.InstallerType -in @('appx';'msi';'msix';'appxbundle';'msixbundle'))) { + } elseif ( ($_Installer.Keys -contains 'ProductCode') -and ($_Installer.InstallerType -in @('appx'; 'msi'; 'msix'; 'appxbundle'; 'msixbundle'))) { $_Installer.Remove('ProductCode') } # If the installer is msix or appx, try getting the new SignatureSha256 @@ -2279,8 +2352,10 @@ if ($PromptSubmit -eq '0') { git switch -d upstream/master if ($LASTEXITCODE -eq '0') { # Make sure path exists and is valid before hashing - if ($script:LocaleManifestPath -and (Test-Path -Path $script:LocaleManifestPath)) { $UniqueBranchID = $(Get-FileHash $script:LocaleManifestPath).Hash[0..6] -Join '' } - else { $UniqueBranchID = 'DEL' } + $UniqueBranchID = '' + if ($script:LocaleManifestPath -and (Test-Path -Path $script:LocaleManifestPath)) { $UniqueBranchID = $UniqueBranchID + $($(Get-FileHash $script:LocaleManifestPath).Hash[0..6] -Join '') } + if ($script:InstallerManifestPath -and (Test-Path -Path $script:InstallerManifestPath)) { $UniqueBranchID = $UniqueBranchID + $($(Get-FileHash $script:InstallerManifestPath).Hash[0..6] -Join '') } + if (Test-String -IsNull $UniqueBranchID) { $UniqueBranchID = 'DEL' } $BranchName = "$PackageIdentifier-$PackageVersion-$UniqueBranchID" # Git branch names cannot start with `.` cannot contain any of {`..`, `\`, `~`, `^`, `:`, ` `, `?`, `@{`, `[`}, and cannot end with {`/`, `.lock`, `.`} $BranchName = $BranchName -replace '[\~,\^,\:,\\,\?,\@\{,\*,\[,\s]{1,}|[.lock|/|\.]*$|^\.{1,}|\.\.', ''