diff --git a/ts/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.ts b/ts/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.ts index 9e84a1c25e9..36bdc3cfd39 100644 --- a/ts/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.ts +++ b/ts/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.ts @@ -4,6 +4,7 @@ import { AjaxCallbackSetup, AjaxResponseException } from "../../../../Ajax/Data" import { DialogCallbackSetup } from "../../../../Ui/Dialog/Data"; import { dialogFactory } from "../../../../Component/Dialog"; import { showDefaultSuccessSnackbar } from "WoltLabSuite/Core/Component/Snackbar"; +import { syncVersion } from "WoltLabSuite/Core/Api/Devtools/Projects/SyncVersion"; interface PipData { dependencies: string[]; @@ -37,6 +38,7 @@ class AcpUiDevtoolsProjectSync { private readonly pips: PipData[] = []; private readonly projectId: number; private queue: PendingPip[] = []; + #syncVersionAfterCompletion = false; constructor(projectId: number) { this.projectId = projectId; @@ -162,6 +164,7 @@ class AcpUiDevtoolsProjectSync { } this.buttonSyncAll.classList.add("disabled"); + this.#syncVersionAfterCompletion = true; this.queue = []; this.pips.forEach((pip) => { @@ -174,9 +177,23 @@ class AcpUiDevtoolsProjectSync { private syncNext(): void { if (this.queue.length === 0) { - this.buttonSyncAll.classList.remove("disabled"); - - showDefaultSuccessSnackbar(); + const showSuccess = () => { + this.buttonSyncAll.classList.remove("disabled"); + + showDefaultSuccessSnackbar(); + }; + + if (this.#syncVersionAfterCompletion) { + void this.#syncPackageVersion() + .then(() => { + showSuccess(); + }) + .finally(() => { + this.#syncVersionAfterCompletion = false; + }); + } else { + showSuccess(); + } return; } @@ -189,6 +206,10 @@ class AcpUiDevtoolsProjectSync { return `${pluginName}-${target}`; } + async #syncPackageVersion(): Promise { + await syncVersion(this.projectId); + } + _ajaxSuccess(data: AjaxResponse): void { const identifier = this.getButtonIdentifier(data.returnValues.pluginName, data.returnValues.target); this.buttons.get(identifier)!.disabled = false; diff --git a/ts/WoltLabSuite/Core/Api/Devtools/Projects/SyncVersion.ts b/ts/WoltLabSuite/Core/Api/Devtools/Projects/SyncVersion.ts new file mode 100644 index 00000000000..7cd3a38ee30 --- /dev/null +++ b/ts/WoltLabSuite/Core/Api/Devtools/Projects/SyncVersion.ts @@ -0,0 +1,19 @@ +/** + * Synchronizes the version of an installed package with the version number in + * the `package.xml` of the project. + * + * @author Alexander Ebert + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.2 + * @woltlabExcludeBundle all + */ + +import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend"; +import { fromInfallibleApiRequest } from "../../Result"; + +export async function syncVersion(projectId: number): Promise<[]> { + return fromInfallibleApiRequest(() => { + return prepareRequest(`${window.WSC_RPC_API_URL}core/devtools/projects/${projectId}/sync-version`).post().fetchAsJson(); + }); +} diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js index 1fbedfa13fb..b58b8d0a47c 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js @@ -1,4 +1,4 @@ -define(["require", "exports", "tslib", "../../../../Ajax", "../../../../Language", "../../../../Component/Dialog", "WoltLabSuite/Core/Component/Snackbar"], function (require, exports, tslib_1, Ajax, Language, Dialog_1, Snackbar_1) { +define(["require", "exports", "tslib", "../../../../Ajax", "../../../../Language", "../../../../Component/Dialog", "WoltLabSuite/Core/Component/Snackbar", "WoltLabSuite/Core/Api/Devtools/Projects/SyncVersion"], function (require, exports, tslib_1, Ajax, Language, Dialog_1, Snackbar_1, SyncVersion_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.init = init; @@ -12,6 +12,7 @@ define(["require", "exports", "tslib", "../../../../Ajax", "../../../../Language pips = []; projectId; queue = []; + #syncVersionAfterCompletion = false; constructor(projectId) { this.projectId = projectId; const restrictedSync = document.getElementById("syncShowOnlyMatches"); @@ -108,6 +109,7 @@ define(["require", "exports", "tslib", "../../../../Ajax", "../../../../Language return; } this.buttonSyncAll.classList.add("disabled"); + this.#syncVersionAfterCompletion = true; this.queue = []; this.pips.forEach((pip) => { pip.targets.forEach((target) => { @@ -118,8 +120,22 @@ define(["require", "exports", "tslib", "../../../../Ajax", "../../../../Language } syncNext() { if (this.queue.length === 0) { - this.buttonSyncAll.classList.remove("disabled"); - (0, Snackbar_1.showDefaultSuccessSnackbar)(); + const showSuccess = () => { + this.buttonSyncAll.classList.remove("disabled"); + (0, Snackbar_1.showDefaultSuccessSnackbar)(); + }; + if (this.#syncVersionAfterCompletion) { + void this.#syncPackageVersion() + .then(() => { + showSuccess(); + }) + .finally(() => { + this.#syncVersionAfterCompletion = false; + }); + } + else { + showSuccess(); + } return; } const next = this.queue.shift(); @@ -128,6 +144,9 @@ define(["require", "exports", "tslib", "../../../../Ajax", "../../../../Language getButtonIdentifier(pluginName, target) { return `${pluginName}-${target}`; } + async #syncPackageVersion() { + await (0, SyncVersion_1.syncVersion)(this.projectId); + } _ajaxSuccess(data) { const identifier = this.getButtonIdentifier(data.returnValues.pluginName, data.returnValues.target); this.buttons.get(identifier).disabled = false; diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Devtools/Projects/SyncVersion.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Devtools/Projects/SyncVersion.js new file mode 100644 index 00000000000..ade15f34585 --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Devtools/Projects/SyncVersion.js @@ -0,0 +1,20 @@ +/** + * Synchronizes the version of an installed package with the version number in + * the `package.xml` of the project. + * + * @author Alexander Ebert + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.2 + * @woltlabExcludeBundle all + */ +define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../../Result"], function (require, exports, Backend_1, Result_1) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.syncVersion = syncVersion; + async function syncVersion(projectId) { + return (0, Result_1.fromInfallibleApiRequest)(() => { + return (0, Backend_1.prepareRequest)(`${window.WSC_RPC_API_URL}core/devtools/projects/${projectId}/sync-version`).post().fetchAsJson(); + }); + } +}); diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php index c903d76b659..589a84ac347 100644 --- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php +++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php @@ -152,6 +152,7 @@ static function (\wcf\event\endpoint\ControllerCollecting $event) { $event->register(new \wcf\system\endpoint\controller\core\comments\responses\RenderResponse()); $event->register(new \wcf\system\endpoint\controller\core\comments\responses\RenderResponses()); $event->register(new \wcf\system\endpoint\controller\core\comments\responses\UpdateResponse()); + $event->register(new \wcf\system\endpoint\controller\core\devtools\projects\SyncVersion()); $event->register(new \wcf\system\endpoint\controller\core\exceptions\RenderException()); $event->register(new \wcf\system\endpoint\controller\core\gridViews\GetRows()); $event->register(new \wcf\system\endpoint\controller\core\gridViews\GetGridView()); diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/devtools/projects/SyncVersion.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/devtools/projects/SyncVersion.class.php new file mode 100644 index 00000000000..ba4b303cd82 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/devtools/projects/SyncVersion.class.php @@ -0,0 +1,68 @@ + + * @since 6.2 + */ +#[PostRequest('/core/devtools/projects/{id:\d+}/sync-version')] +final class SyncVersion implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $project = Helper::fetchObjectFromRequestParameter($variables['id'], DevtoolsProject::class); + + $this->assertProjectCanBeManaged(); + + $package = $project->getPackage(); + if ($package === null) { + throw new UserInputException('projectID'); + } + + $this->syncVersion($project, $package); + + return new JsonResponse([]); + } + + private function syncVersion(DevtoolsProject $project, Package $package): void + { + $projectVersion = $project->getPackageArchive()->getPackageInfo('version'); + if (Package::compareVersion($package->packageVersion, $projectVersion, '>=')) { + return; + } + + (new PackageEditor($package))->update([ + 'packageVersion' => $projectVersion, + ]); + + PackageCacheBuilder::getInstance()->reset(); + } + + private function assertProjectCanBeManaged(): void + { + if (!ENABLE_DEVELOPER_TOOLS || !WCF::getSession()->getPermission('admin.configuration.package.canInstallPackage')) { + throw new PermissionDeniedException(); + } + } +}