Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 0 additions & 4 deletions .github/scripts/build_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,6 @@ def main() -> None:
"version": version,
"download_url": download_url,
}
icon_url = meta.get("icon_url", "")
if isinstance(icon_url, str) and icon_url:
content["icon_url"] = icon_url

if "homepage_url" in meta:
content["homepage_url"] = meta["homepage_url"]

Expand Down
7 changes: 4 additions & 3 deletions .github/scripts/test_build_payload.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def setUp(self):
"manifest_version": 1, "id": "example_module", "type": "module",
"name": "Example", "description": "Example module", "version": "1.2.3",
"author": {"name": "Author", "url": "https://example.com"},
"license": "MIT OR Apache-2.0", "icon_url": "assets/icon.svg",
"license": "MIT OR Apache-2.0",
"requires": {"fossbilling": "^0.8.9 || ^1.0", "php": ">=8.3 <9", "modules": {"branding": "^1.2"}},
}
(self.package / "README.md").write_text("# Example\n")
Expand Down Expand Up @@ -61,11 +61,12 @@ def test_preserves_manifest_metadata_and_compound_requirements(self):
self.assertEqual(create["readme"], "# Example\n")
self.assertEqual(create, {**update, "identifier": "example_module"})

def test_optional_homepage_and_icon(self):
def test_optional_homepage(self):
self.meta["homepage_url"] = "https://example.com"
del self.meta["icon_url"]
create, _ = self.build()
self.assertEqual(create["homepage_url"], "https://example.com")
# icon_url is gone from the payload contract: icons ship as
# `<Name>/icon.<ext>` files and are not directory metadata.
self.assertNotIn("icon_url", create)

def test_reruns_replace_the_same_version_and_preserve_other_requirements(self):
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/schema-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
run: composer install --no-progress --no-interaction --working-dir=tools

- name: Validate Manifest
run: php tools/validate.php
run: tools/bin/console manifest:validate

- name: Upload Manifest Schema to R2
env:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
run: composer install --no-progress --no-interaction --working-dir=tools

- name: Validate Manifest
run: php tools/validate.php
run: tools/bin/console manifest:validate

- name: Test release tooling
run: python3 -m unittest discover -s .github/scripts -p 'test_*.py'
1 change: 0 additions & 1 deletion Branding/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@
"author": {"name": "FOSSBilling", "url": "https://fossbilling.org"},
"license": "Apache-2.0",
"homepage_url": "https://fossbilling.org",
"icon_url": "icon.svg",
"requires": {"fossbilling": ">=0.8.9", "php": ">=8.3", "modules": {}}
}
1 change: 0 additions & 1 deletion Cookieconsent/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,5 @@
"author": {"name": "FOSSBilling", "url": "https://fossbilling.org"},
"license": "Apache-2.0",
"homepage_url": "https://fossbilling.org",
"icon_url": "icon.svg",
"requires": {"fossbilling": ">=0.8.9", "php": ">=8.3", "modules": {}}
}
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,22 @@ layout (`Branding/` for id `branding`):
Branding/
manifest.json # module manifest, v1 spec (required)
README.md # becomes the extensions directory readme (required)
Service.php, Api/, Controller/, templates/, icon.svg, ...
icon.svg # optional package icon; at most one of icon.svg, icon.png, icon.webp, icon.avif
Service.php, Api/, Controller/, templates/, ...
```

`manifest.json` follows the v1 spec (`manifest_version: 1`,
`type: "module"`). The canonical spec is
[`manifest.schema.json`](./manifest.schema.json); every package is
validated against it (plus semantic checks in `tools/validate.php`) by CI.
validated against it (plus semantic checks via `tools/bin/console manifest:validate`) by CI.

## Releasing

Tag the release `<extension-id>-v<semver>` (e.g. `branding-v0.8.9`, version
matching the package's `manifest.json`) and attach the distributable
`.zip` as a release asset. The zip must contain the package directory
contents at the archive root, with no wrapper folder, so it installs as-is.
Publishing a release runs `.github/workflows/publish.yml`, which sends the manifest v1 fields directly to the extensions API and approves the new revision. `type: "module"`, string licenses, optional `homepage_url`, package-relative `icon_url`, author metadata, and the complete `requires` object are preserved. A manual run is available via `workflow_dispatch` with the same tag. Both triggers read package files from that tag, while using publisher tooling from the reviewed workflow ref.
Publishing a release runs `.github/workflows/publish.yml`, which sends the manifest v1 fields directly to the extensions API and approves the new revision. `type: "module"`, string licenses, optional `homepage_url`, author metadata, and the complete `requires` object are preserved. A manual run is available via `workflow_dispatch` with the same tag. Both triggers read package files from that tag, while using publisher tooling from the reviewed workflow ref.

FOSSBilling, PHP, and module constraints are retained verbatim, including compound Composer ranges. Each release stores its own requirements. Deploy the API manifest migration and matching extensions site before running this publisher; it refuses history without `requires` instead of discarding compatibility information.

Expand Down
1 change: 0 additions & 1 deletion manifest.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
},
"license": {"type": "string", "minLength": 1, "pattern": "^[A-Za-z0-9.\\-+_:() |]+$"},
"homepage_url": {"type": "string", "pattern": "^https?://"},
"icon_url": {"type": "string", "pattern": "^(?!/)(?![a-zA-Z][a-zA-Z0-9+.-]*:)(?!.*\\.\\.)[^\\\\]+$"},
"requires": {
"type": "object", "required": ["fossbilling", "php"], "additionalProperties": false,
"properties": {
Expand Down
25 changes: 25 additions & 0 deletions tools/bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

/**
* Copyright 2022-2026 FOSSBilling
* SPDX-License-Identifier: Apache-2.0.
*
* @copyright FOSSBilling (https://www.fossbilling.org)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
*/

require dirname(__DIR__) . '/bootstrap.php';

use FOSSBilling\Tools\Manifest\Command\ValidateCommand;
use Symfony\Component\Console\Application;

$repoRoot = dirname(__DIR__, 2);

$application = new Application('FOSSBilling modules tools');
$application->addCommands([
new ValidateCommand($repoRoot, $repoRoot . '/manifest.schema.json'),
]);
$application->run();
17 changes: 17 additions & 0 deletions tools/bootstrap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

/**
* Copyright 2022-2026 FOSSBilling
* SPDX-License-Identifier: Apache-2.0.
*
* @copyright FOSSBilling (https://www.fossbilling.org)
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache-2.0
*/
$autoload = __DIR__ . '/vendor/autoload.php';
if (!is_file($autoload)) {
fwrite(STDERR, "Install FOSSBilling tooling dependencies: composer install --working-dir=tools\n");
exit(1);
}
require_once $autoload;
28 changes: 23 additions & 5 deletions tools/composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,24 @@
{
"name": "fossbilling/modules-tools",
"description": "Manifest validation tooling (CI + local)",
"require": {"php": ">=8.3", "composer/semver": "^3.5", "composer/spdx-licenses": "^1.5", "justinrainbow/json-schema": "^6.0"},
"config": {"sort-packages": true}
}
"name": "fossbilling/modules-tools",
"description": "FOSSBilling modules repository tooling.",
"license": "Apache-2.0",
"require": {
"php": "~8.5.0",
"composer/semver": "^3.5",
"composer/spdx-licenses": "^1.5",
"justinrainbow/json-schema": "^6.0",
"symfony/console": "^8.1",
"symfony/finder": "^8.1"
},
"autoload": {
"psr-4": {
"FOSSBilling\\Tools\\": "src/"
}
},
"config": {
"sort-packages": true,
"platform": {
"php": "8.5.0"
}
}
}
Loading
Loading