diff --git a/.forgejo/workflows/test.yaml b/.forgejo/workflows/test.yaml index 373e4f7..c52647e 100644 --- a/.forgejo/workflows/test.yaml +++ b/.forgejo/workflows/test.yaml @@ -5,6 +5,7 @@ name: test on: push: + pull_request: workflow_dispatch: jobs: @@ -14,6 +15,9 @@ jobs: steps: - name: Check out uses: https://code.forgejo.org/actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # The manifest check compares against the base of the change. + fetch-depth: 0 - name: Check REUSE compliance run: nix develop -c reuse lint @@ -21,6 +25,24 @@ jobs: - name: Test run: nix develop -c python3 -m unittest discover -s tests -t . -v + # Installers are downloaded and hashed only for the versions this + # change touches: those since the pull request's base, or since the + # commit a push replaced. + - name: Check manifests + env: + EVENT: ${{ forge.event_name }} + BASE: ${{ forge.event.pull_request.base.sha }} + BEFORE: ${{ forge.event.before }} + run: | + if [ "$EVENT" = pull_request ]; then + since=$BASE + elif [ -n "$BEFORE" ] && [ "$BEFORE" != 0000000000000000000000000000000000000000 ]; then + since=$BEFORE + else + since=$(git merge-base origin/main HEAD) + fi + nix develop -c python3 -m wingetsource check --manifests manifests --changed-since "$since" + - name: Push to the cache if: ${{ !cancelled() && forge.ref == 'refs/heads/main' }} uses: https://git.jcollie.dev/jeff/niks3-push@3f85f501658e379783381291241c6f8315a92716 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 049ceff..b0c71c0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,8 +26,9 @@ nix develop winget-pkgs. - `tools/wingetsource/` — the Python program that builds the source: merging manifests (`manifest.py`), writing the index (`index.py`, with - `normalize.py` and `version.py` ported from winget-cli), and packing the - MSIX (`msix.py`). + `normalize.py` and `version.py` ported from winget-cli), packing the + MSIX (`msix.py`), and checking manifests (`check.py`, against winget-cli's + manifest schemas in `schemas/`, copied unchanged). - `tests/` — its tests. `tests/data/winget-cli/` is winget-cli's own name normalization test data, copied unchanged. - `certificate/JCOllie.cer` — the public half of the signing certificate. @@ -82,6 +83,21 @@ A version is added by a new directory, never by editing an existing one: a published manifest is what clients have already installed from. Removing a version is a pull request that deletes its directory. +Check a change before opening the pull request: + +```console +nix develop -c python3 -m wingetsource check --manifests manifests --changed-since origin/main +``` + +Every version is checked against the 1.12.0 manifest schemas, the way winget +validates them, and against the rules here: the identifier is +`JCOllie.`, the directory and file names follow from it and the +version, and the files agree. The installers of the versions changed since +`origin/main` must download from git.jcollie.dev and hash to the SHA-256 their +manifest gives, so those are downloaded; without `--changed-since` every +version's installers are. CI runs the same check on every push and pull +request. + `main` only moves through a merged pull request that has passed validation. ## Licensing diff --git a/README.md b/README.md index 8eeba74..7e7e8e1 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,8 @@ specification, and `reuse lint` passes. - git-pages contributors. *git-pages*. . - Barnett, Matthew. *mrab-regex*, version 2026.7.11. . +- Berman, Julian. *jsonschema*, version 4.26.0. + . - jantari. *rewinged*. . - Microsoft. *msix-packaging*. . - Microsoft. *source command (winget)*. Microsoft Learn. diff --git a/REUSE.toml b/REUSE.toml index 996b89c..639d9de 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -19,3 +19,10 @@ path = ["tests/data/winget-cli/**"] precedence = "aggregate" SPDX-FileCopyrightText = "Copyright (c) Microsoft Corporation. All rights reserved." SPDX-License-Identifier = "MIT" + +# winget-cli's manifest schemas, from schemas/JSON/manifests at v1.29.380. +[[annotations]] +path = ["tools/wingetsource/schemas/**"] +precedence = "aggregate" +SPDX-FileCopyrightText = "Copyright (c) Microsoft Corporation. All rights reserved." +SPDX-License-Identifier = "MIT" diff --git a/flake.nix b/flake.nix index d7a4a11..fc12c71 100644 --- a/flake.nix +++ b/flake.nix @@ -37,6 +37,7 @@ name = "winget-pkgs"; nativeBuildInputs = [ (pkgs.python3.withPackages (ps: [ + ps.jsonschema ps.pyyaml ps.regex ])) diff --git a/tests/helpers.py b/tests/helpers.py index 51c30c7..9c41322 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -23,7 +23,7 @@ def write_manifest(root: pathlib.Path, identifier: str, version: str, *, install ) (directory / f"{identifier}.locale.en-US.yaml").write_text( common - + f"PackageLocale: en-US\nPublisher: Example Publisher\nPackageName: {name or package}\nLicense: MIT\nShortDescription: x\n" + + f"PackageLocale: en-US\nPublisher: Example Publisher\nPackageName: {name or package}\nLicense: MIT\nShortDescription: A thing.\n" + textwrap.dedent(locale) + "ManifestType: defaultLocale\nManifestVersion: 1.12.0\n" ) diff --git a/tests/test_check.py b/tests/test_check.py new file mode 100644 index 0000000..4f8668a --- /dev/null +++ b/tests/test_check.py @@ -0,0 +1,129 @@ +# SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie +# SPDX-License-Identifier: MIT + +import hashlib +import pathlib +import tempfile +import unittest + +from wingetsource import check + +from .helpers import write_manifest + +REPO = pathlib.Path(__file__).parent.parent + +_URL = "https://git.jcollie.dev/jeff/thing/releases/download/v1.0.0/thing.exe" +_CONTENT = b"an installer" + + +def _installer(url: str = _URL, sha256: str = hashlib.sha256(_CONTENT).hexdigest().upper(), extra: str = "") -> str: + return ( + "InstallerType: portable\n" + + extra + + f"Installers:\n- Architecture: x64\n InstallerUrl: {url}\n InstallerSha256: {sha256}\n" + ) + + +def _messages(problems) -> str: + return "\n".join(str(p) for p in problems) + + +class CheckDirectory(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.root = pathlib.Path(self.tmp.name) / "manifests" + + def tearDown(self): + self.tmp.cleanup() + + def problems(self, directory): + return check.check_directory(directory, self.root) + + def test_repository_manifests_pass(self): + tree = REPO / "manifests" + for directory in sorted({p.parent for p in tree.rglob("*.yaml")}): + with self.subTest(directory=str(directory)): + self.assertEqual([], check.check_directory(directory, tree)) + + def test_good_manifest_passes(self): + directory = write_manifest(self.root, "JCOllie.Thing", "1.0.0", installer=_installer()) + self.assertEqual([], self.problems(directory)) + + def test_typed_fields_validate_as_winget_reads_them(self): + # Integers and booleans only exist for the fields winget converts; + # everything else, a version that looks like a number included, is a + # string to the schema. + installer = _installer(extra="InstallerSuccessCodes:\n- 3010\nInstallerAbortsTerminal: yes\n") + directory = write_manifest(self.root, "JCOllie.Thing", "1.10", installer=installer) + self.assertEqual([], self.problems(directory)) + + def test_schema_errors_are_reported(self): + directory = write_manifest(self.root, "JCOllie.Thing", "1.0.0", installer=_installer(extra="Scope: everywhere\n")) + self.assertIn("Scope", _messages(self.problems(directory))) + + def test_identifier_must_be_ours(self): + directory = write_manifest(self.root, "Example.Thing", "1.0.0", installer=_installer()) + self.assertIn("must start with JCOllie.", _messages(self.problems(directory))) + + def test_directory_must_match(self): + directory = write_manifest(self.root, "JCOllie.Thing", "1.0.0", installer=_installer()) + moved = directory.parent / "2.0.0" + directory.rename(moved) + self.assertIn("belongs in", _messages(self.problems(moved))) + + def test_file_names_must_match(self): + directory = write_manifest(self.root, "JCOllie.Thing", "1.0.0", installer=_installer()) + (directory / "JCOllie.Thing.installer.yaml").rename(directory / "installer.yaml") + self.assertIn("should be named JCOllie.Thing.installer.yaml", _messages(self.problems(directory))) + + def test_default_locale_must_agree(self): + directory = write_manifest(self.root, "JCOllie.Thing", "1.0.0", installer=_installer()) + path = directory / "JCOllie.Thing.yaml" + path.write_text(path.read_text().replace("DefaultLocale: en-US", "DefaultLocale: en-GB")) + self.assertIn("DefaultLocale", _messages(self.problems(directory))) + + def test_manifest_version_is_pinned(self): + directory = write_manifest(self.root, "JCOllie.Thing", "1.0.0", installer=_installer()) + path = directory / "JCOllie.Thing.yaml" + path.write_text(path.read_text().replace("ManifestVersion: 1.12.0", "ManifestVersion: 1.10.0")) + self.assertIn("ManifestVersion must be 1.12.0", _messages(self.problems(directory))) + + def test_singletons_are_refused(self): + directory = self.root / "j" / "JCOllie" / "Thing" / "1.0.0" + directory.mkdir(parents=True) + (directory / "JCOllie.Thing.yaml").write_text("ManifestType: singleton\nManifestVersion: 1.12.0\n") + self.assertIn("ManifestType must be one of", _messages(self.problems(directory))) + + +class CheckInstallers(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.root = pathlib.Path(self.tmp.name) / "manifests" + + def tearDown(self): + self.tmp.cleanup() + + @staticmethod + def download(url): + return hashlib.sha256(_CONTENT).digest() + + def test_matching_hash_passes(self): + directory = write_manifest(self.root, "JCOllie.Thing", "1.0.0", installer=_installer()) + self.assertEqual([], check.check_installers(directory, download=self.download)) + + def test_hash_mismatch(self): + directory = write_manifest(self.root, "JCOllie.Thing", "1.0.0", installer=_installer(sha256="0" * 64)) + self.assertIn("SHA-256 is", _messages(check.check_installers(directory, download=self.download))) + + def test_other_hosts_are_refused_without_downloading(self): + def refuse(url): + raise AssertionError("downloaded") + + directory = write_manifest( + self.root, "JCOllie.Thing", "1.0.0", installer=_installer(url="https://codeberg.org/jcollie/thing/thing.exe") + ) + self.assertIn("must download from https://git.jcollie.dev/", _messages(check.check_installers(directory, download=refuse))) + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/wingetsource/__main__.py b/tools/wingetsource/__main__.py index 44b9189..368d6d3 100644 --- a/tools/wingetsource/__main__.py +++ b/tools/wingetsource/__main__.py @@ -1,7 +1,8 @@ # SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie # SPDX-License-Identifier: MIT -"""Build the source: `python -m wingetsource build ...`. +"""Build the source, `python -m wingetsource build ...`, or check manifests +before they are merged, `python -m wingetsource check ...`. The output directory is the whole static site: `source.msix`, a merged file for every manifest under `manifests/`, the signing certificate, and @@ -17,6 +18,7 @@ import subprocess import sys import tempfile +from . import check as checks from . import index, msix from .manifest import ManifestError, load_tree @@ -97,6 +99,27 @@ def build(args: argparse.Namespace) -> int: return 0 +def check(args: argparse.Namespace) -> int: + tree = args.manifests.resolve() + if tree.name != "manifests": + print(f"{args.manifests}: a manifest tree must be a directory named manifests", file=sys.stderr) + return 1 + if args.changed_since: + installers = [d for d in checks.changed_directories(args.manifests, args.changed_since) if d.is_relative_to(tree)] + else: + installers = None + problems = checks.check(tree, installers) + for problem in problems: + try: + problem.path = problem.path.relative_to(pathlib.Path.cwd()) + except ValueError: + pass + print(problem, file=sys.stderr) + checked = "every version" if installers is None else f"{len(installers)} changed version(s)" + print(f"{len(problems)} problem(s); installers checked for {checked}") + return 1 if problems else 0 + + def main() -> int: parser = argparse.ArgumentParser(prog="wingetsource") commands = parser.add_subparsers(dest="command", required=True) @@ -114,6 +137,15 @@ def main() -> int: ) b.set_defaults(func=build) + c = commands.add_parser("check", help="check a manifests/ tree against the schemas and this repository's rules") + c.add_argument("--manifests", type=pathlib.Path, required=True, help="the manifests/ tree") + c.add_argument( + "--changed-since", + metavar="COMMIT", + help="check installers only for versions changed since COMMIT (from where it and HEAD diverge); the default checks every one", + ) + c.set_defaults(func=check) + args = parser.parse_args() try: return args.func(args) diff --git a/tools/wingetsource/check.py b/tools/wingetsource/check.py new file mode 100644 index 0000000..8484f6a --- /dev/null +++ b/tools/wingetsource/check.py @@ -0,0 +1,217 @@ +# SPDX-FileCopyrightText: © 2026 Jeffrey C. Ollie +# SPDX-License-Identifier: MIT + +"""Checking manifests before they are merged. + +Every version directory is checked against Microsoft's manifest schemas, the +way winget itself validates them, and against this repository's own rules: +the directory and file names follow from the identifier and version, the +identifier is `JCOllie.`, and the files agree with each other well +enough to merge. Installers are checked only where asked, usually only for the +versions a pull request touches, since checking one means downloading it: each +must come from git.jcollie.dev and hash to what its manifest says. +""" + +import hashlib +import json +import pathlib +import subprocess +import urllib.parse +import urllib.request +from dataclasses import dataclass + +import jsonschema +import yaml + +from .manifest import ManifestError, load_directory, load_tree + +MANIFEST_VERSION = "1.12.0" +IDENTIFIER_PREFIX = "JCOllie." +INSTALLER_HOST = "git.jcollie.dev" + +_SCHEMAS = pathlib.Path(__file__).with_name("schemas") / f"v{MANIFEST_VERSION}" + +# ManifestSchemaValidation.cpp's ManifestFieldTypes: the only fields winget +# hands the schema as anything but a string. +_SCALAR_TYPES = { + "InstallerSuccessCodes": int, + "InstallerAbortsTerminal": bool, + "InstallLocationRequired": bool, + "RequireExplicitUpgrade": bool, + "DisplayInstallWarnings": bool, + "InstallerReturnCode": int, + "DownloadCommandProhibited": bool, + "ArchiveBinariesDependOnPath": bool, +} + +# What yaml-cpp's as() accepts. +_TRUE = {"y", "Y", "yes", "Yes", "YES", "true", "True", "TRUE", "on", "On", "ON"} +_FALSE = {"n", "N", "no", "No", "NO", "false", "False", "FALSE", "off", "Off", "OFF"} + +_KINDS = ("version", "installer", "defaultLocale", "locale") + + +@dataclass +class Problem: + path: pathlib.Path + message: str + + def __str__(self): + return f"{self.path}: {self.message}" + + +def _to_json(node: yaml.Node, kind: type = str): + """The JSON winget validates: ManifestYamlNodeToJson.""" + if isinstance(node, yaml.MappingNode): + return {k.value: _to_json(v, _SCALAR_TYPES.get(k.value, str)) for k, v in node.value} + if isinstance(node, yaml.SequenceNode): + return [_to_json(v, kind) for v in node.value] + if node.tag == "tag:yaml.org,2002:null": + return None + if kind is int: + try: + return int(node.value, 0) + except ValueError: + return node.value + if kind is bool: + if node.value in _TRUE: + return True + if node.value in _FALSE: + return False + return node.value + + +def _validators() -> dict[str, jsonschema.Draft7Validator]: + return { + kind: jsonschema.Draft7Validator(json.loads((_SCHEMAS / f"manifest.{kind}.{MANIFEST_VERSION}.json").read_text())) + for kind in _KINDS + } + + +def _expected_name(identifier: str, kind: str, document: dict) -> str: + if kind == "version": + return f"{identifier}.yaml" + if kind == "installer": + return f"{identifier}.installer.yaml" + return f"{identifier}.locale.{document.get('PackageLocale')}.yaml" + + +def check_directory(directory: pathlib.Path, tree: pathlib.Path, validators=None) -> list[Problem]: + """The schema and repository rules for one version directory.""" + validators = validators or _validators() + problems = [] + documents = {} + for path in sorted(directory.glob("*.yaml")): + try: + with path.open("rb") as f: + node = yaml.compose(f, Loader=yaml.SafeLoader) + except yaml.YAMLError as e: + problems.append(Problem(path, f"not valid YAML: {e}")) + continue + document = _to_json(node) if isinstance(node, yaml.MappingNode) else None + if not isinstance(document, dict): + problems.append(Problem(path, "not a YAML mapping")) + continue + kind = document.get("ManifestType") + if kind not in _KINDS: + problems.append(Problem(path, f"ManifestType must be one of {', '.join(_KINDS)}, not {kind!r}")) + continue + if document.get("ManifestVersion") != MANIFEST_VERSION: + problems.append(Problem(path, f"ManifestVersion must be {MANIFEST_VERSION}")) + continue + for error in sorted(validators[kind].iter_errors(document), key=lambda e: list(e.absolute_path)): + where = "/".join(str(p) for p in error.absolute_path) or "(document)" + problems.append(Problem(path, f"{where}: {error.message}")) + documents.setdefault(kind, []).append((path, document)) + + if not documents: + return problems or [Problem(directory, "no manifest files")] + for kind in ("version", "installer", "defaultLocale"): + if len(documents.get(kind, [])) != 1: + problems.append(Problem(directory, f"expected exactly one {kind} manifest")) + if problems: + return problems + + version_path, version_doc = documents["version"][0] + identifier = version_doc["PackageIdentifier"] + version = version_doc["PackageVersion"] + + if not identifier.startswith(IDENTIFIER_PREFIX): + problems.append(Problem(version_path, f"PackageIdentifier must start with {IDENTIFIER_PREFIX}")) + expected = tree.joinpath(identifier[0].lower(), *identifier.split("."), version) + if directory != expected: + problems.append(Problem(directory, f"{identifier} {version} belongs in {expected}")) + + for kind, entries in documents.items(): + for path, document in entries: + name = _expected_name(identifier, kind, document) + if path.name != name: + problems.append(Problem(path, f"should be named {name}")) + + default_locale = documents["defaultLocale"][0][1].get("PackageLocale") + if version_doc.get("DefaultLocale") != default_locale: + problems.append(Problem(version_path, f"DefaultLocale is not the defaultLocale manifest's PackageLocale ({default_locale})")) + + if not problems: + try: + load_directory(directory) + except ManifestError as e: + problems.append(Problem(directory, str(e))) + return problems + + +def _download_sha256(url: str) -> bytes: + digest = hashlib.sha256() + with urllib.request.urlopen(url, timeout=60) as response: + while chunk := response.read(1 << 20): + digest.update(chunk) + return digest.digest() + + +def check_installers(directory: pathlib.Path, download=_download_sha256) -> list[Problem]: + """Each installer comes from git.jcollie.dev with the hash given.""" + problems = [] + path = next(directory.glob("*.installer.yaml")) + with path.open("rb") as f: + document = _to_json(yaml.compose(f, Loader=yaml.SafeLoader)) + for installer in document.get("Installers") or []: + url = installer.get("InstallerUrl", "") + parsed = urllib.parse.urlsplit(url) + if parsed.scheme != "https" or parsed.hostname != INSTALLER_HOST: + problems.append(Problem(path, f"{url}: installers must download from https://{INSTALLER_HOST}/")) + continue + try: + actual = download(url) + except OSError as e: + problems.append(Problem(path, f"{url}: download failed: {e}")) + continue + if actual.hex() != installer.get("InstallerSha256", "").lower(): + problems.append(Problem(path, f"{url}: SHA-256 is {actual.hex().upper()}, not {installer.get('InstallerSha256')}")) + return problems + + +def changed_directories(tree: pathlib.Path, since: str) -> list[pathlib.Path]: + """The version directories under `tree` with files changed since `since` + (a commit, compared from where it and HEAD diverge) that still exist.""" + names = subprocess.run( + ["git", "diff", "--name-only", "--relative", "--no-renames", f"{since}...HEAD", "--", str(tree)], + check=True, capture_output=True, text=True, + ).stdout.splitlines() + return sorted({pathlib.Path(n).parent.resolve() for n in names if pathlib.Path(n).parent.is_dir()}) + + +def check(tree: pathlib.Path, installer_directories: list[pathlib.Path] | None) -> list[Problem]: + """Every rule for `tree`; installers only for `installer_directories`, + or for every version when it is None.""" + validators = _validators() + directories = sorted({p.parent for p in tree.rglob("*.yaml")}) + problems = [p for d in directories for p in check_directory(d, tree, validators)] + if not problems: + try: + load_tree(tree) + except ManifestError as e: + problems.append(Problem(tree, str(e))) + for directory in directories if installer_directories is None else installer_directories: + if not any(p.path == directory or p.path.parent == directory for p in problems): + problems.extend(check_installers(directory)) + return problems diff --git a/tools/wingetsource/schemas/v1.12.0/manifest.defaultLocale.1.12.0.json b/tools/wingetsource/schemas/v1.12.0/manifest.defaultLocale.1.12.0.json new file mode 100644 index 0000000..5d96d68 --- /dev/null +++ b/tools/wingetsource/schemas/v1.12.0/manifest.defaultLocale.1.12.0.json @@ -0,0 +1,280 @@ +{ + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.12.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.12.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.12.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/tools/wingetsource/schemas/v1.12.0/manifest.installer.1.12.0.json b/tools/wingetsource/schemas/v1.12.0/manifest.installer.1.12.0.json new file mode 100644 index 0000000..2f63464 --- /dev/null +++ b/tools/wingetsource/schemas/v1.12.0/manifest.installer.1.12.0.json @@ -0,0 +1,921 @@ +{ + "$id": "https://aka.ms/winget-manifest.installer.1.12.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.12.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." + }, + "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" + } + }, + "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" + }, + "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.12.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/tools/wingetsource/schemas/v1.12.0/manifest.locale.1.12.0.json b/tools/wingetsource/schemas/v1.12.0/manifest.locale.1.12.0.json new file mode 100644 index 0000000..f21e130 --- /dev/null +++ b/tools/wingetsource/schemas/v1.12.0/manifest.locale.1.12.0.json @@ -0,0 +1,271 @@ +{ + "$id": "https://aka.ms/winget-manifest.locale.1.12.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.12.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.12.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/tools/wingetsource/schemas/v1.12.0/manifest.version.1.12.0.json b/tools/wingetsource/schemas/v1.12.0/manifest.version.1.12.0.json new file mode 100644 index 0000000..2e4a2c8 --- /dev/null +++ b/tools/wingetsource/schemas/v1.12.0/manifest.version.1.12.0.json @@ -0,0 +1,46 @@ +{ + "$id": "https://aka.ms/winget-manifest.version.1.12.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.12.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.12.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" + ] +}