Improve update checking and add UI for major flarum update

This commit is contained in:
SychO9 2021-10-01 17:52:15 +01:00
parent 0aed124911
commit 2ce28f8e5c
9 changed files with 255 additions and 24 deletions

View File

@ -322,6 +322,70 @@ var Installer = /*#__PURE__*/function (_Component) {
/***/ }),
/***/ "./src/admin/components/MajorUpdater.tsx":
/*!***********************************************!*\
!*** ./src/admin/components/MajorUpdater.tsx ***!
\***********************************************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return MajorUpdater; });
/* harmony import */ var _babel_runtime_helpers_esm_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! @babel/runtime/helpers/esm/inheritsLoose */ "./node_modules/@babel/runtime/helpers/esm/inheritsLoose.js");
/* harmony import */ var flarum_admin_app__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! flarum/admin/app */ "flarum/admin/app");
/* harmony import */ var flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(flarum_admin_app__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var flarum_common_Component__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! flarum/common/Component */ "flarum/common/Component");
/* harmony import */ var flarum_common_Component__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(flarum_common_Component__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var flarum_common_components_Button__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! flarum/common/components/Button */ "flarum/common/components/Button");
/* harmony import */ var flarum_common_components_Button__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(flarum_common_components_Button__WEBPACK_IMPORTED_MODULE_3__);
/* harmony import */ var flarum_common_components_Tooltip__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! flarum/common/components/Tooltip */ "flarum/common/components/Tooltip");
/* harmony import */ var flarum_common_components_Tooltip__WEBPACK_IMPORTED_MODULE_4___default = /*#__PURE__*/__webpack_require__.n(flarum_common_components_Tooltip__WEBPACK_IMPORTED_MODULE_4__);
var MajorUpdater = /*#__PURE__*/function (_Component) {
Object(_babel_runtime_helpers_esm_inheritsLoose__WEBPACK_IMPORTED_MODULE_0__["default"])(MajorUpdater, _Component);
function MajorUpdater() {
return _Component.apply(this, arguments) || this;
}
var _proto = MajorUpdater.prototype;
_proto.view = function view(vnode) {
return m("div", {
className: "Form-group PackageManager-majorUpdate"
}, m("img", {
alt: "flarum logo",
src: flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.forum.attribute('baseUrl') + '/assets/extensions/sycho-package-manager/flarum.svg'
}), m("label", null, flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.major_updater.title', {
version: this.attrs.coreUpdate['latest-major']
})), m("p", {
className: "helpText"
}, flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.major_updater.description')), m("div", {
className: "PackageManager-updaterControls"
}, m(flarum_common_components_Tooltip__WEBPACK_IMPORTED_MODULE_4___default.a, {
text: flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.major_updater.dry_run_help')
}, m(flarum_common_components_Button__WEBPACK_IMPORTED_MODULE_3___default.a, {
className: "Button",
icon: "fas fa-vial"
}, flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.major_updater.dry_run'))), m(flarum_common_components_Button__WEBPACK_IMPORTED_MODULE_3___default.a, {
className: "Button",
icon: "fas fa-play"
}, flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.major_updater.update'))));
};
return MajorUpdater;
}(flarum_common_Component__WEBPACK_IMPORTED_MODULE_2___default.a);
/***/ }),
/***/ "./src/admin/components/Updater.tsx":
@ -354,6 +418,8 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var flarum_common_utils_classList__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(flarum_common_utils_classList__WEBPACK_IMPORTED_MODULE_9__);
/* harmony import */ var flarum_common_components_LoadingIndicator__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(/*! flarum/common/components/LoadingIndicator */ "flarum/common/components/LoadingIndicator");
/* harmony import */ var flarum_common_components_LoadingIndicator__WEBPACK_IMPORTED_MODULE_10___default = /*#__PURE__*/__webpack_require__.n(flarum_common_components_LoadingIndicator__WEBPACK_IMPORTED_MODULE_10__);
/* harmony import */ var _MajorUpdater__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(/*! ./MajorUpdater */ "./src/admin/components/MajorUpdater.tsx");
@ -407,7 +473,7 @@ var Updater = /*#__PURE__*/function (_Component) {
};
}
return m("div", {
return [m("div", {
className: "Form-group"
}, m("label", null, flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.updater.updater_title')), m("p", {
className: "helpText"
@ -439,7 +505,9 @@ var Updater = /*#__PURE__*/function (_Component) {
className: "PackageManager-extensions-grid"
}, core ? this.extensionItem(core, true) : null, extensions.map(function (extension) {
return _this2.extensionItem(extension);
}))) : null);
}))) : null), coreUpdate && coreUpdate['latest-major'] ? m(_MajorUpdater__WEBPACK_IMPORTED_MODULE_11__["default"], {
coreUpdate: coreUpdate
}) : null];
};
_proto.extensionItem = function extensionItem(extension, isCore) {
@ -463,9 +531,11 @@ var Updater = /*#__PURE__*/function (_Component) {
className: "PackageManager-extension-version"
}, m("span", {
className: "PackageManager-extension-version-current"
}, extension.version), m("span", {
className: "PackageManager-extension-version-latest Label"
}, extension.newPackageUpdate.latest))), m("div", {
}, this.version(extension.version)), extension.newPackageUpdate['latest-minor'] ? m("span", {
className: "PackageManager-extension-version-latest PackageManager-extension-version-latest--minor"
}, this.version(extension.newPackageUpdate['latest-minor'])) : null, extension.newPackageUpdate['latest-major'] && !isCore ? m("span", {
className: "PackageManager-extension-version-latest PackageManager-extension-version-latest--major"
}, this.version(extension.newPackageUpdate['latest-major'])) : null)), m("div", {
className: "PackageManager-extension-controls"
}, m(flarum_common_components_Tooltip__WEBPACK_IMPORTED_MODULE_7___default.a, {
text: flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.translator.trans('sycho-package-manager.admin.extensions.update')
@ -477,10 +547,14 @@ var Updater = /*#__PURE__*/function (_Component) {
}))));
};
_proto.version = function version(v) {
return 'v' + v.replace('v', '');
};
_proto.getExtensionUpdates = function getExtensionUpdates() {
var _this$lastUpdateCheck2, _this$lastUpdateCheck3, _this$lastUpdateCheck4;
var updates = (_this$lastUpdateCheck2 = this.lastUpdateCheck) == null ? void 0 : (_this$lastUpdateCheck3 = _this$lastUpdateCheck2.updates) == null ? void 0 : (_this$lastUpdateCheck4 = _this$lastUpdateCheck3.installed) == null ? void 0 : _this$lastUpdateCheck4.filter(function (composerPackage) {
(_this$lastUpdateCheck2 = this.lastUpdateCheck) == null ? void 0 : (_this$lastUpdateCheck3 = _this$lastUpdateCheck2.updates) == null ? void 0 : (_this$lastUpdateCheck4 = _this$lastUpdateCheck3.installed) == null ? void 0 : _this$lastUpdateCheck4.filter(function (composerPackage) {
var extension = flarum_admin_app__WEBPACK_IMPORTED_MODULE_1___default.a.data.extensions[composerPackage.name.replace('/', '-').replace(/(flarum-ext-)|(flarum-)/, '')];
var safeToUpdate = ['semver-safe-update', 'update-possible'].includes(composerPackage['latest-status']);
@ -613,6 +687,8 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var _components_Updater__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(/*! ./components/Updater */ "./src/admin/components/Updater.tsx");
/* harmony import */ var flarum_admin_utils_isExtensionEnabled__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(/*! flarum/admin/utils/isExtensionEnabled */ "flarum/admin/utils/isExtensionEnabled");
/* harmony import */ var flarum_admin_utils_isExtensionEnabled__WEBPACK_IMPORTED_MODULE_8___default = /*#__PURE__*/__webpack_require__.n(flarum_admin_utils_isExtensionEnabled__WEBPACK_IMPORTED_MODULE_8__);
/* harmony import */ var _components_MajorUpdater__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(/*! ./components/MajorUpdater */ "./src/admin/components/MajorUpdater.tsx");

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,32 @@
import app from 'flarum/admin/app';
import Component, { ComponentAttrs } from 'flarum/common/Component';
import Mithril from 'mithril';
import Button from 'flarum/common/components/Button';
import Tooltip from 'flarum/common/components/Tooltip';
import { UpdatedPackage } from './Updater';
interface MajorUpdaterAttrs extends ComponentAttrs {
coreUpdate: UpdatedPackage;
}
export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttrs> extends Component<T> {
view(vnode: Mithril.Vnode<ComponentAttrs, this>): Mithril.Children {
return (
<div className="Form-group PackageManager-majorUpdate">
<img alt="flarum logo" src={app.forum.attribute('baseUrl') + '/assets/extensions/sycho-package-manager/flarum.svg'} />
<label>{app.translator.trans('sycho-package-manager.admin.major_updater.title', { version: this.attrs.coreUpdate['latest-major'] })}</label>
<p className="helpText">{app.translator.trans('sycho-package-manager.admin.major_updater.description')}</p>
<div className="PackageManager-updaterControls">
<Tooltip text={app.translator.trans('sycho-package-manager.admin.major_updater.dry_run_help')}>
<Button className="Button" icon="fas fa-vial">
{app.translator.trans('sycho-package-manager.admin.major_updater.dry_run')}
</Button>
</Tooltip>
<Button className="Button" icon="fas fa-play">
{app.translator.trans('sycho-package-manager.admin.major_updater.update')}
</Button>
</div>
</div>
);
}
}

View File

@ -8,11 +8,14 @@ import Tooltip from 'flarum/common/components/Tooltip';
import errorHandler from '../utils/errorHandler';
import classList from 'flarum/common/utils/classList';
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
import MajorUpdater from './MajorUpdater';
type UpdatedPackage = {
export type UpdatedPackage = {
name: string;
version: string;
latest: string;
'latest-minor': string | null;
'latest-major': string | null;
'latest-status': string;
description: string;
};
@ -50,7 +53,7 @@ export default class Updater extends Component {
};
}
return (
return [
<div className="Form-group">
<label>{app.translator.trans('sycho-package-manager.admin.updater.updater_title')}</label>
<p className="helpText">{app.translator.trans('sycho-package-manager.admin.updater.updater_help')}</p>
@ -94,21 +97,36 @@ export default class Updater extends Component {
</div>
</div>
) : null}
</div>
);
</div>,
coreUpdate && coreUpdate['latest-major'] ? <MajorUpdater coreUpdate={coreUpdate} /> : null,
];
}
extensionItem(extension: any, isCore: boolean = false) {
return (
<div className={classList({ 'PackageManager-extension': true, 'PackageManager-extension--core': isCore })}>
<div
className={classList({
'PackageManager-extension': true,
'PackageManager-extension--core': isCore,
})}
>
<div className="PackageManager-extension-icon ExtensionIcon" style={extension.icon}>
{extension.icon ? icon(extension.icon.name) : ''}
</div>
<div className="PackageManager-extension-info">
<div className="PackageManager-extension-name">{extension.title || extension.extra['flarum-extension'].title}</div>
<div className="PackageManager-extension-version">
<span className="PackageManager-extension-version-current">{extension.version}</span>
<span className="PackageManager-extension-version-latest Label">{extension.newPackageUpdate.latest}</span>
<span className="PackageManager-extension-version-current">{this.version(extension.version)}</span>
{extension.newPackageUpdate['latest-minor'] ? (
<span className="PackageManager-extension-version-latest PackageManager-extension-version-latest--minor">
{this.version(extension.newPackageUpdate['latest-minor'])}
</span>
) : null}
{extension.newPackageUpdate['latest-major'] && !isCore ? (
<span className="PackageManager-extension-version-latest PackageManager-extension-version-latest--major">
{this.version(extension.newPackageUpdate['latest-major'])}
</span>
) : null}
</div>
</div>
<div className="PackageManager-extension-controls">
@ -125,8 +143,12 @@ export default class Updater extends Component {
);
}
version(v: string) {
return 'v' + v.replace('v', '');
}
getExtensionUpdates() {
const updates = this.lastUpdateCheck?.updates?.installed?.filter((composerPackage: UpdatedPackage) => {
this.lastUpdateCheck?.updates?.installed?.filter((composerPackage: UpdatedPackage) => {
const extension = app.data.extensions[composerPackage.name.replace('/', '-').replace(/(flarum-ext-)|(flarum-)/, '')];
const safeToUpdate = ['semver-safe-update', 'update-possible'].includes(composerPackage['latest-status']);
@ -141,7 +163,7 @@ export default class Updater extends Component {
}
getCoreUpdate(): UpdatedPackage | undefined {
return this.lastUpdateCheck?.updates?.installed?.filter((composerPackage: any) => composerPackage.name === 'flarum/core').pop();
return this.lastUpdateCheck?.updates?.installed?.filter((composerPackage: UpdatedPackage) => composerPackage.name === 'flarum/core').pop();
}
checkForUpdates() {

View File

@ -7,6 +7,7 @@ import LoadingModal from 'flarum/admin/components/LoadingModal';
import Installer from './components/Installer';
import Updater from './components/Updater';
import isExtensionEnabled from 'flarum/admin/utils/isExtensionEnabled';
import MajorUpdater from './components/MajorUpdater';
app.initializers.add('sycho-package-manager', (app) => {
app.extensionData

View File

@ -25,6 +25,7 @@
display: flex;
flex-wrap: wrap;
gap: 8px;
grid-area: controls;
}
.PackageManager-extensions {
@ -63,11 +64,19 @@
gap: 8px;
&-latest {
background: @alert-success-bg;
color: @alert-success-color;
border-radius: 30px;
padding: 0 6px;
font-weight: bold;
&--minor {
background-color: @alert-success-bg;
color: @alert-success-color;
}
&--major {
background-color: @alert-bg;
color: @alert-color;
}
}
}
@ -84,3 +93,29 @@
filter: grayscale(1) brightness(3.5);
}
}
.PackageManager-majorUpdate {
border: 2px solid @control-danger-color;
border-radius: @border-radius;
padding: 16px;
background-color: lighten(@control-danger-bg, 5.5);
display: grid;
grid-template-areas:
"logo title"
"logo helpText"
"logo controls";
grid-gap: 0 16px;
align-items: center;
> img {
grid-area: logo;
}
> label {
grid-area: title;
}
> .helpText {
grid-area: helpText;
}
}

View File

@ -22,6 +22,13 @@ sycho-package-manager:
file_permissions: >
The package manager requires read and write permissions on the following files and directories: composer.json, composer.lock, vendor, storage/.composer
major_updater:
description: Major Flarum updates are not backwards compatible, meaning that some of your currently installed extensions, and manually made modifications might not work with this new version.
dry_run: Dry Run
dry_run_help: A dry run emulates the update to see if your current setup can safely update, this does not mean that your manual made custom modifications will work in the newer version.
title: Flarum {version} Major Update Available
update: Update
updater:
check_for_updates: Check for updates
flarum: Flarum Core

View File

@ -6,9 +6,7 @@
namespace SychO\PackageManager\Command;
use Carbon\Carbon;
use Composer\Console\Application;
use Flarum\Settings\SettingsRepositoryInterface;
use SychO\PackageManager\Exception\ComposerCommandFailedException;
use SychO\PackageManager\LastUpdateCheck;
use SychO\PackageManager\OutputLogger;
@ -40,8 +38,22 @@ class CheckForUpdatesHandler
}
/**
* @throws \Flarum\User\Exception\PermissionDeniedException
* @throws ComposerCommandFailedException
* We run two commands here
*
* `composer outdated -D --format json`
* This queries latest versions for all direct packages, so it can include major updates,
* that are not necessarily compatible with the current flarum version.
* That includes flarum/core itself, so for example if we are on flarum/core v1.8.0
* and there are v1.8.1 and v2.0.0 available, the command would only let us know of v2.0.0.
*
* `composer outdated -D --minor-only --format json`
* This only lists latest minor updates, we need to run this as well not only to be able to know
* of these minor versions in addition to major ones, but especially for the flarum/core, as explained above
* we need to know of minor core updates, even if there is a major version available.
*
* The results from both commands are properly processed and merged to have new key values `latest-minor` and `latest-major`.
*
* @throws \Flarum\User\Exception\PermissionDeniedException|ComposerCommandFailedException
*/
public function handle(CheckForUpdates $command)
{
@ -49,10 +61,56 @@ class CheckForUpdatesHandler
$actor->assertAdmin();
$firstOutput = $this->runComposerCommand(false);
$firstOutput = json_decode($firstOutput, true);
$majorUpdates = false;
foreach ($firstOutput['installed'] as $package) {
if ($package['latest-status'] === 'update-possible') {
$majorUpdates = true;
break;
}
}
if ($majorUpdates) {
$secondOutput = $this->runComposerCommand(true);
$secondOutput = json_decode($secondOutput, true);
} else {
$secondOutput = ['installed' => []];
}
foreach ($firstOutput['installed'] as &$mainPackageUpdate) {
$mainPackageUpdate['latest-minor'] = $mainPackageUpdate['latest-major'] = null;
if ($mainPackageUpdate['latest-status'] === 'update-possible') {
$mainPackageUpdate['latest-major'] = $mainPackageUpdate['latest'];
$minorPackageUpdate = array_filter($secondOutput['installed'], function ($package) use ($mainPackageUpdate) {
return $package['name'] === $mainPackageUpdate['name'];
})[0] ?? null;
if ($minorPackageUpdate) {
$mainPackageUpdate['latest-minor'] = $minorPackageUpdate['latest'];
}
} else {
$mainPackageUpdate['latest-minor'] = $mainPackageUpdate['latest'];
}
}
return $this->lastUpdateCheck->save($firstOutput);
}
/**
* @throws ComposerCommandFailedException
*/
protected function runComposerCommand(bool $minorOnly): string
{
$output = new BufferedOutput();
$input = new ArrayInput([
'command' => 'outdated',
'-D' => true,
'--minor-only' => $minorOnly,
'--format' => 'json',
]);
@ -65,6 +123,6 @@ class CheckForUpdatesHandler
throw new ComposerCommandFailedException('', $output);
}
return $this->lastUpdateCheck->save(json_decode($output, true));
return $output;
}
}

View File

@ -47,7 +47,7 @@ class LastUpdateCheck
if (isset($lastUpdateCheck['updates']) && ! empty($lastUpdateCheck['updates']['installed'])) {
$updatesListChanged = false;
$pattern = str_replace('*', '.*', preg_quote($name));
$pattern = preg_quote(str_replace('*', '.*', $name));
foreach ($lastUpdateCheck['updates']['installed'] as $k => $package) {
if (($wildcard && Str::of($package['name'])->test("/($pattern)/")) || $package['name'] === $name) {