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
90 changes: 6 additions & 84 deletions ts/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,6 @@ 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[];
pluginName: string;
targets: string[];
}

type PendingPip = [string, string];

interface AjaxResponse {
Expand All @@ -36,14 +30,15 @@ class AcpUiDevtoolsProjectSync {
private readonly buttonStatus = new Map<string, HTMLElement>();
private readonly buttonSyncAll: HTMLButtonElement = document.getElementById("devtoolsSyncAll") as HTMLButtonElement;
private readonly container = document.getElementById("syncPipMatches")!;
private readonly pips: PipData[] = [];
readonly #fullSyncQueue: PendingPip[];
private readonly projectId: number;
private queue: PendingPip[] = [];
#allowUnsafe = false;
#syncVersionAfterCompletion = false;

constructor(projectId: number) {
constructor(projectId: number, fullSyncQueue: PendingPip[]) {
this.projectId = projectId;
this.#fullSyncQueue = fullSyncQueue;

const restrictedSync = document.getElementById("syncShowOnlyMatches") as HTMLInputElement;
restrictedSync.addEventListener("change", () => {
Expand All @@ -59,20 +54,15 @@ class AcpUiDevtoolsProjectSync {
// Browsers may restore the checked state on reload.
updateUnsafePips();

const existingPips: string[] = [];
const knownPips: string[] = [];
const tmpPips: PipData[] = [];
this.container
.querySelectorAll(".jsHasPipTargets:not(.jsSkipTargetDetection)")
.forEach((pip: HTMLTableRowElement) => {
const pluginName = pip.dataset.pluginName!;
const targets: string[] = [];

this.container
.querySelectorAll(`.jsHasPipTargets[data-plugin-name="${pluginName}"] .jsInvokePip`)
.forEach((button: HTMLButtonElement) => {
const target = button.dataset.target!;
targets.push(target);

button.addEventListener("click", (event) => {
event.preventDefault();
Expand All @@ -93,71 +83,8 @@ class AcpUiDevtoolsProjectSync {
) as HTMLElement,
);
});

// Unsafe PIPs must never be part of a full sync.
if (pip.dataset.isUnsafe === "true") {
return;
}

const data: PipData = {
dependencies: JSON.parse(pip.dataset.syncDependencies!),
pluginName,
targets,
};

if (data.dependencies.length > 0) {
tmpPips.push(data);
} else {
this.pips.push(data);
knownPips.push(pluginName);
}

existingPips.push(pluginName);
});

let resolvedDependency = false;
while (tmpPips.length > 0) {
resolvedDependency = false;

tmpPips.forEach((item, index) => {
if (resolvedDependency) {
return;
}

const openDependencies = item.dependencies.filter((dependency) => {
// Ignore any dependencies that are not present.
if (existingPips.indexOf(dependency) === -1) {
window.console.info(`The dependency "${dependency}" does not exist and has been ignored.`);
return false;
}

return !knownPips.includes(dependency);
});

if (openDependencies.length === 0) {
knownPips.push(item.pluginName);
this.pips.push(item);
tmpPips.splice(index, 1);

resolvedDependency = true;
}
});

if (!resolvedDependency) {
// We could not resolve any dependency, either because there is no more pip
// in `tmpPips` or we're facing a circular dependency. In case there are items
// left, we simply append them to the end and hope for the operation to
// complete anyway, despite unmatched dependencies.
tmpPips.forEach((pip) => {
window.console.warn("Unable to resolve dependencies for", pip);

this.pips.push(pip);
});

break;
}
}

this.buttonSyncAll.addEventListener("click", () => this.syncAll());
}

Expand All @@ -183,12 +110,7 @@ class AcpUiDevtoolsProjectSync {
this.buttonSyncAll.classList.add("disabled");
this.#syncVersionAfterCompletion = true;

this.queue = [];
this.pips.forEach((pip) => {
pip.targets.forEach((target) => {
this.queue.push([pip.pluginName, target]);
});
});
this.queue = [...this.#fullSyncQueue];
this.syncNext();
}

Expand Down Expand Up @@ -296,8 +218,8 @@ class AcpUiDevtoolsProjectSync {

let acpUiDevtoolsProjectSync: AcpUiDevtoolsProjectSync;

export function init(projectId: number): void {
export function init(projectId: number, fullSyncQueue: PendingPip[]): void {
if (!acpUiDevtoolsProjectSync) {
acpUiDevtoolsProjectSync = new AcpUiDevtoolsProjectSync(projectId);
acpUiDevtoolsProjectSync = new AcpUiDevtoolsProjectSync(projectId, fullSyncQueue);
}
}
5 changes: 1 addition & 4 deletions wcfsetup/install/files/acp/templates/devtoolsProjectSync.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@
data-is-important="{if $pip->isImportant()}true{else}false{/if}"
{if $_targetCount}
class="jsHasPipTargets"
{if !$_isUnsafe}
data-sync-dependencies="{$pip->getSyncDependencies(true)}"
{/if}
{/if}
>
<td class="columnText"{if $_targetCount > 0} rowspan="{$_targetCount}"{/if}>
Expand Down Expand Up @@ -118,7 +115,7 @@
{jsphrase name='wcf.acp.devtools.sync.status.failure'}
{jsphrase name='wcf.acp.devtools.sync.syncAll'}

AcpUiDevtoolsProjectSync.init({$object->projectID});
AcpUiDevtoolsProjectSync.init({$object->projectID}, {unsafe:$fullSyncQueue|json});
});
</script>

Expand Down
33 changes: 33 additions & 0 deletions wcfsetup/install/files/devtools.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env php
<?php

/**
* Non-interactive command line interface of the developer tools.
*
* @author Alexander Ebert
* @copyright 2001-2026 WoltLab GmbH
* @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
* @since 6.3
*/

if (\PHP_SAPI !== 'cli') {
\http_response_code(400);

exit;
}

if (\function_exists('posix_getuid') && \posix_getuid() === 0) {
\fwrite(\STDERR, "Refusing to execute as root.\n");

exit(1);
}

\define('PACKAGE_ID', 1);

// include config
require_once(__DIR__ . '/app.config.inc.php');

// initiate wcf core
require_once(WCF_DIR . 'lib/system/WCF.class.php');

exit(new \wcf\system\devtools\DevtoolsCLIWCF()->run(\array_slice($_SERVER['argv'], 1)));

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public function assignVariables()
'objectID' => $this->object->projectID,
'object' => $this->object,
'project' => $this->object,
'fullSyncQueue' => $this->object->getFullSyncQueue(),
]);
}
}
Loading
Loading