Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
9d95d97
Add changelog
nvdaes Aug 26, 2025
4a7f9b4
Add changelog to json schema
nvdaes Sep 25, 2025
5427327
Merge remote-tracking branch 'origin/main' into changelog
nvdaes Sep 25, 2025
23e4bb4
Address code review
nvdaes Sep 25, 2025
3ece59f
Update _validate/addonVersion_schema.json
seanbudd Sep 25, 2025
1f5dc75
Apply suggestions from code review
nvdaes Sep 25, 2025
d9f3f49
Address review
nvdaes Sep 25, 2025
f2efd30
Address review
nvdaes Sep 25, 2025
b0754e6
Assign None to changelog when appropriate
nvdaes Sep 25, 2025
9835dd8
Merge
nvdaes Oct 1, 2025
07d493f
Pre-commit auto-fix
pre-commit-ci[bot] Oct 1, 2025
4dcd7df
Merge branch 'main' into changelog
nvdaes Oct 2, 2025
b921d6f
Fix
nvdaes Oct 2, 2025
ff43517
Fix
nvdaes Oct 2, 2025
aaf2b85
Remove unassigned addonData
nvdaes Oct 2, 2025
8b1ed2a
Fix test
nvdaes Oct 2, 2025
38da96d
Fix error message
nvdaes Oct 2, 2025
ecb8957
harmonize test for changelog with homepage
nvdaes Oct 2, 2025
098a480
Fix test
nvdaes Oct 2, 2025
cbe5d18
Remove function
nvdaes Oct 2, 2025
c93e4a5
Commend python preference to test locally
nvdaes Oct 2, 2025
a0b53f8
Lint fixes
nvdaes Oct 2, 2025
a35e5db
Add translated changelog optional and fix linting errors by casting t…
nvdaes Oct 3, 2025
3529858
Fix pyproject
nvdaes Oct 4, 2025
c816603
Add ignore type comments
nvdaes Oct 4, 2025
288ea2f
Add ignore type comment
nvdaes Oct 4, 2025
40274dd
Address review
nvdaes Oct 7, 2025
f776545
Address review
nvdaes Oct 7, 2025
ebe905a
Remove accidentally committed files
nvdaes Oct 7, 2025
75d6e0d
Try to ensure that homepage and changelog are str or None
nvdaes Oct 7, 2025
bb03c83
Address feedback
nvdaes Oct 7, 2025
9a044f7
Merge remote-tracking branch 'nvaccess/main' into changelog
nvdaes Oct 8, 2025
cd3e0a4
Apply suggestions from code review
seanbudd Oct 10, 2025
0064120
Apply suggestions from code review
seanbudd Oct 10, 2025
d0af4bd
Apply suggestions from code review
seanbudd Oct 10, 2025
438f427
Update _validate/createJson.py
seanbudd Oct 10, 2025
ffbfe0a
Apply review suggestions
nvdaes Oct 10, 2025
7fd4b0c
Update createJson casting fields to str according to schema
nvdaes Oct 13, 2025
3385e8f
Cast optionals in validate
nvdaes Oct 13, 2025
98f2703
Cast translated changelog in regenerate translations to ensure that i…
nvdaes Oct 13, 2025
92b193f
Add null type to json schema
nvdaes Oct 14, 2025
2d838ae
Update _validate/validate.py
nvdaes Oct 15, 2025
c000bff
Update regenerateTranslations
nvdaes Oct 15, 2025
89e3ba7
Pre-commit auto-fix
pre-commit-ci[bot] Oct 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions _validate/addonManifest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ class AddonManifest(ConfigObj):
# Long description with further information and instructions
description = string(default=None)

# Document changes between the previous and the current versions.
changelog = string(default=None)

# Name of the author or entity that created the add-on
author = string()

Expand Down Expand Up @@ -89,8 +92,8 @@ def __init__(self, input: str | TextIOBase, translatedInput: str | None = None):
self._translatedConfig = None
if translatedInput is not None:
self._translatedConfig = ConfigObj(translatedInput, encoding="utf-8", default_encoding="utf-8")
for key in ("summary", "description"):
val: str = self._translatedConfig.get(key) # type: ignore[reportUnknownMemberType]
for key in ("summary", "description", "changelog"):
val: str | None = self._translatedConfig.get(key) # type: ignore[reportUnknownMemberType]
if val:
self[key] = val

Expand Down
37 changes: 30 additions & 7 deletions _validate/addonVersion_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,13 @@
"sourceURL": "https://github.com/nvaccess/addon-datastore/",
"license": "GPL v2",
"licenseURL": "https://github.com/nvaccess/addon-datastore/license.MD",
"changelog": "New features",
"translations": [
{
"language": "de",
"displayName": "Mein Addon",
"description": "erleichtert das Durchführen von xyz"
"description": "erleichtert das Durchführen von xyz",
"changelog": "Neue Funktionen"
}
],
"reviewUrl": "https://github.com/nvaccess/addon-datastore/discussions/1942#discussioncomment-7453248",
Expand Down Expand Up @@ -124,7 +126,7 @@
"Makes doing XYZ easier"
],
"title": "The description (en) of the addon",
"type": "string"
"type": ["string", "null"]
},
"homepage": {
"$id": "#/properties/homepage",
Expand All @@ -135,7 +137,7 @@
],
"pattern": "^https:.*",
"title": "The homepage URL for the addon.",
"type": "string"
"type": ["string", "null"]
},
"minNVDAVersion": {
"$ref": "#/$defs/canonicalVersion",
Expand Down Expand Up @@ -226,7 +228,17 @@
"https://github.com/nvaccess/addon-datastore/license.MD"
],
"title": "The URL of the license",
"type": "string"
"type": ["string", "null"]
},
"changelog": {
"$id": "#/properties/changelog",
"default": "",
"description": "Changes between the previous and the current version",
"examples": [
"New feature"
],
"title": "Add-on changelog (en)",
"type": ["string", "null"]
},
"legacy": {
"$id": "#/properties/legacy",
Expand All @@ -247,7 +259,8 @@
{
"language": "de",
"displayName": "Mein Addon",
"description": "erleichtert das Durchführen von xyz"
"description": "erleichtert das Durchführen von xyz",
"changelog": "Neue Funktionen"
}
]
],
Expand Down Expand Up @@ -357,7 +370,8 @@
{
"language": "de",
"displayName": "Mein Addon",
"description": "erleichtert das Durchführen von xyz"
"description": "erleichtert das Durchführen von xyz",
"changelog": "Neue Funktionen"
}
],
"required": [
Expand Down Expand Up @@ -392,7 +406,16 @@
"erleichtert das Durchführen von xyz"
],
"title": "The translated description",
"type": "string"
"type": ["string", "null"]
},
"changelog": {
"default": "",
"description": "Translated description of changes between the previous and this add-on version",
"examples": [
"Neue Funktionen"
],
"title": "The translated changelog",
"type": ["string", "null"]
}
}
}
Expand Down
26 changes: 21 additions & 5 deletions _validate/createJson.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class AddonData:
addonId: str
displayName: str
URL: str
description: str
description: str | None
sha256: str
addonVersionName: str
addonVersionNumber: dict[str, int]
Expand All @@ -32,9 +32,10 @@ class AddonData:
sourceURL: str
license: str
homepage: str | None
changelog: str | None
licenseURL: str | None
submissionTime: int
translations: list[dict[str, str]]
translations: list[dict[str, str | None]]


def getSha256(addonPath: str) -> str:
Expand Down Expand Up @@ -108,17 +109,31 @@ def _createDataclassMatchingJsonSchema(

# Add optional fields
homepage: str | None = manifest.get("url") # type: ignore[reportUnknownMemberType]
if not homepage or homepage == "None":
if homepage == "None":
# The config default is None
# which is parsed by configobj as a string not a NoneType
homepage = None

translations: list[dict[str, str]] = []
changelog: str | None = manifest.get("changelog") # type: ignore[reportUnknownMemberType]
if changelog == "None":
# The config default is None
# which is parsed by configobj as a string not a NoneType
changelog = None
translations: list[dict[str, str | None]] = []
for langCode, translatedManifest in getAddonManifestLocalizations(manifest):
# Add optional translated changelog.
translatedChangelog: str | None = translatedManifest.get("changelog") # type: ignore[reportUnknownMemberType]
if translatedChangelog == "None":
# The config default is None
# which is parsed by configobj as a string not a NoneType
translatedChangelog = None

try:
translations.append(
{
"language": langCode,
"displayName": cast(str, translatedManifest["summary"]),
"description": cast(str, translatedManifest["description"]),
"changelog": translatedChangelog,
},
)
except KeyError as e:
Expand All @@ -141,6 +156,7 @@ def _createDataclassMatchingJsonSchema(
sourceURL=sourceUrl,
license=licenseName,
homepage=homepage,
changelog=changelog,
licenseURL=licenseUrl,
submissionTime=getCurrentTime(),
translations=translations,
Expand Down
12 changes: 11 additions & 1 deletion _validate/regenerateTranslations.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,23 @@ def regenerateJsonFile(filePath: str, errorFilePath: str | None) -> None:
with open(errorFilePath, "w") as errorFile:
errorFile.write(f"Validation Errors:\n{manifest.errors}")
return

changelog = manifest.get("changelog") # type: ignore[reportUnknownMemberType]
if changelog == "None":
# The config default is None
# which is parsed by configobj as a string not a NoneType
changelog = None
for langCode, manifest in getAddonManifestLocalizations(manifest):
translatedChangelog = manifest.get("changelog") # type: ignore[reportUnknownMemberType]
if translatedChangelog == "None":
# The config default is None
# which is parsed by configobj as a string not a NoneType
translatedChangelog = None
addonData["translations"].append(
{
"language": langCode,
"displayName": manifest["summary"],
"description": manifest["description"],
"changelog": translatedChangelog,
},
)

Expand Down
14 changes: 14 additions & 0 deletions _validate/validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,19 @@ def checkDescriptionMatches(manifest: AddonManifest, submission: JsonObjT) -> Va
)


def checkChangelogMatches(manifest: AddonManifest, submission: JsonObjT) -> ValidationErrorGenerator:
"""The submission changelog must match the *.nvda-addon manifest changelog field."""
changelog = manifest.get("changelog") # type: ignore[reportUnknownMemberType]
if changelog == "None":
# The config default is None which is parsed by configobj as a string not a NoneType
changelog = None
if changelog != submission.get("changelog"):
yield (
f"Submission 'changelog' must be set to '{changelog}' "
f"in json file instead of {submission.get('changelog')}"
)


def checkUrlMatchesHomepage(manifest: AddonManifest, submission: JsonObjT) -> ValidationErrorGenerator:
"""The submission homepage must match the *.nvda-addon manifest url field."""
manifestUrl = manifest.get("url") # type: ignore[reportUnknownMemberType]
Expand Down Expand Up @@ -332,6 +345,7 @@ def validateSubmission(submissionFilePath: str, verFilename: str) -> ValidationE
manifest = getAddonManifest(addonDestPath)
yield from checkSummaryMatchesDisplayName(manifest, submissionData)
yield from checkDescriptionMatches(manifest, submissionData)
yield from checkChangelogMatches(manifest, submissionData)
yield from checkUrlMatchesHomepage(manifest, submissionData)
yield from checkAddonId(manifest, submissionFilePath, submissionData)
yield from checkMinNVDAVersionMatches(manifest, submissionData)
Expand Down
1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ reportMissingTypeStubs = false

[tool.uv]
default-groups = "all"
python-preference = "only-system"
environments = ["sys_platform == 'win32'"]
required-version = ">=0.8"

Expand Down
3 changes: 2 additions & 1 deletion tests/testData/addons/fake/13.0.0.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"displayName": "mock addon",
"description": "The description for the addon",
"changelog": "Changes for this add-on version",
"homepage": "https://nvaccess.org",
"publisher": "Name of addon author or organisation",
"minNVDAVersion": {
Expand All @@ -23,7 +24,7 @@
"channel": "stable",
"URL": "https://github.com/nvaccess/dont/use/this/address/fake.nvda-addon",
"sha256-comment": "SHA for the fake.nvda-addon file",
"sha256": "e27fa778cb99f83ececeb0bc089033929eab5a2fa475ce63e68f50b03b6ab585",
"sha256": "50a8011a807665bcb8fdd177c276fef3b3f7f754796c5990ebe14e80c28b14ef",
"sourceURL": "https://github.com/nvaccess/dont/use/this/address",
"license": "GPL v2",
"licenseURL": "https://www.gnu.org/licenses/gpl-2.0.html",
Expand Down
Binary file modified tests/testData/fake.nvda-addon
Binary file not shown.
1 change: 1 addition & 0 deletions tests/testData/manifest.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name = fake
summary = "mock addon"
description = """The description for the addon"""
changelog = """Changes for this add-on version"""
author = "Name of addon author or organisation"
url = https://nvaccess.org
version = 13.0
Expand Down
1 change: 1 addition & 0 deletions tests/test_createJson.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def test_validVersion(self):
sourceURL="https://example.com",
license="GPL v2",
homepage="https://example.com",
changelog="""Changes for this add-on version""",
licenseURL="https://www.gnu.org/licenses/gpl-2.0.html",
submissionTime=createJson.getCurrentTime(),
translations=[],
Expand Down
28 changes: 27 additions & 1 deletion tests/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def test_missingHTTPsAndExt(self):
class Validate_checkSha256(unittest.TestCase):
"""Tests for the checkSha256 function"""

validSha = "e27fa778cb99f83ececeb0bc089033929eab5a2fa475ce63e68f50b03b6ab585"
validSha = "50a8011a807665bcb8fdd177c276fef3b3f7f754796c5990ebe14e80c28b14ef"

def test_valid(self):
errors = validate.checkSha256(ADDON_PACKAGE, expectedSha=self.validSha.upper())
Expand Down Expand Up @@ -152,6 +152,32 @@ def test_invalid(self):
)


class Validate_checkChangelogMatches(unittest.TestCase):
def setUp(self):
self.submissionData = getValidAddonSubmission()
self.manifest = getAddonManifest()

def test_valid(self):
errors = list(
validate.checkChangelogMatches(self.manifest, self.submissionData),
)
self.assertEqual(errors, [])

def test_invalid(self):
badChangelog = "bad changelog"
self.submissionData["changelog"] = badChangelog
errors = list(
validate.checkChangelogMatches(self.manifest, self.submissionData),
)
self.assertEqual(
errors,
[
f"Submission 'changelog' must be set to '{self.manifest['changelog']}' in json file"
f" instead of {badChangelog}",
],
)


class Validate_checkAddonId(unittest.TestCase):
"""
Manifest 'name' considered source of truth for addonID
Expand Down
Loading