From 3edaf824f3f27838400df0e48e24d515d180dfcd Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Sat, 18 Apr 2026 14:16:49 -0500 Subject: [PATCH 1/9] Add support for Manifests with No Installers --- .../latest/manifest.defaultLocale.latest.json | 6 +- .../latest/manifest.installer.latest.json | 59 +- .../latest/manifest.locale.latest.json | 6 +- .../latest/manifest.singleton.latest.json | 59 +- .../latest/manifest.version.latest.json | 6 +- .../manifest.defaultLocale.1.28.0.json | 280 ++++ .../v1.28.0/manifest.installer.1.28.0.json | 999 ++++++++++++++ .../v1.28.0/manifest.locale.1.28.0.json | 271 ++++ .../v1.28.0/manifest.singleton.1.28.0.json | 1228 +++++++++++++++++ .../v1.28.0/manifest.version.1.28.0.json | 46 + src/AppInstallerCLICore/Resources.h | 3 + .../Workflows/InstallFlow.cpp | 14 + .../Workflows/ShowFlow.cpp | 6 +- .../Shared/Strings/en-us/winget.resw | 11 + src/AppInstallerCLITests/InstallFlow.cpp | 46 + .../ManifestComparator.cpp | 58 + src/AppInstallerCLITests/ShowFlow.cpp | 19 + .../TestData/InstallFlowTest_NoInstaller.yaml | 16 + ...InstallFlowTest_NoInstaller_NoMessage.yaml | 15 + .../Manifest-Bad-NoInstaller-WithUrl.yaml | 18 + ...RootUnavailableMessage-NotNoInstaller.yaml | 20 + ...Bad-UnavailableMessage-NotNoInstaller.yaml | 19 + ...od-NoInstaller-RootUnavailableMessage.yaml | 19 + .../TestData/Manifest-Good-NoInstaller.yaml | 22 + .../TestData/ManifestV1_29-Singleton.yaml | 209 +++ ...ManifestV1_29-MultiFile-DefaultLocale.yaml | 41 + .../ManifestV1_29-MultiFile-Installer.yaml | 241 ++++ .../ManifestV1_29-MultiFile-Locale.yaml | 40 + .../ManifestV1_29-MultiFile-Version.yaml | 7 + src/AppInstallerCLITests/YamlManifest.cpp | 77 ++ .../Manifest/ManifestCommon.cpp | 12 +- .../Manifest/ManifestComparator.cpp | 33 + .../Manifest/ManifestSchemaValidation.cpp | 12 +- .../Manifest/ManifestValidation.cpp | 19 + .../Manifest/ManifestYamlPopulator.cpp | 10 + .../Manifest/YamlWriter.cpp | 2 + .../Public/winget/ManifestCommon.h | 4 + .../Public/winget/ManifestInstaller.h | 2 + .../Public/AppInstallerErrors.h | 1 + src/ManifestSchema/ManifestSchema.h | 6 + src/ManifestSchema/ManifestSchema.rc | 16 +- .../Manifest/ManifestVersion.cs | 5 + 42 files changed, 3947 insertions(+), 36 deletions(-) create mode 100644 schemas/JSON/manifests/v1.28.0/manifest.defaultLocale.1.28.0.json create mode 100644 schemas/JSON/manifests/v1.28.0/manifest.installer.1.28.0.json create mode 100644 schemas/JSON/manifests/v1.28.0/manifest.locale.1.28.0.json create mode 100644 schemas/JSON/manifests/v1.28.0/manifest.singleton.1.28.0.json create mode 100644 schemas/JSON/manifests/v1.28.0/manifest.version.1.28.0.json create mode 100644 src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml create mode 100644 src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller_NoMessage.yaml create mode 100644 src/AppInstallerCLITests/TestData/Manifest-Bad-NoInstaller-WithUrl.yaml create mode 100644 src/AppInstallerCLITests/TestData/Manifest-Bad-RootUnavailableMessage-NotNoInstaller.yaml create mode 100644 src/AppInstallerCLITests/TestData/Manifest-Bad-UnavailableMessage-NotNoInstaller.yaml create mode 100644 src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootUnavailableMessage.yaml create mode 100644 src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml create mode 100644 src/AppInstallerCLITests/TestData/ManifestV1_29-Singleton.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-DefaultLocale.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Installer.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Locale.yaml create mode 100644 src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Version.yaml diff --git a/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json b/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json index dd9e967b82..5d6b2a3c29 100644 --- a/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json +++ b/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.defaultlocale.1.28.0.schema.json", + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.29.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.28.0", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.29.0", "definitions": { "Url": { "type": [ "string", "null" ], @@ -261,7 +261,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.28.0", + "default": "1.29.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/schemas/JSON/manifests/latest/manifest.installer.latest.json b/schemas/JSON/manifests/latest/manifest.installer.latest.json index 5442e77897..091e7742cc 100644 --- a/schemas/JSON/manifests/latest/manifest.installer.latest.json +++ b/schemas/JSON/manifests/latest/manifest.installer.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.installer.1.28.0.schema.json", + "$id": "https://aka.ms/winget-manifest.installer.1.29.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.28.0", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.29.0", "definitions": { "PackageIdentifier": { "type": "string", @@ -66,7 +66,8 @@ "burn", "pwa", "portable", - "font" + "font", + "noinstaller" ], "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" }, @@ -840,13 +841,29 @@ }, "DesiredStateConfiguration": { "$ref": "#/definitions/DesiredStateConfiguration" + }, + "UnavailableMessage": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "A message to display when the installer is not available (only valid for noinstaller type)" } }, "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] + "Architecture" + ], + "if": { + "properties": { + "InstallerType": { "not": { "const": "noinstaller" } } + }, + "required": ["InstallerType"] + }, + "then": { + "required": [ + "InstallerUrl", + "InstallerSha256" + ] + } } }, "type": "object", @@ -968,6 +985,12 @@ "DesiredStateConfiguration": { "$ref": "#/definitions/DesiredStateConfiguration" }, + "UnavailableMessage": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "A message to display when the installer is not available (only valid for noinstaller type)" + }, "Installers": { "type": "array", "items": { @@ -984,7 +1007,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.28.0", + "default": "1.29.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } @@ -995,5 +1018,23 @@ "Installers", "ManifestType", "ManifestVersion" - ] + ], + "if": { + "not": { + "properties": { + "InstallerType": { "const": "noinstaller" } + }, + "required": ["InstallerType"] + } + }, + "then": { + "properties": { + "Installers": { + "items": { + "if": { "not": { "required": ["InstallerType"] } }, + "then": { "required": ["InstallerUrl", "InstallerSha256"] } + } + } + } + } } diff --git a/schemas/JSON/manifests/latest/manifest.locale.latest.json b/schemas/JSON/manifests/latest/manifest.locale.latest.json index e381fb7381..01ee829966 100644 --- a/schemas/JSON/manifests/latest/manifest.locale.latest.json +++ b/schemas/JSON/manifests/latest/manifest.locale.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.locale.1.28.0.schema.json", + "$id": "https://aka.ms/winget-manifest.locale.1.29.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.28.0", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.29.0", "definitions": { "Url": { "type": [ "string", "null" ], @@ -256,7 +256,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.28.0", + "default": "1.29.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/schemas/JSON/manifests/latest/manifest.singleton.latest.json b/schemas/JSON/manifests/latest/manifest.singleton.latest.json index 413f523c22..5e753fe178 100644 --- a/schemas/JSON/manifests/latest/manifest.singleton.latest.json +++ b/schemas/JSON/manifests/latest/manifest.singleton.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.singleton.1.28.0.schema.json", + "$id": "https://aka.ms/winget-manifest.singleton.1.29.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app in the OWC. v1.28.0", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.29.0", "definitions": { "PackageIdentifier": { "type": "string", @@ -168,7 +168,8 @@ "burn", "pwa", "portable", - "font" + "font", + "noinstaller" ], "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" }, @@ -941,13 +942,29 @@ }, "DesiredStateConfiguration": { "$ref": "#/definitions/DesiredStateConfiguration" + }, + "UnavailableMessage": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "A message to display when the installer is not available (only valid for noinstaller type)" } }, "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] + "Architecture" + ], + "if": { + "properties": { + "InstallerType": { "not": { "const": "noinstaller" } } + }, + "required": ["InstallerType"] + }, + "then": { + "required": [ + "InstallerUrl", + "InstallerSha256" + ] + } } }, "type": "object", @@ -1192,6 +1209,12 @@ "DesiredStateConfiguration": { "$ref": "#/definitions/DesiredStateConfiguration" }, + "UnavailableMessage": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "A message to display when the installer is not available (only valid for noinstaller type)" + }, "Installers": { "type": "array", "items": { @@ -1208,7 +1231,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.28.0", + "default": "1.29.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } @@ -1224,5 +1247,23 @@ "Installers", "ManifestType", "ManifestVersion" - ] + ], + "if": { + "not": { + "properties": { + "InstallerType": { "const": "noinstaller" } + }, + "required": ["InstallerType"] + } + }, + "then": { + "properties": { + "Installers": { + "items": { + "if": { "not": { "required": ["InstallerType"] } }, + "then": { "required": ["InstallerUrl", "InstallerSha256"] } + } + } + } + } } diff --git a/schemas/JSON/manifests/latest/manifest.version.latest.json b/schemas/JSON/manifests/latest/manifest.version.latest.json index 7d36364257..73383654f5 100644 --- a/schemas/JSON/manifests/latest/manifest.version.latest.json +++ b/schemas/JSON/manifests/latest/manifest.version.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.version.1.28.0.schema.json", + "$id": "https://aka.ms/winget-manifest.version.1.29.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.28.0", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.29.0", "type": "object", "properties": { "PackageIdentifier": { @@ -31,7 +31,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.28.0", + "default": "1.29.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/schemas/JSON/manifests/v1.28.0/manifest.defaultLocale.1.28.0.json b/schemas/JSON/manifests/v1.28.0/manifest.defaultLocale.1.28.0.json new file mode 100644 index 0000000000..dd9e967b82 --- /dev/null +++ b/schemas/JSON/manifests/v1.28.0/manifest.defaultLocale.1.28.0.json @@ -0,0 +1,280 @@ +{ + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.28.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.28.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "defaultLocale", + "const": "defaultLocale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.28.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.28.0/manifest.installer.1.28.0.json b/schemas/JSON/manifests/v1.28.0/manifest.installer.1.28.0.json new file mode 100644 index 0000000000..5442e77897 --- /dev/null +++ b/schemas/JSON/manifests/v1.28.0/manifest.installer.1.28.0.json @@ -0,0 +1,999 @@ +{ + "$id": "https://aka.ms/winget-manifest.installer.1.28.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.28.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The installer meta-data locale" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Url type" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable", + "font" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable", + "font" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + }, + "Repair": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The 'Repair' value must be passed to the installer, ModifyPath ARP command, or uninstaller ARP command when the user opts for a repair." + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious", + "deny" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "DownloadCommandProhibited": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." + }, + "RepairBehavior": { + "type": [ "string", "null" ], + "enum": [ + "modify", + "uninstaller", + "installer" + ], + "description": "The repair method" + }, + "ArchiveBinariesDependOnPath": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the install location should be added directly to the PATH environment variable. Only applies to an archive containing portable packages." + }, + "Authentication": { + "type": [ "object", "null" ], + "properties": { + "AuthenticationType": { + "type": "string", + "enum": [ + "none", + "microsoftEntraId", + "microsoftEntraIdForAzureBlobStorage" + ], + "description": "The authentication type" + }, + "MicrosoftEntraIdAuthenticationInfo": { + "type": [ "object", "null" ], + "properties": { + "Resource": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The resource value for Microsoft Entra Id authentication." + }, + "Scope": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The scope value for Microsoft Entra Id authentication." + } + }, + "description": "The Microsoft Entra Id authentication info" + } + }, + "required": [ + "AuthenticationType" + ], + "description": "The authentication requirement for downloading the installer." + }, + "DesiredStateConfiguration": { + "type": [ "object", "null" ], + "description": "References to desired state configuration (DSC) resources that are related to the package.", + "properties": { + "PowerShell": { + "type": [ "array", "null" ], + "description": "Contains data about DSC resources that are contained in PowerShell modules.", + "uniqueItems": true, + "maxItems": 16, + "items": { + "type": "object", + "title": "PowerShell DSC Module Item", + "properties": { + "RepositoryUrl": { + "$ref": "#/definitions/Url" + }, + "ModuleName": { + "type": "string", + "description": "The name of the module containing resources.", + "$comment": "From nuget package id, although PowerShell convention is slightly more strict: https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu1017", + "pattern": "^\\w+([.-]\\w+)*$", + "maxLength": 100 + }, + "Resources": { + "type": "array", + "description": "The resources contained within the module.", + "maxItems": 64, + "items": { + "type": "object", + "title": "PowerShell DSC Resource Item", + "properties": { + "Name": { + "type": "string", + "description": "The name of the resource.", + "$comment": "Needs to be an identifier in the various languages (MOF, PS class name), could not find any direct description.", + "pattern": "^[A-Za-z][-_A-Za-z0-9]*$", + "maxLength": 100 + } + } + } + } + }, + "required": [ "RepositoryUrl", "ModuleName", "Resources" ] + } + }, + "DSCv3": { + "type": [ "object", "null" ], + "description": "Contains data about DSC resources that are contained in the package using the DSC v3 specification.", + "properties": { + "Resources": { + "type": "array", + "description": "The resources contained within the package.", + "maxItems": 128, + "items": { + "type": "object", + "title": "DSCv3 Resource Item", + "properties": { + "Type": { + "type": "string", + "description": "The name of the resource.", + "$comment": "Pulled from DSCv3 definition; matches `Publisher.Product.Component/ResourceName` where the Product and Component are optional.", + "pattern": "^\\w+(\\.\\w+){0,2}\\/\\w+$", + "maxLength": 256 + } + } + } + } + }, + "required": [ "Resources" ] + } + } + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + }, + "ArchiveBinariesDependOnPath": { + "$ref": "#/definitions/ArchiveBinariesDependOnPath" + }, + "Authentication": { + "$ref": "#/definitions/Authentication" + }, + "DesiredStateConfiguration": { + "$ref": "#/definitions/DesiredStateConfiguration" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + }, + "ArchiveBinariesDependOnPath": { + "$ref": "#/definitions/ArchiveBinariesDependOnPath" + }, + "Authentication": { + "$ref": "#/definitions/Authentication" + }, + "DesiredStateConfiguration": { + "$ref": "#/definitions/DesiredStateConfiguration" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "installer", + "const": "installer", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.28.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.28.0/manifest.locale.1.28.0.json b/schemas/JSON/manifests/v1.28.0/manifest.locale.1.28.0.json new file mode 100644 index 0000000000..e381fb7381 --- /dev/null +++ b/schemas/JSON/manifests/v1.28.0/manifest.locale.1.28.0.json @@ -0,0 +1,271 @@ +{ + "$id": "https://aka.ms/winget-manifest.locale.1.28.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.28.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "locale", + "const": "locale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.28.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.28.0/manifest.singleton.1.28.0.json b/schemas/JSON/manifests/v1.28.0/manifest.singleton.1.28.0.json new file mode 100644 index 0000000000..413f523c22 --- /dev/null +++ b/schemas/JSON/manifests/v1.28.0/manifest.singleton.1.28.0.json @@ -0,0 +1,1228 @@ +{ + "$id": "https://aka.ms/winget-manifest.singleton.1.28.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.28.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable", + "font" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable", + "font" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + }, + "Repair": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The 'Repair' value must be passed to the installer, ModifyPath ARP command, or uninstaller ARP command when the user opts for a repair" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious", + "deny" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "DownloadCommandProhibited": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." + }, + "RepairBehavior": { + "type": [ "string", "null" ], + "enum": [ + "modify", + "uninstaller", + "installer" + ], + "description": "The repair method" + }, + "ArchiveBinariesDependOnPath": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the install location should be added directly to the PATH environment variable. Only applies to an archive containing portable packages." + }, + "Authentication": { + "type": [ "object", "null" ], + "properties": { + "AuthenticationType": { + "type": "string", + "enum": [ + "none", + "microsoftEntraId", + "microsoftEntraIdForAzureBlobStorage" + ], + "description": "The authentication type" + }, + "MicrosoftEntraIdAuthenticationInfo": { + "type": [ "object", "null" ], + "properties": { + "Resource": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The resource value for Microsoft Entra Id authentication." + }, + "Scope": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The scope value for Microsoft Entra Id authentication." + } + }, + "description": "The Microsoft Entra Id authentication info" + } + }, + "required": [ + "AuthenticationType" + ], + "description": "The authentication requirement for downloading the installer." + }, + "DesiredStateConfiguration": { + "type": [ "object", "null" ], + "description": "References to desired state configuration (DSC) resources that are related to the package.", + "properties": { + "PowerShell": { + "type": [ "array", "null" ], + "description": "Contains data about DSC resources that are contained in PowerShell modules.", + "uniqueItems": true, + "maxItems": 16, + "items": { + "type": "object", + "title": "PowerShell DSC Module Item", + "properties": { + "RepositoryUrl": { + "$ref": "#/definitions/Url" + }, + "ModuleName": { + "type": "string", + "description": "The name of the module containing resources.", + "$comment": "From nuget package id, although PowerShell convention is slightly more strict: https://learn.microsoft.com/en-us/nuget/reference/errors-and-warnings/nu1017", + "pattern": "^\\w+([.-]\\w+)*$", + "maxLength": 100 + }, + "Resources": { + "type": "array", + "description": "The resources contained within the module.", + "maxItems": 64, + "items": { + "type": "object", + "title": "PowerShell DSC Resource Item", + "properties": { + "Name": { + "type": "string", + "description": "The name of the resource.", + "$comment": "Needs to be an identifier in the various languages (MOF, PS class name), could not find any direct description.", + "pattern": "^[A-Za-z][-_A-Za-z0-9]*$", + "maxLength": 100 + } + } + } + } + }, + "required": [ "RepositoryUrl", "ModuleName", "Resources" ] + } + }, + "DSCv3": { + "type": [ "object", "null" ], + "description": "Contains data about DSC resources that are contained in the package using the DSC v3 specification.", + "properties": { + "Resources": { + "type": "array", + "description": "The resources contained within the package.", + "maxItems": 128, + "items": { + "type": "object", + "title": "DSCv3 Resource Item", + "properties": { + "Type": { + "type": "string", + "description": "The name of the resource.", + "$comment": "Pulled from DSCv3 definition; matches `Publisher.Product.Component/ResourceName` where the Product and Component are optional.", + "pattern": "^\\w+(\\.\\w+){0,2}\\/\\w+$", + "maxLength": 256 + } + } + } + } + }, + "required": [ "Resources" ] + } + } + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + }, + "ArchiveBinariesDependOnPath": { + "$ref": "#/definitions/ArchiveBinariesDependOnPath" + }, + "Authentication": { + "$ref": "#/definitions/Authentication" + }, + "DesiredStateConfiguration": { + "$ref": "#/definitions/DesiredStateConfiguration" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "PackageLocale": { + "$ref": "#/definitions/Locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + }, + "ArchiveBinariesDependOnPath": { + "$ref": "#/definitions/ArchiveBinariesDependOnPath" + }, + "Authentication": { + "$ref": "#/definitions/Authentication" + }, + "DesiredStateConfiguration": { + "$ref": "#/definitions/DesiredStateConfiguration" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1 + }, + "ManifestType": { + "type": "string", + "default": "singleton", + "const": "singleton", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.28.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.28.0/manifest.version.1.28.0.json b/schemas/JSON/manifests/v1.28.0/manifest.version.1.28.0.json new file mode 100644 index 0000000000..7d36364257 --- /dev/null +++ b/schemas/JSON/manifests/v1.28.0/manifest.version.1.28.0.json @@ -0,0 +1,46 @@ +{ + "$id": "https://aka.ms/winget-manifest.version.1.28.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.28.0", + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "DefaultLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The default package meta-data locale" + }, + "ManifestType": { + "type": "string", + "default": "version", + "const": "version", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.28.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "DefaultLocale", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index b35f9b3dc9..f771be622f 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -396,6 +396,8 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverrideRequired); WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashVerified); WINGET_DEFINE_RESOURCE_STRINGID(InstallerLogAvailable); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerNotAvailable); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerNotAvailableDefaultMessage); WINGET_DEFINE_RESOURCE_STRINGID(InstallerProhibitsElevation); WINGET_DEFINE_RESOURCE_STRINGID(InstallerRequiresInstallLocation); WINGET_DEFINE_RESOURCE_STRINGID(InstallersAbortTerminal); @@ -665,6 +667,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerSha256); WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerType); WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelUnavailableMessage); WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicense); WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicenseUrl); WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelMoniker); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index e52234a139..446a8c316f 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -320,6 +320,20 @@ namespace AppInstaller::CLI::Workflow AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); } + if (installer->EffectiveInstallerType() == InstallerTypeEnum::NoInstaller) + { + if (!installer->UnavailableMessage.empty()) + { + context.Reporter.Error() << installer->UnavailableMessage << std::endl; + } + else + { + context.Reporter.Error() << Resource::String::InstallerNotAvailableDefaultMessage << std::endl; + } + context.Reporter.Error() << Resource::String::InstallerNotAvailable << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_NOT_AVAILABLE); + } + context << EnsureSupportForDownload << EnsureSupportForInstall; diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index eb872b2110..1d1b42c713 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -154,11 +154,15 @@ namespace AppInstaller::CLI::Workflow } ShowSingleLineField(info, Resource::String::ShowLabelInstallerType, shownInstallerType, true); ShowSingleLineField(info, Resource::String::ShowLabelInstallerLocale, installer->Locale, true); - ShowSingleLineField(info, Resource::String::ShowLabelInstallerUrl, installer->Url, true); + if (installer->EffectiveInstallerType() != Manifest::InstallerTypeEnum::NoInstaller) + { + ShowSingleLineField(info, Resource::String::ShowLabelInstallerUrl, installer->Url, true); + } ShowSingleLineField(info, Resource::String::ShowLabelInstallerSha256, (installer->Sha256.empty()) ? "" : Utility::SHA256::ConvertToString(installer->Sha256), true); ShowSingleLineField(info, Resource::String::ShowLabelInstallerProductId, installer->ProductId, true); ShowSingleLineField(info, Resource::String::ShowLabelInstallerReleaseDate, installer->ReleaseDate, true); ShowSingleLineField(info, Resource::String::ShowLabelInstallerOfflineDistributionSupported, Utility::ConvertBoolToString(!installer->DownloadCommandProhibited), true); + ShowSingleLineField(info, Resource::String::ShowLabelUnavailableMessage, installer->UnavailableMessage, true); const auto& dependencies = installer->Dependencies; diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index b562e3022a..7b2ac3bd19 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1137,6 +1137,9 @@ Do you agree to the terms? Installer Url: + + Unavailable Message: + License: @@ -1339,6 +1342,14 @@ Do you agree to the terms? Installer log is available at: {0} {Locked="{0}"} Message displayed to inform the user about the system path of a diagnostic files containing information about the installer. {0} is a placeholder replaced by the diagnostic file system path. + + The installer for this package is no longer available. + Error message displayed when a package's installer has been intentionally withdrawn and no download URL is present. + + + The installer for this version is no longer available. + Default message shown to the user when an installer is unavailable and no custom message was provided in the manifest. + The following packages were found among the working sources. Please specify one of them using the --source option to proceed. diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp index 64fb767bbc..3b5dc2c39c 100644 --- a/src/AppInstallerCLITests/InstallFlow.cpp +++ b/src/AppInstallerCLITests/InstallFlow.cpp @@ -1440,3 +1440,49 @@ TEST_CASE("InstallFlow_InstallWithReboot", "[InstallFlow][workflow][reboot]") REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); } } + +TEST_CASE("InstallFlow_NoInstaller_WithUnavailableMessage", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_NoInstaller.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify termination with the NoInstaller error code + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INSTALLER_NOT_AVAILABLE); + + // Verify the custom UnavailableMessage is shown + REQUIRE(installOutput.str().find("Contact vendor for installer") != std::string::npos); + + // Verify installer was not executed + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); +} + +TEST_CASE("InstallFlow_NoInstaller_DefaultMessage", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_NoInstaller_NoMessage.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify termination with the NoInstaller error code + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INSTALLER_NOT_AVAILABLE); + + // Verify the default unavailable message is shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerNotAvailableDefaultMessage).get()) != std::string::npos); + + // Verify installer was not executed + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); +} diff --git a/src/AppInstallerCLITests/ManifestComparator.cpp b/src/AppInstallerCLITests/ManifestComparator.cpp index 986af6ac48..06f7263c70 100644 --- a/src/AppInstallerCLITests/ManifestComparator.cpp +++ b/src/AppInstallerCLITests/ManifestComparator.cpp @@ -225,6 +225,21 @@ TEST_CASE("ManifestComparator_InstalledTypeFilter", "[manifest_comparator]") RequireInstaller(result, msix); RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledType }); } + SECTION("NoInstaller With MSI Installed") + { + Manifest noInstallerManifest; + ManifestInstaller noInstaller = AddInstaller(noInstallerManifest, Architecture::Neutral, InstallerTypeEnum::NoInstaller); + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msi); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(noInstallerManifest); + + // NoInstaller should always pass the InstalledType filter regardless of what is installed + RequireInstaller(result, noInstaller); + REQUIRE(inapplicabilities.size() == 0); + } } TEST_CASE("ManifestComparator_InstalledTypeCompare", "[manifest_comparator]") @@ -950,3 +965,46 @@ TEST_CASE("ManifestComparator_InstallerCompatibilitySet_Weaker_Than_Architecture RequireInstaller(result, target); } + +TEST_CASE("ManifestComparator_NoInstallerLast", "[manifest_comparator]") +{ + SECTION("NoInstaller ranked below real installer") + { + Manifest manifest; + ManifestInstaller exe = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe); + ManifestInstaller noInstaller = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::NoInstaller); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + // Exe should be preferred over NoInstaller + RequireInstaller(result, exe); + } + SECTION("NoInstaller selected when only option") + { + Manifest manifest; + ManifestInstaller noInstaller = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::NoInstaller); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + // NoInstaller should still be selected as the only available option + RequireInstaller(result, noInstaller); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("NoInstaller ranked below real installer with installed type metadata") + { + Manifest manifest; + ManifestInstaller msi = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi); + ManifestInstaller noInstaller = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::NoInstaller); + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msi); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + // Msi should be preferred over NoInstaller even when MSI is the installed type + RequireInstaller(result, msi); + } +} diff --git a/src/AppInstallerCLITests/ShowFlow.cpp b/src/AppInstallerCLITests/ShowFlow.cpp index f354fe2692..bed12ac29a 100644 --- a/src/AppInstallerCLITests/ShowFlow.cpp +++ b/src/AppInstallerCLITests/ShowFlow.cpp @@ -102,3 +102,22 @@ TEST_CASE("ShowFlow_NestedInstallerType", "[ShowFlow][workflow]") REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelInstallerType)) != std::string::npos); REQUIRE(showOutput.str().find("exe (zip)") != std::string::npos); } + +TEST_CASE("ShowFlow_NoInstaller_UnavailableMessage", "[ShowFlow][workflow]") +{ + std::ostringstream showOutput; + TestContext context{ showOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_NoInstaller.yaml").GetPath().u8string()); + + ShowCommand show({}); + show.Execute(context); + INFO(showOutput.str()); + + // Verify InstallerUrl label is NOT shown for NoInstaller type + REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelInstallerUrl)) == std::string::npos); + + // Verify UnavailableMessage label and value are shown + REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelUnavailableMessage)) != std::string::npos); + REQUIRE(showOutput.str().find("Contact vendor for installer") != std::string::npos); +} diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml new file mode 100644 index 0000000000..15fe989283 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml @@ -0,0 +1,16 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestNoInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +Publisher: Microsoft Corporation +PackageName: AppInstaller Test NoInstaller +License: Test +ShortDescription: AppInstaller Test NoInstaller + +Installers: + - Architecture: x64 + InstallerType: noinstaller + UnavailableMessage: Contact vendor for installer +ManifestType: singleton +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller_NoMessage.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller_NoMessage.yaml new file mode 100644 index 0000000000..e72ab2d6f4 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller_NoMessage.yaml @@ -0,0 +1,15 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestNoInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +Publisher: Microsoft Corporation +PackageName: AppInstaller Test NoInstaller +License: Test +ShortDescription: AppInstaller Test NoInstaller + +Installers: + - Architecture: x64 + InstallerType: noinstaller +ManifestType: singleton +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-NoInstaller-WithUrl.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-NoInstaller-WithUrl.yaml new file mode 100644 index 0000000000..a905cba50c --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-NoInstaller-WithUrl.yaml @@ -0,0 +1,18 @@ +# Bad manifest. InstallerType noinstaller must not have InstallerUrl. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PackageName: MSIX SDK +License: MIT License +ShortDescription: This is MSIX SDK + +Installers: + - Architecture: x64 + InstallerType: noinstaller + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 +ManifestType: singleton +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-RootUnavailableMessage-NotNoInstaller.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-RootUnavailableMessage-NotNoInstaller.yaml new file mode 100644 index 0000000000..5ed6927a8f --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-RootUnavailableMessage-NotNoInstaller.yaml @@ -0,0 +1,20 @@ +# Bad manifest. UnavailableMessage is only valid for noinstaller type. +# This manifest has InstallerType: exe at root with UnavailableMessage at root — invalid. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PackageName: MSIX SDK +License: MIT License +ShortDescription: This is MSIX SDK +InstallerType: exe +UnavailableMessage: This should not be allowed for exe type. + +Installers: + - Architecture: x64 + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 +ManifestType: singleton +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-UnavailableMessage-NotNoInstaller.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-UnavailableMessage-NotNoInstaller.yaml new file mode 100644 index 0000000000..ae5fd5dc65 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-UnavailableMessage-NotNoInstaller.yaml @@ -0,0 +1,19 @@ +# Bad manifest. UnavailableMessage is only valid for InstallerType noinstaller. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PackageName: MSIX SDK +License: MIT License +ShortDescription: This is MSIX SDK + +Installers: + - Architecture: x64 + InstallerType: exe + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + UnavailableMessage: This is not allowed on non-noinstaller types. +ManifestType: singleton +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootUnavailableMessage.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootUnavailableMessage.yaml new file mode 100644 index 0000000000..546aa44c39 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootUnavailableMessage.yaml @@ -0,0 +1,19 @@ +# Good manifest. InstallerType: noinstaller and UnavailableMessage are at the root level +# and inherited by all installer entries. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PackageName: MSIX SDK +License: MIT License +ShortDescription: This is MSIX SDK +InstallerType: noinstaller +UnavailableMessage: This software has been discontinued by the publisher. + +Installers: + - Architecture: x64 + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 +ManifestType: singleton +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml new file mode 100644 index 0000000000..82cef7d86c --- /dev/null +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml @@ -0,0 +1,22 @@ +# Good manifest. InstallerType noinstaller is valid with optional UnavailableMessage and no InstallerUrl required. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PackageName: MSIX SDK +License: MIT License +ShortDescription: This is MSIX SDK + +Installers: + - Architecture: x64 + InstallerType: noinstaller + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + ProductCode: '{ABCDEF12-1234-1234-1234-ABCDEF123456}' + AppsAndFeaturesEntries: + - ProductCode: '{ABCDEF12-1234-1234-1234-ABCDEF123456}' + UpgradeCode: '{FEDCBA98-8765-8765-8765-FEDCBA987654}' + UnavailableMessage: This software has been discontinued by the publisher. +ManifestType: singleton +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_29-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_29-Singleton.yaml new file mode 100644 index 0000000000..848fa15452 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/ManifestV1_29-Singleton.yaml @@ -0,0 +1,209 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "msixsdk" +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://testIcon-en-US + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade + Repair: /repair +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +RepairBehavior: modify +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - US +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" +DownloadCommandProhibited: true +ArchiveBinariesDependOnPath: true +DesiredStateConfiguration: + DSCv3: + Resources: + - Type: Microsoft.WinGet/AdminSettings + - Type: Microsoft.WinGet/Package + - Type: Microsoft.WinGet/Source + - Type: Microsoft.WinGet/UserSettingsFile + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + Repair: /r + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedArguments: + - location + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com + DownloadCommandProhibited: false + ArchiveBinariesDependOnPath: false +ManifestType: singleton +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-DefaultLocale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-DefaultLocale.yaml new file mode 100644 index 0000000000..691b691294 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-DefaultLocale.yaml @@ -0,0 +1,41 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.29.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: "Default installation notes" +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "msixsdk" +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://testIcon-en-US + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +ManifestType: defaultLocale +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Installer.yaml new file mode 100644 index 0000000000..41055bb1a4 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Installer.yaml @@ -0,0 +1,241 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.29.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade + Repair: /repair +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +RepairBehavior: modify +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - "US" +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" +DownloadCommandProhibited: true +ArchiveBinariesDependOnPath: true +DesiredStateConfiguration: + DSCv3: + Resources: + - Type: Microsoft.WinGet/AdminSettings + - Type: Microsoft.WinGet/Package + - Type: Microsoft.WinGet/Source + - Type: Microsoft.WinGet/UserSettingsFile + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + Repair: /r + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com + UnsupportedArguments: + - location + DownloadCommandProhibited: false + ArchiveBinariesDependOnPath: false + - Architecture: x64 + InstallerType: exe + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + ProductCode: "{Bar}" + InstallerSwitches: + Repair: /r + UpgradeBehavior: deny + RepairBehavior: uninstaller + DesiredStateConfiguration: + DSCv3: + Resources: + - Type: None/None + - Architecture: x86 + InstallerType: portable + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + DisplayInstallWarnings: false + Commands: + - standalone + ExpectedReturnCodes: + - InstallerReturnCode: 11 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com + DesiredStateConfiguration: + DSCv3: + - Architecture: x64 + InstallerType: zip + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + NestedInstallerType: portable + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath1 + PortableCommandAlias: portableAlias1 + - RelativeFilePath: relativeFilePath2 + PortableCommandAlias: portableAlias2 + InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp2" + Files: + - RelativeFilePath: "main2.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: other + InvocationParameter: "/arg2" + DisplayName: "DisplayName2" + ArchiveBinariesDependOnPath: true + - Architecture: x64 + InstallerType: burn + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + ProductCode: "{Bar}" + UpgradeBehavior: deny + RepairBehavior: modify + - Architecture: neutral + InstallerType: zip + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + NestedInstallerType: font + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath1.otf + - RelativeFilePath: relativeFilePath2.ttf + - RelativeFilePath: relativeFilePath3.fnt + - RelativeFilePath: relativeFilePath4.ttc + - RelativeFilePath: relativeFilePath5.otc + - Architecture: neutral + InstallerType: font + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 +ManifestType: installer +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Locale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Locale.yaml new file mode 100644 index 0000000000..105c276e49 --- /dev/null +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Locale.yaml @@ -0,0 +1,40 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.locale.1.29.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-GB +Publisher: Microsoft UK +PublisherUrl: https://www.microsoft.com/UK +PublisherSupportUrl: https://www.microsoft.com/support/UK +PrivacyUrl: https://www.microsoft.com/privacy/UK +Author: Microsoft UK +PackageName: MSIX SDK UK +PackageUrl: https://www.microsoft.com/msixsdk/home/UK +License: MIT License UK +LicenseUrl: https://www.microsoft.com/msixsdk/license/UK +Copyright: Copyright Microsoft Corporation UK +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK +ShortDescription: This is MSIX SDK UK +Description: The MSIX SDK project is an effort to enable developers UK +Tags: + - "appxsdkUK" + - "msixsdkUK" +ReleaseNotes: Release notes +ReleaseNotesUrl: https://ReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes +Agreements: + - AgreementLabel: Label + Agreement: Text + AgreementUrl: https://AgreementUrl.net +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://localeTestIcon-en-GB + IconFileType: png + IconResolution: 32x32 + IconTheme: light + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321 +ManifestType: locale +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Version.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Version.yaml new file mode 100644 index 0000000000..17ea29c9ad --- /dev/null +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Version.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.29.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +DefaultLocale: en-US +ManifestType: version +ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 7574007329..830b60d34d 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -842,6 +842,8 @@ TEST_CASE("ReadGoodManifests", "[ManifestValidation]") { "Manifest-Good-Switches.yaml" }, { "Manifest-Good-DefaultExpectedReturnCodeInInstallerSuccessCodes.yaml" }, { "Manifest-Good-InstallerTypeZip-PortableExe.yaml" }, + { "Manifest-Good-NoInstaller.yaml" }, + { "Manifest-Good-NoInstaller-RootUnavailableMessage.yaml" }, { "Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml" }, }; @@ -917,6 +919,9 @@ TEST_CASE("ReadBadManifests", "[ManifestValidation]") { "Manifest-Bad-ApproximateVersionInArpVersion.yaml", "Approximate version not allowed. [DisplayVersion]" }, { "Manifest-Bad-InstallerTypeZip-PortableNotExe.yaml", "The file type of the referenced file is not allowed. [RelativeFilePath] Value: ScriptedApplication.bat" }, { "Manifest-Bad-InstallerTypeZip-PortableNotExe_Root.yaml", "The file type of the referenced file is not allowed. [RelativeFilePath] Value: ScriptedApplication.bat" }, + { "Manifest-Bad-NoInstaller-WithUrl.yaml", "Field is not supported. [InstallerUrl]" }, + { "Manifest-Bad-UnavailableMessage-NotNoInstaller.yaml", "Field is not supported. [UnavailableMessage]" }, + { "Manifest-Bad-RootUnavailableMessage-NotNoInstaller.yaml", "Field is not supported. [UnavailableMessage]" }, }; for (auto const& testCase : TestCases) @@ -1038,6 +1043,7 @@ WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_9) WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_10) WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_12) WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_28) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_29) void WriteSingletonManifestAndVerifyContents(const std::vector& singleton, const std::vector& multiFiles, std::string_view version) { @@ -1090,6 +1096,7 @@ WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_9) WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_10) WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_12) WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_28) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_29) // Since Authentication is not supported in community repo and will cause manifest validation failure, // we are not adding Authentication in v1_10 manifests. Instead a separate test is created for Authentication. @@ -1189,6 +1196,76 @@ TEST_CASE("ReadWriteValidateV1_28ManifestWithPowerShellDSC", "[ManifestCreation] RequireContainerInfoPresent(exportedManifest.Installers[0].DesiredStateConfiguration, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); } +TEST_CASE("ReadWriteValidateV1_29ManifestWithNoInstaller", "[ManifestCreation][ManifestVersionCreation]") +{ + // Read manifest + TempDirectory testDirectory{ "TestManifest" }; + CopyTestDataFilesToFolder({ "Manifest-Good-NoInstaller.yaml" }, testDirectory); + Manifest testManifest = YamlParser::CreateFromPath(testDirectory); + + // Validate schema + ManifestValidateOption validateOption; + validateOption.SchemaValidationOnly = true; + validateOption.ThrowOnWarning = true; + YamlParser::CreateFromPath(testDirectory, validateOption); + + // Verify content + REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_29 }); + REQUIRE(testManifest.Installers.size() == 1); + REQUIRE(testManifest.Installers[0].BaseInstallerType == InstallerTypeEnum::NoInstaller); + REQUIRE(testManifest.Installers[0].EffectiveInstallerType() == InstallerTypeEnum::NoInstaller); + REQUIRE(testManifest.Installers[0].UnavailableMessage == "This software has been discontinued by the publisher."); + REQUIRE(!testManifest.Installers[0].Sha256.empty()); + REQUIRE(testManifest.Installers[0].ProductCode == "{ABCDEF12-1234-1234-1234-ABCDEF123456}"); + REQUIRE(testManifest.Installers[0].AppsAndFeaturesEntries.size() == 1); + REQUIRE(testManifest.Installers[0].AppsAndFeaturesEntries[0].ProductCode == "{ABCDEF12-1234-1234-1234-ABCDEF123456}"); + + // Manifest validation should succeed + auto errors = ValidateManifest(testManifest, true); + REQUIRE(errors.empty()); + + // Write manifest + TempDirectory exportedDirectory{ "ExportedManifest" }; + std::filesystem::path exportedManifestPath = exportedDirectory.GetPath() / "ExportedManifest.yaml"; + YamlWriter::OutputYamlFile(testManifest, testManifest.Installers[0], exportedManifestPath); + + // Read back and validate content round-trips correctly + REQUIRE(std::filesystem::exists(exportedManifestPath)); + Manifest exportedManifest = YamlParser::CreateFromPath(exportedDirectory); + REQUIRE(exportedManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_29 }); + REQUIRE(exportedManifest.Installers.size() == 1); + REQUIRE(exportedManifest.Installers[0].BaseInstallerType == InstallerTypeEnum::NoInstaller); + REQUIRE(exportedManifest.Installers[0].UnavailableMessage == "This software has been discontinued by the publisher."); + REQUIRE(exportedManifest.Installers[0].ProductCode == "{ABCDEF12-1234-1234-1234-ABCDEF123456}"); + REQUIRE(exportedManifest.Installers[0].AppsAndFeaturesEntries.size() == 1); + REQUIRE(exportedManifest.Installers[0].AppsAndFeaturesEntries[0].ProductCode == "{ABCDEF12-1234-1234-1234-ABCDEF123456}"); +} + +TEST_CASE("ReadValidateV1_29ManifestWithRootUnavailableMessage", "[ManifestCreation][ManifestVersionCreation]") +{ + // Read singleton manifest with InstallerType and UnavailableMessage at root level + TempDirectory testDirectory{ "TestManifest" }; + CopyTestDataFilesToFolder({ "Manifest-Good-NoInstaller-RootUnavailableMessage.yaml" }, testDirectory); + Manifest testManifest = YamlParser::CreateFromPath(testDirectory); + + // Validate schema + ManifestValidateOption validateOption; + validateOption.SchemaValidationOnly = true; + validateOption.ThrowOnWarning = true; + YamlParser::CreateFromPath(testDirectory, validateOption); + + // Verify root-level InstallerType and UnavailableMessage were inherited by the installer entry + REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_29 }); + REQUIRE(testManifest.Installers.size() == 1); + REQUIRE(testManifest.Installers[0].BaseInstallerType == InstallerTypeEnum::NoInstaller); + REQUIRE(testManifest.Installers[0].EffectiveInstallerType() == InstallerTypeEnum::NoInstaller); + REQUIRE(testManifest.Installers[0].UnavailableMessage == "This software has been discontinued by the publisher."); + + // Manifest validation should succeed + auto errors = ValidateManifest(testManifest, true); + REQUIRE(errors.empty()); +} + TEST_CASE("WriteManifestWithMultipleLocale", "[ManifestCreation]") { Manifest multiLocaleManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-MultiLocale.yaml")); diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp index 18fb24e5fa..b98418844e 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -166,6 +166,10 @@ namespace AppInstaller::Manifest { result = InstallerTypeEnum::Font; } + else if (inStrLower == "noinstaller") + { + result = InstallerTypeEnum::NoInstaller; + } return result; } @@ -589,6 +593,8 @@ namespace AppInstaller::Manifest return "portable"sv; case InstallerTypeEnum::Font: return "font"sv; + case InstallerTypeEnum::NoInstaller: + return "noinstaller"sv; } return "unknown"sv; @@ -907,7 +913,8 @@ namespace AppInstaller::Manifest installerType == InstallerTypeEnum::Nullsoft || installerType == InstallerTypeEnum::Wix || installerType == InstallerTypeEnum::Burn || - installerType == InstallerTypeEnum::Portable + installerType == InstallerTypeEnum::Portable || + installerType == InstallerTypeEnum::NoInstaller ); } @@ -920,7 +927,8 @@ namespace AppInstaller::Manifest installerType == InstallerTypeEnum::Nullsoft || installerType == InstallerTypeEnum::Wix || installerType == InstallerTypeEnum::Burn || - installerType == InstallerTypeEnum::Portable + installerType == InstallerTypeEnum::Portable || + installerType == InstallerTypeEnum::NoInstaller ); } diff --git a/src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp index fc4ddd09e4..9ea8cab41c 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp @@ -351,6 +351,11 @@ namespace AppInstaller::Manifest InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override { + // NoInstaller is always applicable for type compatibility; it never blocks on installed type. + if (installer.EffectiveInstallerType() == InstallerTypeEnum::NoInstaller) + { + return InapplicabilityFlags::None; + } return IsInstallerCompatibleWith(installer, m_installedType) ? InapplicabilityFlags::None : InapplicabilityFlags::InstalledType; } @@ -731,6 +736,32 @@ namespace AppInstaller::Manifest Manifest::string_t m_market; }; + + // Ranks NoInstaller below any real installer type so that a real installer is always preferred. + struct NoInstallerLastComparator : public details::ComparisonField + { + NoInstallerLastComparator() : details::ComparisonField("NoInstaller Last") {} + + InapplicabilityFlags IsApplicable(const ManifestInstaller&) override + { + return InapplicabilityFlags::None; + } + + std::string ExplainInapplicable(const ManifestInstaller&) override { return {}; } + + details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override + { + bool firstIsNoInstaller = (first.EffectiveInstallerType() == InstallerTypeEnum::NoInstaller); + bool secondIsNoInstaller = (second.EffectiveInstallerType() == InstallerTypeEnum::NoInstaller); + + if (!firstIsNoInstaller && secondIsNoInstaller) + { + return details::ComparisonResult::StrongPositive; + } + + return details::ComparisonResult::Negative; + } + }; } ManifestComparator::ManifestComparator(const Options& options) @@ -777,6 +808,8 @@ namespace AppInstaller::Manifest // Only applies when preference exists: // Weak if first is in preference list and second is not AddComparator(InstallerTypeComparator::Create(options)); + // Strong if first is a real installer and second is NoInstaller; ensures NoInstaller is only selected as a last resort. + AddComparator(std::make_unique()); } InstallerAndInapplicabilities ManifestComparator::GetPreferredInstaller(const Manifest& manifest) diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp index c3821093c0..ea97c30ab0 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -272,7 +272,17 @@ namespace AppInstaller::Manifest::YamlParser int idx = MANIFESTSCHEMA_NO_RESOURCE; std::map resourceMap; - if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_28 }) + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_29 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_29_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_29_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_29_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_29_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_29_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_28 }) { resourceMap = { { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_28_SINGLETON }, diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp index edb076e4ff..a72a446b01 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -249,6 +249,20 @@ namespace AppInstaller::Manifest resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "ProductId"); } } + else if (installer.EffectiveInstallerType() == InstallerTypeEnum::NoInstaller) + { + // For NoInstaller type, InstallerUrl must not be present and InstallerSha256 is optional. + // The installer is retained for package correlation; installation is intentionally blocked. + if (!installer.Url.empty()) + { + resultErrors.emplace_back(ManifestError::FieldNotSupported, "InstallerUrl"); + } + // ProductId should not be used + if (!installer.ProductId.empty()) + { + resultErrors.emplace_back(ManifestError::FieldNotSupported, "ProductId"); + } + } else { // For other types, Url and Sha256 are required @@ -265,6 +279,11 @@ namespace AppInstaller::Manifest { resultErrors.emplace_back(ManifestError::FieldNotSupported, "ProductId"); } + // UnavailableMessage is only valid for NoInstaller type + if (!installer.UnavailableMessage.empty()) + { + resultErrors.emplace_back(ManifestError::FieldNotSupported, "UnavailableMessage"); + } // Ensure that each URL has a one to one mapping with a Sha256 and // warn if a Sha256 has a one to many mapping with a URL diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index ad02015068..67bc155247 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -414,6 +414,16 @@ namespace AppInstaller::Manifest std::move(fields_v1_28.begin(), fields_v1_28.end(), std::inserter(result, result.end())); } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_29 }) + { + std::vector fields_v1_29 = + { + { "UnavailableMessage", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->UnavailableMessage = value.as(); return {}; } }, + }; + + std::move(fields_v1_29.begin(), fields_v1_29.end(), std::inserter(result, result.end())); + } } return result; diff --git a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp index eb12738e01..c5a1cc5aa8 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp @@ -81,6 +81,7 @@ namespace AppInstaller::Manifest::YamlWriter constexpr std::string_view DesiredStateConfigurationPowerShellResourceName = "Name"sv; constexpr std::string_view DesiredStateConfigurationDSCv3 = "DSCv3"sv; constexpr std::string_view DesiredStateConfigurationDSCv3ResourceType = "Type"sv; + constexpr std::string_view UnavailableMessage = "UnavailableMessage"sv; // Installer switches constexpr std::string_view InstallerSwitches = "InstallerSwitches"sv; @@ -724,6 +725,7 @@ namespace AppInstaller::Manifest::YamlWriter ProcessUnsupportedOSArchitecture(out, installer.UnsupportedOSArchitectures); ProcessAuthentication(out, installer.AuthInfo); ProcessDesiredStateConfiguration(out, installer.DesiredStateConfiguration); + WRITE_PROPERTY_IF_EXISTS(out, UnavailableMessage, installer.UnavailableMessage); } void ProcessInstaller(YAML::Emitter& out, const ManifestInstaller& installer) diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index f97a21f34c..028c786ec1 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -57,6 +57,9 @@ namespace AppInstaller::Manifest // V1.28 manifest version constexpr std::string_view s_ManifestVersionV1_28 = "1.28.0"sv; + // V1.29 manifest version + constexpr std::string_view s_ManifestVersionV1_29 = "1.29.0"sv; + // Any new manifest version must also be added to src\WinGetUtilInterop\Manifest\ManifestVersion.cs. // The manifest extension for the MS Store @@ -115,6 +118,7 @@ namespace AppInstaller::Manifest MSStore, Portable, Font, + NoInstaller, }; enum class UpdateBehaviorEnum diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index cc0f3a7f4b..94e4eba6c8 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -122,5 +122,7 @@ namespace AppInstaller::Manifest Authentication::AuthenticationInfo AuthInfo; std::vector DesiredStateConfiguration; + + string_t UnavailableMessage; }; } diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h index 6cbd652bc6..2a91e29403 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -186,6 +186,7 @@ #define APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED ((HRESULT)0x8A150113) #define APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED ((HRESULT)0x8A150114) #define APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR ((HRESULT)0x8A150115) +#define APPINSTALLER_CLI_ERROR_INSTALLER_NOT_AVAILABLE ((HRESULT)0x8A150116) // Status values for check package installed status results. // Partial success has the success bit(first bit) set to 0. diff --git a/src/ManifestSchema/ManifestSchema.h b/src/ManifestSchema/ManifestSchema.h index 5949ba6413..f3fd1999e7 100644 --- a/src/ManifestSchema/ManifestSchema.h +++ b/src/ManifestSchema/ManifestSchema.h @@ -75,6 +75,12 @@ #define IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE 255 #define IDX_MANIFEST_SCHEMA_V1_28_LOCALE 256 +#define IDX_MANIFEST_SCHEMA_V1_29_SINGLETON 257 +#define IDX_MANIFEST_SCHEMA_V1_29_VERSION 258 +#define IDX_MANIFEST_SCHEMA_V1_29_INSTALLER 259 +#define IDX_MANIFEST_SCHEMA_V1_29_DEFAULTLOCALE 260 +#define IDX_MANIFEST_SCHEMA_V1_29_LOCALE 261 + // Packages schema starts at 300 // Certificates start at 400 // If we get to 300, either move the others or skip over them diff --git a/src/ManifestSchema/ManifestSchema.rc b/src/ManifestSchema/ManifestSchema.rc index 5973c89dd3..0fc4ec73c8 100644 --- a/src/ManifestSchema/ManifestSchema.rc +++ b/src/ManifestSchema/ManifestSchema.rc @@ -125,8 +125,14 @@ IDX_MANIFEST_SCHEMA_V1_12_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\ IDX_MANIFEST_SCHEMA_V1_12_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.defaultLocale.1.12.0.json" IDX_MANIFEST_SCHEMA_V1_12_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.locale.1.12.0.json" -IDX_MANIFEST_SCHEMA_V1_28_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.singleton.latest.json" -IDX_MANIFEST_SCHEMA_V1_28_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.version.latest.json" -IDX_MANIFEST_SCHEMA_V1_28_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.installer.latest.json" -IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.defaultLocale.latest.json" -IDX_MANIFEST_SCHEMA_V1_28_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.locale.latest.json" +IDX_MANIFEST_SCHEMA_V1_28_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.28.0\\manifest.singleton.1.28.0.json" +IDX_MANIFEST_SCHEMA_V1_28_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.28.0\\manifest.version.1.28.0.json" +IDX_MANIFEST_SCHEMA_V1_28_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.28.0\\manifest.installer.1.28.0.json" +IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.28.0\\manifest.defaultLocale.1.28.0.json" +IDX_MANIFEST_SCHEMA_V1_28_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.28.0\\manifest.locale.1.28.0.json" + +IDX_MANIFEST_SCHEMA_V1_29_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.singleton.latest.json" +IDX_MANIFEST_SCHEMA_V1_29_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.version.latest.json" +IDX_MANIFEST_SCHEMA_V1_29_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.installer.latest.json" +IDX_MANIFEST_SCHEMA_V1_29_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.defaultLocale.latest.json" +IDX_MANIFEST_SCHEMA_V1_29_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.locale.latest.json" diff --git a/src/WinGetUtilInterop/Manifest/ManifestVersion.cs b/src/WinGetUtilInterop/Manifest/ManifestVersion.cs index eb09b369fe..896854a360 100644 --- a/src/WinGetUtilInterop/Manifest/ManifestVersion.cs +++ b/src/WinGetUtilInterop/Manifest/ManifestVersion.cs @@ -68,6 +68,11 @@ public static class ManifestVersion /// public const string ManifestVersionV1_28 = "1.28.0"; + /// + /// V1.29 manifest version. + /// + public const string ManifestVersionV1_29 = "1.29.0"; + #pragma warning restore SA1310 // Field names should not contain underscore } } From 326138ffca9c3a0059516370ef804ea9d56c01d7 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Sat, 18 Apr 2026 14:28:01 -0500 Subject: [PATCH 2/9] Ensure only single instance of message is shown --- src/AppInstallerCLICore/Resources.h | 1 - src/AppInstallerCLICore/Workflows/InstallFlow.cpp | 3 +-- src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw | 4 ---- src/AppInstallerCLITests/InstallFlow.cpp | 5 ++++- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index f771be622f..6a6bbb7ada 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -397,7 +397,6 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashVerified); WINGET_DEFINE_RESOURCE_STRINGID(InstallerLogAvailable); WINGET_DEFINE_RESOURCE_STRINGID(InstallerNotAvailable); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerNotAvailableDefaultMessage); WINGET_DEFINE_RESOURCE_STRINGID(InstallerProhibitsElevation); WINGET_DEFINE_RESOURCE_STRINGID(InstallerRequiresInstallLocation); WINGET_DEFINE_RESOURCE_STRINGID(InstallersAbortTerminal); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 446a8c316f..0d133e66b6 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -328,9 +328,8 @@ namespace AppInstaller::CLI::Workflow } else { - context.Reporter.Error() << Resource::String::InstallerNotAvailableDefaultMessage << std::endl; + context.Reporter.Error() << Resource::String::InstallerNotAvailable << std::endl; } - context.Reporter.Error() << Resource::String::InstallerNotAvailable << std::endl; AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_NOT_AVAILABLE); } diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 7b2ac3bd19..5469575812 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1346,10 +1346,6 @@ Do you agree to the terms? The installer for this package is no longer available. Error message displayed when a package's installer has been intentionally withdrawn and no download URL is present. - - The installer for this version is no longer available. - Default message shown to the user when an installer is unavailable and no custom message was provided in the manifest. - The following packages were found among the working sources. Please specify one of them using the --source option to proceed. diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp index 3b5dc2c39c..5d96183040 100644 --- a/src/AppInstallerCLITests/InstallFlow.cpp +++ b/src/AppInstallerCLITests/InstallFlow.cpp @@ -1460,6 +1460,9 @@ TEST_CASE("InstallFlow_NoInstaller_WithUnavailableMessage", "[InstallFlow][workf // Verify the custom UnavailableMessage is shown REQUIRE(installOutput.str().find("Contact vendor for installer") != std::string::npos); + // Verify the generic fallback message is NOT shown when a custom message is provided + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerNotAvailable).get()) == std::string::npos); + // Verify installer was not executed REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); } @@ -1481,7 +1484,7 @@ TEST_CASE("InstallFlow_NoInstaller_DefaultMessage", "[InstallFlow][workflow]") REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INSTALLER_NOT_AVAILABLE); // Verify the default unavailable message is shown - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerNotAvailableDefaultMessage).get()) != std::string::npos); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerNotAvailable).get()) != std::string::npos); // Verify installer was not executed REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); From 05e8e5cefecc0236c295bec79a92ab361b89e184 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Sat, 18 Apr 2026 14:30:54 -0500 Subject: [PATCH 3/9] Fix show flow indicating download is possible --- src/AppInstallerCLICore/Workflows/ShowFlow.cpp | 3 ++- src/AppInstallerCLITests/ShowFlow.cpp | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index 1d1b42c713..96d3e7eb2b 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -161,7 +161,8 @@ namespace AppInstaller::CLI::Workflow ShowSingleLineField(info, Resource::String::ShowLabelInstallerSha256, (installer->Sha256.empty()) ? "" : Utility::SHA256::ConvertToString(installer->Sha256), true); ShowSingleLineField(info, Resource::String::ShowLabelInstallerProductId, installer->ProductId, true); ShowSingleLineField(info, Resource::String::ShowLabelInstallerReleaseDate, installer->ReleaseDate, true); - ShowSingleLineField(info, Resource::String::ShowLabelInstallerOfflineDistributionSupported, Utility::ConvertBoolToString(!installer->DownloadCommandProhibited), true); + const bool offlineDistributionSupported = (installer->EffectiveInstallerType() != Manifest::InstallerTypeEnum::NoInstaller) && !installer->DownloadCommandProhibited; + ShowSingleLineField(info, Resource::String::ShowLabelInstallerOfflineDistributionSupported, Utility::ConvertBoolToString(offlineDistributionSupported), true); ShowSingleLineField(info, Resource::String::ShowLabelUnavailableMessage, installer->UnavailableMessage, true); const auto& dependencies = installer->Dependencies; diff --git a/src/AppInstallerCLITests/ShowFlow.cpp b/src/AppInstallerCLITests/ShowFlow.cpp index bed12ac29a..86cd331d02 100644 --- a/src/AppInstallerCLITests/ShowFlow.cpp +++ b/src/AppInstallerCLITests/ShowFlow.cpp @@ -117,6 +117,10 @@ TEST_CASE("ShowFlow_NoInstaller_UnavailableMessage", "[ShowFlow][workflow]") // Verify InstallerUrl label is NOT shown for NoInstaller type REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelInstallerUrl)) == std::string::npos); + // Verify offline distribution is shown as false for NoInstaller type + REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelInstallerOfflineDistributionSupported)) != std::string::npos); + REQUIRE(showOutput.str().find("false") != std::string::npos); + // Verify UnavailableMessage label and value are shown REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelUnavailableMessage)) != std::string::npos); REQUIRE(showOutput.str().find("Contact vendor for installer") != std::string::npos); From df6e0ff21a766d291897109fbeea5a8db4c13451 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Sat, 18 Apr 2026 14:40:51 -0500 Subject: [PATCH 4/9] Rename field for clarity --- .../latest/manifest.installer.latest.json | 20 +++++++++--------- .../latest/manifest.singleton.latest.json | 20 +++++++++--------- src/AppInstallerCLICore/Resources.h | 2 +- .../Workflows/InstallFlow.cpp | 4 ++-- .../Workflows/ShowFlow.cpp | 2 +- .../Shared/Strings/en-us/winget.resw | 4 ++-- .../AppInstallerCLITests.vcxproj | 21 +++++++++++++++++++ src/AppInstallerCLITests/InstallFlow.cpp | 4 ++-- src/AppInstallerCLITests/ShowFlow.cpp | 6 +++--- .../TestData/InstallFlowTest_NoInstaller.yaml | 2 +- ...erAvailabilityMessage-NotNoInstaller.yaml} | 4 ++-- ...erAvailabilityMessage-NotNoInstaller.yaml} | 6 +++--- ...ler-RootInstallerAvailabilityMessage.yaml} | 4 ++-- .../TestData/Manifest-Good-NoInstaller.yaml | 4 ++-- src/AppInstallerCLITests/YamlManifest.cpp | 19 +++++++++-------- .../Manifest/ManifestValidation.cpp | 6 +++--- .../Manifest/ManifestYamlPopulator.cpp | 2 +- .../Manifest/YamlWriter.cpp | 4 ++-- .../Public/winget/ManifestInstaller.h | 2 +- 19 files changed, 79 insertions(+), 57 deletions(-) rename src/AppInstallerCLITests/TestData/{Manifest-Bad-UnavailableMessage-NotNoInstaller.yaml => Manifest-Bad-InstallerAvailabilityMessage-NotNoInstaller.yaml} (75%) rename src/AppInstallerCLITests/TestData/{Manifest-Bad-RootUnavailableMessage-NotNoInstaller.yaml => Manifest-Bad-RootInstallerAvailabilityMessage-NotNoInstaller.yaml} (68%) rename src/AppInstallerCLITests/TestData/{Manifest-Good-NoInstaller-RootUnavailableMessage.yaml => Manifest-Good-NoInstaller-RootInstallerAvailabilityMessage.yaml} (73%) diff --git a/schemas/JSON/manifests/latest/manifest.installer.latest.json b/schemas/JSON/manifests/latest/manifest.installer.latest.json index 091e7742cc..ad9121183b 100644 --- a/schemas/JSON/manifests/latest/manifest.installer.latest.json +++ b/schemas/JSON/manifests/latest/manifest.installer.latest.json @@ -3,6 +3,12 @@ "$schema": "http://json-schema.org/draft-07/schema#", "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.29.0", "definitions": { + "InstallerAvailabilityMessage": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "A message to display when the installer is not available (only valid for noinstaller type)" + }, "PackageIdentifier": { "type": "string", "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", @@ -842,11 +848,8 @@ "DesiredStateConfiguration": { "$ref": "#/definitions/DesiredStateConfiguration" }, - "UnavailableMessage": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "A message to display when the installer is not available (only valid for noinstaller type)" + "InstallerAvailabilityMessage": { + "$ref": "#/definitions/InstallerAvailabilityMessage" } }, "required": [ @@ -985,11 +988,8 @@ "DesiredStateConfiguration": { "$ref": "#/definitions/DesiredStateConfiguration" }, - "UnavailableMessage": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "A message to display when the installer is not available (only valid for noinstaller type)" + "InstallerAvailabilityMessage": { + "$ref": "#/definitions/InstallerAvailabilityMessage" }, "Installers": { "type": "array", diff --git a/schemas/JSON/manifests/latest/manifest.singleton.latest.json b/schemas/JSON/manifests/latest/manifest.singleton.latest.json index 5e753fe178..d4a9377c96 100644 --- a/schemas/JSON/manifests/latest/manifest.singleton.latest.json +++ b/schemas/JSON/manifests/latest/manifest.singleton.latest.json @@ -3,6 +3,12 @@ "$schema": "http://json-schema.org/draft-07/schema#", "description": "A representation of a single-file manifest representing an app in the OWC. v1.29.0", "definitions": { + "InstallerAvailabilityMessage": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "A message to display when the installer is not available (only valid for noinstaller type)" + }, "PackageIdentifier": { "type": "string", "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", @@ -943,11 +949,8 @@ "DesiredStateConfiguration": { "$ref": "#/definitions/DesiredStateConfiguration" }, - "UnavailableMessage": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "A message to display when the installer is not available (only valid for noinstaller type)" + "InstallerAvailabilityMessage": { + "$ref": "#/definitions/InstallerAvailabilityMessage" } }, "required": [ @@ -1209,11 +1212,8 @@ "DesiredStateConfiguration": { "$ref": "#/definitions/DesiredStateConfiguration" }, - "UnavailableMessage": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "A message to display when the installer is not available (only valid for noinstaller type)" + "InstallerAvailabilityMessage": { + "$ref": "#/definitions/InstallerAvailabilityMessage" }, "Installers": { "type": "array", diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index 6a6bbb7ada..caee3057c0 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -666,7 +666,7 @@ namespace AppInstaller::CLI::Resource WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerSha256); WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerType); WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelUnavailableMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerAvailabilityMessage); WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicense); WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicenseUrl); WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelMoniker); diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 0d133e66b6..ff973a3abe 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -322,9 +322,9 @@ namespace AppInstaller::CLI::Workflow if (installer->EffectiveInstallerType() == InstallerTypeEnum::NoInstaller) { - if (!installer->UnavailableMessage.empty()) + if (!installer->InstallerAvailabilityMessage.empty()) { - context.Reporter.Error() << installer->UnavailableMessage << std::endl; + context.Reporter.Error() << installer->InstallerAvailabilityMessage << std::endl; } else { diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index 96d3e7eb2b..c6b5e07389 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -163,7 +163,7 @@ namespace AppInstaller::CLI::Workflow ShowSingleLineField(info, Resource::String::ShowLabelInstallerReleaseDate, installer->ReleaseDate, true); const bool offlineDistributionSupported = (installer->EffectiveInstallerType() != Manifest::InstallerTypeEnum::NoInstaller) && !installer->DownloadCommandProhibited; ShowSingleLineField(info, Resource::String::ShowLabelInstallerOfflineDistributionSupported, Utility::ConvertBoolToString(offlineDistributionSupported), true); - ShowSingleLineField(info, Resource::String::ShowLabelUnavailableMessage, installer->UnavailableMessage, true); + ShowSingleLineField(info, Resource::String::ShowLabelInstallerAvailabilityMessage, installer->InstallerAvailabilityMessage, true); const auto& dependencies = installer->Dependencies; diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 5469575812..0c36ffeeab 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1137,8 +1137,8 @@ Do you agree to the terms? Installer Url: - - Unavailable Message: + + Installer Availability Message: License: diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index ba0ced5853..00e5784c79 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -432,6 +432,27 @@ true + + true + + + true + + + true + + + true + + + true + + + true + + + true + true diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp index 5d96183040..dec3236826 100644 --- a/src/AppInstallerCLITests/InstallFlow.cpp +++ b/src/AppInstallerCLITests/InstallFlow.cpp @@ -1441,7 +1441,7 @@ TEST_CASE("InstallFlow_InstallWithReboot", "[InstallFlow][workflow][reboot]") } } -TEST_CASE("InstallFlow_NoInstaller_WithUnavailableMessage", "[InstallFlow][workflow]") +TEST_CASE("InstallFlow_NoInstaller_WithInstallerAvailabilityMessage", "[InstallFlow][workflow]") { TestCommon::TempFile installResultPath("TestExeInstalled.txt"); @@ -1457,7 +1457,7 @@ TEST_CASE("InstallFlow_NoInstaller_WithUnavailableMessage", "[InstallFlow][workf // Verify termination with the NoInstaller error code REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INSTALLER_NOT_AVAILABLE); - // Verify the custom UnavailableMessage is shown + // Verify the custom InstallerAvailabilityMessage is shown REQUIRE(installOutput.str().find("Contact vendor for installer") != std::string::npos); // Verify the generic fallback message is NOT shown when a custom message is provided diff --git a/src/AppInstallerCLITests/ShowFlow.cpp b/src/AppInstallerCLITests/ShowFlow.cpp index 86cd331d02..86a8e4796d 100644 --- a/src/AppInstallerCLITests/ShowFlow.cpp +++ b/src/AppInstallerCLITests/ShowFlow.cpp @@ -103,7 +103,7 @@ TEST_CASE("ShowFlow_NestedInstallerType", "[ShowFlow][workflow]") REQUIRE(showOutput.str().find("exe (zip)") != std::string::npos); } -TEST_CASE("ShowFlow_NoInstaller_UnavailableMessage", "[ShowFlow][workflow]") +TEST_CASE("ShowFlow_NoInstaller_InstallerAvailabilityMessage", "[ShowFlow][workflow]") { std::ostringstream showOutput; TestContext context{ showOutput, std::cin }; @@ -121,7 +121,7 @@ TEST_CASE("ShowFlow_NoInstaller_UnavailableMessage", "[ShowFlow][workflow]") REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelInstallerOfflineDistributionSupported)) != std::string::npos); REQUIRE(showOutput.str().find("false") != std::string::npos); - // Verify UnavailableMessage label and value are shown - REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelUnavailableMessage)) != std::string::npos); + // Verify InstallerAvailabilityMessage label and value are shown + REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelInstallerAvailabilityMessage)) != std::string::npos); REQUIRE(showOutput.str().find("Contact vendor for installer") != std::string::npos); } diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml index 15fe989283..1b0383f804 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml @@ -11,6 +11,6 @@ ShortDescription: AppInstaller Test NoInstaller Installers: - Architecture: x64 InstallerType: noinstaller - UnavailableMessage: Contact vendor for installer + InstallerAvailabilityMessage: Contact vendor for installer ManifestType: singleton ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-UnavailableMessage-NotNoInstaller.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerAvailabilityMessage-NotNoInstaller.yaml similarity index 75% rename from src/AppInstallerCLITests/TestData/Manifest-Bad-UnavailableMessage-NotNoInstaller.yaml rename to src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerAvailabilityMessage-NotNoInstaller.yaml index ae5fd5dc65..fcd7826631 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-UnavailableMessage-NotNoInstaller.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerAvailabilityMessage-NotNoInstaller.yaml @@ -1,4 +1,4 @@ -# Bad manifest. UnavailableMessage is only valid for InstallerType noinstaller. +# Bad manifest. InstallerAvailabilityMessage is only valid for InstallerType noinstaller. # yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json PackageIdentifier: microsoft.msixsdk @@ -14,6 +14,6 @@ Installers: InstallerType: exe InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - UnavailableMessage: This is not allowed on non-noinstaller types. + InstallerAvailabilityMessage: This is not allowed on non-noinstaller types. ManifestType: singleton ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-RootUnavailableMessage-NotNoInstaller.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-RootInstallerAvailabilityMessage-NotNoInstaller.yaml similarity index 68% rename from src/AppInstallerCLITests/TestData/Manifest-Bad-RootUnavailableMessage-NotNoInstaller.yaml rename to src/AppInstallerCLITests/TestData/Manifest-Bad-RootInstallerAvailabilityMessage-NotNoInstaller.yaml index 5ed6927a8f..f0d7074cf8 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-RootUnavailableMessage-NotNoInstaller.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-RootInstallerAvailabilityMessage-NotNoInstaller.yaml @@ -1,5 +1,5 @@ -# Bad manifest. UnavailableMessage is only valid for noinstaller type. -# This manifest has InstallerType: exe at root with UnavailableMessage at root — invalid. +# Bad manifest. InstallerAvailabilityMessage is only valid for noinstaller type. +# This manifest has InstallerType: exe at root with InstallerAvailabilityMessage at root — invalid. # yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json PackageIdentifier: microsoft.msixsdk @@ -10,7 +10,7 @@ PackageName: MSIX SDK License: MIT License ShortDescription: This is MSIX SDK InstallerType: exe -UnavailableMessage: This should not be allowed for exe type. +InstallerAvailabilityMessage: This should not be allowed for exe type. Installers: - Architecture: x64 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootUnavailableMessage.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootInstallerAvailabilityMessage.yaml similarity index 73% rename from src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootUnavailableMessage.yaml rename to src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootInstallerAvailabilityMessage.yaml index 546aa44c39..3bc83e76fe 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootUnavailableMessage.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootInstallerAvailabilityMessage.yaml @@ -1,4 +1,4 @@ -# Good manifest. InstallerType: noinstaller and UnavailableMessage are at the root level +# Good manifest. InstallerType: noinstaller and InstallerAvailabilityMessage are at the root level # and inherited by all installer entries. # yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json @@ -10,7 +10,7 @@ PackageName: MSIX SDK License: MIT License ShortDescription: This is MSIX SDK InstallerType: noinstaller -UnavailableMessage: This software has been discontinued by the publisher. +InstallerAvailabilityMessage: This software has been discontinued by the publisher. Installers: - Architecture: x64 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml index 82cef7d86c..8d8e6ae72c 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml @@ -1,4 +1,4 @@ -# Good manifest. InstallerType noinstaller is valid with optional UnavailableMessage and no InstallerUrl required. +# Good manifest. InstallerType noinstaller is valid with optional InstallerAvailabilityMessage and no InstallerUrl required. # yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json PackageIdentifier: microsoft.msixsdk @@ -17,6 +17,6 @@ Installers: AppsAndFeaturesEntries: - ProductCode: '{ABCDEF12-1234-1234-1234-ABCDEF123456}' UpgradeCode: '{FEDCBA98-8765-8765-8765-FEDCBA987654}' - UnavailableMessage: This software has been discontinued by the publisher. + InstallerAvailabilityMessage: This software has been discontinued by the publisher. ManifestType: singleton ManifestVersion: 1.29.0 diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 830b60d34d..7e704befe6 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -843,6 +843,7 @@ TEST_CASE("ReadGoodManifests", "[ManifestValidation]") { "Manifest-Good-DefaultExpectedReturnCodeInInstallerSuccessCodes.yaml" }, { "Manifest-Good-InstallerTypeZip-PortableExe.yaml" }, { "Manifest-Good-NoInstaller.yaml" }, + { "Manifest-Good-NoInstaller-RootInstallerAvailabilityMessage.yaml" }, { "Manifest-Good-NoInstaller-RootUnavailableMessage.yaml" }, { "Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml" }, }; @@ -920,8 +921,8 @@ TEST_CASE("ReadBadManifests", "[ManifestValidation]") { "Manifest-Bad-InstallerTypeZip-PortableNotExe.yaml", "The file type of the referenced file is not allowed. [RelativeFilePath] Value: ScriptedApplication.bat" }, { "Manifest-Bad-InstallerTypeZip-PortableNotExe_Root.yaml", "The file type of the referenced file is not allowed. [RelativeFilePath] Value: ScriptedApplication.bat" }, { "Manifest-Bad-NoInstaller-WithUrl.yaml", "Field is not supported. [InstallerUrl]" }, - { "Manifest-Bad-UnavailableMessage-NotNoInstaller.yaml", "Field is not supported. [UnavailableMessage]" }, - { "Manifest-Bad-RootUnavailableMessage-NotNoInstaller.yaml", "Field is not supported. [UnavailableMessage]" }, + { "Manifest-Bad-InstallerAvailabilityMessage-NotNoInstaller.yaml", "Field is not supported. [InstallerAvailabilityMessage]" }, + { "Manifest-Bad-RootInstallerAvailabilityMessage-NotNoInstaller.yaml", "Field is not supported. [InstallerAvailabilityMessage]" }, }; for (auto const& testCase : TestCases) @@ -1214,7 +1215,7 @@ TEST_CASE("ReadWriteValidateV1_29ManifestWithNoInstaller", "[ManifestCreation][M REQUIRE(testManifest.Installers.size() == 1); REQUIRE(testManifest.Installers[0].BaseInstallerType == InstallerTypeEnum::NoInstaller); REQUIRE(testManifest.Installers[0].EffectiveInstallerType() == InstallerTypeEnum::NoInstaller); - REQUIRE(testManifest.Installers[0].UnavailableMessage == "This software has been discontinued by the publisher."); + REQUIRE(testManifest.Installers[0].InstallerAvailabilityMessage == "This software has been discontinued by the publisher."); REQUIRE(!testManifest.Installers[0].Sha256.empty()); REQUIRE(testManifest.Installers[0].ProductCode == "{ABCDEF12-1234-1234-1234-ABCDEF123456}"); REQUIRE(testManifest.Installers[0].AppsAndFeaturesEntries.size() == 1); @@ -1235,17 +1236,17 @@ TEST_CASE("ReadWriteValidateV1_29ManifestWithNoInstaller", "[ManifestCreation][M REQUIRE(exportedManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_29 }); REQUIRE(exportedManifest.Installers.size() == 1); REQUIRE(exportedManifest.Installers[0].BaseInstallerType == InstallerTypeEnum::NoInstaller); - REQUIRE(exportedManifest.Installers[0].UnavailableMessage == "This software has been discontinued by the publisher."); + REQUIRE(exportedManifest.Installers[0].InstallerAvailabilityMessage == "This software has been discontinued by the publisher."); REQUIRE(exportedManifest.Installers[0].ProductCode == "{ABCDEF12-1234-1234-1234-ABCDEF123456}"); REQUIRE(exportedManifest.Installers[0].AppsAndFeaturesEntries.size() == 1); REQUIRE(exportedManifest.Installers[0].AppsAndFeaturesEntries[0].ProductCode == "{ABCDEF12-1234-1234-1234-ABCDEF123456}"); } -TEST_CASE("ReadValidateV1_29ManifestWithRootUnavailableMessage", "[ManifestCreation][ManifestVersionCreation]") +TEST_CASE("ReadValidateV1_29ManifestWithRootInstallerAvailabilityMessage", "[ManifestCreation][ManifestVersionCreation]") { - // Read singleton manifest with InstallerType and UnavailableMessage at root level + // Read singleton manifest with InstallerType and InstallerAvailabilityMessage at root level TempDirectory testDirectory{ "TestManifest" }; - CopyTestDataFilesToFolder({ "Manifest-Good-NoInstaller-RootUnavailableMessage.yaml" }, testDirectory); + CopyTestDataFilesToFolder({ "Manifest-Good-NoInstaller-RootInstallerAvailabilityMessage.yaml" }, testDirectory); Manifest testManifest = YamlParser::CreateFromPath(testDirectory); // Validate schema @@ -1254,12 +1255,12 @@ TEST_CASE("ReadValidateV1_29ManifestWithRootUnavailableMessage", "[ManifestCreat validateOption.ThrowOnWarning = true; YamlParser::CreateFromPath(testDirectory, validateOption); - // Verify root-level InstallerType and UnavailableMessage were inherited by the installer entry + // Verify root-level InstallerType and InstallerAvailabilityMessage were inherited by the installer entry REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_29 }); REQUIRE(testManifest.Installers.size() == 1); REQUIRE(testManifest.Installers[0].BaseInstallerType == InstallerTypeEnum::NoInstaller); REQUIRE(testManifest.Installers[0].EffectiveInstallerType() == InstallerTypeEnum::NoInstaller); - REQUIRE(testManifest.Installers[0].UnavailableMessage == "This software has been discontinued by the publisher."); + REQUIRE(testManifest.Installers[0].InstallerAvailabilityMessage == "This software has been discontinued by the publisher."); // Manifest validation should succeed auto errors = ValidateManifest(testManifest, true); diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp index a72a446b01..3163bf317c 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -279,10 +279,10 @@ namespace AppInstaller::Manifest { resultErrors.emplace_back(ManifestError::FieldNotSupported, "ProductId"); } - // UnavailableMessage is only valid for NoInstaller type - if (!installer.UnavailableMessage.empty()) + // InstallerAvailabilityMessage is only valid for NoInstaller type + if (!installer.InstallerAvailabilityMessage.empty()) { - resultErrors.emplace_back(ManifestError::FieldNotSupported, "UnavailableMessage"); + resultErrors.emplace_back(ManifestError::FieldNotSupported, "InstallerAvailabilityMessage"); } // Ensure that each URL has a one to one mapping with a Sha256 and diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 67bc155247..f42dd42392 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -419,7 +419,7 @@ namespace AppInstaller::Manifest { std::vector fields_v1_29 = { - { "UnavailableMessage", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->UnavailableMessage = value.as(); return {}; } }, + { "InstallerAvailabilityMessage", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->InstallerAvailabilityMessage = value.as(); return {}; } }, }; std::move(fields_v1_29.begin(), fields_v1_29.end(), std::inserter(result, result.end())); diff --git a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp index c5a1cc5aa8..546ee7a53e 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp @@ -81,7 +81,7 @@ namespace AppInstaller::Manifest::YamlWriter constexpr std::string_view DesiredStateConfigurationPowerShellResourceName = "Name"sv; constexpr std::string_view DesiredStateConfigurationDSCv3 = "DSCv3"sv; constexpr std::string_view DesiredStateConfigurationDSCv3ResourceType = "Type"sv; - constexpr std::string_view UnavailableMessage = "UnavailableMessage"sv; + constexpr std::string_view InstallerAvailabilityMessage = "InstallerAvailabilityMessage"sv; // Installer switches constexpr std::string_view InstallerSwitches = "InstallerSwitches"sv; @@ -725,7 +725,7 @@ namespace AppInstaller::Manifest::YamlWriter ProcessUnsupportedOSArchitecture(out, installer.UnsupportedOSArchitectures); ProcessAuthentication(out, installer.AuthInfo); ProcessDesiredStateConfiguration(out, installer.DesiredStateConfiguration); - WRITE_PROPERTY_IF_EXISTS(out, UnavailableMessage, installer.UnavailableMessage); + WRITE_PROPERTY_IF_EXISTS(out, InstallerAvailabilityMessage, installer.InstallerAvailabilityMessage); } void ProcessInstaller(YAML::Emitter& out, const ManifestInstaller& installer) diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index 94e4eba6c8..d6c05ccc96 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -123,6 +123,6 @@ namespace AppInstaller::Manifest std::vector DesiredStateConfiguration; - string_t UnavailableMessage; + string_t InstallerAvailabilityMessage; }; } From 38957146bd5791958645243114ab9c1ca80d9563 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Sat, 18 Apr 2026 14:56:33 -0500 Subject: [PATCH 5/9] Update release notes --- doc/ReleaseNotes.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index 57c970aeb6..94de30a946 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -1,6 +1,16 @@ -## New in v1.29 +## New in v1.30 + +# New Feature: NoInstaller manifest type + +Manifests can now declare `InstallerType: noinstaller` (manifest version `1.29.0`). This type is intended for software a publisher no longer offers as a direct download, allowing winget to continue correlating against the installed package without providing an installer URL. + +Key behaviours: + +- **`winget install`** — immediately stops with the package's `InstallerAvailabilityMessage` if one is set, or a default "The installer for this package is no longer available." message otherwise. The exit code is `0x8A150116` (`APPINSTALLER_CLI_ERROR_INSTALLER_NOT_AVAILABLE`). +- **`winget show`** — displays `Installer Availability Message:` instead of `Installer Url:`, and shows `Offline Distribution Supported: false`. +- **`winget upgrade`** — a real installer is always preferred over a `noinstaller` entry when both exist for the same package. A `noinstaller` entry is only selected when it is the only applicable option, at which point the install flow blocks as above. +- **Manifest fields** — `InstallerUrl` must not be present. `InstallerSha256`, `ProductCode`, and `AppsAndFeaturesEntries` are all supported for package correlation. `InstallerAvailabilityMessage` (optional, max 512 characters) may be set at root or per-installer level. -Nothing yet. ## Bug Fixes From 07617de259d6ed83530d40e0ebd8233419247a08 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Sat, 18 Apr 2026 15:10:21 -0500 Subject: [PATCH 6/9] Add missing test files --- .../AppInstallerCLITests.vcxproj | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index 00e5784c79..fedae2a6ef 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -1140,6 +1140,21 @@ true + + true + + + true + + + true + + + true + + + true + From bba0d612096825655100c1a7aa24df097f38666d Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Sat, 18 Apr 2026 15:21:09 -0500 Subject: [PATCH 7/9] Spelling --- .github/actions/spelling/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 6551758cbd..fe89740d41 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -382,6 +382,7 @@ NOAGGREGATION NOCLOSE NOCRLF NOEXPAND +noinstaller NOLINKINFO nomem NONAME From d620de5454f70d6b6a7627c5b9d8d666f3fbe200 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 17:17:36 -0500 Subject: [PATCH 8/9] Update for 1.30 with latest changes from master --- doc/ReleaseNotes.md | 2 +- .../latest/manifest.defaultLocale.latest.json | 6 +++--- .../latest/manifest.installer.latest.json | 6 +++--- .../manifests/latest/manifest.locale.latest.json | 6 +++--- .../latest/manifest.singleton.latest.json | 6 +++--- .../manifests/latest/manifest.version.latest.json | 6 +++--- .../AppInstallerCLITests.vcxproj | 10 +++++----- .../TestData/InstallFlowTest_NoInstaller.yaml | 4 ++-- .../InstallFlowTest_NoInstaller_NoMessage.yaml | 4 ++-- ...stallerAvailabilityMessage-NotNoInstaller.yaml | 4 ++-- .../Manifest-Bad-NoInstaller-WithUrl.yaml | 4 ++-- ...stallerAvailabilityMessage-NotNoInstaller.yaml | 4 ++-- ...nstaller-RootInstallerAvailabilityMessage.yaml | 4 ++-- .../TestData/Manifest-Good-NoInstaller.yaml | 4 ++-- ...ingleton.yaml => ManifestV1_30-Singleton.yaml} | 4 ++-- .../ManifestV1_30-MultiFile-DefaultLocale.yaml} | 4 ++-- .../ManifestV1_30-MultiFile-Installer.yaml} | 4 ++-- .../ManifestV1_30-MultiFile-Locale.yaml} | 4 ++-- .../ManifestV1_30-MultiFile-Version.yaml} | 4 ++-- src/AppInstallerCLITests/YamlManifest.cpp | 15 +++++++-------- .../Manifest/ManifestSchemaValidation.cpp | 12 ++++++------ .../Manifest/ManifestYamlPopulator.cpp | 6 +++--- .../Public/winget/ManifestCommon.h | 4 ++-- src/ManifestSchema/ManifestSchema.h | 10 +++++----- src/ManifestSchema/ManifestSchema.rc | 10 +++++----- src/WinGetUtilInterop/Manifest/ManifestVersion.cs | 4 ++-- 26 files changed, 75 insertions(+), 76 deletions(-) rename src/AppInstallerCLITests/TestData/{ManifestV1_29-Singleton.yaml => ManifestV1_30-Singleton.yaml} (99%) rename src/AppInstallerCLITests/TestData/{MultiFileManifestV1_29/ManifestV1_29-MultiFile-DefaultLocale.yaml => MultiFileManifestV1_30/ManifestV1_30-MultiFile-DefaultLocale.yaml} (96%) rename src/AppInstallerCLITests/TestData/{MultiFileManifestV1_29/ManifestV1_29-MultiFile-Installer.yaml => MultiFileManifestV1_30/ManifestV1_30-MultiFile-Installer.yaml} (99%) rename src/AppInstallerCLITests/TestData/{MultiFileManifestV1_29/ManifestV1_29-MultiFile-Locale.yaml => MultiFileManifestV1_30/ManifestV1_30-MultiFile-Locale.yaml} (96%) rename src/AppInstallerCLITests/TestData/{MultiFileManifestV1_29/ManifestV1_29-MultiFile-Version.yaml => MultiFileManifestV1_30/ManifestV1_30-MultiFile-Version.yaml} (77%) diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index 94de30a946..40b1a54b4e 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -2,7 +2,7 @@ # New Feature: NoInstaller manifest type -Manifests can now declare `InstallerType: noinstaller` (manifest version `1.29.0`). This type is intended for software a publisher no longer offers as a direct download, allowing winget to continue correlating against the installed package without providing an installer URL. +Manifests can now declare `InstallerType: noinstaller` (manifest version `1.30.0`). This type is intended for software a publisher no longer offers as a direct download, allowing winget to continue correlating against the installed package without providing an installer URL. Key behaviours: diff --git a/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json b/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json index 5d6b2a3c29..ed1336e8c3 100644 --- a/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json +++ b/schemas/JSON/manifests/latest/manifest.defaultLocale.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.defaultlocale.1.29.0.schema.json", + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.30.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.29.0", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.30.0", "definitions": { "Url": { "type": [ "string", "null" ], @@ -261,7 +261,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.29.0", + "default": "1.30.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/schemas/JSON/manifests/latest/manifest.installer.latest.json b/schemas/JSON/manifests/latest/manifest.installer.latest.json index ad9121183b..57b8287e6d 100644 --- a/schemas/JSON/manifests/latest/manifest.installer.latest.json +++ b/schemas/JSON/manifests/latest/manifest.installer.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.installer.1.29.0.schema.json", + "$id": "https://aka.ms/winget-manifest.installer.1.30.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.29.0", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.30.0", "definitions": { "InstallerAvailabilityMessage": { "type": "string", @@ -1007,7 +1007,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.29.0", + "default": "1.30.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/schemas/JSON/manifests/latest/manifest.locale.latest.json b/schemas/JSON/manifests/latest/manifest.locale.latest.json index 01ee829966..440e65df90 100644 --- a/schemas/JSON/manifests/latest/manifest.locale.latest.json +++ b/schemas/JSON/manifests/latest/manifest.locale.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.locale.1.29.0.schema.json", + "$id": "https://aka.ms/winget-manifest.locale.1.30.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.29.0", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.30.0", "definitions": { "Url": { "type": [ "string", "null" ], @@ -256,7 +256,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.29.0", + "default": "1.30.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/schemas/JSON/manifests/latest/manifest.singleton.latest.json b/schemas/JSON/manifests/latest/manifest.singleton.latest.json index d4a9377c96..98b1705f27 100644 --- a/schemas/JSON/manifests/latest/manifest.singleton.latest.json +++ b/schemas/JSON/manifests/latest/manifest.singleton.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.singleton.1.29.0.schema.json", + "$id": "https://aka.ms/winget-manifest.singleton.1.30.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app in the OWC. v1.29.0", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.30.0", "definitions": { "InstallerAvailabilityMessage": { "type": "string", @@ -1231,7 +1231,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.29.0", + "default": "1.30.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/schemas/JSON/manifests/latest/manifest.version.latest.json b/schemas/JSON/manifests/latest/manifest.version.latest.json index 73383654f5..3361540128 100644 --- a/schemas/JSON/manifests/latest/manifest.version.latest.json +++ b/schemas/JSON/manifests/latest/manifest.version.latest.json @@ -1,7 +1,7 @@ { - "$id": "https://aka.ms/winget-manifest.version.1.29.0.schema.json", + "$id": "https://aka.ms/winget-manifest.version.1.30.0.schema.json", "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.29.0", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.30.0", "type": "object", "properties": { "PackageIdentifier": { @@ -31,7 +31,7 @@ }, "ManifestVersion": { "type": "string", - "default": "1.29.0", + "default": "1.30.0", "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", "description": "The manifest syntax version" } diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index fedae2a6ef..7505c37f90 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -1140,19 +1140,19 @@ true - + true - + true - + true - + true - + true diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml index 1b0383f804..77bc5b0ba9 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.30.0.schema.json PackageIdentifier: AppInstallerCliTest.TestNoInstaller PackageVersion: 1.0.0.0 @@ -13,4 +13,4 @@ Installers: InstallerType: noinstaller InstallerAvailabilityMessage: Contact vendor for installer ManifestType: singleton -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller_NoMessage.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller_NoMessage.yaml index e72ab2d6f4..536c41945e 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller_NoMessage.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoInstaller_NoMessage.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.30.0.schema.json PackageIdentifier: AppInstallerCliTest.TestNoInstaller PackageVersion: 1.0.0.0 @@ -12,4 +12,4 @@ Installers: - Architecture: x64 InstallerType: noinstaller ManifestType: singleton -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerAvailabilityMessage-NotNoInstaller.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerAvailabilityMessage-NotNoInstaller.yaml index fcd7826631..b3b0295fb5 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerAvailabilityMessage-NotNoInstaller.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerAvailabilityMessage-NotNoInstaller.yaml @@ -1,5 +1,5 @@ # Bad manifest. InstallerAvailabilityMessage is only valid for InstallerType noinstaller. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.30.0.schema.json PackageIdentifier: microsoft.msixsdk PackageVersion: 1.7.32 @@ -16,4 +16,4 @@ Installers: InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 InstallerAvailabilityMessage: This is not allowed on non-noinstaller types. ManifestType: singleton -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-NoInstaller-WithUrl.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-NoInstaller-WithUrl.yaml index a905cba50c..352253f9ed 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-NoInstaller-WithUrl.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-NoInstaller-WithUrl.yaml @@ -1,5 +1,5 @@ # Bad manifest. InstallerType noinstaller must not have InstallerUrl. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.30.0.schema.json PackageIdentifier: microsoft.msixsdk PackageVersion: 1.7.32 @@ -15,4 +15,4 @@ Installers: InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 ManifestType: singleton -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-RootInstallerAvailabilityMessage-NotNoInstaller.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-RootInstallerAvailabilityMessage-NotNoInstaller.yaml index f0d7074cf8..164bdb5ea2 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-RootInstallerAvailabilityMessage-NotNoInstaller.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-RootInstallerAvailabilityMessage-NotNoInstaller.yaml @@ -1,6 +1,6 @@ # Bad manifest. InstallerAvailabilityMessage is only valid for noinstaller type. # This manifest has InstallerType: exe at root with InstallerAvailabilityMessage at root — invalid. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.30.0.schema.json PackageIdentifier: microsoft.msixsdk PackageVersion: 1.7.32 @@ -17,4 +17,4 @@ Installers: InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 ManifestType: singleton -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootInstallerAvailabilityMessage.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootInstallerAvailabilityMessage.yaml index 3bc83e76fe..2250b04609 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootInstallerAvailabilityMessage.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller-RootInstallerAvailabilityMessage.yaml @@ -1,6 +1,6 @@ # Good manifest. InstallerType: noinstaller and InstallerAvailabilityMessage are at the root level # and inherited by all installer entries. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.30.0.schema.json PackageIdentifier: microsoft.msixsdk PackageVersion: 1.7.32 @@ -16,4 +16,4 @@ Installers: - Architecture: x64 InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 ManifestType: singleton -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml index 8d8e6ae72c..1108d0d694 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-NoInstaller.yaml @@ -1,5 +1,5 @@ # Good manifest. InstallerType noinstaller is valid with optional InstallerAvailabilityMessage and no InstallerUrl required. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.30.0.schema.json PackageIdentifier: microsoft.msixsdk PackageVersion: 1.7.32 @@ -19,4 +19,4 @@ Installers: UpgradeCode: '{FEDCBA98-8765-8765-8765-FEDCBA987654}' InstallerAvailabilityMessage: This software has been discontinued by the publisher. ManifestType: singleton -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_29-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_30-Singleton.yaml similarity index 99% rename from src/AppInstallerCLITests/TestData/ManifestV1_29-Singleton.yaml rename to src/AppInstallerCLITests/TestData/ManifestV1_30-Singleton.yaml index 848fa15452..36ab7fcd19 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_29-Singleton.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_30-Singleton.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.30.0.schema.json PackageIdentifier: microsoft.msixsdk PackageVersion: 1.7.32 @@ -206,4 +206,4 @@ Installers: DownloadCommandProhibited: false ArchiveBinariesDependOnPath: false ManifestType: singleton -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-DefaultLocale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-DefaultLocale.yaml similarity index 96% rename from src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-DefaultLocale.yaml rename to src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-DefaultLocale.yaml index 691b691294..bc2f756a6d 100644 --- a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-DefaultLocale.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-DefaultLocale.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.30.0.schema.json PackageIdentifier: microsoft.msixsdk PackageVersion: 1.7.32 @@ -38,4 +38,4 @@ Agreements: Agreement: DefaultText AgreementUrl: https://DefaultAgreementUrl.net ManifestType: defaultLocale -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-Installer.yaml similarity index 99% rename from src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Installer.yaml rename to src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-Installer.yaml index 41055bb1a4..adf2c0dc06 100644 --- a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Installer.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-Installer.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.30.0.schema.json PackageIdentifier: microsoft.msixsdk PackageVersion: 1.7.32 @@ -238,4 +238,4 @@ Installers: InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 ManifestType: installer -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Locale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-Locale.yaml similarity index 96% rename from src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Locale.yaml rename to src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-Locale.yaml index 105c276e49..b4c95c3881 100644 --- a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Locale.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-Locale.yaml @@ -1,4 +1,4 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.locale.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.locale.1.30.0.schema.json PackageIdentifier: microsoft.msixsdk PackageVersion: 1.7.32 @@ -37,4 +37,4 @@ Icons: IconTheme: light IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321 ManifestType: locale -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Version.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-Version.yaml similarity index 77% rename from src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Version.yaml rename to src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-Version.yaml index 17ea29c9ad..7816325a0b 100644 --- a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_29/ManifestV1_29-MultiFile-Version.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_30/ManifestV1_30-MultiFile-Version.yaml @@ -1,7 +1,7 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.29.0.schema.json +# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.30.0.schema.json PackageIdentifier: microsoft.msixsdk PackageVersion: 1.7.32 DefaultLocale: en-US ManifestType: version -ManifestVersion: 1.29.0 +ManifestVersion: 1.30.0 diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index 7e704befe6..19e398a693 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -844,7 +844,6 @@ TEST_CASE("ReadGoodManifests", "[ManifestValidation]") { "Manifest-Good-InstallerTypeZip-PortableExe.yaml" }, { "Manifest-Good-NoInstaller.yaml" }, { "Manifest-Good-NoInstaller-RootInstallerAvailabilityMessage.yaml" }, - { "Manifest-Good-NoInstaller-RootUnavailableMessage.yaml" }, { "Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml" }, }; @@ -1044,7 +1043,7 @@ WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_9) WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_10) WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_12) WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_28) -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_29) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_30) void WriteSingletonManifestAndVerifyContents(const std::vector& singleton, const std::vector& multiFiles, std::string_view version) { @@ -1097,7 +1096,7 @@ WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_9) WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_10) WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_12) WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_28) -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_29) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_30) // Since Authentication is not supported in community repo and will cause manifest validation failure, // we are not adding Authentication in v1_10 manifests. Instead a separate test is created for Authentication. @@ -1197,7 +1196,7 @@ TEST_CASE("ReadWriteValidateV1_28ManifestWithPowerShellDSC", "[ManifestCreation] RequireContainerInfoPresent(exportedManifest.Installers[0].DesiredStateConfiguration, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); } -TEST_CASE("ReadWriteValidateV1_29ManifestWithNoInstaller", "[ManifestCreation][ManifestVersionCreation]") +TEST_CASE("ReadWriteValidateV1_30ManifestWithNoInstaller", "[ManifestCreation][ManifestVersionCreation]") { // Read manifest TempDirectory testDirectory{ "TestManifest" }; @@ -1211,7 +1210,7 @@ TEST_CASE("ReadWriteValidateV1_29ManifestWithNoInstaller", "[ManifestCreation][M YamlParser::CreateFromPath(testDirectory, validateOption); // Verify content - REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_29 }); + REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_30 }); REQUIRE(testManifest.Installers.size() == 1); REQUIRE(testManifest.Installers[0].BaseInstallerType == InstallerTypeEnum::NoInstaller); REQUIRE(testManifest.Installers[0].EffectiveInstallerType() == InstallerTypeEnum::NoInstaller); @@ -1233,7 +1232,7 @@ TEST_CASE("ReadWriteValidateV1_29ManifestWithNoInstaller", "[ManifestCreation][M // Read back and validate content round-trips correctly REQUIRE(std::filesystem::exists(exportedManifestPath)); Manifest exportedManifest = YamlParser::CreateFromPath(exportedDirectory); - REQUIRE(exportedManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_29 }); + REQUIRE(exportedManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_30 }); REQUIRE(exportedManifest.Installers.size() == 1); REQUIRE(exportedManifest.Installers[0].BaseInstallerType == InstallerTypeEnum::NoInstaller); REQUIRE(exportedManifest.Installers[0].InstallerAvailabilityMessage == "This software has been discontinued by the publisher."); @@ -1242,7 +1241,7 @@ TEST_CASE("ReadWriteValidateV1_29ManifestWithNoInstaller", "[ManifestCreation][M REQUIRE(exportedManifest.Installers[0].AppsAndFeaturesEntries[0].ProductCode == "{ABCDEF12-1234-1234-1234-ABCDEF123456}"); } -TEST_CASE("ReadValidateV1_29ManifestWithRootInstallerAvailabilityMessage", "[ManifestCreation][ManifestVersionCreation]") +TEST_CASE("ReadValidateV1_30ManifestWithRootInstallerAvailabilityMessage", "[ManifestCreation][ManifestVersionCreation]") { // Read singleton manifest with InstallerType and InstallerAvailabilityMessage at root level TempDirectory testDirectory{ "TestManifest" }; @@ -1256,7 +1255,7 @@ TEST_CASE("ReadValidateV1_29ManifestWithRootInstallerAvailabilityMessage", "[Man YamlParser::CreateFromPath(testDirectory, validateOption); // Verify root-level InstallerType and InstallerAvailabilityMessage were inherited by the installer entry - REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_29 }); + REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_30 }); REQUIRE(testManifest.Installers.size() == 1); REQUIRE(testManifest.Installers[0].BaseInstallerType == InstallerTypeEnum::NoInstaller); REQUIRE(testManifest.Installers[0].EffectiveInstallerType() == InstallerTypeEnum::NoInstaller); diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp index ea97c30ab0..b58c2e7a32 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -272,14 +272,14 @@ namespace AppInstaller::Manifest::YamlParser int idx = MANIFESTSCHEMA_NO_RESOURCE; std::map resourceMap; - if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_29 }) + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_30 }) { resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_29_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_29_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_29_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_29_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_29_LOCALE }, + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_30_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_30_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_30_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_30_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_30_LOCALE }, }; } else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_28 }) diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index f42dd42392..1420878248 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -415,14 +415,14 @@ namespace AppInstaller::Manifest std::move(fields_v1_28.begin(), fields_v1_28.end(), std::inserter(result, result.end())); } - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_29 }) + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_30 }) { - std::vector fields_v1_29 = + std::vector fields_v1_30 = { { "InstallerAvailabilityMessage", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->InstallerAvailabilityMessage = value.as(); return {}; } }, }; - std::move(fields_v1_29.begin(), fields_v1_29.end(), std::inserter(result, result.end())); + std::move(fields_v1_30.begin(), fields_v1_30.end(), std::inserter(result, result.end())); } } diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 028c786ec1..e47e7b89eb 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -57,8 +57,8 @@ namespace AppInstaller::Manifest // V1.28 manifest version constexpr std::string_view s_ManifestVersionV1_28 = "1.28.0"sv; - // V1.29 manifest version - constexpr std::string_view s_ManifestVersionV1_29 = "1.29.0"sv; + // V1.30 manifest version + constexpr std::string_view s_ManifestVersionV1_30 = "1.30.0"sv; // Any new manifest version must also be added to src\WinGetUtilInterop\Manifest\ManifestVersion.cs. diff --git a/src/ManifestSchema/ManifestSchema.h b/src/ManifestSchema/ManifestSchema.h index f3fd1999e7..23db3c82f8 100644 --- a/src/ManifestSchema/ManifestSchema.h +++ b/src/ManifestSchema/ManifestSchema.h @@ -75,11 +75,11 @@ #define IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE 255 #define IDX_MANIFEST_SCHEMA_V1_28_LOCALE 256 -#define IDX_MANIFEST_SCHEMA_V1_29_SINGLETON 257 -#define IDX_MANIFEST_SCHEMA_V1_29_VERSION 258 -#define IDX_MANIFEST_SCHEMA_V1_29_INSTALLER 259 -#define IDX_MANIFEST_SCHEMA_V1_29_DEFAULTLOCALE 260 -#define IDX_MANIFEST_SCHEMA_V1_29_LOCALE 261 +#define IDX_MANIFEST_SCHEMA_V1_30_SINGLETON 257 +#define IDX_MANIFEST_SCHEMA_V1_30_VERSION 258 +#define IDX_MANIFEST_SCHEMA_V1_30_INSTALLER 259 +#define IDX_MANIFEST_SCHEMA_V1_30_DEFAULTLOCALE 260 +#define IDX_MANIFEST_SCHEMA_V1_30_LOCALE 261 // Packages schema starts at 300 // Certificates start at 400 diff --git a/src/ManifestSchema/ManifestSchema.rc b/src/ManifestSchema/ManifestSchema.rc index 0fc4ec73c8..ec0f137cfe 100644 --- a/src/ManifestSchema/ManifestSchema.rc +++ b/src/ManifestSchema/ManifestSchema.rc @@ -131,8 +131,8 @@ IDX_MANIFEST_SCHEMA_V1_28_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\ IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.28.0\\manifest.defaultLocale.1.28.0.json" IDX_MANIFEST_SCHEMA_V1_28_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.28.0\\manifest.locale.1.28.0.json" -IDX_MANIFEST_SCHEMA_V1_29_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.singleton.latest.json" -IDX_MANIFEST_SCHEMA_V1_29_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.version.latest.json" -IDX_MANIFEST_SCHEMA_V1_29_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.installer.latest.json" -IDX_MANIFEST_SCHEMA_V1_29_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.defaultLocale.latest.json" -IDX_MANIFEST_SCHEMA_V1_29_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.locale.latest.json" +IDX_MANIFEST_SCHEMA_V1_30_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.singleton.latest.json" +IDX_MANIFEST_SCHEMA_V1_30_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.version.latest.json" +IDX_MANIFEST_SCHEMA_V1_30_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.installer.latest.json" +IDX_MANIFEST_SCHEMA_V1_30_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.defaultLocale.latest.json" +IDX_MANIFEST_SCHEMA_V1_30_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.locale.latest.json" diff --git a/src/WinGetUtilInterop/Manifest/ManifestVersion.cs b/src/WinGetUtilInterop/Manifest/ManifestVersion.cs index 896854a360..825aa42449 100644 --- a/src/WinGetUtilInterop/Manifest/ManifestVersion.cs +++ b/src/WinGetUtilInterop/Manifest/ManifestVersion.cs @@ -69,9 +69,9 @@ public static class ManifestVersion public const string ManifestVersionV1_28 = "1.28.0"; /// - /// V1.29 manifest version. + /// V1.30 manifest version. /// - public const string ManifestVersionV1_29 = "1.29.0"; + public const string ManifestVersionV1_30 = "1.30.0"; #pragma warning restore SA1310 // Field names should not contain underscore } From 456f9b4f54f1c8191c01c1fc1f88782ab19e5b9e Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Mon, 15 Jun 2026 17:34:23 -0500 Subject: [PATCH 9/9] Rest source support --- doc/ReleaseNotes.md | 1 + src/AppInstallerCLITests/RestClient.cpp | 43 +++++++++++++++++- .../AppInstallerRepositoryCore.vcxproj | 4 ++ ...AppInstallerRepositoryCore.vcxproj.filters | 18 ++++++++ .../ManifestJSONParser.cpp | 7 ++- .../Rest/RestClient.cpp | 6 +++ .../Rest/Schema/1_30/Interface.h | 21 +++++++++ .../Schema/1_30/Json/ManifestDeserializer.h | 18 ++++++++ .../1_30/Json/ManifestDeserializer_1_30.cpp | 44 +++++++++++++++++++ .../Rest/Schema/1_30/RestInterface_1_30.cpp | 26 +++++++++++ .../Rest/Schema/CommonRestConstants.h | 1 + 11 files changed, 187 insertions(+), 2 deletions(-) create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_30/Interface.h create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_30/Json/ManifestDeserializer.h create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_30/Json/ManifestDeserializer_1_30.cpp create mode 100644 src/AppInstallerRepositoryCore/Rest/Schema/1_30/RestInterface_1_30.cpp diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index 40b1a54b4e..2bcd7c5aba 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -10,6 +10,7 @@ Key behaviours: - **`winget show`** — displays `Installer Availability Message:` instead of `Installer Url:`, and shows `Offline Distribution Supported: false`. - **`winget upgrade`** — a real installer is always preferred over a `noinstaller` entry when both exist for the same package. A `noinstaller` entry is only selected when it is the only applicable option, at which point the install flow blocks as above. - **Manifest fields** — `InstallerUrl` must not be present. `InstallerSha256`, `ProductCode`, and `AppsAndFeaturesEntries` are all supported for package correlation. `InstallerAvailabilityMessage` (optional, max 512 characters) may be set at root or per-installer level. +- **REST source contract** — REST source negotiation now supports contract version `1.30.0`, including `noinstaller` and `InstallerAvailabilityMessage` payload parsing. ## Bug Fixes diff --git a/src/AppInstallerCLITests/RestClient.cpp b/src/AppInstallerCLITests/RestClient.cpp index cb40b602a1..4e58969236 100644 --- a/src/AppInstallerCLITests/RestClient.cpp +++ b/src/AppInstallerCLITests/RestClient.cpp @@ -10,6 +10,7 @@ #include #include #include +#include using namespace AppInstaller; using namespace AppInstaller::Http; @@ -62,8 +63,11 @@ TEST_CASE("GetSupportedInterface", "[RestSource]") Version version{ "1.0.0" }; REQUIRE(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, version, {})->GetVersion() == version); + Version version_1_30{ "1.30.0" }; + REQUIRE(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, version_1_30, {})->GetVersion() == version_1_30); + // Update this test to next version so that we don't forget to add to supported versions before rest e2e tests are available. - Version invalid{ "1.29.0" }; + Version invalid{ "1.31.0" }; REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, invalid, {}), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); Authentication::AuthenticationArguments authArgs; @@ -81,6 +85,43 @@ TEST_CASE("GetSupportedInterface", "[RestSource]") REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, infoWithInvalidAuthenticationInfo, authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO); } +TEST_CASE("ManifestJSONParser_130_NoInstaller", "[RestSource]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "1.0.0", + "DefaultLocale": { + "PackageLocale": "en-US", + "PackageName": "Foo Bar", + "Publisher": "Foo Corp", + "ShortDescription": "Foo Bar package" + }, + "Installers": [ + { + "Architecture": "x64", + "InstallerType": "noinstaller", + "InstallerAvailabilityMessage": "This software has been discontinued by the publisher." + } + ] + } + ] + } + })delimiter"); + + AppInstaller::Repository::JSON::ManifestJSONParser parser{ Version{ "1.30.0" } }; + auto manifests = parser.Deserialize(web::json::value::parse(sample)); + + REQUIRE(manifests.size() == 1); + REQUIRE(manifests[0].ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.30.0" }); + REQUIRE(manifests[0].Installers.size() == 1); + REQUIRE(manifests[0].Installers[0].BaseInstallerType == AppInstaller::Manifest::InstallerTypeEnum::NoInstaller); + REQUIRE(manifests[0].Installers[0].InstallerAvailabilityMessage == "This software has been discontinued by the publisher."); +} + TEST_CASE("GetInformation_Success", "[RestSource]") { utility::string_t sample = _XPLATSTR( diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index 34cc155a46..b99fc9a275 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -380,6 +380,8 @@ + + @@ -484,6 +486,8 @@ + + diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters index 64f1b186c2..4793413d29 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -127,6 +127,12 @@ {b4c8d9e2-f3a5-6b7c-0d1e-2f3a4b5c6d7e} + + {9392efce-cc38-4f86-b79d-2648f70f72ca} + + + {bd578f93-4af6-4a7a-b95d-f88e67fbe881} + @@ -513,6 +519,12 @@ Rest\Schema\1_28\Json + + Rest\Schema\1_30 + + + Rest\Schema\1_30\Json + Rest @@ -806,6 +818,12 @@ Rest\Schema\1_28\Json + + Rest\Schema\1_30 + + + Rest\Schema\1_30\Json + Rest diff --git a/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp b/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp index 4e77e823fa..e6c177008f 100644 --- a/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp +++ b/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp @@ -12,6 +12,7 @@ #include "Rest/Schema/1_10/Json/ManifestDeserializer.h" #include "Rest/Schema/1_12/Json/ManifestDeserializer.h" #include "Rest/Schema/1_28/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_30/Json/ManifestDeserializer.h" namespace AppInstaller::Repository::JSON { @@ -66,10 +67,14 @@ namespace AppInstaller::Repository::JSON { m_pImpl->m_deserializer = std::make_unique(); } - else + else if (parts.size() > 1 && parts[1].Integer < 30) { m_pImpl->m_deserializer = std::make_unique(); } + else + { + m_pImpl->m_deserializer = std::make_unique(); + } } else { diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp index 26fd141d73..6097f9dd98 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp @@ -13,6 +13,7 @@ #include "Rest/Schema/1_10/Interface.h" #include "Rest/Schema/1_12/Interface.h" #include "Rest/Schema/1_28/Interface.h" +#include "Rest/Schema/1_30/Interface.h" #include "Rest/Schema/InformationResponseDeserializer.h" #include "Rest/Schema/CommonRestConstants.h" #include @@ -39,6 +40,7 @@ namespace AppInstaller::Repository::Rest Version_1_10_0, Version_1_12_0, Version_1_28_0, + Version_1_30_0, }; constexpr std::string_view WindowsPackageManagerHeader = "Windows-Package-Manager"sv; @@ -222,6 +224,10 @@ namespace AppInstaller::Repository::Rest { return std::make_unique(api, helper, information, additionalHeaders, authArgs); } + else if (version == Version_1_30_0) + { + return std::make_unique(api, helper, information, additionalHeaders, authArgs); + } THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_30/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_30/Interface.h new file mode 100644 index 0000000000..976cc8c4c8 --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_30/Interface.h @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_28/Interface.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_30 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public V1_28::Interface + { + Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, Authentication::AuthenticationArguments authArgs = {}); + + Interface(const Interface&) = delete; + Interface& operator=(const Interface&) = delete; + + Interface(Interface&&) = default; + Interface& operator=(Interface&&) = default; + + Utility::Version GetVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_30/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_30/Json/ManifestDeserializer.h new file mode 100644 index 0000000000..92f2b8b42f --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_30/Json/ManifestDeserializer.h @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_28/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_30::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_28::Json::ManifestDeserializer + { + protected: + std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; + + Manifest::InstallerTypeEnum ConvertToInstallerType(std::string_view in) const override; + + Manifest::ManifestVer GetManifestVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_30/Json/ManifestDeserializer_1_30.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_30/Json/ManifestDeserializer_1_30.cpp new file mode 100644 index 0000000000..74c135d06e --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_30/Json/ManifestDeserializer_1_30.cpp @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestDeserializer.h" +#include + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_30::Json +{ + namespace + { + constexpr std::string_view InstallerAvailabilityMessage = "InstallerAvailabilityMessage"sv; + } + + std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const + { + auto result = V1_28::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); + + if (result) + { + result->InstallerAvailabilityMessage = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerAvailabilityMessage)).value_or(""); + } + + return result; + } + + Manifest::InstallerTypeEnum ManifestDeserializer::ConvertToInstallerType(std::string_view in) const + { + std::string inStrLower = Utility::ToLower(in); + + if (inStrLower == "noinstaller") + { + return InstallerTypeEnum::NoInstaller; + } + + return V1_28::Json::ManifestDeserializer::ConvertToInstallerType(inStrLower); + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1_30; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_30/RestInterface_1_30.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_30/RestInterface_1_30.cpp new file mode 100644 index 0000000000..f26d46035b --- /dev/null +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_30/RestInterface_1_30.cpp @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_30/Interface.h" +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/IRestClient.h" +#include +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_30 +{ + Interface::Interface( + const std::string& restApi, + const Http::HttpClientHelper& httpClientHelper, + IRestClient::Information information, + const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders, + Authentication::AuthenticationArguments authArgs) : V1_28::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders, std::move(authArgs)) + { + m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_30_0.ToString()); + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_30_0; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h b/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h index 5e365cfe97..958ba830bb 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h @@ -16,6 +16,7 @@ namespace AppInstaller::Repository::Rest::Schema const Utility::Version Version_1_10_0{ "1.10.0" }; const Utility::Version Version_1_12_0{ "1.12.0" }; const Utility::Version Version_1_28_0{ "1.28.0" }; + const Utility::Version Version_1_30_0{ "1.30.0" }; // General API response constants constexpr std::string_view Data = "Data"sv;