From b4f33fb130c7fdedac864c7c46842da10d0fb4db Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Sat, 5 Sep 2026 22:43:05 +0000 Subject: [PATCH 1/8] description: validate that at least one archive source is present --- flatpaker/description.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/flatpaker/description.py b/flatpaker/description.py index 3754ae7..3c02f99 100644 --- a/flatpaker/description.py +++ b/flatpaker/description.py @@ -143,6 +143,13 @@ class Sources(BaseModel): patches: list[Patch] = Field(default_factory=list) files: list[File] = Field(default_factory=list) + @field_validator('archives', mode='after') + @classmethod + def _validate_field(cls, value: list[Archive]) -> list[Archive]: + if not value: + raise ValueError('At least one "archive" entry is required') + return value + class Quirks(BaseModel): """The quirks section of the build toml description.""" From 35c41fa3901a4757afea409ce3a52d08b01dda64 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Sat, 5 Sep 2026 22:49:05 +0000 Subject: [PATCH 2/8] pyproject: fix accidental git commit --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 631d3b0..d3902ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ quote-style = "preserve" [tool.bumpversion] - current_version = "0.1.1t" + current_version = "0.1.1" parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" serialize = ["{major}.{minor}.{patch}"] search = "{current_version}" From 8765c43ebeab6394b3cea3c252d1fef1c47e57c4 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Sat, 5 Sep 2026 22:49:37 +0000 Subject: [PATCH 3/8] entry: fix help message typo --- flatpaker/entry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flatpaker/entry.py b/flatpaker/entry.py index f26732a..3355d25 100644 --- a/flatpaker/entry.py +++ b/flatpaker/entry.py @@ -202,7 +202,7 @@ def _parse_args() -> BaseArguments: build_parser.set_defaults(action='build') validate_parser = subparsers.add_parser( - 'validate', help='validate buidl configurations') + 'validate', help='validate build configurations') validate_parser.add_argument('descriptions', nargs='+', type=pathlib.Path, help="One or more Toml description file") validate_parser.set_defaults(action='validate') From c0739109c74d3177c07bf080f3c71f83bd58b906 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Sat, 5 Sep 2026 22:50:57 +0000 Subject: [PATCH 4/8] entry: set dataclass slots to True not False What was I thinking? --- flatpaker/entry.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/flatpaker/entry.py b/flatpaker/entry.py index 3355d25..2b6723f 100644 --- a/flatpaker/entry.py +++ b/flatpaker/entry.py @@ -57,7 +57,7 @@ class GenerateArguments(BaseArguments, typing.Protocol): class ValidateArguments(typing.Protocol): descriptions: list[pathlib.Path] -@dataclasses.dataclass(slots=False, eq=False) +@dataclasses.dataclass(slots=True, eq=False) class FlatManagerConfig: remote: str @@ -65,7 +65,7 @@ class FlatManagerConfig: token: str -@dataclasses.dataclass(slots=False, eq=False) +@dataclasses.dataclass(slots=True, eq=False) class _BuildCommonConfig: """Common configuration for "build" and "build-runtimes".""" @@ -78,14 +78,14 @@ class _BuildCommonConfig: flat_manager: FlatManagerConfig | None -@dataclasses.dataclass(slots=False, eq=False) +@dataclasses.dataclass(slots=True, eq=False) class BuildFlatpakConfig(_BuildCommonConfig): """Configuration for "build".""" descriptions: list[pathlib.Path] -@dataclasses.dataclass(slots=False, eq=False) +@dataclasses.dataclass(slots=True, eq=False) class BuildRuntimeConfig(_BuildCommonConfig): """Configuration for "build-runtimes".""" @@ -93,7 +93,7 @@ class BuildRuntimeConfig(_BuildCommonConfig): update: bool -@dataclasses.dataclass(slots=False, eq=False) +@dataclasses.dataclass(slots=True, eq=False) class GenerateConfig: """Configuration for "generate".""" @@ -105,7 +105,7 @@ class GenerateConfig: files: list[str] -@dataclasses.dataclass(slots=False, eq=False) +@dataclasses.dataclass(slots=True, eq=False) class ValidateConfig: """Configuration for "validate".""" From a5942a15f176e63ed1b67c9d7e7666e869a55b57 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Sat, 5 Sep 2026 22:59:11 +0000 Subject: [PATCH 5/8] devcontainer: turn of pylance type checking We need to rely on mypy for this, as pylance doesn't have the necessary information to correctly analyze pydantic Models. --- .devcontainer/devcontainer.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a0d691d..67ee70a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -31,7 +31,12 @@ "ms-python.python", "njpwerner.autodocstring", "tamasfe.even-better-toml", - ] + ], + "settings": { + // Pylance doesn't understand the pydantic types and generates lots of + // false positive warnings. + "python.analysis.typeCheckingMode": "off", + } } }, From f27e6169619a643829105d180bcc53b15958cbf0 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Sat, 5 Sep 2026 23:14:15 +0000 Subject: [PATCH 6/8] actions: Add an action to dump schema from pydantic models The generated schema is not yet on par with the handwritten one, but doing it in this order allows me to compare the output as I work. --- flatpaker/actions/schema.py | 28 ++++++++++++++++++++++++++++ flatpaker/entry.py | 26 ++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 flatpaker/actions/schema.py diff --git a/flatpaker/actions/schema.py b/flatpaker/actions/schema.py new file mode 100644 index 0000000..813b5dd --- /dev/null +++ b/flatpaker/actions/schema.py @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: MIT +# Copyright © 2026 Dylan Baker + +"""Dump JSON schema from pydantic models.""" + +from __future__ import annotations + +import json +import pathlib +import sys + +from flatpaker.description import Description + + +def dump_schema(path: pathlib.Path) -> bool: + """Dump JSON schmea from Pydantic model.""" + # TODO: there are a load of exceptions that could be generated here and need + # to be caught and turned into failures + try: + # Ensure the directory exists before attempting to write the file + path.parent.mkdir(parents=True, exist_ok=True) + with path.open('w') as f: + json.dump(Description.model_json_schema(by_alias=True), f, indent=4) + except PermissionError: + print(f'Error: Failed to open {path!s} for writing', file=sys.stderr) + else: + return True + return False diff --git a/flatpaker/entry.py b/flatpaker/entry.py index 2b6723f..a760b25 100644 --- a/flatpaker/entry.py +++ b/flatpaker/entry.py @@ -15,6 +15,7 @@ from flatpaker.actions.build_flatpak import build_flatpak from flatpaker.actions.build_runtime import build_runtimes from flatpaker.actions.generate import generate +from flatpaker.actions.schema import dump_schema from flatpaker.actions.validate import validate if typing.TYPE_CHECKING: @@ -22,7 +23,7 @@ from flatpaker.description import EngineName class BaseArguments(typing.Protocol): - action: typing.Literal['build', 'build-runtimes', 'generate', 'validate'] + action: typing.Literal['build', 'build-runtimes', 'generate', 'validate', 'schema'] class BaseBuildArguments(BaseArguments, typing.Protocol): repo: str @@ -57,6 +58,10 @@ class GenerateArguments(BaseArguments, typing.Protocol): class ValidateArguments(typing.Protocol): descriptions: list[pathlib.Path] + class SchemaArguments(typing.Protocol): + output: str + + @dataclasses.dataclass(slots=True, eq=False) class FlatManagerConfig: @@ -112,6 +117,12 @@ class ValidateConfig: descriptions: list[pathlib.Path] +@dataclasses.dataclass(slots=True, eq=False) +class SchemaConfig: + """Configuration for "validate".""" + + output: pathlib.Path + def static_deltas(args: BuildRuntimeConfig | BuildFlatpakConfig) -> None: if not (args.deltas or args.export != 'repo'): @@ -206,6 +217,11 @@ def _parse_args() -> BaseArguments: validate_parser.add_argument('descriptions', nargs='+', type=pathlib.Path, help="One or more Toml description file") validate_parser.set_defaults(action='validate') + schema_parser = subparsers.add_parser( + 'schema', help='validate build configurations') + schema_parser.add_argument('-o', '--output', action='store', default='.', help='Where to write the schema file') + schema_parser.set_defaults(action='schema') + _all_runtimes = ['renpy8', 'renpy7', 'renpy7-py3', 'rpgmaker'] runtimes_parser = subparsers.add_parser( 'build-runtimes', help='Build custom Platforms and Sdks', parents=[pp]) @@ -321,7 +337,7 @@ def _flat_manager_config(args: BaseBuildArguments) -> FlatManagerConfig | None: return FlatManagerConfig(remote, repo, token) -def _args_to_config() -> BuildFlatpakConfig | BuildRuntimeConfig | GenerateConfig | ValidateConfig: +def _args_to_config() -> BuildFlatpakConfig | BuildRuntimeConfig | GenerateConfig | ValidateConfig | SchemaConfig: args = _parse_args() match args.action: case 'build': @@ -362,6 +378,10 @@ def _args_to_config() -> BuildFlatpakConfig | BuildRuntimeConfig | GenerateConfi case 'validate': vargs = typing.cast('ValidateArguments', args) return ValidateConfig(descriptions=vargs.descriptions) + case 'schema': + scargs = typing.cast('SchemaArguments', args) + path = pathlib.Path(scargs.output) / 'flatpaker.schema.json' + return SchemaConfig(output=path.absolute()) def main() -> None: @@ -381,5 +401,7 @@ def main() -> None: success = generate(config) case ValidateConfig(): success = validate(config) + case SchemaConfig(): + success = dump_schema(config.output) sys.exit(0 if success else 1) From 20d370b8b5f622a7e5df38cf8a4cd27d899aeeda Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Sun, 6 Sep 2026 03:27:29 +0000 Subject: [PATCH 7/8] WIP: description: Annotate pydantic model for JSON schema generation --- flatpaker/description.py | 220 ++++++++++++++++++++++++++++++++++----- 1 file changed, 193 insertions(+), 27 deletions(-) diff --git a/flatpaker/description.py b/flatpaker/description.py index 3c02f99..aa61cf5 100644 --- a/flatpaker/description.py +++ b/flatpaker/description.py @@ -12,7 +12,7 @@ import warnings import tomlkit -from pydantic import BaseModel, Field, field_validator, model_validator +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from flatpaker.util import sanitize_name, validate_name @@ -57,10 +57,38 @@ class Common(BaseModel): """The common section of the build toml description.""" - reverse_url: str - name: str - engine: EngineName - categories: list[Category] = Field(default_factory=list) + model_config = ConfigDict(title='common') + + reverse_url: str = Field( + title='Reverse URL', + json_schema_extra={ + 'description': ('A reverse root URL for the game. ' + 'The Game name will be automatically appended from the name field. ' + 'This cannot be changed.'), + 'example': 'com.github.user', + }, + ) + name: str = Field( + title='Proper Name', + json_schema_extra={ + 'description': 'The proper name with punctionation and spaces for this game', + 'example': "Soldier's Quest", + }, + ) + engine: EngineName = Field( + title='Engine name', + json_schema_extra={ + 'description': 'Which engine (and runtime) this game uses', + }, + ) + categories: list[Category] = Field( + default_factory=list, + title='Freedesktop Application Categories', + json_schema_extra={ + 'description': ("Valid categories for the game's .destkop file. " + "'Game' is added automatically"), + }, + ) @field_validator('reverse_url', mode='before') @classmethod @@ -77,11 +105,41 @@ def appid(self) -> str: class AppData(BaseModel): """The appdata section of the build toml description.""" - summary: str - description: str - content_rating: dict[ContentFields, ContentRating] = Field(default_factory=dict) - releases: dict[str, str] = Field(default_factory=dict) - license: str = 'LicenseRef-Proprietary' + model_config = ConfigDict(title='Application Metadata') + + summary: str = Field( + title='Application summary', + json_schema_extra={ + 'description': 'A short description of the game', + }, + ) + description: str = Field( + title='Application description', + json_schema_extra={ + 'description': 'A longer description of the game.', + }, + ) + content_rating: dict[ContentFields, ContentRating] = Field( + default_factory=dict, + title='OARS Content Rating Information', + json_schema_extra={ + 'description': 'Content information for the current game.', + }, + ) + releases: dict[str, str] = Field( + default_factory=dict, + title='Release information for this game', + json_schema_extra={ + 'description': 'Mapping of releases in the form `"YYYY-MM-DD" = "X.Y.Z"', + }, + ) + license: str = Field( + default='LicenseRef-Proprietary', + title='SPDX License Expression', + json_schema_extra={ + 'description': 'A valid SPDX license Expression for this game', + }, + ) @field_validator('releases', mode='after') @classmethod @@ -94,8 +152,21 @@ def _validate_field(cls, value: dict[str, str]) -> dict[str, str]: class _Source(BaseModel): """Shared base class for sources.""" - path: pathlib.Path - sha256: str | None = Field(None, min_length=64, max_length=64) + path: pathlib.Path = Field( + title='Path to source', + json_schema_extra={ + 'description': 'A local path to the sources. Must be relative to this file, or absolute.', + }, + ) + sha256: str | None = Field( + None, + min_length=64, + max_length=64, + title='SHA256 Hash', + json_schema_extra={ + 'description': 'Hexedecimal representatio of the SHA256 of this file.', + }, + ) @field_validator('path', mode='before') @classmethod @@ -119,29 +190,86 @@ def __validate_sha256(cls, v: str | None) -> str | None: # pylint: disable=W023 class File(_Source): """A file entry in the sources section of the build toml description.""" - dest: str = 'game' - commands: list[str] = Field(default_factory=list) + model_config = ConfigDict(title='A single file type source') + + dest: str = Field( + 'game', + title='Installation location', + json_schema_extra={ + 'description': 'Relative path to install this file to.', + }, + ) + commands: list[str] = Field( + default_factory=list, + title='Extra shell commands', + json_schema_extra={ + 'description': ('Shell commands are run immediately after copying this ' + 'source to the build directory'), + }, + ) class Patch(_Source): """A patch entry in the sources section of the build toml description.""" - strip_components: int = 1 + model_config = ConfigDict(title='A patch source') + + strip_components: int = Field( + 1, + title='Patch strip argument', + json_schema_extra={ + 'description': 'number of path elements to strip from patches. Passed to `patch -p N`', + }, + ) class Archive(_Source): """An archive entry in the sources section of the build toml description.""" - commands: list[str] = Field(default_factory=list) - strip_components: int = 1 + model_config = ConfigDict(title='An archive source') + + commands: list[str] = Field( + default_factory=list, + title='Extra shell commands', + json_schema_extra={ + 'description': ('Shell commands are run immediately after copying this ' + 'source to the build directory'), + }, + ) + strip_components: int = Field( + 1, + title='Patch strip argument', + json_schema_extra={ + 'description': 'number of path elements to strip from patches. Passed to `patch -p N`', + }, + ) class Sources(BaseModel): """The sources section of the build toml description.""" - archives: list[Archive] = Field(default_factory=list) - patches: list[Patch] = Field(default_factory=list) - files: list[File] = Field(default_factory=list) + model_config = ConfigDict(title='The sources for this build') + + archives: list[Archive] = Field( + title='Archive Sources', + json_schema_extra={ + 'description': 'Archive type sources, such as .7z, .zip, .tar.gz, etc.', + }, + ) + patches: list[Patch] = Field( + default_factory=list, + title='Patch sources', + json_schema_extra={ + 'description': 'Patches to be applied after all archives are unpacked', + }, + ) + files: list[File] = Field( + default_factory=list, + title='source file', + json_schema_extra={ + 'description': 'extra files to add the build directory', + }, + ) @field_validator('archives', mode='after') @classmethod @@ -152,11 +280,42 @@ def _validate_field(cls, value: list[Archive]) -> list[Archive]: class Quirks(BaseModel): - """The quirks section of the build toml description.""" - - force_window_gui_icon: bool = False - x_configure_prologue: str | None = Field(None) - x_renpy_archived_window_gui_icon: str | None = Field(None) + """Quirks of this build that have built-in wokarounds. + + There are a number of common issues, particularly in Ren'Py games that + flatpaker provides build steps to deal with. + """ + + model_config = ConfigDict(title='Quirks of this game to work around') + + force_window_gui_icon: bool = Field( + False, + title='Force use of img/windows_gui/icon.png', + json_schema_extra={ + 'description': ('Normally icons from a .exe or .icns if they are avilable, ' + 'however, some games do not customize these icon sources ' + 'but do provide a customized incon in the img directory. ' + 'Setting this to true forces the use of that icon.'), + }, + ) + x_configure_prologue: str | None = Field( + None, + title='Commands to run after unpacking all sources', + json_schema_extra={ + 'description': ('Extra shell commands to run after unpacking sources. ' + 'This has been replaced with the `commands` field in sources'), + 'deprecated': True, + }, + ) + x_renpy_archived_window_gui_icon: str | None = Field( + None, + title='Extract the icon from an RPA archive.', + json_schema_extra={ + 'description': ('Extract an icon from an .rpa icon. This is deprecated, and is ' + 'now an alias for force_window_gui_icon'), + 'deprecated': True, + }, + ) @field_validator('x_configure_prologue', mode='before') @classmethod @@ -166,7 +325,7 @@ def _validate_prologue(cls, v: str | None, info: ValidationInfo) -> str | None: file = info.context.get('file') assert isinstance(file, pathlib.PurePath) warnings.warn(f'{file.as_posix()}: [quirks.x_configure_prologue]: use [[sources.archives.commands]] instead', - DeprecationWarning) + DeprecationWarning) return v @@ -193,7 +352,14 @@ def _validate_only_one_icon_override(self) -> Self: class Description(BaseModel): - """The build toml description.""" + """A flatpaker build description for a Ren'Py or RPGMaker MV or MZ game""" + + model_config = ConfigDict( + title='flatpaker', + json_schema_extra={ + '$id': 'https://github.com/dcbaker/flatpaker/flatpaker.schema.json', + }, + ) common: Common appdata: AppData From 789c46d1ffc675a3d57c11884e8fff9c371ccbf7 Mon Sep 17 00:00:00 2001 From: Dylan Baker Date: Sun, 6 Sep 2026 03:59:11 +0000 Subject: [PATCH 8/8] schema: Update from Pydantic models This means the schema always matches the pydantic model. This has nice advantages for staying in sync. --- flatpaker.schema.json | 515 +++++++++++++++++++++++++----------------- 1 file changed, 310 insertions(+), 205 deletions(-) diff --git a/flatpaker.schema.json b/flatpaker.schema.json index de7d66d..66fe146 100644 --- a/flatpaker.schema.json +++ b/flatpaker.schema.json @@ -1,240 +1,356 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com.com/dcbaker/flatpaker/flatpaker.schema.json", - "title": "flatpaker", - "description": "A description of a Ren'py or RPGMaker game to package as a Flatpak", - "type": "object", - "properties": { - "common": { - "description": "Common properties used in the flatpak, appdata, and desktop files.", - "type": "object", + "$defs": { + "AppData": { + "description": "The appdata section of the build toml description.", "properties": { - "name": { - "type": "string", - "description": "The name of the application in proper casing." + "summary": { + "description": "A short description of the game", + "title": "Application summary", + "type": "string" + }, + "description": { + "description": "A longer description of the game.", + "title": "Application description", + "type": "string" + }, + "content_rating": { + "additionalProperties": { + "enum": [ + "none", + "mild", + "moderate", + "intense" + ], + "type": "string" + }, + "description": "Content information for the current game.", + "propertyNames": { + "enum": [ + "drugs-alcohol", + "drugs-narcotics", + "drugs-tobacco", + "language-descrimination", + "language-humor", + "language-profanity", + "money-gambling", + "money-purchasing", + "sex-nudity", + "sex-themes", + "social-audio", + "social-chat", + "social-contacts", + "social-info", + "social-location", + "violence-bloodshed", + "violence-cartoon", + "violence-fantasy", + "violence-realistic", + "violence-sexual" + ] + }, + "title": "OARS Content Rating Information", + "type": "object" + }, + "releases": { + "additionalProperties": { + "type": "string" + }, + "description": "Mapping of releases in the form `\"YYYY-MM-DD\" = \"X.Y.Z\"", + "title": "Release information for this game", + "type": "object" }, + "license": { + "default": "LicenseRef-Proprietary", + "description": "A valid SPDX license Expression for this game", + "title": "SPDX License Expression", + "type": "string" + } + }, + "required": [ + "summary", + "description" + ], + "title": "Application Metadata", + "type": "object" + }, + "Archive": { + "description": "An archive entry in the sources section of the build toml description.", + "properties": { + "path": { + "description": "A local path to the sources. Must be relative to this file, or absolute.", + "format": "path", + "title": "Path to source", + "type": "string" + }, + "sha256": { + "anyOf": [ + { + "maxLength": 64, + "minLength": 64, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Hexedecimal representatio of the SHA256 of this file.", + "title": "SHA256 Hash" + }, + "commands": { + "description": "Shell commands are run immediately after copying this source to the build directory", + "items": { + "type": "string" + }, + "title": "Extra shell commands", + "type": "array" + }, + "strip_components": { + "default": 1, + "description": "number of path elements to strip from patches. Passed to `patch -p N`", + "title": "Patch strip argument", + "type": "integer" + } + }, + "required": [ + "path" + ], + "title": "An archive source", + "type": "object" + }, + "Common": { + "description": "The common section of the build toml description.", + "properties": { "reverse_url": { - "type": "string", - "description": "A reverse root url for the game. For example, com.github.User. The name will be appended automatically" + "description": "A reverse root URL for the game. The Game name will be automatically appended from the name field. This cannot be changed.", + "example": "com.github.user", + "title": "Reverse URL", + "type": "string" + }, + "name": { + "description": "The proper name with punctionation and spaces for this game", + "example": "Soldier's Quest", + "title": "Proper Name", + "type": "string" }, "engine": { - "type": "string", - "enum": ["renpy8", "renpy7", "renpy7-py3", "rpgmaker"], - "description": "What engine this game is for." + "description": "Which engine (and runtime) this game uses", + "enum": [ + "renpy8", + "renpy7", + "renpy7-py3", + "rpgmaker" + ], + "title": "Engine name", + "type": "string" }, "categories": { - "type": "array", - "description": "Valid categories for the game's desktop file. 'Game' is added automatically", + "description": "Valid categories for the game's .destkop file. 'Game' is added automatically", "items": { - "type": "string", "enum": [ - "Adult", "ActionGame", "AdventureGame", "ArcadeGame", "BoardGame", "BlocksGame", - "CardGame", "KidsGame", "LogicGame", "RolePlaying", "Shooter", "Simulation", - "SportsGame", "StrategyGame" - ] - } + "ActionGame", + "Adult", + "AdventureGame", + "Amusement", + "ArcadeGame", + "BlocksGame", + "BoardGame", + "CardGame", + "Emulator", + "KidsGame", + "LogicGame", + "RolePlaying", + "Shooter", + "Simulation", + "SportsGame", + "StrategyGame" + ], + "type": "string" + }, + "title": "Freedesktop Application Categories", + "type": "array" } }, "required": [ - "name", "reverse_url", + "name", "engine" ], - "additionalProperties": false + "title": "common", + "type": "object" }, - "appdata": { - "description": "Application metadata.", - "type": "object", + "File": { + "description": "A file entry in the sources section of the build toml description.", "properties": { - "summary": { - " type": "string" - }, - "description": { + "path": { + "description": "A local path to the sources. Must be relative to this file, or absolute.", + "format": "path", + "title": "Path to source", "type": "string" }, - "content_rating": { - "type": "object", - "properties": { - "drugs-alcohol": { - "$ref": "#/$defs/content_ratings" - }, - "drugs-tobacco": { - "$ref": "#/$defs/content_ratings" - }, - "language-discrimination": { - "$ref": "#/$defs/content_ratings" - }, - "language-humor": { - "$ref": "#/$defs/content_ratings" - }, - "language-profanity": { - "$ref": "#/$defs/content_ratings" - }, - "money-gambling": { - "$ref": "#/$defs/content_ratings" - }, - "money-purchasing": { - "$ref": "#/$defs/content_ratings" - }, - "sex-nudity": { - "$ref": "#/$defs/content_ratings" - }, - "sex-themes": { - "$ref": "#/$defs/content_ratings" - }, - "social-audio": { - "$ref": "#/$defs/content_ratings" + "sha256": { + "anyOf": [ + { + "maxLength": 64, + "minLength": 64, + "type": "string" }, - "social-chat": { - "$ref": "#/$defs/content_ratings" - }, - "social-contacts": { - "$ref": "#/$defs/content_ratings" - }, - "social-info": { - "$ref": "#/$defs/content_ratings" - }, - "social-location": { - "$ref": "#/$defs/content_ratings" - }, - "violence-bloodshed": { - "$ref": "#/$defs/content_ratings" - }, - "violence-cartoon": { - "$ref": "#/$defs/content_ratings" - }, - "violence-fantasy": { - "$ref": "#/$defs/content_ratings" - }, - "violence-realistic": { - "$ref": "#/$defs/content_ratings" - }, - "violence-sexual": { - "$ref": "#/$defs/content_ratings" + { + "type": "null" } - }, - "additionalProperties": false + ], + "default": null, + "description": "Hexedecimal representatio of the SHA256 of this file.", + "title": "SHA256 Hash" }, - "releases": { - "type": "object", - "description": "A list of releases in the from `version : date`. Date should be YYYY-MM-DD", - "additionalProperties": { - "type": "string" - } - }, - "license": { - "description": "An SPDX license expression. If unset will default to proprietary", + "dest": { + "default": "game", + "description": "Relative path to install this file to.", + "title": "Installation location", "type": "string" + }, + "commands": { + "description": "Shell commands are run immediately after copying this source to the build directory", + "items": { + "type": "string" + }, + "title": "Extra shell commands", + "type": "array" } }, "required": [ - "summary", - "description" + "path" ], - "additionalProperties": false + "title": "A single file type source", + "type": "object" }, - "sources": { - "description": "Optionally, sources for this description.", - "type": "object", + "Patch": { + "description": "A patch entry in the sources section of the build toml description.", "properties": { - "archives": { - "description": "A list of archives used to build the project", - "type": "array", - "items": { - "type": "object", - "properties": { - "path": { - "type": "string" - }, - "strip_components": { - "type": "number" - }, - "sha256": { - "type": "string", - "description": "Optionally, a sha256 checksum. This will be generated if not provided" - }, - "commands": { - "type": "array", - "items": "string" - } - }, - "required": [ - "path" - ], - "additionalProperties": false - } + "path": { + "description": "A local path to the sources. Must be relative to this file, or absolute.", + "format": "path", + "title": "Path to source", + "type": "string" }, - "files": { - "description": "Single file sources", - "type": "array", - "items": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "The Path to the file" - }, - "dest": { - "type": "string", - "description": "Where to install the file. .rpy files are automatically merged into the game directory, other files likely need an explicit destination" - }, - "sha256": { - "type": "string", - "description": "Optionally, a sha256 checksum. This will be generated if not provided" - }, - "commands": { - "type": "array", - "items": "string" - } + "sha256": { + "anyOf": [ + { + "maxLength": 64, + "minLength": 64, + "type": "string" }, - "required": [ - "path" - ], - "additionalProperties": false - } + { + "type": "null" + } + ], + "default": null, + "description": "Hexedecimal representatio of the SHA256 of this file.", + "title": "SHA256 Hash" }, - "patches": { - "description": "Unix patch files to apply", - "type": "array", - "items": { - "type": "object", - "properties": { - "path": { - "type": "string" - }, - "strip_components": { - "type": "number" - } - }, - "required": [ - "path" - ], - "additionalProperties": false - } + "strip_components": { + "default": 1, + "description": "number of path elements to strip from patches. Passed to `patch -p N`", + "title": "Patch strip argument", + "type": "integer" } }, "required": [ - "archives" + "path" ], - "additionalProperties": false + "title": "A patch source", + "type": "object" }, - "quirks": { - "description": "Workarounds for the quirks in games that cannot be easily resolved without flatpaker support.", - "type": "object", + "Quirks": { + "description": "Quirks of this build that have built-in wokarounds.\n\nThere are a number of common issues, particularly in Ren'Py games that\nflatpaker provides build steps to deal with.", "properties": { "force_window_gui_icon": { - "description": "For Ren'Py only. Use the window_gui.png file for the icon instead of extracting from the .exe or icns files.", + "default": false, + "description": "Normally icons from a .exe or .icns if they are avilable, however, some games do not customize these icon sources but do provide a customized incon in the img directory. Setting this to true forces the use of that icon.", + "title": "Force use of img/windows_gui/icon.png", "type": "boolean" }, "x_configure_prologue": { - "description": "A shell snippet to be run before any of the automated build steps. Because sometimes you just need an escape hatch", - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "deprecated": true, + "description": "Extra shell commands to run after unpacking sources. This has been replaced with the `commands` field in sources", + "title": "Commands to run after unpacking all sources" }, "x_renpy_archived_window_gui_icon": { - "description": "Extract a windows_gui.png icon from the named .rpa", - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "deprecated": true, + "description": "Extract an icon from an .rpa icon. This is deprecated, and is now an alias for force_window_gui_icon", + "title": "Extract the icon from an RPA archive." } - } + }, + "title": "Quirks of this game to work around", + "type": "object" + }, + "Sources": { + "description": "The sources section of the build toml description.", + "properties": { + "archives": { + "description": "Archive type sources, such as .7z, .zip, .tar.gz, etc.", + "items": { + "$ref": "#/$defs/Archive" + }, + "title": "Archive Sources", + "type": "array" + }, + "patches": { + "description": "Patches to be applied after all archives are unpacked", + "items": { + "$ref": "#/$defs/Patch" + }, + "title": "Patch sources", + "type": "array" + }, + "files": { + "description": "extra files to add the build directory", + "items": { + "$ref": "#/$defs/File" + }, + "title": "source file", + "type": "array" + } + }, + "required": [ + "archives" + ], + "title": "The sources for this build", + "type": "object" + } + }, + "$id": "https://github.com/dcbaker/flatpaker/flatpaker.schema.json", + "description": "A flatpaker build description for a Ren'Py or RPGMaker MV or MZ game", + "properties": { + "common": { + "$ref": "#/$defs/Common" + }, + "appdata": { + "$ref": "#/$defs/AppData" + }, + "quirks": { + "$ref": "#/$defs/Quirks" + }, + "sources": { + "$ref": "#/$defs/Sources" } }, "required": [ @@ -242,17 +358,6 @@ "appdata", "sources" ], - "additionalProperties": false, - "$defs": { - "content_ratings": { - "type": "string", - "enum": [ - "unknown", - "none", - "mild", - "moderate", - "intense" - ] - } - } -} + "title": "flatpaker", + "type": "object" +} \ No newline at end of file