mirror of
https://github.com/flarum/core.git
synced 2025-08-03 15:07:53 +02:00
Merge remote-tracking branch 'extensions_package_manager/REWRITE'
This commit is contained in:
19
extensions/package-manager/.editorconfig
Executable file
19
extensions/package-manager/.editorconfig
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
# EditorConfig helps developers define and maintain consistent
|
||||||
|
# coding styles between different editors and IDEs
|
||||||
|
# editorconfig.org
|
||||||
|
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.{diff,md}]
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[*.{php,xml,json}]
|
||||||
|
indent_size = 4
|
19
extensions/package-manager/.gitattributes
vendored
Normal file
19
extensions/package-manager/.gitattributes
vendored
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
.gitattributes export-ignore
|
||||||
|
.gitignore export-ignore
|
||||||
|
.gitmodules export-ignore
|
||||||
|
.github export-ignore
|
||||||
|
.travis export-ignore
|
||||||
|
.travis.yml export-ignore
|
||||||
|
.editorconfig export-ignore
|
||||||
|
.styleci.yml export-ignore
|
||||||
|
|
||||||
|
phpunit.xml export-ignore
|
||||||
|
tests export-ignore
|
||||||
|
|
||||||
|
js/dist/* -diff
|
||||||
|
js/dist/* linguist-generated
|
||||||
|
js/dist-typings/* linguist-generated
|
||||||
|
js/yarn.lock -diff
|
||||||
|
js/package-lock.json -diff
|
||||||
|
|
||||||
|
* text=auto eol=lf
|
17
extensions/package-manager/.github/workflows/backend.yml
vendored
Normal file
17
extensions/package-manager/.github/workflows/backend.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
name: Package Manager PHP
|
||||||
|
|
||||||
|
on: [workflow_dispatch, push, pull_request]
|
||||||
|
|
||||||
|
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
|
||||||
|
# This will break your current script.
|
||||||
|
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run:
|
||||||
|
uses: flarum/.github/.github/workflows/REUSABLE_backend.yml@as/support-npm-yarn
|
||||||
|
with:
|
||||||
|
enable_backend_testing: true
|
||||||
|
|
||||||
|
backend_directory: .
|
||||||
|
|
||||||
|
php_versions: '["7.4", "8.0"]'
|
23
extensions/package-manager/.github/workflows/frontend.yml
vendored
Executable file
23
extensions/package-manager/.github/workflows/frontend.yml
vendored
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
name: Package Manager JS
|
||||||
|
|
||||||
|
on: [workflow_dispatch, push, pull_request]
|
||||||
|
|
||||||
|
# The reusable workflow definitions will be moved to the `flarum/framework` repo soon.
|
||||||
|
# This will break your current script.
|
||||||
|
# When this happens, run `flarum-cli audit infra --fix` to update your infrastructure.
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
run:
|
||||||
|
uses: flarum/.github/.github/workflows/REUSABLE_frontend.yml@as/support-npm-yarn
|
||||||
|
with:
|
||||||
|
enable_bundlewatch: false
|
||||||
|
enable_prettier: true
|
||||||
|
enable_typescript: true
|
||||||
|
|
||||||
|
frontend_directory: ./js
|
||||||
|
backend_directory: .
|
||||||
|
package_manager: yarn
|
||||||
|
main_git_branch: main
|
||||||
|
|
||||||
|
secrets:
|
||||||
|
bundlewatch_github_token: ${{ secrets.BUNDLEWATCH_GITHUB_TOKEN }}
|
12
extensions/package-manager/.gitignore
vendored
Executable file
12
extensions/package-manager/.gitignore
vendored
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
/vendor
|
||||||
|
composer.lock
|
||||||
|
composer.phar
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
tests/.phpunit.result.cache
|
||||||
|
/tests/integration/tmp
|
||||||
|
.vagrant
|
||||||
|
.idea/*
|
||||||
|
.vscode
|
||||||
|
js/coverage-ts
|
14
extensions/package-manager/.styleci.yml
Normal file
14
extensions/package-manager/.styleci.yml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
preset: recommended
|
||||||
|
|
||||||
|
enabled:
|
||||||
|
- logical_not_operators_with_successor_space
|
||||||
|
|
||||||
|
disabled:
|
||||||
|
- align_double_arrow
|
||||||
|
- blank_line_after_opening_tag
|
||||||
|
- multiline_array_trailing_comma
|
||||||
|
- new_with_braces
|
||||||
|
- phpdoc_align
|
||||||
|
- phpdoc_order
|
||||||
|
- phpdoc_separation
|
||||||
|
- phpdoc_types
|
21
extensions/package-manager/LICENSE.md
Executable file
21
extensions/package-manager/LICENSE.md
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) Sami Mazouz
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
5
extensions/package-manager/README.md
Executable file
5
extensions/package-manager/README.md
Executable file
@@ -0,0 +1,5 @@
|
|||||||
|
# Package Manager
|
||||||
|
|
||||||
|
*An Experiment.*
|
||||||
|
|
||||||
|
Read: https://github.com/flarum/package-manager/wiki
|
20
extensions/package-manager/assets/flarum.svg
Executable file
20
extensions/package-manager/assets/flarum.svg
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg width="100px" height="100px" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||||
|
<title>only symbol</title>
|
||||||
|
<defs>
|
||||||
|
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-1">
|
||||||
|
<stop stop-color="#D22929" offset="0%"></stop>
|
||||||
|
<stop stop-color="#B71717" offset="100%"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="linearGradient-2">
|
||||||
|
<stop stop-color="#E7762E" offset="0%"></stop>
|
||||||
|
<stop stop-color="#E7562E" offset="100%"></stop>
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<g id="Page-1-Copy" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||||
|
<g id="symbol" transform="translate(19.000000, 14.000000)">
|
||||||
|
<polygon id="Rectangle-7" fill="url(#linearGradient-1)" transform="translate(18.992475, 60.055970) scale(1, -1) translate(-18.992475, -60.055970) " points="3.28100097 67.7843544 3.25585284 71.4179104 34.729097 71.4179104 34.729097 48.6940299"></polygon>
|
||||||
|
<path d="M1.50233444,0 C0.67261804,0 -6.34784439e-15,0.673057357 -4.66645372e-15,1.50356766 L8.86811991e-14,47.6119403 C0.0903997122,49.1366194 0.0127042007,50.726258 4.86812431,53.3284828 C4.86812431,53.3284828 0.110480342,48.7194253 7.59698997,48.6940299 L60.7759197,48.6940299 L60.7759197,0 L1.50233444,0 Z" id="Rectangle-6" fill="url(#linearGradient-2)"></path>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
100
extensions/package-manager/composer.json
Executable file
100
extensions/package-manager/composer.json
Executable file
@@ -0,0 +1,100 @@
|
|||||||
|
{
|
||||||
|
"name": "flarum/package-manager",
|
||||||
|
"description": "A Flarum Package Manager.",
|
||||||
|
"keywords": [
|
||||||
|
"extensions",
|
||||||
|
"composer",
|
||||||
|
"packages",
|
||||||
|
"manager",
|
||||||
|
"updater"
|
||||||
|
],
|
||||||
|
"type": "flarum-extension",
|
||||||
|
"license": "MIT",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Flarum",
|
||||||
|
"email": "info@flarum.org",
|
||||||
|
"homepage": "https://flarum.org/team"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/flarum/package-manager/issues",
|
||||||
|
"source": "https://github.com/flarum/package-manager"
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"flarum/core": "^1.0.0",
|
||||||
|
"composer/composer": "^2.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"flarum/testing": "^1.0.0",
|
||||||
|
"flarum/tags": "*"
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"flarum-extension": {
|
||||||
|
"title": "Package Manager",
|
||||||
|
"icon": {
|
||||||
|
"name": "fas fa-box-open",
|
||||||
|
"backgroundColor": "#117187",
|
||||||
|
"color": "#fff"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"flarum-cli": {
|
||||||
|
"excludeScaffolding": [
|
||||||
|
".github/workflows/backend.yml",
|
||||||
|
"js/src/admin/index.ts",
|
||||||
|
"tests/phpunit.integration.xml",
|
||||||
|
"tests/integration/setup.php"
|
||||||
|
],
|
||||||
|
"excludeScaffoldingConfigKeys": {
|
||||||
|
"composer.json": [
|
||||||
|
"scripts.test:setup"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"modules": {
|
||||||
|
"admin": true,
|
||||||
|
"forum": false,
|
||||||
|
"js": true,
|
||||||
|
"jsCommon": false,
|
||||||
|
"css": true,
|
||||||
|
"gitConf": true,
|
||||||
|
"githubActions": true,
|
||||||
|
"prettier": true,
|
||||||
|
"typescript": true,
|
||||||
|
"bundlewatch": false,
|
||||||
|
"backendTesting": true,
|
||||||
|
"editorConfig": true,
|
||||||
|
"styleci": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Flarum\\PackageManager\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"Flarum\\PackageManager\\Tests\\": "tests/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": [
|
||||||
|
"@test:unit",
|
||||||
|
"@test:integration"
|
||||||
|
],
|
||||||
|
"test:unit": "phpunit -c tests/phpunit.unit.xml",
|
||||||
|
"test:integration": "phpunit -c tests/phpunit.integration.xml",
|
||||||
|
"test:setup": [
|
||||||
|
"@php tests/integration/setup.php",
|
||||||
|
"cd $FLARUM_TEST_TMP_DIR_LOCAL && composer install"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"scripts-descriptions": {
|
||||||
|
"test": "Runs all tests.",
|
||||||
|
"test:unit": "Runs all unit tests.",
|
||||||
|
"test:integration": "Runs all integration tests.",
|
||||||
|
"test:setup": "Sets up a database for use with integration tests. Execute this only once."
|
||||||
|
},
|
||||||
|
"minimum-stability": "dev",
|
||||||
|
"prefer-stable": true
|
||||||
|
}
|
63
extensions/package-manager/extend.php
Executable file
63
extensions/package-manager/extend.php
Executable file
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager;
|
||||||
|
|
||||||
|
use Flarum\Extend;
|
||||||
|
use Flarum\Foundation\Paths;
|
||||||
|
use Flarum\Frontend\Document;
|
||||||
|
use Flarum\PackageManager\Exception\ComposerCommandFailedException;
|
||||||
|
use Flarum\PackageManager\Exception\ExceptionHandler;
|
||||||
|
use Flarum\PackageManager\Exception\ComposerRequireFailedException;
|
||||||
|
use Flarum\PackageManager\Exception\ComposerUpdateFailedException;
|
||||||
|
use Flarum\PackageManager\Exception\MajorUpdateFailedException;
|
||||||
|
use Flarum\PackageManager\Settings\LastUpdateCheck;
|
||||||
|
use Flarum\PackageManager\Settings\LastUpdateRun;
|
||||||
|
|
||||||
|
return [
|
||||||
|
(new Extend\Routes('api'))
|
||||||
|
->post('/package-manager/extensions', 'package-manager.extensions.require', Api\Controller\RequireExtensionController::class)
|
||||||
|
->patch('/package-manager/extensions/{id}', 'package-manager.extensions.update', Api\Controller\UpdateExtensionController::class)
|
||||||
|
->delete('/package-manager/extensions/{id}', 'package-manager.extensions.remove', Api\Controller\RemoveExtensionController::class)
|
||||||
|
->post('/package-manager/check-for-updates', 'package-manager.check-for-updates', Api\Controller\CheckForUpdatesController::class)
|
||||||
|
->post('/package-manager/why-not', 'package-manager.why-not', Api\Controller\WhyNotController::class)
|
||||||
|
->post('/package-manager/minor-update', 'package-manager.minor-update', Api\Controller\MinorUpdateController::class)
|
||||||
|
->post('/package-manager/major-update', 'package-manager.major-update', Api\Controller\MajorUpdateController::class)
|
||||||
|
->post('/package-manager/global-update', 'package-manager.global-update', Api\Controller\GlobalUpdateController::class),
|
||||||
|
|
||||||
|
(new Extend\Frontend('admin'))
|
||||||
|
->css(__DIR__ . '/less/admin.less')
|
||||||
|
->js(__DIR__ . '/js/dist/admin.js')
|
||||||
|
->content(function (Document $document) {
|
||||||
|
$paths = resolve(Paths::class);
|
||||||
|
|
||||||
|
$document->payload['isRequiredDirectoriesWritable'] = is_writable($paths->vendor)
|
||||||
|
&& is_writable($paths->storage.'/.composer')
|
||||||
|
&& is_writable($paths->base.'/composer.json')
|
||||||
|
&& is_writable($paths->base.'/composer.lock');
|
||||||
|
}),
|
||||||
|
|
||||||
|
new Extend\Locales(__DIR__ . '/locale'),
|
||||||
|
|
||||||
|
(new Extend\Settings())
|
||||||
|
->default(LastUpdateCheck::key(), json_encode(LastUpdateCheck::default()))
|
||||||
|
->default(LastUpdateRun::key(), json_encode(LastUpdateRun::default())),
|
||||||
|
|
||||||
|
(new Extend\ServiceProvider)
|
||||||
|
->register(PackageManagerServiceProvider::class),
|
||||||
|
|
||||||
|
(new Extend\ErrorHandling)
|
||||||
|
->handler(ComposerCommandFailedException::class, ExceptionHandler::class)
|
||||||
|
->handler(ComposerRequireFailedException::class, ExceptionHandler::class)
|
||||||
|
->handler(ComposerUpdateFailedException::class, ExceptionHandler::class)
|
||||||
|
->handler(MajorUpdateFailedException::class, ExceptionHandler::class)
|
||||||
|
->status('extension_already_installed', 409)
|
||||||
|
->status('extension_not_installed', 409)
|
||||||
|
->status('no_new_major_version', 409),
|
||||||
|
];
|
9
extensions/package-manager/js/.gitignore
vendored
Normal file
9
extensions/package-manager/js/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
.pnp.*
|
||||||
|
.yarn/*
|
||||||
|
!.yarn/patches
|
||||||
|
!.yarn/plugins
|
||||||
|
!.yarn/releases
|
||||||
|
!.yarn/sdks
|
||||||
|
!.yarn/versions
|
||||||
|
|
||||||
|
node_modules
|
1
extensions/package-manager/js/admin.ts
Normal file
1
extensions/package-manager/js/admin.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './src/admin';
|
20
extensions/package-manager/js/dist-typings/components/ExtensionItem.d.ts
vendored
Normal file
20
extensions/package-manager/js/dist-typings/components/ExtensionItem.d.ts
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import Mithril from 'mithril';
|
||||||
|
import Component, { ComponentAttrs } from 'flarum/common/Component';
|
||||||
|
import { Extension as BaseExtension } from 'flarum/admin/AdminApplication';
|
||||||
|
import { UpdatedPackage } from './Updater';
|
||||||
|
export declare type Extension = BaseExtension & {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
export interface ExtensionItemAttrs extends ComponentAttrs {
|
||||||
|
extension: Extension;
|
||||||
|
updates: UpdatedPackage;
|
||||||
|
onClickUpdate: CallableFunction;
|
||||||
|
whyNotWarning?: boolean;
|
||||||
|
isCore?: boolean;
|
||||||
|
updatable?: boolean;
|
||||||
|
isDanger?: boolean;
|
||||||
|
}
|
||||||
|
export default class ExtensionItem<Attrs extends ExtensionItemAttrs = ExtensionItemAttrs> extends Component<Attrs> {
|
||||||
|
view(vnode: Mithril.Vnode<Attrs, this>): Mithril.Children;
|
||||||
|
private version;
|
||||||
|
}
|
11
extensions/package-manager/js/dist-typings/components/Installer.d.ts
vendored
Normal file
11
extensions/package-manager/js/dist-typings/components/Installer.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import type Mithril from 'mithril';
|
||||||
|
import Component from 'flarum/common/Component';
|
||||||
|
import Stream from 'flarum/common/utils/Stream';
|
||||||
|
export default class Installer<Attrs> extends Component<Attrs> {
|
||||||
|
packageName: Stream<string>;
|
||||||
|
isLoading: boolean;
|
||||||
|
oninit(vnode: Mithril.Vnode<Attrs, this>): void;
|
||||||
|
view(): Mithril.Children;
|
||||||
|
data(): any;
|
||||||
|
onsubmit(): void;
|
||||||
|
}
|
15
extensions/package-manager/js/dist-typings/components/MajorUpdater.d.ts
vendored
Normal file
15
extensions/package-manager/js/dist-typings/components/MajorUpdater.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import Component, { ComponentAttrs } from 'flarum/common/Component';
|
||||||
|
import Mithril from 'mithril';
|
||||||
|
import { UpdatedPackage, UpdateState } from './Updater';
|
||||||
|
interface MajorUpdaterAttrs extends ComponentAttrs {
|
||||||
|
coreUpdate: UpdatedPackage;
|
||||||
|
updateState: UpdateState;
|
||||||
|
}
|
||||||
|
export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttrs> extends Component<T> {
|
||||||
|
isLoading: string | null;
|
||||||
|
updateState: UpdateState;
|
||||||
|
oninit(vnode: Mithril.Vnode<T, this>): void;
|
||||||
|
view(vnode: Mithril.Vnode<T, this>): Mithril.Children;
|
||||||
|
update(dryRun: boolean): void;
|
||||||
|
}
|
||||||
|
export {};
|
47
extensions/package-manager/js/dist-typings/components/Updater.d.ts
vendored
Normal file
47
extensions/package-manager/js/dist-typings/components/Updater.d.ts
vendored
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import Mithril from 'mithril';
|
||||||
|
import Component from 'flarum/common/Component';
|
||||||
|
import { Extension } from './ExtensionItem';
|
||||||
|
export declare type UpdatedPackage = {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
latest: string;
|
||||||
|
'latest-minor': string | null;
|
||||||
|
'latest-major': string | null;
|
||||||
|
'latest-status': string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
export declare type ComposerUpdates = {
|
||||||
|
installed: UpdatedPackage[];
|
||||||
|
};
|
||||||
|
export declare type LastUpdateCheck = {
|
||||||
|
checkedAt: Date | null;
|
||||||
|
updates: ComposerUpdates;
|
||||||
|
};
|
||||||
|
declare type UpdateType = 'major' | 'minor' | 'global';
|
||||||
|
declare type UpdateStatus = 'success' | 'failure' | null;
|
||||||
|
export declare type UpdateState = {
|
||||||
|
ranAt: Date | null;
|
||||||
|
status: UpdateStatus;
|
||||||
|
limitedPackages: string[];
|
||||||
|
incompatibleExtensions: string[];
|
||||||
|
};
|
||||||
|
export declare type LastUpdateRun = {
|
||||||
|
[key in UpdateType]: UpdateState;
|
||||||
|
} & {
|
||||||
|
limitedPackages: () => string[];
|
||||||
|
};
|
||||||
|
export default class Updater<Attrs> extends Component<Attrs> {
|
||||||
|
isLoading: string | null;
|
||||||
|
packageUpdates: Record<string, UpdatedPackage>;
|
||||||
|
lastUpdateCheck: LastUpdateCheck;
|
||||||
|
get lastUpdateRun(): LastUpdateRun;
|
||||||
|
oninit(vnode: Mithril.Vnode<Attrs, this>): void;
|
||||||
|
view(): (JSX.Element | null)[];
|
||||||
|
getExtensionUpdates(): Extension[];
|
||||||
|
getCoreUpdate(): UpdatedPackage | undefined;
|
||||||
|
checkForUpdates(): void;
|
||||||
|
updateCoreMinor(): void;
|
||||||
|
updateExtension(extension: any): void;
|
||||||
|
updateGlobally(): void;
|
||||||
|
}
|
||||||
|
export {};
|
15
extensions/package-manager/js/dist-typings/components/WhyNotModal.d.ts
vendored
Normal file
15
extensions/package-manager/js/dist-typings/components/WhyNotModal.d.ts
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/// <reference path="../../../vendor/flarum/core/js/src/common/translator-icu-rich.d.ts" />
|
||||||
|
import Mithril from 'mithril';
|
||||||
|
import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal';
|
||||||
|
export interface WhyNotModalAttrs extends IInternalModalAttrs {
|
||||||
|
package: string;
|
||||||
|
}
|
||||||
|
export default class WhyNotModal<Attrs extends WhyNotModalAttrs = WhyNotModalAttrs> extends Modal<Attrs> {
|
||||||
|
loading: boolean;
|
||||||
|
whyNot: string | null;
|
||||||
|
className(): string;
|
||||||
|
title(): import("@askvortsov/rich-icu-message-formatter").NestedStringArray;
|
||||||
|
oncreate(vnode: Mithril.VnodeDOM<Attrs, this>): void;
|
||||||
|
content(): JSX.Element;
|
||||||
|
requestWhyNot(): void;
|
||||||
|
}
|
1
extensions/package-manager/js/dist-typings/index.d.ts
generated
vendored
Normal file
1
extensions/package-manager/js/dist-typings/index.d.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export {};
|
1
extensions/package-manager/js/dist-typings/utils/errorHandler.d.ts
vendored
Normal file
1
extensions/package-manager/js/dist-typings/utils/errorHandler.d.ts
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export default function (e: any): void;
|
1211
extensions/package-manager/js/dist/admin.js
generated
vendored
Executable file
1211
extensions/package-manager/js/dist/admin.js
generated
vendored
Executable file
File diff suppressed because it is too large
Load Diff
1
extensions/package-manager/js/dist/admin.js.map
generated
vendored
Executable file
1
extensions/package-manager/js/dist/admin.js.map
generated
vendored
Executable file
File diff suppressed because one or more lines are too long
28
extensions/package-manager/js/package.json
Executable file
28
extensions/package-manager/js/package.json
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"name": "@flarum/package-manager",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"prettier": "@flarum/prettier-config",
|
||||||
|
"devDependencies": {
|
||||||
|
"prettier": "^2.5.1",
|
||||||
|
"flarum-webpack-config": "^2.0.0",
|
||||||
|
"webpack": "^5.65.0",
|
||||||
|
"webpack-cli": "^4.9.1",
|
||||||
|
"@flarum/prettier-config": "^1.0.0",
|
||||||
|
"flarum-tsconfig": "^1.0.2",
|
||||||
|
"typescript": "^4.5.4",
|
||||||
|
"typescript-coverage-report": "^0.6.1"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "webpack --mode development --watch",
|
||||||
|
"build": "webpack --mode production",
|
||||||
|
"format": "prettier --write src",
|
||||||
|
"format-check": "prettier --check src",
|
||||||
|
"ci": "yarn install --immutable --immutable-cache",
|
||||||
|
"analyze": "cross-env ANALYZER=true yarn run build",
|
||||||
|
"clean-typings": "npx rimraf dist-typings && mkdir dist-typings",
|
||||||
|
"build-typings": "yarn run clean-typings && tsc && [ -e src/@types ] && cp -r src/@types dist-typings/@types",
|
||||||
|
"check-typings": "tsc --noEmit --emitDeclarationOnly false",
|
||||||
|
"check-typings-coverage": "typescript-coverage-report"
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,89 @@
|
|||||||
|
import Mithril from 'mithril';
|
||||||
|
import app from 'flarum/admin/app';
|
||||||
|
import Component, { ComponentAttrs } from 'flarum/common/Component';
|
||||||
|
import classList from 'flarum/common/utils/classList';
|
||||||
|
import icon from 'flarum/common/helpers/icon';
|
||||||
|
import Tooltip from 'flarum/common/components/Tooltip';
|
||||||
|
import Button from 'flarum/common/components/Button';
|
||||||
|
import { Extension as BaseExtension } from 'flarum/admin/AdminApplication';
|
||||||
|
import { UpdatedPackage } from './Updater';
|
||||||
|
import WhyNotModal from './WhyNotModal';
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @todo fix in core
|
||||||
|
*/
|
||||||
|
export type Extension = BaseExtension & {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface ExtensionItemAttrs extends ComponentAttrs {
|
||||||
|
extension: Extension;
|
||||||
|
updates: UpdatedPackage;
|
||||||
|
onClickUpdate: CallableFunction;
|
||||||
|
whyNotWarning?: boolean;
|
||||||
|
isCore?: boolean;
|
||||||
|
updatable?: boolean;
|
||||||
|
isDanger?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ExtensionItem<Attrs extends ExtensionItemAttrs = ExtensionItemAttrs> extends Component<Attrs> {
|
||||||
|
view(vnode: Mithril.Vnode<Attrs, this>): Mithril.Children {
|
||||||
|
const { extension, updates, onClickUpdate, whyNotWarning, isCore, isDanger } = this.attrs;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classList({
|
||||||
|
'PackageManager-extension': true,
|
||||||
|
'PackageManager-extension--core': isCore,
|
||||||
|
'PackageManager-extension--danger': isDanger,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<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.extra['flarum-extension'].title}</div>
|
||||||
|
<div className="PackageManager-extension-version">
|
||||||
|
<span className="PackageManager-extension-version-current">{this.version(extension.version)}</span>
|
||||||
|
{updates['latest-minor'] ? (
|
||||||
|
<span className="PackageManager-extension-version-latest PackageManager-extension-version-latest--minor">
|
||||||
|
{this.version(updates['latest-minor']!)}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
{updates['latest-major'] && !isCore ? (
|
||||||
|
<span className="PackageManager-extension-version-latest PackageManager-extension-version-latest--major">
|
||||||
|
{this.version(updates['latest-major']!)}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="PackageManager-extension-controls">
|
||||||
|
{onClickUpdate ? (
|
||||||
|
<Tooltip text={app.translator.trans('flarum-package-manager.admin.extensions.update')}>
|
||||||
|
<Button
|
||||||
|
icon="fas fa-arrow-alt-circle-up"
|
||||||
|
className="Button Button--icon Button--flat"
|
||||||
|
onclick={onClickUpdate}
|
||||||
|
aria-label={app.translator.trans('flarum-package-manager.admin.extensions.update')}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
{whyNotWarning ? (
|
||||||
|
<Tooltip text={app.translator.trans('flarum-package-manager.admin.extensions.check_why_it_failed_updating')}>
|
||||||
|
<Button
|
||||||
|
icon="fas fa-exclamation-circle"
|
||||||
|
className="Button Button--icon Button--flat Button--danger"
|
||||||
|
onclick={() => app.modal.show(WhyNotModal, { package: extension.name })}
|
||||||
|
aria-label={app.translator.trans('flarum-package-manager.admin.extensions.check_why_it_failed_updating')}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private version(v: string): string {
|
||||||
|
return 'v' + v.replace('v', '');
|
||||||
|
}
|
||||||
|
}
|
71
extensions/package-manager/js/src/admin/components/Installer.tsx
Executable file
71
extensions/package-manager/js/src/admin/components/Installer.tsx
Executable file
@@ -0,0 +1,71 @@
|
|||||||
|
import type Mithril from 'mithril';
|
||||||
|
import app from 'flarum/admin/app';
|
||||||
|
import Component from 'flarum/common/Component';
|
||||||
|
import Button from 'flarum/common/components/Button';
|
||||||
|
import Stream from 'flarum/common/utils/Stream';
|
||||||
|
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||||
|
import errorHandler from '../utils/errorHandler';
|
||||||
|
|
||||||
|
export default class Installer<Attrs> extends Component<Attrs> {
|
||||||
|
packageName!: Stream<string>;
|
||||||
|
isLoading: boolean = false;
|
||||||
|
|
||||||
|
oninit(vnode: Mithril.Vnode<Attrs, this>): void {
|
||||||
|
super.oninit(vnode);
|
||||||
|
|
||||||
|
this.packageName = Stream('');
|
||||||
|
}
|
||||||
|
|
||||||
|
view(): Mithril.Children {
|
||||||
|
return (
|
||||||
|
<div className="Form-group">
|
||||||
|
<label htmlFor="install-extension">{app.translator.trans('flarum-package-manager.admin.extensions.install')}</label>
|
||||||
|
<p className="helpText">
|
||||||
|
{app.translator.trans('flarum-package-manager.admin.extensions.install_help', {
|
||||||
|
extiverse: <a href="https://extiverse.com">extiverse.com</a>,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<div className="FormControl-container">
|
||||||
|
<input className="FormControl" id="install-extension" placeholder="vendor/package-name" bidi={this.packageName} />
|
||||||
|
<Button className="Button" icon="fas fa-download" onclick={this.onsubmit.bind(this)} loading={this.isLoading}>
|
||||||
|
{app.translator.trans('flarum-package-manager.admin.extensions.proceed')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
data(): any {
|
||||||
|
return {
|
||||||
|
package: this.packageName(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
onsubmit(): void {
|
||||||
|
this.isLoading = true;
|
||||||
|
app.modal.show(LoadingModal);
|
||||||
|
|
||||||
|
app
|
||||||
|
.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${app.forum.attribute('apiUrl')}/package-manager/extensions`,
|
||||||
|
body: {
|
||||||
|
data: this.data(),
|
||||||
|
},
|
||||||
|
errorHandler,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
const extensionId = response.id;
|
||||||
|
app.alerts.show(
|
||||||
|
{ type: 'success' },
|
||||||
|
app.translator.trans('flarum-package-manager.admin.extensions.successful_install', { extension: extensionId })
|
||||||
|
);
|
||||||
|
window.location.href = `${app.forum.attribute('adminUrl')}#/extension/${extensionId}`;
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
m.redraw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,109 @@
|
|||||||
|
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, UpdateState } from './Updater';
|
||||||
|
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||||
|
import errorHandler from '../utils/errorHandler';
|
||||||
|
import Alert from 'flarum/common/components/Alert';
|
||||||
|
import WhyNotModal from './WhyNotModal';
|
||||||
|
import RequestError from 'flarum/common/utils/RequestError';
|
||||||
|
import ExtensionItem, { Extension } from './ExtensionItem';
|
||||||
|
|
||||||
|
interface MajorUpdaterAttrs extends ComponentAttrs {
|
||||||
|
coreUpdate: UpdatedPackage;
|
||||||
|
updateState: UpdateState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class MajorUpdater<T extends MajorUpdaterAttrs = MajorUpdaterAttrs> extends Component<T> {
|
||||||
|
isLoading: string | null = null;
|
||||||
|
updateState!: UpdateState;
|
||||||
|
|
||||||
|
oninit(vnode: Mithril.Vnode<T, this>) {
|
||||||
|
super.oninit(vnode);
|
||||||
|
|
||||||
|
this.updateState = this.attrs.updateState;
|
||||||
|
}
|
||||||
|
|
||||||
|
view(vnode: Mithril.Vnode<T, this>): Mithril.Children {
|
||||||
|
// @todo move Form-group--danger class to core for reuse
|
||||||
|
return (
|
||||||
|
<div className="Form-group Form-group--danger PackageManager-majorUpdate">
|
||||||
|
<img alt="flarum logo" src={app.forum.attribute('baseUrl') + '/assets/extensions/flarum-package-manager/flarum.svg'} />
|
||||||
|
<label>{app.translator.trans('flarum-package-manager.admin.major_updater.title', { version: this.attrs.coreUpdate['latest-major'] })}</label>
|
||||||
|
<p className="helpText">{app.translator.trans('flarum-package-manager.admin.major_updater.description')}</p>
|
||||||
|
<div className="PackageManager-updaterControls">
|
||||||
|
<Tooltip text={app.translator.trans('flarum-package-manager.admin.major_updater.dry_run_help')}>
|
||||||
|
<Button className="Button" icon="fas fa-vial" onclick={this.update.bind(this, true)}>
|
||||||
|
{app.translator.trans('flarum-package-manager.admin.major_updater.dry_run')}
|
||||||
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Button className="Button Button--danger" icon="fas fa-play" onclick={this.update.bind(this, false)}>
|
||||||
|
{app.translator.trans('flarum-package-manager.admin.major_updater.update')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{this.updateState.incompatibleExtensions.length ? (
|
||||||
|
<div className="PackageManager-majorUpdate-incompatibleExtensions PackageManager-extensions-grid">
|
||||||
|
{this.updateState.incompatibleExtensions.map((extension: string) => (
|
||||||
|
<ExtensionItem
|
||||||
|
extension={app.data.extensions[extension.replace('flarum-', '').replace('flarum-ext-', '').replace('/', '-')]}
|
||||||
|
updates={{}}
|
||||||
|
onClickUpdate={null}
|
||||||
|
isDanger={true}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
{this.updateState.status === 'failure' ? (
|
||||||
|
<Alert
|
||||||
|
type="error"
|
||||||
|
className="PackageManager-majorUpdate-failure"
|
||||||
|
dismissible={false}
|
||||||
|
controls={[
|
||||||
|
<Button
|
||||||
|
className="Button Button--text PackageManager-majorUpdate-failure-details"
|
||||||
|
icon="fas fa-question-circle"
|
||||||
|
onclick={() => app.modal.show(WhyNotModal, { package: 'flarum/core' })}
|
||||||
|
>
|
||||||
|
{app.translator.trans('flarum-package-manager.admin.major_updater.failure.why')}
|
||||||
|
</Button>,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<p className="PackageManager-majorUpdate-failure-desc">
|
||||||
|
{app.translator.trans('flarum-package-manager.admin.major_updater.failure.desc')}
|
||||||
|
</p>
|
||||||
|
</Alert>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(dryRun: boolean) {
|
||||||
|
this.isLoading = `update-${dryRun ? 'dry-run' : 'run'}`;
|
||||||
|
app.modal.show(LoadingModal);
|
||||||
|
|
||||||
|
app
|
||||||
|
.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${app.forum.attribute('apiUrl')}/package-manager/major-update`,
|
||||||
|
body: {
|
||||||
|
data: { dryRun },
|
||||||
|
},
|
||||||
|
errorHandler,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.update_successful'));
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.catch((e: RequestError) => {
|
||||||
|
app.modal.close();
|
||||||
|
this.updateState.status = 'failure';
|
||||||
|
this.updateState.incompatibleExtensions = e.response?.errors?.pop()?.incompatible_extensions as string[];
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = null;
|
||||||
|
m.redraw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
254
extensions/package-manager/js/src/admin/components/Updater.tsx
Executable file
254
extensions/package-manager/js/src/admin/components/Updater.tsx
Executable file
@@ -0,0 +1,254 @@
|
|||||||
|
import Mithril from 'mithril';
|
||||||
|
import app from 'flarum/admin/app';
|
||||||
|
import Component from 'flarum/common/Component';
|
||||||
|
import Button from 'flarum/common/components/Button';
|
||||||
|
import humanTime from 'flarum/common/helpers/humanTime';
|
||||||
|
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||||
|
import errorHandler from '../utils/errorHandler';
|
||||||
|
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||||
|
import MajorUpdater from './MajorUpdater';
|
||||||
|
import ExtensionItem, { Extension } from './ExtensionItem';
|
||||||
|
|
||||||
|
export type UpdatedPackage = {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
latest: string;
|
||||||
|
'latest-minor': string | null;
|
||||||
|
'latest-major': string | null;
|
||||||
|
'latest-status': string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ComposerUpdates = {
|
||||||
|
installed: UpdatedPackage[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LastUpdateCheck = {
|
||||||
|
checkedAt: Date | null;
|
||||||
|
updates: ComposerUpdates;
|
||||||
|
};
|
||||||
|
|
||||||
|
type UpdateType = 'major' | 'minor' | 'global';
|
||||||
|
type UpdateStatus = 'success' | 'failure' | null;
|
||||||
|
export type UpdateState = {
|
||||||
|
ranAt: Date | null;
|
||||||
|
status: UpdateStatus;
|
||||||
|
limitedPackages: string[];
|
||||||
|
incompatibleExtensions: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LastUpdateRun = {
|
||||||
|
[key in UpdateType]: UpdateState;
|
||||||
|
} & {
|
||||||
|
limitedPackages: () => string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default class Updater<Attrs> extends Component<Attrs> {
|
||||||
|
isLoading: string | null = null;
|
||||||
|
packageUpdates: Record<string, UpdatedPackage> = {};
|
||||||
|
lastUpdateCheck: LastUpdateCheck = JSON.parse(app.data.settings['flarum-package-manager.last_update_check']) as LastUpdateCheck;
|
||||||
|
get lastUpdateRun(): LastUpdateRun {
|
||||||
|
const lastUpdateRun = JSON.parse(app.data.settings['flarum-package-manager.last_update_run']) as LastUpdateRun;
|
||||||
|
|
||||||
|
lastUpdateRun.limitedPackages = () => [
|
||||||
|
...lastUpdateRun.major.limitedPackages,
|
||||||
|
...lastUpdateRun.minor.limitedPackages,
|
||||||
|
...lastUpdateRun.global.limitedPackages,
|
||||||
|
];
|
||||||
|
|
||||||
|
return lastUpdateRun;
|
||||||
|
}
|
||||||
|
|
||||||
|
oninit(vnode: Mithril.Vnode<Attrs, this>) {
|
||||||
|
super.oninit(vnode);
|
||||||
|
}
|
||||||
|
|
||||||
|
view() {
|
||||||
|
const extensions = this.getExtensionUpdates();
|
||||||
|
let coreUpdate: UpdatedPackage | undefined = this.getCoreUpdate();
|
||||||
|
let core: any;
|
||||||
|
|
||||||
|
if (coreUpdate) {
|
||||||
|
core = {
|
||||||
|
id: 'flarum-core',
|
||||||
|
name: 'flarum/core',
|
||||||
|
version: app.data.settings.version,
|
||||||
|
icon: {
|
||||||
|
backgroundImage: `url(${app.forum.attribute('baseUrl')}/assets/extensions/flarum-package-manager/flarum.svg`,
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
'flarum-extension': {
|
||||||
|
title: app.translator.trans('flarum-package-manager.admin.updater.flarum'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
<div className="Form-group">
|
||||||
|
<label>{app.translator.trans('flarum-package-manager.admin.updater.updater_title')}</label>
|
||||||
|
<p className="helpText">{app.translator.trans('flarum-package-manager.admin.updater.updater_help')}</p>
|
||||||
|
{this.lastUpdateCheck?.checkedAt && (
|
||||||
|
<p className="PackageManager-lastUpdatedAt">
|
||||||
|
<span className="PackageManager-lastUpdatedAt-label">
|
||||||
|
{app.translator.trans('flarum-package-manager.admin.updater.last_update_checked_at')}
|
||||||
|
</span>
|
||||||
|
<span className="PackageManager-lastUpdatedAt-value">{humanTime(this.lastUpdateCheck.checkedAt)}</span>
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
<div className="PackageManager-updaterControls">
|
||||||
|
<Button
|
||||||
|
className="Button"
|
||||||
|
icon="fas fa-sync-alt"
|
||||||
|
onclick={this.checkForUpdates.bind(this)}
|
||||||
|
loading={this.isLoading === 'check'}
|
||||||
|
disabled={this.isLoading !== null && this.isLoading !== 'check'}
|
||||||
|
>
|
||||||
|
{app.translator.trans('flarum-package-manager.admin.updater.check_for_updates')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
className="Button"
|
||||||
|
icon="fas fa-play"
|
||||||
|
onclick={this.updateGlobally.bind(this)}
|
||||||
|
loading={this.isLoading === 'global-update'}
|
||||||
|
disabled={this.isLoading !== null && this.isLoading !== 'global-update'}
|
||||||
|
>
|
||||||
|
{app.translator.trans('flarum-package-manager.admin.updater.run_global_update')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
{this.isLoading !== null ? (
|
||||||
|
<div className="PackageManager-extensions">
|
||||||
|
<LoadingIndicator />
|
||||||
|
</div>
|
||||||
|
) : extensions.length || core ? (
|
||||||
|
<div className="PackageManager-extensions">
|
||||||
|
<div className="PackageManager-extensions-grid">
|
||||||
|
{core ? (
|
||||||
|
<ExtensionItem
|
||||||
|
extension={core}
|
||||||
|
updates={coreUpdate}
|
||||||
|
isCore={true}
|
||||||
|
onClickUpdate={this.updateCoreMinor.bind(this)}
|
||||||
|
whyNotWarning={this.lastUpdateRun.limitedPackages().includes('flarum/core')}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
{extensions.map((extension: Extension) => (
|
||||||
|
<ExtensionItem
|
||||||
|
extension={extension}
|
||||||
|
updates={this.packageUpdates[extension.id]}
|
||||||
|
onClickUpdate={this.updateExtension.bind(this, extension)}
|
||||||
|
whyNotWarning={this.lastUpdateRun.limitedPackages().includes(extension.name)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>,
|
||||||
|
coreUpdate && coreUpdate['latest-major'] ? <MajorUpdater coreUpdate={coreUpdate} updateState={this.lastUpdateRun.major} /> : null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
getExtensionUpdates(): Extension[] {
|
||||||
|
this.lastUpdateCheck?.updates?.installed?.filter((composerPackage: UpdatedPackage) => {
|
||||||
|
const id = composerPackage.name.replace('/', '-').replace(/(flarum-ext-)|(flarum-)/, '');
|
||||||
|
|
||||||
|
const extension = app.data.extensions[id];
|
||||||
|
const safeToUpdate = ['semver-safe-update', 'update-possible'].includes(composerPackage['latest-status']);
|
||||||
|
|
||||||
|
if (extension && safeToUpdate) {
|
||||||
|
this.packageUpdates[extension.id] = composerPackage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return extension && safeToUpdate;
|
||||||
|
});
|
||||||
|
|
||||||
|
return (Object.values(app.data.extensions) as Extension[]).filter((extension: Extension) => this.packageUpdates[extension.id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCoreUpdate(): UpdatedPackage | undefined {
|
||||||
|
return this.lastUpdateCheck?.updates?.installed?.filter((composerPackage: UpdatedPackage) => composerPackage.name === 'flarum/core').pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
checkForUpdates() {
|
||||||
|
this.isLoading = 'check';
|
||||||
|
|
||||||
|
app
|
||||||
|
.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${app.forum.attribute('apiUrl')}/package-manager/check-for-updates`,
|
||||||
|
errorHandler,
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
this.lastUpdateCheck = response as LastUpdateCheck;
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = null;
|
||||||
|
m.redraw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCoreMinor() {
|
||||||
|
if (confirm(app.translator.trans('flarum-package-manager.admin.minor_update_confirmation.content'))) {
|
||||||
|
app.modal.show(LoadingModal);
|
||||||
|
this.isLoading = 'minor-update';
|
||||||
|
|
||||||
|
app
|
||||||
|
.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${app.forum.attribute('apiUrl')}/package-manager/minor-update`,
|
||||||
|
errorHandler,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.update_successful'));
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = null;
|
||||||
|
m.redraw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateExtension(extension: any) {
|
||||||
|
app.modal.show(LoadingModal);
|
||||||
|
this.isLoading = 'extension-update';
|
||||||
|
|
||||||
|
app
|
||||||
|
.request({
|
||||||
|
method: 'PATCH',
|
||||||
|
url: `${app.forum.attribute('apiUrl')}/package-manager/extensions/${extension.id}`,
|
||||||
|
errorHandler,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
app.alerts.show(
|
||||||
|
{ type: 'success' },
|
||||||
|
app.translator.trans('flarum-package-manager.admin.extensions.successful_update', { extension: extension.extra['flarum-extension'].title })
|
||||||
|
);
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = null;
|
||||||
|
m.redraw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateGlobally() {
|
||||||
|
app.modal.show(LoadingModal);
|
||||||
|
this.isLoading = 'global-update';
|
||||||
|
|
||||||
|
app
|
||||||
|
.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${app.forum.attribute('apiUrl')}/package-manager/global-update`,
|
||||||
|
errorHandler,
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.updater.global_update_successful'));
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.isLoading = null;
|
||||||
|
m.redraw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,51 @@
|
|||||||
|
import app from 'flarum/admin/app';
|
||||||
|
import Mithril from 'mithril';
|
||||||
|
import Modal, { IInternalModalAttrs } from 'flarum/common/components/Modal';
|
||||||
|
import LoadingIndicator from 'flarum/common/components/LoadingIndicator';
|
||||||
|
import errorHandler from '../utils/errorHandler';
|
||||||
|
|
||||||
|
export interface WhyNotModalAttrs extends IInternalModalAttrs {
|
||||||
|
package: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class WhyNotModal<Attrs extends WhyNotModalAttrs = WhyNotModalAttrs> extends Modal<Attrs> {
|
||||||
|
loading: boolean = true;
|
||||||
|
whyNot: string | null = null;
|
||||||
|
|
||||||
|
className() {
|
||||||
|
return 'Modal--large WhyNotModal';
|
||||||
|
}
|
||||||
|
|
||||||
|
title() {
|
||||||
|
return app.translator.trans('flarum-package-manager.admin.why_not_modal.title');
|
||||||
|
}
|
||||||
|
|
||||||
|
oncreate(vnode: Mithril.VnodeDOM<Attrs, this>) {
|
||||||
|
super.oncreate(vnode);
|
||||||
|
|
||||||
|
this.requestWhyNot();
|
||||||
|
}
|
||||||
|
|
||||||
|
content() {
|
||||||
|
return <div className="Modal-body">{this.loading ? <LoadingIndicator /> : <pre className="WhyNotModal-contents">{this.whyNot}</pre>}</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestWhyNot(): void {
|
||||||
|
app
|
||||||
|
.request({
|
||||||
|
method: 'POST',
|
||||||
|
url: `${app.forum.attribute('apiUrl')}/package-manager/why-not`,
|
||||||
|
body: {
|
||||||
|
data: {
|
||||||
|
package: this.attrs.package,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
errorHandler,
|
||||||
|
})
|
||||||
|
.then((response: any) => {
|
||||||
|
this.loading = false;
|
||||||
|
this.whyNot = response.data.whyNot;
|
||||||
|
m.redraw();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
73
extensions/package-manager/js/src/admin/index.tsx
Executable file
73
extensions/package-manager/js/src/admin/index.tsx
Executable file
@@ -0,0 +1,73 @@
|
|||||||
|
import { extend } from 'flarum/common/extend';
|
||||||
|
import app from 'flarum/admin/app';
|
||||||
|
import Alert from 'flarum/common/components/Alert';
|
||||||
|
import ExtensionPage from 'flarum/admin/components/ExtensionPage';
|
||||||
|
import Button from 'flarum/common/components/Button';
|
||||||
|
import LoadingModal from 'flarum/admin/components/LoadingModal';
|
||||||
|
import Installer from './components/Installer';
|
||||||
|
import Updater from './components/Updater';
|
||||||
|
import isExtensionEnabled from 'flarum/admin/utils/isExtensionEnabled';
|
||||||
|
|
||||||
|
app.initializers.add('flarum-package-manager', (app) => {
|
||||||
|
app.extensionData
|
||||||
|
.for('flarum-package-manager')
|
||||||
|
.registerSetting(() => {
|
||||||
|
if (!app.data.isRequiredDirectoriesWritable) {
|
||||||
|
return (
|
||||||
|
<div className="Form-group">
|
||||||
|
<Alert type="warning" dismissible={false}>
|
||||||
|
{app.translator.trans('flarum-package-manager.admin.file_permissions')}
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.registerSetting(() => {
|
||||||
|
if (app.data.isRequiredDirectoriesWritable) {
|
||||||
|
return <Installer />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.registerSetting(() => {
|
||||||
|
if (app.data.isRequiredDirectoriesWritable) {
|
||||||
|
return <Updater />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
|
||||||
|
extend(ExtensionPage.prototype, 'topItems', function (items) {
|
||||||
|
if (this.extension.id === 'flarum-package-manager' || isExtensionEnabled(this.extension.id)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
items.add(
|
||||||
|
'remove',
|
||||||
|
<Button
|
||||||
|
className="Button Button--danger"
|
||||||
|
icon="fas fa-times"
|
||||||
|
onclick={() => {
|
||||||
|
app.modal.show(LoadingModal);
|
||||||
|
|
||||||
|
app
|
||||||
|
.request({
|
||||||
|
url: `${app.forum.attribute('apiUrl')}/package-manager/extensions/${this.extension.id}`,
|
||||||
|
method: 'DELETE',
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
app.alerts.show({ type: 'success' }, app.translator.trans('flarum-package-manager.admin.extensions.successful_remove'));
|
||||||
|
window.location = app.forum.attribute('adminUrl');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
app.modal.close();
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
29
extensions/package-manager/js/src/admin/utils/errorHandler.ts
Executable file
29
extensions/package-manager/js/src/admin/utils/errorHandler.ts
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
import app from 'flarum/admin/app';
|
||||||
|
|
||||||
|
export default function (e: any) {
|
||||||
|
const error = e.response.errors[0];
|
||||||
|
|
||||||
|
if (!['composer_command_failure', 'extension_already_installed', 'extension_not_installed'].includes(error.code)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (error.code) {
|
||||||
|
case 'composer_command_failure':
|
||||||
|
if (error.guessed_cause) {
|
||||||
|
app.alerts.show({ type: 'error' }, app.translator.trans(`flarum-package-manager.admin.exceptions.guessed_cause.${error.guessed_cause}`));
|
||||||
|
app.modal.close();
|
||||||
|
} else {
|
||||||
|
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-package-manager.admin.exceptions.composer_command_failure'));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'extension_already_installed':
|
||||||
|
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-package-manager.admin.exceptions.extension_already_installed'));
|
||||||
|
app.modal.close();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'extension_not_installed':
|
||||||
|
app.alerts.show({ type: 'error' }, app.translator.trans('flarum-package-manager.admin.exceptions.extension_not_installed'));
|
||||||
|
app.modal.close();
|
||||||
|
}
|
||||||
|
}
|
16
extensions/package-manager/js/tsconfig.json
Executable file
16
extensions/package-manager/js/tsconfig.json
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
// Use Flarum's tsconfig as a starting point
|
||||||
|
"extends": "flarum-tsconfig",
|
||||||
|
// This will match all .ts, .tsx, .d.ts, .js, .jsx files in your `src` folder
|
||||||
|
// and also tells your Typescript server to read core's global typings for
|
||||||
|
// access to `dayjs` and `$` in the global namespace.
|
||||||
|
"include": ["src/**/*", "../vendor/flarum/core/js/dist-typings/@types/**/*", "@types/**/*"],
|
||||||
|
"compilerOptions": {
|
||||||
|
// This will output typings to `dist-typings`
|
||||||
|
"declarationDir": "./dist-typings",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"flarum/*": ["../vendor/flarum/core/js/dist-typings/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
extensions/package-manager/js/webpack.config.js
Executable file
1
extensions/package-manager/js/webpack.config.js
Executable file
@@ -0,0 +1 @@
|
|||||||
|
module.exports = require('flarum-webpack-config')();
|
2868
extensions/package-manager/js/yarn.lock
Normal file
2868
extensions/package-manager/js/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
157
extensions/package-manager/less/admin.less
Executable file
157
extensions/package-manager/less/admin.less
Executable file
@@ -0,0 +1,157 @@
|
|||||||
|
.FormControl-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ComposerFailureModal-output {
|
||||||
|
white-space: break-spaces;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flarum-package-manager-Page .ExtensionPage-settings .Form-group:last-child {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PackageManager-lastUpdatedAt {
|
||||||
|
color: var(--control-color);
|
||||||
|
|
||||||
|
&-label {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.PackageManager-updaterControls {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
grid-area: controls;
|
||||||
|
}
|
||||||
|
|
||||||
|
.PackageManager-extensions {
|
||||||
|
&-grid {
|
||||||
|
--gap: 12px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, calc(~"100% / 3 - var(--gap)"));
|
||||||
|
gap: var(--gap);
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.PackageManager-extension {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background-color: var(--control-bg);
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
|
||||||
|
&-controls {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-icon {
|
||||||
|
--size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-version {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
|
||||||
|
&-latest {
|
||||||
|
border-radius: 30px;
|
||||||
|
padding: 0 6px;
|
||||||
|
font-weight: bold;
|
||||||
|
|
||||||
|
&--minor {
|
||||||
|
background-color: var(--alert-success-bg);
|
||||||
|
color: var(--alert-success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--major {
|
||||||
|
background-color: var(--alert-bg);
|
||||||
|
color: var(--alert-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--core {
|
||||||
|
--bg-hover: darken(#e7672e, 5);
|
||||||
|
background-color: #e7672e;
|
||||||
|
color: #fff;
|
||||||
|
--button-color: #fff;
|
||||||
|
--button-bg-hover: var(--bg-hover);
|
||||||
|
|
||||||
|
.Button--danger {
|
||||||
|
color: #fff;
|
||||||
|
--button-bg-hover: var(--bg-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--core &-icon {
|
||||||
|
background-size: 100%;
|
||||||
|
background-color: transparent;
|
||||||
|
filter: grayscale(1) brightness(3.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&--danger {
|
||||||
|
background-color: var(--control-danger-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.PackageManager-majorUpdate {
|
||||||
|
--space: 16px;
|
||||||
|
padding: var(--space);
|
||||||
|
display: grid;
|
||||||
|
grid-template-areas:
|
||||||
|
"title logo"
|
||||||
|
"helpText logo"
|
||||||
|
"controls logo"
|
||||||
|
"extensions extensions"
|
||||||
|
"failure failure";
|
||||||
|
grid-gap: 0 var(--space);
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
> img {
|
||||||
|
grid-area: logo;
|
||||||
|
}
|
||||||
|
|
||||||
|
> label {
|
||||||
|
grid-area: title;
|
||||||
|
}
|
||||||
|
|
||||||
|
> .helpText {
|
||||||
|
grid-area: helpText;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-failure {
|
||||||
|
--border-radius: 0;
|
||||||
|
grid-area: failure;
|
||||||
|
margin: calc(~"0px - var(--space)");
|
||||||
|
margin-top: var(--space);
|
||||||
|
}
|
||||||
|
|
||||||
|
&-incompatibleExtensions {
|
||||||
|
grid-area: extensions;
|
||||||
|
margin-top: var(--space);
|
||||||
|
padding-top: var(--space);
|
||||||
|
border-top: 1px solid var(--control-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.Form-group--danger {
|
||||||
|
border: 2px solid var(--alert-error-bg);
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.WhyNotModal {
|
||||||
|
&-contents {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
}
|
53
extensions/package-manager/locale/en.yml
Executable file
53
extensions/package-manager/locale/en.yml
Executable file
@@ -0,0 +1,53 @@
|
|||||||
|
flarum-package-manager:
|
||||||
|
admin:
|
||||||
|
exceptions:
|
||||||
|
composer_command_failure: Failed to execute. Check the composer logs in storage/logs/composer.
|
||||||
|
extension_already_installed: Extension is already installed.
|
||||||
|
extension_not_installed: Extension not found.
|
||||||
|
|
||||||
|
guessed_cause:
|
||||||
|
extension_incompatible_with_instance: The extension is most likely incompatible with your current Flarum instance.
|
||||||
|
extensions_incompatible_with_new_major: >
|
||||||
|
Some installed extensions are not compatible with the newest major release.
|
||||||
|
Please wait until the extensions are updated to be compatible by the authors, or remove them before proceeding.
|
||||||
|
|
||||||
|
extensions:
|
||||||
|
check_why_it_failed_updating: Show why it did not update to the latest.
|
||||||
|
install: Install a new extension
|
||||||
|
install_help: Fill in the extension package name to proceed. Visit {extiverse} to browse extensions.
|
||||||
|
proceed: Proceed
|
||||||
|
successful_install: "{extension} was installed successfully, redirecting.."
|
||||||
|
successful_remove: Extension removed successfully.
|
||||||
|
successful_update: "{extension} was updated successfully, redirecting.."
|
||||||
|
update: Update
|
||||||
|
|
||||||
|
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.
|
||||||
|
failure:
|
||||||
|
desc: The last major update failed, some installed extensions are not compatible with the new major release.
|
||||||
|
why: Find out more
|
||||||
|
|
||||||
|
title: Flarum {version} Major Update Available
|
||||||
|
update: Update
|
||||||
|
|
||||||
|
minor_update_confirmation:
|
||||||
|
content: This will also update any other extensions/packages with availabe updates.
|
||||||
|
|
||||||
|
updater:
|
||||||
|
check_for_updates: Check for updates
|
||||||
|
flarum: Flarum Core
|
||||||
|
global_update_successful: Successfully updated all packages.
|
||||||
|
last_update_checked_at: "Last Update Check: "
|
||||||
|
run_global_update: Run Global Update
|
||||||
|
updater_title: Updates
|
||||||
|
updater_help: Runs a check for new extension and Flarum updates.
|
||||||
|
|
||||||
|
update_successful: Flarum successfully updated.
|
||||||
|
|
||||||
|
why_not_modal:
|
||||||
|
title: Why Won't it Update
|
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Flarum\Database\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
|
||||||
|
return Migration::createTable(
|
||||||
|
'generic_tasks',
|
||||||
|
function (Blueprint $table) {
|
||||||
|
$table->increments('id');
|
||||||
|
$table->string('status', 50)->nullable();
|
||||||
|
$table->string('command', 50);
|
||||||
|
$table->string('command_class')->nullable();
|
||||||
|
$table->string('package', 100)->nullable();
|
||||||
|
$table->mediumText('output');
|
||||||
|
$table->dateTime('created_at');
|
||||||
|
$table->dateTime('started_at')->nullable();
|
||||||
|
$table->dateTime('finished_at')->nullable();
|
||||||
|
}
|
||||||
|
);
|
43
extensions/package-manager/src/Api/Controller/CheckForUpdatesController.php
Executable file
43
extensions/package-manager/src/Api/Controller/CheckForUpdatesController.php
Executable file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Api\Controller;
|
||||||
|
|
||||||
|
use Flarum\Http\RequestUtil;
|
||||||
|
use Illuminate\Contracts\Bus\Dispatcher;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Flarum\PackageManager\Command\CheckForUpdates;
|
||||||
|
|
||||||
|
class CheckForUpdatesController implements RequestHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $bus;
|
||||||
|
|
||||||
|
public function __construct(Dispatcher $bus)
|
||||||
|
{
|
||||||
|
$this->bus = $bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$actor = RequestUtil::getActor($request);
|
||||||
|
|
||||||
|
$lastUpdateCheck = $this->bus->dispatch(
|
||||||
|
new CheckForUpdates($actor)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JsonResponse($lastUpdateCheck);
|
||||||
|
}
|
||||||
|
}
|
45
extensions/package-manager/src/Api/Controller/GlobalUpdateController.php
Executable file
45
extensions/package-manager/src/Api/Controller/GlobalUpdateController.php
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Api\Controller;
|
||||||
|
|
||||||
|
use Flarum\Bus\Dispatcher;
|
||||||
|
use Flarum\Http\RequestUtil;
|
||||||
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Flarum\PackageManager\Command\GlobalUpdate;
|
||||||
|
|
||||||
|
class GlobalUpdateController implements RequestHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $bus;
|
||||||
|
|
||||||
|
public function __construct(Dispatcher $bus)
|
||||||
|
{
|
||||||
|
$this->bus = $bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||||
|
*/
|
||||||
|
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$actor = RequestUtil::getActor($request);
|
||||||
|
|
||||||
|
$this->bus->dispatch(
|
||||||
|
new GlobalUpdate($actor)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new EmptyResponse(200);
|
||||||
|
}
|
||||||
|
}
|
32
extensions/package-manager/src/Api/Controller/ListTaskController.php
Executable file
32
extensions/package-manager/src/Api/Controller/ListTaskController.php
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Api\Controller;
|
||||||
|
|
||||||
|
use Flarum\Http\RequestUtil;
|
||||||
|
use Flarum\PackageManager\Api\Serializer\TaskSerializer;
|
||||||
|
use Flarum\PackageManager\Task;
|
||||||
|
use Flarum\Api\Controller\AbstractListController;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Tobscure\JsonApi\Document;
|
||||||
|
|
||||||
|
class ListTaskController extends AbstractListController
|
||||||
|
{
|
||||||
|
public $serializer = TaskSerializer::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||||
|
*/
|
||||||
|
protected function data(ServerRequestInterface $request, Document $document)
|
||||||
|
{
|
||||||
|
RequestUtil::getActor($request)->assertAdmin();
|
||||||
|
|
||||||
|
return Task::query()->orderBy('created_at', 'desc')->get();
|
||||||
|
}
|
||||||
|
}
|
44
extensions/package-manager/src/Api/Controller/MajorUpdateController.php
Executable file
44
extensions/package-manager/src/Api/Controller/MajorUpdateController.php
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Api\Controller;
|
||||||
|
|
||||||
|
use Flarum\Bus\Dispatcher;
|
||||||
|
use Flarum\Http\RequestUtil;
|
||||||
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Flarum\PackageManager\Command\MajorUpdate;
|
||||||
|
|
||||||
|
class MajorUpdateController implements RequestHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $bus;
|
||||||
|
|
||||||
|
public function __construct(Dispatcher $bus)
|
||||||
|
{
|
||||||
|
$this->bus = $bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$actor = RequestUtil::getActor($request);
|
||||||
|
$dryRun = (bool) (int) Arr::get($request->getParsedBody(), 'data.dryRun', 0);
|
||||||
|
|
||||||
|
$this->bus->dispatch(
|
||||||
|
new MajorUpdate($actor, $dryRun)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new EmptyResponse(200);
|
||||||
|
}
|
||||||
|
}
|
45
extensions/package-manager/src/Api/Controller/MinorUpdateController.php
Executable file
45
extensions/package-manager/src/Api/Controller/MinorUpdateController.php
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Api\Controller;
|
||||||
|
|
||||||
|
use Flarum\Bus\Dispatcher;
|
||||||
|
use Flarum\Http\RequestUtil;
|
||||||
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Flarum\PackageManager\Command\MinorUpdate;
|
||||||
|
|
||||||
|
class MinorUpdateController implements RequestHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $bus;
|
||||||
|
|
||||||
|
public function __construct(Dispatcher $bus)
|
||||||
|
{
|
||||||
|
$this->bus = $bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||||
|
*/
|
||||||
|
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$actor = RequestUtil::getActor($request);
|
||||||
|
|
||||||
|
$this->bus->dispatch(
|
||||||
|
new MinorUpdate($actor)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new EmptyResponse(200);
|
||||||
|
}
|
||||||
|
}
|
44
extensions/package-manager/src/Api/Controller/RemoveExtensionController.php
Executable file
44
extensions/package-manager/src/Api/Controller/RemoveExtensionController.php
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Api\Controller;
|
||||||
|
|
||||||
|
use Flarum\Bus\Dispatcher;
|
||||||
|
use Flarum\Http\RequestUtil;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Flarum\PackageManager\Command\RemoveExtension;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
|
||||||
|
class RemoveExtensionController implements RequestHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $bus;
|
||||||
|
|
||||||
|
public function __construct(Dispatcher $bus)
|
||||||
|
{
|
||||||
|
$this->bus = $bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$actor = RequestUtil::getActor($request);
|
||||||
|
$extensionId = Arr::get($request->getQueryParams(), 'id');
|
||||||
|
|
||||||
|
$this->bus->dispatch(
|
||||||
|
new RemoveExtension($actor, $extensionId)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new EmptyResponse(200);
|
||||||
|
}
|
||||||
|
}
|
48
extensions/package-manager/src/Api/Controller/RequireExtensionController.php
Executable file
48
extensions/package-manager/src/Api/Controller/RequireExtensionController.php
Executable file
@@ -0,0 +1,48 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Api\Controller;
|
||||||
|
|
||||||
|
use Flarum\Bus\Dispatcher;
|
||||||
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Flarum\PackageManager\Api\Serializer\ExtensionSerializer;
|
||||||
|
use Flarum\PackageManager\Command\RequireExtension;
|
||||||
|
use Flarum\PackageManager\Extension\ExtensionUtils;
|
||||||
|
use Flarum\Api\Controller\AbstractCreateController;
|
||||||
|
use Flarum\Http\RequestUtil;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Tobscure\JsonApi\Document;
|
||||||
|
|
||||||
|
class RequireExtensionController implements RequestHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $bus;
|
||||||
|
|
||||||
|
public function __construct(Dispatcher $bus)
|
||||||
|
{
|
||||||
|
$this->bus = $bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$actor = RequestUtil::getActor($request);
|
||||||
|
$package = Arr::get($request->getParsedBody(), 'data.package');
|
||||||
|
|
||||||
|
$data = $this->bus->dispatch(
|
||||||
|
new RequireExtension($actor, $package)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JsonResponse($data);
|
||||||
|
}
|
||||||
|
}
|
44
extensions/package-manager/src/Api/Controller/UpdateExtensionController.php
Executable file
44
extensions/package-manager/src/Api/Controller/UpdateExtensionController.php
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Api\Controller;
|
||||||
|
|
||||||
|
use Flarum\Bus\Dispatcher;
|
||||||
|
use Flarum\Http\RequestUtil;
|
||||||
|
use Laminas\Diactoros\Response\EmptyResponse;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Flarum\PackageManager\Command\UpdateExtension;
|
||||||
|
|
||||||
|
class UpdateExtensionController implements RequestHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $bus;
|
||||||
|
|
||||||
|
public function __construct(Dispatcher $bus)
|
||||||
|
{
|
||||||
|
$this->bus = $bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$actor = RequestUtil::getActor($request);
|
||||||
|
$extensionId = Arr::get($request->getQueryParams(), 'id');
|
||||||
|
|
||||||
|
$this->bus->dispatch(
|
||||||
|
new UpdateExtension($actor, $extensionId)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new EmptyResponse(200);
|
||||||
|
}
|
||||||
|
}
|
47
extensions/package-manager/src/Api/Controller/WhyNotController.php
Executable file
47
extensions/package-manager/src/Api/Controller/WhyNotController.php
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Api\Controller;
|
||||||
|
|
||||||
|
use Flarum\Bus\Dispatcher;
|
||||||
|
use Flarum\Http\RequestUtil;
|
||||||
|
use Flarum\PackageManager\Command\WhyNot;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Laminas\Diactoros\Response\JsonResponse;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
class WhyNotController implements RequestHandlerInterface
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $bus;
|
||||||
|
|
||||||
|
public function __construct(Dispatcher $bus)
|
||||||
|
{
|
||||||
|
$this->bus = $bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(ServerRequestInterface $request): ResponseInterface
|
||||||
|
{
|
||||||
|
$actor = RequestUtil::getActor($request);
|
||||||
|
$package = Arr::get($request->getParsedBody(), 'data.package', '');
|
||||||
|
$version = Arr::get($request->getParsedBody(), 'data.version', '*');
|
||||||
|
|
||||||
|
$whyNot = $this->bus->dispatch(
|
||||||
|
new WhyNot($actor, $package, $version)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'data' => compact('whyNot')
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
25
extensions/package-manager/src/Command/CheckForUpdates.php
Executable file
25
extensions/package-manager/src/Command/CheckForUpdates.php
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class CheckForUpdates
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Flarum\User\User
|
||||||
|
*/
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
public function __construct(User $actor)
|
||||||
|
{
|
||||||
|
$this->actor = $actor;
|
||||||
|
}
|
||||||
|
}
|
127
extensions/package-manager/src/Command/CheckForUpdatesHandler.php
Executable file
127
extensions/package-manager/src/Command/CheckForUpdatesHandler.php
Executable file
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||||
|
use Flarum\PackageManager\Exception\ComposerCommandFailedException;
|
||||||
|
use Flarum\PackageManager\Settings\LastUpdateCheck;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
|
||||||
|
class CheckForUpdatesHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ComposerAdapter
|
||||||
|
*/
|
||||||
|
protected $composer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Flarum\PackageManager\Settings\LastUpdateCheck
|
||||||
|
*/
|
||||||
|
protected $lastUpdateCheck;
|
||||||
|
|
||||||
|
public function __construct(ComposerAdapter $composer, LastUpdateCheck $lastUpdateCheck)
|
||||||
|
{
|
||||||
|
$this->composer = $composer;
|
||||||
|
$this->lastUpdateCheck = $lastUpdateCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @todo integration test
|
||||||
|
*/
|
||||||
|
public function handle(CheckForUpdates $command)
|
||||||
|
{
|
||||||
|
$actor = $command->actor;
|
||||||
|
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! isset($secondOutput)) {
|
||||||
|
$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
|
||||||
|
->with('installed', $firstOutput['installed'])
|
||||||
|
->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws ComposerCommandFailedException
|
||||||
|
*/
|
||||||
|
protected function runComposerCommand(bool $minorOnly): string
|
||||||
|
{
|
||||||
|
$input = [
|
||||||
|
'command' => 'outdated',
|
||||||
|
'-D' => true,
|
||||||
|
'--format' => 'json',
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($minorOnly) {
|
||||||
|
$input['--minor-only'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = $this->composer->run(new ArrayInput($input));
|
||||||
|
|
||||||
|
if ($output->getExitCode() !== 0) {
|
||||||
|
throw new ComposerCommandFailedException('', $output->getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output->getContents();
|
||||||
|
}
|
||||||
|
}
|
25
extensions/package-manager/src/Command/GlobalUpdate.php
Normal file
25
extensions/package-manager/src/Command/GlobalUpdate.php
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class GlobalUpdate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Flarum\User\User
|
||||||
|
*/
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
public function __construct(User $actor)
|
||||||
|
{
|
||||||
|
$this->actor = $actor;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\Bus\Dispatcher as FlarumDispatcher;
|
||||||
|
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Flarum\PackageManager\Event\FlarumUpdated;
|
||||||
|
use Flarum\PackageManager\Exception\ComposerUpdateFailedException;
|
||||||
|
use Symfony\Component\Console\Input\StringInput;
|
||||||
|
|
||||||
|
class GlobalUpdateHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ComposerAdapter
|
||||||
|
*/
|
||||||
|
protected $composer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $events;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var FlarumDispatcher
|
||||||
|
*/
|
||||||
|
protected $commandDispatcher;
|
||||||
|
|
||||||
|
public function __construct(ComposerAdapter $composer, Dispatcher $events, FlarumDispatcher $commandDispatcher)
|
||||||
|
{
|
||||||
|
$this->composer = $composer;
|
||||||
|
$this->events = $events;
|
||||||
|
$this->commandDispatcher = $commandDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Flarum\User\Exception\PermissionDeniedException|ComposerUpdateFailedException
|
||||||
|
*/
|
||||||
|
public function handle(GlobalUpdate $command)
|
||||||
|
{
|
||||||
|
$command->actor->assertAdmin();
|
||||||
|
|
||||||
|
$output = $this->composer->run(
|
||||||
|
new StringInput("update --prefer-dist --no-dev -a --with-all-dependencies")
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($output->getExitCode() !== 0) {
|
||||||
|
throw new ComposerUpdateFailedException('*', $output->getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->events->dispatch(
|
||||||
|
new FlarumUpdated($command->actor, FlarumUpdated::GLOBAL)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
31
extensions/package-manager/src/Command/MajorUpdate.php
Normal file
31
extensions/package-manager/src/Command/MajorUpdate.php
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class MajorUpdate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Flarum\User\User
|
||||||
|
*/
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
public $dryRun;
|
||||||
|
|
||||||
|
public function __construct(User $actor, bool $dryRun)
|
||||||
|
{
|
||||||
|
$this->actor = $actor;
|
||||||
|
$this->dryRun = $dryRun;
|
||||||
|
}
|
||||||
|
}
|
129
extensions/package-manager/src/Command/MajorUpdateHandler.php
Normal file
129
extensions/package-manager/src/Command/MajorUpdateHandler.php
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||||
|
use Flarum\PackageManager\Composer\ComposerJson;
|
||||||
|
use Flarum\PackageManager\Exception\MajorUpdateFailedException;
|
||||||
|
use Flarum\PackageManager\Exception\NoNewMajorVersionException;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Flarum\PackageManager\Event\FlarumUpdated;
|
||||||
|
use Flarum\PackageManager\Settings\LastUpdateCheck;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
|
||||||
|
class MajorUpdateHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ComposerAdapter
|
||||||
|
*/
|
||||||
|
protected $composer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LastUpdateCheck
|
||||||
|
*/
|
||||||
|
protected $lastUpdateCheck;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $events;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ComposerJson
|
||||||
|
*/
|
||||||
|
protected $composerJson;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ComposerAdapter $composer
|
||||||
|
* @param LastUpdateCheck $lastUpdateCheck
|
||||||
|
* @param Dispatcher $events
|
||||||
|
* @param ComposerJson $composerJson
|
||||||
|
*/
|
||||||
|
public function __construct(ComposerAdapter $composer, LastUpdateCheck $lastUpdateCheck, Dispatcher $events, ComposerJson $composerJson)
|
||||||
|
{
|
||||||
|
$this->composer = $composer;
|
||||||
|
$this->lastUpdateCheck = $lastUpdateCheck;
|
||||||
|
$this->events = $events;
|
||||||
|
$this->composerJson = $composerJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the version constraint for all directly required packages in the root composer.json to *.
|
||||||
|
* Set flarum/core version constraint to new major version.
|
||||||
|
* Run composer update --prefer-dist --no-plugins --no-dev -a --with-all-dependencies.
|
||||||
|
* Clear cache.
|
||||||
|
* Run migrations.
|
||||||
|
*
|
||||||
|
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||||
|
* @throws NoNewMajorVersionException|MajorUpdateFailedException
|
||||||
|
*/
|
||||||
|
public function handle(MajorUpdate $command)
|
||||||
|
{
|
||||||
|
$command->actor->assertAdmin();
|
||||||
|
|
||||||
|
$majorVersion = $this->lastUpdateCheck->getNewMajorVersion();
|
||||||
|
|
||||||
|
if (! $majorVersion) {
|
||||||
|
throw new NoNewMajorVersionException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->updateComposerJson($majorVersion);
|
||||||
|
|
||||||
|
$this->runCommand($command->dryRun, $majorVersion);
|
||||||
|
|
||||||
|
if ($command->dryRun) {
|
||||||
|
$this->composerJson->revert();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->events->dispatch(
|
||||||
|
new FlarumUpdated($command->actor, FlarumUpdated::MAJOR)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo change minimum stability to 'stable' and any other similar params
|
||||||
|
*/
|
||||||
|
protected function updateComposerJson(string $majorVersion): void
|
||||||
|
{
|
||||||
|
$versionNumber = str_replace('v', '', $majorVersion);
|
||||||
|
|
||||||
|
$this->composerJson->require('*', '*');
|
||||||
|
$this->composerJson->require('flarum/core', '^'.$versionNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws MajorUpdateFailedException
|
||||||
|
*/
|
||||||
|
protected function runCommand(bool $dryRun, string $majorVersion): void
|
||||||
|
{
|
||||||
|
$input = [
|
||||||
|
'command' => 'update',
|
||||||
|
'--prefer-dist' => true,
|
||||||
|
'--no-plugins' => true,
|
||||||
|
'--no-dev' => true,
|
||||||
|
'-a' => true,
|
||||||
|
'--with-all-dependencies' => true,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($dryRun) {
|
||||||
|
$input['--dry-run'] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = $this->composer->run(new ArrayInput($input));
|
||||||
|
|
||||||
|
if ($output->getExitCode() !== 0) {
|
||||||
|
throw new MajorUpdateFailedException('*', $output->getContents(), $majorVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
25
extensions/package-manager/src/Command/MinorUpdate.php
Executable file
25
extensions/package-manager/src/Command/MinorUpdate.php
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class MinorUpdate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var \Flarum\User\User
|
||||||
|
*/
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
public function __construct(User $actor)
|
||||||
|
{
|
||||||
|
$this->actor = $actor;
|
||||||
|
}
|
||||||
|
}
|
77
extensions/package-manager/src/Command/MinorUpdateHandler.php
Executable file
77
extensions/package-manager/src/Command/MinorUpdateHandler.php
Executable file
@@ -0,0 +1,77 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||||
|
use Flarum\PackageManager\Composer\ComposerJson;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Flarum\PackageManager\Event\FlarumUpdated;
|
||||||
|
use Flarum\PackageManager\Exception\ComposerUpdateFailedException;
|
||||||
|
use Flarum\PackageManager\Settings\LastUpdateCheck;
|
||||||
|
use Symfony\Component\Console\Input\StringInput;
|
||||||
|
|
||||||
|
class MinorUpdateHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ComposerAdapter
|
||||||
|
*/
|
||||||
|
protected $composer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var \Flarum\PackageManager\Settings\LastUpdateCheck
|
||||||
|
*/
|
||||||
|
protected $lastUpdateCheck;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $events;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ComposerJson
|
||||||
|
*/
|
||||||
|
protected $composerJson;
|
||||||
|
|
||||||
|
public function __construct(ComposerAdapter $composer, LastUpdateCheck $lastUpdateCheck, Dispatcher $events, ComposerJson $composerJson)
|
||||||
|
{
|
||||||
|
$this->composer = $composer;
|
||||||
|
$this->lastUpdateCheck = $lastUpdateCheck;
|
||||||
|
$this->events = $events;
|
||||||
|
$this->composerJson = $composerJson;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||||
|
* @throws ComposerUpdateFailedException
|
||||||
|
*/
|
||||||
|
public function handle(MinorUpdate $command)
|
||||||
|
{
|
||||||
|
$command->actor->assertAdmin();
|
||||||
|
|
||||||
|
$coreRequirement = $this->composerJson->get()['require']['flarum/core'];
|
||||||
|
|
||||||
|
$this->composerJson->require('*', '*');
|
||||||
|
$this->composerJson->require('flarum/core', $coreRequirement);
|
||||||
|
|
||||||
|
$output = $this->composer->run(
|
||||||
|
new StringInput("update --prefer-dist --no-dev -a --with-all-dependencies")
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($output->getExitCode() !== 0) {
|
||||||
|
throw new ComposerUpdateFailedException('flarum/*', $output->getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->events->dispatch(
|
||||||
|
new FlarumUpdated($command->actor, FlarumUpdated::MINOR)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
31
extensions/package-manager/src/Command/RemoveExtension.php
Executable file
31
extensions/package-manager/src/Command/RemoveExtension.php
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class RemoveExtension
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User
|
||||||
|
*/
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $extensionId;
|
||||||
|
|
||||||
|
public function __construct(User $actor, string $extensionId)
|
||||||
|
{
|
||||||
|
$this->actor = $actor;
|
||||||
|
$this->extensionId = $extensionId;
|
||||||
|
}
|
||||||
|
}
|
70
extensions/package-manager/src/Command/RemoveExtensionHandler.php
Executable file
70
extensions/package-manager/src/Command/RemoveExtensionHandler.php
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\Extension\ExtensionManager;
|
||||||
|
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Flarum\PackageManager\Exception\ComposerCommandFailedException;
|
||||||
|
use Flarum\PackageManager\Exception\ExtensionNotInstalledException;
|
||||||
|
use Flarum\PackageManager\Extension\Event\Removed;
|
||||||
|
use Symfony\Component\Console\Input\StringInput;
|
||||||
|
|
||||||
|
class RemoveExtensionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ComposerAdapter
|
||||||
|
*/
|
||||||
|
protected $composer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ExtensionManager
|
||||||
|
*/
|
||||||
|
protected $extensions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $events;
|
||||||
|
|
||||||
|
public function __construct(ComposerAdapter $composer, ExtensionManager $extensions, Dispatcher $events)
|
||||||
|
{
|
||||||
|
$this->composer = $composer;
|
||||||
|
$this->extensions = $extensions;
|
||||||
|
$this->events = $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function handle(RemoveExtension $command)
|
||||||
|
{
|
||||||
|
$command->actor->assertAdmin();
|
||||||
|
|
||||||
|
$extension = $this->extensions->getExtension($command->extensionId);
|
||||||
|
|
||||||
|
if (empty($extension)) {
|
||||||
|
throw new ExtensionNotInstalledException($command->extensionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = $this->composer->run(
|
||||||
|
new StringInput("remove $extension->name")
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($output->getExitCode() !== 0) {
|
||||||
|
throw new ComposerCommandFailedException($extension->name, $output->getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->events->dispatch(
|
||||||
|
new Removed($extension)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
31
extensions/package-manager/src/Command/RequireExtension.php
Executable file
31
extensions/package-manager/src/Command/RequireExtension.php
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class RequireExtension
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User
|
||||||
|
*/
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $package;
|
||||||
|
|
||||||
|
public function __construct(User $actor, string $package)
|
||||||
|
{
|
||||||
|
$this->actor = $actor;
|
||||||
|
$this->package = $package;
|
||||||
|
}
|
||||||
|
}
|
90
extensions/package-manager/src/Command/RequireExtensionHandler.php
Executable file
90
extensions/package-manager/src/Command/RequireExtensionHandler.php
Executable file
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\Extension\ExtensionManager;
|
||||||
|
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Flarum\PackageManager\Exception\ComposerRequireFailedException;
|
||||||
|
use Flarum\PackageManager\Exception\ExtensionAlreadyInstalledException;
|
||||||
|
use Flarum\PackageManager\Extension\Event\Installed;
|
||||||
|
use Flarum\PackageManager\Extension\ExtensionUtils;
|
||||||
|
use Flarum\PackageManager\RequirePackageValidator;
|
||||||
|
use Symfony\Component\Console\Input\StringInput;
|
||||||
|
|
||||||
|
class RequireExtensionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ComposerAdapter
|
||||||
|
*/
|
||||||
|
protected $composer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ExtensionManager
|
||||||
|
*/
|
||||||
|
protected $extensions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var RequirePackageValidator
|
||||||
|
*/
|
||||||
|
protected $validator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $events;
|
||||||
|
|
||||||
|
public function __construct(ComposerAdapter $composer, ExtensionManager $extensions, RequirePackageValidator $validator, Dispatcher $events)
|
||||||
|
{
|
||||||
|
$this->composer = $composer;
|
||||||
|
$this->extensions = $extensions;
|
||||||
|
$this->validator = $validator;
|
||||||
|
$this->events = $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function handle(RequireExtension $command)
|
||||||
|
{
|
||||||
|
$command->actor->assertAdmin();
|
||||||
|
|
||||||
|
$this->validator->assertValid(['package' => $command->package]);
|
||||||
|
|
||||||
|
$extensionId = ExtensionUtils::nameToId($command->package);
|
||||||
|
$extension = $this->extensions->getExtension($extensionId);
|
||||||
|
|
||||||
|
if (! empty($extension)) {
|
||||||
|
throw new ExtensionAlreadyInstalledException($extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
$packageName = $command->package;
|
||||||
|
|
||||||
|
// Auto append :* if not requiring a specific version.
|
||||||
|
if (strpos($packageName, ':') === false) {
|
||||||
|
$packageName .= ":*";
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = $this->composer->run(
|
||||||
|
new StringInput("require $packageName")
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($output->getExitCode() !== 0) {
|
||||||
|
throw new ComposerRequireFailedException($packageName, $output->getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->events->dispatch(
|
||||||
|
new Installed($extensionId)
|
||||||
|
);
|
||||||
|
|
||||||
|
return ['id' => $extensionId];
|
||||||
|
}
|
||||||
|
}
|
31
extensions/package-manager/src/Command/UpdateExtension.php
Executable file
31
extensions/package-manager/src/Command/UpdateExtension.php
Executable file
@@ -0,0 +1,31 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class UpdateExtension
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User
|
||||||
|
*/
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $extensionId;
|
||||||
|
|
||||||
|
public function __construct(User $actor, string $extensionId)
|
||||||
|
{
|
||||||
|
$this->actor = $actor;
|
||||||
|
$this->extensionId = $extensionId;
|
||||||
|
}
|
||||||
|
}
|
93
extensions/package-manager/src/Command/UpdateExtensionHandler.php
Executable file
93
extensions/package-manager/src/Command/UpdateExtensionHandler.php
Executable file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\Extension\ExtensionManager;
|
||||||
|
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Flarum\PackageManager\Exception\ComposerUpdateFailedException;
|
||||||
|
use Flarum\PackageManager\Exception\ExtensionNotInstalledException;
|
||||||
|
use Flarum\PackageManager\Extension\Event\Updated;
|
||||||
|
use Flarum\PackageManager\UpdateExtensionValidator;
|
||||||
|
use Flarum\PackageManager\Settings\LastUpdateCheck;
|
||||||
|
use Symfony\Component\Console\Input\StringInput;
|
||||||
|
|
||||||
|
class UpdateExtensionHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ComposerAdapter
|
||||||
|
*/
|
||||||
|
protected $composer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ExtensionManager
|
||||||
|
*/
|
||||||
|
protected $extensions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var UpdateExtensionValidator
|
||||||
|
*/
|
||||||
|
protected $validator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var LastUpdateCheck
|
||||||
|
*/
|
||||||
|
protected $lastUpdateCheck;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $events;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
ComposerAdapter $composer,
|
||||||
|
ExtensionManager $extensions,
|
||||||
|
UpdateExtensionValidator $validator,
|
||||||
|
LastUpdateCheck $lastUpdateCheck,
|
||||||
|
Dispatcher $events)
|
||||||
|
{
|
||||||
|
$this->composer = $composer;
|
||||||
|
$this->extensions = $extensions;
|
||||||
|
$this->validator = $validator;
|
||||||
|
$this->lastUpdateCheck = $lastUpdateCheck;
|
||||||
|
$this->events = $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function handle(UpdateExtension $command)
|
||||||
|
{
|
||||||
|
$command->actor->assertAdmin();
|
||||||
|
|
||||||
|
$this->validator->assertValid(['extensionId' => $command->extensionId]);
|
||||||
|
|
||||||
|
$extension = $this->extensions->getExtension($command->extensionId);
|
||||||
|
|
||||||
|
if (empty($extension)) {
|
||||||
|
throw new ExtensionNotInstalledException($command->extensionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = $this->composer->run(
|
||||||
|
new StringInput("require $extension->name:*")
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($output->getExitCode() !== 0) {
|
||||||
|
throw new ComposerUpdateFailedException($extension->name, $output->getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->events->dispatch(
|
||||||
|
new Updated($command->actor, $extension)
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
37
extensions/package-manager/src/Command/WhyNot.php
Executable file
37
extensions/package-manager/src/Command/WhyNot.php
Executable file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class WhyNot
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User
|
||||||
|
*/
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $package;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $version;
|
||||||
|
|
||||||
|
public function __construct(User $actor, string $package, string $version)
|
||||||
|
{
|
||||||
|
$this->actor = $actor;
|
||||||
|
$this->package = $package;
|
||||||
|
$this->version = $version;
|
||||||
|
}
|
||||||
|
}
|
65
extensions/package-manager/src/Command/WhyNotHandler.php
Executable file
65
extensions/package-manager/src/Command/WhyNotHandler.php
Executable file
@@ -0,0 +1,65 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Command;
|
||||||
|
|
||||||
|
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Flarum\PackageManager\Exception\ComposerRequireFailedException;
|
||||||
|
use Flarum\PackageManager\WhyNotValidator;
|
||||||
|
use Symfony\Component\Console\Input\StringInput;
|
||||||
|
|
||||||
|
class WhyNotHandler
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ComposerAdapter
|
||||||
|
*/
|
||||||
|
protected $composer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var WhyNotValidator
|
||||||
|
*/
|
||||||
|
protected $validator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
protected $events;
|
||||||
|
|
||||||
|
public function __construct(ComposerAdapter $composer, WhyNotValidator $validator, Dispatcher $events)
|
||||||
|
{
|
||||||
|
$this->composer = $composer;
|
||||||
|
$this->validator = $validator;
|
||||||
|
$this->events = $events;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Flarum\User\Exception\PermissionDeniedException
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function handle(WhyNot $command)
|
||||||
|
{
|
||||||
|
$command->actor->assertAdmin();
|
||||||
|
|
||||||
|
$this->validator->assertValid([
|
||||||
|
'package' => $command->package,
|
||||||
|
'version' => $command->version
|
||||||
|
]);
|
||||||
|
|
||||||
|
$output = $this->composer->run(
|
||||||
|
new StringInput("why-not $command->package $command->version")
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($output->getExitCode() !== 0) {
|
||||||
|
throw new ComposerRequireFailedException($command->package, $output->getContents());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $output->getContents();
|
||||||
|
}
|
||||||
|
}
|
67
extensions/package-manager/src/Composer/ComposerAdapter.php
Normal file
67
extensions/package-manager/src/Composer/ComposerAdapter.php
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Composer;
|
||||||
|
|
||||||
|
use Composer\Console\Application;
|
||||||
|
use Flarum\Foundation\Paths;
|
||||||
|
use Flarum\PackageManager\OutputLogger;
|
||||||
|
use Symfony\Component\Console\Input\InputInterface;
|
||||||
|
use Symfony\Component\Console\Output\BufferedOutput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
class ComposerAdapter
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Application
|
||||||
|
*/
|
||||||
|
private $application;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var OutputLogger
|
||||||
|
*/
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var BufferedOutput
|
||||||
|
*/
|
||||||
|
private $output;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Paths
|
||||||
|
*/
|
||||||
|
private $paths;
|
||||||
|
|
||||||
|
public function __construct(Application $application, OutputLogger $logger, Paths $paths)
|
||||||
|
{
|
||||||
|
$this->application = $application;
|
||||||
|
$this->logger = $logger;
|
||||||
|
$this->paths = $paths;
|
||||||
|
$this->output = new BufferedOutput();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run(InputInterface $input): ComposerOutput
|
||||||
|
{
|
||||||
|
$this->application->resetComposer();
|
||||||
|
|
||||||
|
// This hack is necessary so that relative path repositories are resolved properly.
|
||||||
|
$currDir = getcwd();
|
||||||
|
chdir($this->paths->base);
|
||||||
|
$exitCode = $this->application->run($input, $this->output);
|
||||||
|
chdir($currDir);
|
||||||
|
|
||||||
|
$outputContents = $this->output->fetch();
|
||||||
|
|
||||||
|
$this->logger->log($input->__toString(), $outputContents, $exitCode);
|
||||||
|
|
||||||
|
return new ComposerOutput($exitCode, $outputContents);
|
||||||
|
}
|
||||||
|
}
|
90
extensions/package-manager/src/Composer/ComposerJson.php
Normal file
90
extensions/package-manager/src/Composer/ComposerJson.php
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Composer;
|
||||||
|
|
||||||
|
use Flarum\Foundation\Paths;
|
||||||
|
use Illuminate\Filesystem\Filesystem;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class ComposerJson
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Paths
|
||||||
|
*/
|
||||||
|
protected $paths;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Filesystem
|
||||||
|
*/
|
||||||
|
protected $filesystem;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected $initialJson;
|
||||||
|
|
||||||
|
public function __construct(Paths $paths, Filesystem $filesystem)
|
||||||
|
{
|
||||||
|
$this->paths = $paths;
|
||||||
|
$this->filesystem = $filesystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function require(string $packageName, string $version): void
|
||||||
|
{
|
||||||
|
$composerJson = $this->get();
|
||||||
|
|
||||||
|
if (strpos($packageName, '*') === false) {
|
||||||
|
$composerJson['require'][$packageName] = $version;
|
||||||
|
} else {
|
||||||
|
foreach ($composerJson['require'] as $p => $v) {
|
||||||
|
if ($version === '*@dev') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$wildcardPackageName = str_replace('\*', '.*', preg_quote($packageName, '/'));
|
||||||
|
|
||||||
|
if (Str::of($p)->test("/($wildcardPackageName)/")) {
|
||||||
|
$composerJson['require'][$p] = $version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->set($composerJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function revert(): void
|
||||||
|
{
|
||||||
|
$this->set($this->initialJson);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getComposerJsonPath(): string
|
||||||
|
{
|
||||||
|
return $this->paths->base . '/composer.json';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Illuminate\Contracts\Filesystem\FileNotFoundException
|
||||||
|
*/
|
||||||
|
public function get(): array
|
||||||
|
{
|
||||||
|
$json = json_decode($this->filesystem->get($this->getComposerJsonPath()), true);
|
||||||
|
|
||||||
|
if (! $this->initialJson) {
|
||||||
|
$this->initialJson = $json;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $json;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function set(array $json): void
|
||||||
|
{
|
||||||
|
$this->filesystem->put($this->getComposerJsonPath(), json_encode($json, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||||
|
}
|
||||||
|
}
|
39
extensions/package-manager/src/Composer/ComposerOutput.php
Normal file
39
extensions/package-manager/src/Composer/ComposerOutput.php
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Composer;
|
||||||
|
|
||||||
|
class ComposerOutput
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var int
|
||||||
|
*/
|
||||||
|
protected $exitCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $contents;
|
||||||
|
|
||||||
|
public function __construct(int $exitCode, string $contents)
|
||||||
|
{
|
||||||
|
$this->exitCode = $exitCode;
|
||||||
|
$this->contents = $contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getExitCode(): int
|
||||||
|
{
|
||||||
|
return $this->exitCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContents(): string
|
||||||
|
{
|
||||||
|
return $this->contents;
|
||||||
|
}
|
||||||
|
}
|
35
extensions/package-manager/src/Event/FlarumUpdated.php
Normal file
35
extensions/package-manager/src/Event/FlarumUpdated.php
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Event;
|
||||||
|
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class FlarumUpdated
|
||||||
|
{
|
||||||
|
public const GLOBAL = 'global';
|
||||||
|
public const MINOR = 'minor';
|
||||||
|
public const MAJOR = 'major';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var User
|
||||||
|
*/
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $type;
|
||||||
|
|
||||||
|
public function __construct(User $actor, string $type)
|
||||||
|
{
|
||||||
|
$this->actor = $actor;
|
||||||
|
$this->type = $type;
|
||||||
|
}
|
||||||
|
}
|
42
extensions/package-manager/src/Exception/ComposerCommandFailedException.php
Executable file
42
extensions/package-manager/src/Exception/ComposerCommandFailedException.php
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Exception;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ComposerCommandFailedException extends Exception
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $packageName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
public $details = [];
|
||||||
|
|
||||||
|
public function __construct(string $packageName, string $output)
|
||||||
|
{
|
||||||
|
$this->packageName = $packageName;
|
||||||
|
|
||||||
|
parent::__construct($output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function guessCause(): ?string
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function getRawPackageName(): string
|
||||||
|
{
|
||||||
|
return preg_replace('/^([A-z0-9-_\/]+)(?::.*|)$/i', '$1', $this->packageName);
|
||||||
|
}
|
||||||
|
}
|
30
extensions/package-manager/src/Exception/ComposerRequireFailedException.php
Executable file
30
extensions/package-manager/src/Exception/ComposerRequireFailedException.php
Executable file
@@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Exception;
|
||||||
|
|
||||||
|
class ComposerRequireFailedException extends ComposerCommandFailedException
|
||||||
|
{
|
||||||
|
protected const INCOMPATIBLE_REGEX = '/(?:(?: +- {PACKAGE_NAME}(?: v[0-9A-z.-]+ requires|\[[^\[\]]+\] require) flarum\/core)|(?:Could not find a version of package {PACKAGE_NAME} matching your minim)|(?: +- Root composer.json requires {PACKAGE_NAME} [^,]+, found {PACKAGE_NAME}\[[^\[\]]+\]+ but it does not match your minimum-stability))/m';
|
||||||
|
|
||||||
|
public function guessCause(): ?string
|
||||||
|
{
|
||||||
|
$hasMatches = preg_match(
|
||||||
|
str_replace('{PACKAGE_NAME}', preg_quote($this->getRawPackageName(), '/'), self::INCOMPATIBLE_REGEX),
|
||||||
|
$this->getMessage(),
|
||||||
|
$matches
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($hasMatches) {
|
||||||
|
return 'extension_incompatible_with_instance';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
15
extensions/package-manager/src/Exception/ComposerUpdateFailedException.php
Executable file
15
extensions/package-manager/src/Exception/ComposerUpdateFailedException.php
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Exception;
|
||||||
|
|
||||||
|
class ComposerUpdateFailedException extends ComposerCommandFailedException
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
44
extensions/package-manager/src/Exception/ExceptionHandler.php
Executable file
44
extensions/package-manager/src/Exception/ExceptionHandler.php
Executable file
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Exception;
|
||||||
|
|
||||||
|
use Flarum\Foundation\ErrorHandling\HandledError;
|
||||||
|
|
||||||
|
class ExceptionHandler
|
||||||
|
{
|
||||||
|
public function handle(ComposerCommandFailedException $e): HandledError
|
||||||
|
{
|
||||||
|
return (new HandledError(
|
||||||
|
$e,
|
||||||
|
'composer_command_failure',
|
||||||
|
409
|
||||||
|
))->withDetails($this->errorDetails($e));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function errorDetails(ComposerCommandFailedException $e): array
|
||||||
|
{
|
||||||
|
$details = [];
|
||||||
|
|
||||||
|
if ($guessedCause = $this->guessCause($e)) {
|
||||||
|
$details['guessed_cause'] = $guessedCause;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($e->details)) {
|
||||||
|
$details = array_merge($details, $e->details);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [$details];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function guessCause(ComposerCommandFailedException $e): ?string
|
||||||
|
{
|
||||||
|
return $e->guessCause();
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,27 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Exception;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Flarum\Extension\Extension;
|
||||||
|
use Flarum\Foundation\KnownError;
|
||||||
|
|
||||||
|
class ExtensionAlreadyInstalledException extends Exception implements KnownError
|
||||||
|
{
|
||||||
|
public function __construct(Extension $extension)
|
||||||
|
{
|
||||||
|
parent::__construct("Extension {$extension->getTitle()} is already installed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return 'extension_already_installed';
|
||||||
|
}
|
||||||
|
}
|
26
extensions/package-manager/src/Exception/ExtensionNotInstalledException.php
Executable file
26
extensions/package-manager/src/Exception/ExtensionNotInstalledException.php
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Exception;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Flarum\Foundation\KnownError;
|
||||||
|
|
||||||
|
class ExtensionNotInstalledException extends Exception implements KnownError
|
||||||
|
{
|
||||||
|
public function __construct(string $extensionId)
|
||||||
|
{
|
||||||
|
parent::__construct("Extension {$extensionId} is not installed.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return 'extension_not_installed';
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,54 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Exception;
|
||||||
|
|
||||||
|
use Composer\Semver\Semver;
|
||||||
|
use Flarum\PackageManager\Event\FlarumUpdated;
|
||||||
|
use Flarum\PackageManager\Settings\LastUpdateRun;
|
||||||
|
|
||||||
|
class MajorUpdateFailedException extends ComposerCommandFailedException
|
||||||
|
{
|
||||||
|
private const INCOMPATIBLE_REGEX = '/^ +- (?<ext>[A-z0-9\/-]+) [A-z0-9.-_\/]+ requires flarum\/core (?<coreReq>(?:[A-z0-9.><=_ -](?!->))+)/m';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
private $majorVersion;
|
||||||
|
|
||||||
|
public function __construct(string $packageName, string $output, string $majorVersion)
|
||||||
|
{
|
||||||
|
$this->majorVersion = $majorVersion;
|
||||||
|
|
||||||
|
parent::__construct($packageName, $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function guessCause(): ?string
|
||||||
|
{
|
||||||
|
if (preg_match_all(self::INCOMPATIBLE_REGEX, $this->getMessage(), $matches) !== false) {
|
||||||
|
$this->details['incompatible_extensions'] = [];
|
||||||
|
|
||||||
|
foreach ($matches['ext'] as $k => $name) {
|
||||||
|
if (! Semver::satisfies($this->majorVersion, $matches['coreReq'][$k])) {
|
||||||
|
$this->details['incompatible_extensions'][] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(LastUpdateRun::class)
|
||||||
|
->for(FlarumUpdated::MAJOR)
|
||||||
|
->with('status', LastUpdateRun::FAILURE)
|
||||||
|
->with('incompatibleExtensions', $this->details['incompatible_extensions'])
|
||||||
|
->save();
|
||||||
|
|
||||||
|
return 'extensions_incompatible_with_new_major';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
26
extensions/package-manager/src/Exception/NoNewMajorVersionException.php
Executable file
26
extensions/package-manager/src/Exception/NoNewMajorVersionException.php
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Exception;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Flarum\Foundation\KnownError;
|
||||||
|
|
||||||
|
class NoNewMajorVersionException extends Exception implements KnownError
|
||||||
|
{
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct("No new major version known of. Try checking for updates first.");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): string
|
||||||
|
{
|
||||||
|
return 'no_new_major_version';
|
||||||
|
}
|
||||||
|
}
|
23
extensions/package-manager/src/Extension/Event/Installed.php
Executable file
23
extensions/package-manager/src/Extension/Event/Installed.php
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Extension\Event;
|
||||||
|
|
||||||
|
class Installed
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $extensionId;
|
||||||
|
|
||||||
|
public function __construct(string $extensionId)
|
||||||
|
{
|
||||||
|
$this->extensionId = $extensionId;
|
||||||
|
}
|
||||||
|
}
|
25
extensions/package-manager/src/Extension/Event/Removed.php
Executable file
25
extensions/package-manager/src/Extension/Event/Removed.php
Executable file
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Extension\Event;
|
||||||
|
|
||||||
|
use Flarum\Extension\Extension;
|
||||||
|
|
||||||
|
class Removed
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var Extension
|
||||||
|
*/
|
||||||
|
public $extension;
|
||||||
|
|
||||||
|
public function __construct(Extension $extension)
|
||||||
|
{
|
||||||
|
$this->extension = $extension;
|
||||||
|
}
|
||||||
|
}
|
32
extensions/package-manager/src/Extension/Event/Updated.php
Executable file
32
extensions/package-manager/src/Extension/Event/Updated.php
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Extension\Event;
|
||||||
|
|
||||||
|
use Flarum\Extension\Extension;
|
||||||
|
use Flarum\User\User;
|
||||||
|
|
||||||
|
class Updated
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var User
|
||||||
|
*/
|
||||||
|
public $actor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Extension
|
||||||
|
*/
|
||||||
|
public $extension;
|
||||||
|
|
||||||
|
public function __construct(User $actor, Extension $extension)
|
||||||
|
{
|
||||||
|
$this->actor = $actor;
|
||||||
|
$this->extension = $extension;
|
||||||
|
}
|
||||||
|
}
|
21
extensions/package-manager/src/Extension/ExtensionUtils.php
Executable file
21
extensions/package-manager/src/Extension/ExtensionUtils.php
Executable file
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Extension;
|
||||||
|
|
||||||
|
class ExtensionUtils
|
||||||
|
{
|
||||||
|
public static function nameToId(string $name): string
|
||||||
|
{
|
||||||
|
[$vendor, $package] = explode('/', $name);
|
||||||
|
$package = str_replace(['flarum-ext-', 'flarum-'], '', $package);
|
||||||
|
|
||||||
|
return "$vendor-$package";
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,52 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Listener;
|
||||||
|
|
||||||
|
use Composer\Command\ClearCacheCommand;
|
||||||
|
use Flarum\Database\Console\MigrateCommand;
|
||||||
|
use Flarum\Foundation\Console\AssetsPublishCommand;
|
||||||
|
use Flarum\PackageManager\Event\FlarumUpdated;
|
||||||
|
use Symfony\Component\Console\Input\ArrayInput;
|
||||||
|
use Symfony\Component\Console\Output\NullOutput;
|
||||||
|
|
||||||
|
class ClearCacheAfterUpdate
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var ClearCacheCommand
|
||||||
|
*/
|
||||||
|
private $clearCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var AssetsPublishCommand
|
||||||
|
*/
|
||||||
|
private $publishAssets;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var MigrateCommand
|
||||||
|
*/
|
||||||
|
private $migrate;
|
||||||
|
|
||||||
|
public function __construct(ClearCacheCommand $clearCache, AssetsPublishCommand $publishAssets, MigrateCommand $migrate)
|
||||||
|
{
|
||||||
|
$this->clearCache = $clearCache;
|
||||||
|
$this->publishAssets = $publishAssets;
|
||||||
|
$this->migrate = $migrate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function handle(FlarumUpdated $event): void
|
||||||
|
{
|
||||||
|
$this->clearCache->run(new ArrayInput([]), new NullOutput());
|
||||||
|
$this->migrate->run(new ArrayInput([]), new NullOutput());
|
||||||
|
$this->publishAssets->run(new ArrayInput([]), new NullOutput());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,68 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Listener;
|
||||||
|
|
||||||
|
use Flarum\Bus\Dispatcher;
|
||||||
|
use Flarum\PackageManager\Command\CheckForUpdates;
|
||||||
|
use Flarum\PackageManager\Event\FlarumUpdated;
|
||||||
|
use Flarum\PackageManager\Extension\Event\Updated;
|
||||||
|
use Flarum\PackageManager\Settings\LastUpdateCheck;
|
||||||
|
use Flarum\PackageManager\Settings\LastUpdateRun;
|
||||||
|
|
||||||
|
class ReCheckForUpdates
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var LastUpdateRun
|
||||||
|
*/
|
||||||
|
private $lastUpdateRun;
|
||||||
|
/**
|
||||||
|
* @var LastUpdateCheck
|
||||||
|
*/
|
||||||
|
private $lastUpdateCheck;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var Dispatcher
|
||||||
|
*/
|
||||||
|
private $bus;
|
||||||
|
|
||||||
|
public function __construct(LastUpdateRun $lastUpdateRun, LastUpdateCheck $lastUpdateCheck, Dispatcher $bus)
|
||||||
|
{
|
||||||
|
$this->lastUpdateRun = $lastUpdateRun;
|
||||||
|
$this->lastUpdateCheck = $lastUpdateCheck;
|
||||||
|
$this->bus = $bus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param FlarumUpdated|Updated $event
|
||||||
|
*/
|
||||||
|
public function handle($event): void
|
||||||
|
{
|
||||||
|
$previousUpdateCheck = $this->lastUpdateCheck->get();
|
||||||
|
|
||||||
|
$lastUpdateCheck = $this->bus->dispatch(
|
||||||
|
new CheckForUpdates($event->actor)
|
||||||
|
);
|
||||||
|
|
||||||
|
if ($event instanceof FlarumUpdated) {
|
||||||
|
$mapPackageName = function (array $package) {
|
||||||
|
return $package['name'];
|
||||||
|
};
|
||||||
|
|
||||||
|
$previousPackages = array_map($mapPackageName, $previousUpdateCheck['updates']['installed']);
|
||||||
|
$lastPackages = array_map($mapPackageName, $lastUpdateCheck['updates']['installed']);
|
||||||
|
|
||||||
|
$this->lastUpdateRun
|
||||||
|
->for($event->type)
|
||||||
|
->with('status', LastUpdateRun::SUCCESS)
|
||||||
|
->with('limitedPackages', array_intersect($previousPackages, $lastPackages))
|
||||||
|
->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
extensions/package-manager/src/OutputLogger.php
Normal file
36
extensions/package-manager/src/OutputLogger.php
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager;
|
||||||
|
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
|
class OutputLogger
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var LoggerInterface
|
||||||
|
*/
|
||||||
|
protected $logger;
|
||||||
|
|
||||||
|
public function __construct(LoggerInterface $logger)
|
||||||
|
{
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function log(string $input, string $output, int $exitCode): void
|
||||||
|
{
|
||||||
|
$content = "$input\n$output";
|
||||||
|
|
||||||
|
if ($exitCode === 0) {
|
||||||
|
$this->logger->info($content);
|
||||||
|
} else {
|
||||||
|
$this->logger->error($content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
97
extensions/package-manager/src/PackageManagerServiceProvider.php
Executable file
97
extensions/package-manager/src/PackageManagerServiceProvider.php
Executable file
@@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager;
|
||||||
|
|
||||||
|
use Composer\Config;
|
||||||
|
use Composer\Console\Application;
|
||||||
|
use Flarum\Extension\ExtensionManager;
|
||||||
|
use Flarum\Foundation\AbstractServiceProvider;
|
||||||
|
use Flarum\Foundation\Paths;
|
||||||
|
use Flarum\Frontend\RecompileFrontendAssets;
|
||||||
|
use Flarum\Locale\LocaleManager;
|
||||||
|
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||||
|
use Flarum\PackageManager\Listener\ReCheckForUpdates;
|
||||||
|
use Illuminate\Contracts\Container\Container;
|
||||||
|
use Illuminate\Contracts\Events\Dispatcher;
|
||||||
|
use Monolog\Formatter\LineFormatter;
|
||||||
|
use Monolog\Handler\RotatingFileHandler;
|
||||||
|
use Monolog\Logger;
|
||||||
|
use Flarum\PackageManager\Event\FlarumUpdated;
|
||||||
|
use Flarum\PackageManager\Extension\Event\Updated;
|
||||||
|
use Flarum\PackageManager\Listener\ClearCacheAfterUpdate;
|
||||||
|
|
||||||
|
class PackageManagerServiceProvider extends AbstractServiceProvider
|
||||||
|
{
|
||||||
|
public function register()
|
||||||
|
{
|
||||||
|
$this->container->singleton(ComposerAdapter::class, function (Container $container) {
|
||||||
|
// This should only ever be resolved when running composer commands,
|
||||||
|
// because we modify other environment configurations.
|
||||||
|
$composer = new Application();
|
||||||
|
$composer->setAutoExit(false);
|
||||||
|
|
||||||
|
/** @var Paths $paths */
|
||||||
|
$paths = $container->make(Paths::class);
|
||||||
|
|
||||||
|
putenv("COMPOSER_HOME={$paths->storage}/.composer");
|
||||||
|
putenv("COMPOSER={$paths->base}/composer.json");
|
||||||
|
putenv("COMPOSER_DISABLE_XDEBUG_WARN=1");
|
||||||
|
Config::$defaultConfig['vendor-dir'] = $paths->vendor;
|
||||||
|
|
||||||
|
// When running simple require, update and remove commands on packages,
|
||||||
|
// composer 2 doesn't really need this much unless the extensions are very loaded dependency wise,
|
||||||
|
// but this is necessary for running flarum updates.
|
||||||
|
@ini_set('memory_limit', '1G');
|
||||||
|
@set_time_limit(5 * 60);
|
||||||
|
|
||||||
|
return new ComposerAdapter($composer, $container->make(OutputLogger::class), $container->make(Paths::class));
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->container->alias(ComposerAdapter::class, 'flarum.composer');
|
||||||
|
|
||||||
|
$this->container->singleton(OutputLogger::class, function (Container $container) {
|
||||||
|
$logPath = $container->make(Paths::class)->storage.'/logs/composer/output.log';
|
||||||
|
$handler = new RotatingFileHandler($logPath, Logger::INFO);
|
||||||
|
$handler->setFormatter(new LineFormatter(null, null, true, true));
|
||||||
|
|
||||||
|
$logger = new Logger('composer', [$handler]);
|
||||||
|
|
||||||
|
return new OutputLogger($logger);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(Container $container)
|
||||||
|
{
|
||||||
|
/** @var Dispatcher $events */
|
||||||
|
$events = $container->make('events');
|
||||||
|
|
||||||
|
$events->listen(
|
||||||
|
[Updated::class],
|
||||||
|
function (Updated $event) use ($container) {
|
||||||
|
/** @var ExtensionManager $extensions */
|
||||||
|
$extensions = $container->make(ExtensionManager::class);
|
||||||
|
|
||||||
|
if ($extensions->isEnabled($event->extension->getId())) {
|
||||||
|
$recompile = new RecompileFrontendAssets(
|
||||||
|
$container->make('flarum.assets.forum'),
|
||||||
|
$container->make(LocaleManager::class)
|
||||||
|
);
|
||||||
|
$recompile->flush();
|
||||||
|
|
||||||
|
$extensions->migrate($event->extension);
|
||||||
|
$event->extension->copyAssetsTo($container->make('filesystem')->disk('flarum-assets'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
$events->listen(FlarumUpdated::class, ClearCacheAfterUpdate::class);
|
||||||
|
$events->listen([FlarumUpdated::class, Updated::class], ReCheckForUpdates::class);
|
||||||
|
}
|
||||||
|
}
|
24
extensions/package-manager/src/RequirePackageValidator.php
Executable file
24
extensions/package-manager/src/RequirePackageValidator.php
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager;
|
||||||
|
|
||||||
|
use Flarum\Foundation\AbstractValidator;
|
||||||
|
|
||||||
|
class RequirePackageValidator extends AbstractValidator
|
||||||
|
{
|
||||||
|
public const PACKAGE_NAME_REGEX = '/^[A-z0-9-_]+\/[A-z-0-9]+(?::[A-z-0-9.->=<_]+){0,1}$/i';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected $rules = [
|
||||||
|
'package' => ['required', 'string', 'regex:'.self::PACKAGE_NAME_REGEX]
|
||||||
|
];
|
||||||
|
}
|
23
extensions/package-manager/src/Settings/JsonSetting.php
Normal file
23
extensions/package-manager/src/Settings/JsonSetting.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Settings;
|
||||||
|
|
||||||
|
interface JsonSetting
|
||||||
|
{
|
||||||
|
public function with(string $key, $value): self;
|
||||||
|
|
||||||
|
public function save(): array;
|
||||||
|
|
||||||
|
public function get(): array;
|
||||||
|
|
||||||
|
public static function key(): string;
|
||||||
|
|
||||||
|
public static function default(): array;
|
||||||
|
}
|
78
extensions/package-manager/src/Settings/LastUpdateCheck.php
Executable file
78
extensions/package-manager/src/Settings/LastUpdateCheck.php
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Settings;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
class LastUpdateCheck implements JsonSetting
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var SettingsRepositoryInterface
|
||||||
|
*/
|
||||||
|
protected $settings;
|
||||||
|
|
||||||
|
protected $data = [];
|
||||||
|
|
||||||
|
public function __construct(SettingsRepositoryInterface $settings)
|
||||||
|
{
|
||||||
|
$this->settings = $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function with(string $key, $value): JsonSetting
|
||||||
|
{
|
||||||
|
$this->data[$key] = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(): array
|
||||||
|
{
|
||||||
|
$lastUpdateCheck = [
|
||||||
|
'checkedAt' => Carbon::now(),
|
||||||
|
'updates' => $this->data,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->settings->set($this->key(), json_encode($lastUpdateCheck));
|
||||||
|
|
||||||
|
return $lastUpdateCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(): array
|
||||||
|
{
|
||||||
|
return json_decode($this->settings->get($this->key()), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function key(): string
|
||||||
|
{
|
||||||
|
return 'flarum-package-manager.last_update_check';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function default(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'checkedAt' => null,
|
||||||
|
'updates' => [
|
||||||
|
'installed' => [],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getNewMajorVersion(): ?string
|
||||||
|
{
|
||||||
|
$core = Arr::first(Arr::get($this->get(), 'updates.installed', []), function ($package) {
|
||||||
|
return $package['name'] === 'flarum/core';
|
||||||
|
});
|
||||||
|
|
||||||
|
return $core ? $core['latest-major'] : null;
|
||||||
|
}
|
||||||
|
}
|
97
extensions/package-manager/src/Settings/LastUpdateRun.php
Normal file
97
extensions/package-manager/src/Settings/LastUpdateRun.php
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Settings;
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use Flarum\PackageManager\Event\FlarumUpdated;
|
||||||
|
use Flarum\Settings\SettingsRepositoryInterface;
|
||||||
|
|
||||||
|
class LastUpdateRun implements JsonSetting
|
||||||
|
{
|
||||||
|
public const SUCCESS = 'success';
|
||||||
|
public const FAILURE = 'failure';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var SettingsRepositoryInterface
|
||||||
|
*/
|
||||||
|
protected $settings;
|
||||||
|
|
||||||
|
protected $data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var {'major'|'minor'|'global'}
|
||||||
|
*/
|
||||||
|
protected $activeUpdate;
|
||||||
|
|
||||||
|
public function __construct(SettingsRepositoryInterface $settings)
|
||||||
|
{
|
||||||
|
$this->settings = $settings;
|
||||||
|
$this->data = self::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function for(string $update): self
|
||||||
|
{
|
||||||
|
if (! in_array($update, [FlarumUpdated::MAJOR, FlarumUpdated::MINOR, FlarumUpdated::GLOBAL])) {
|
||||||
|
throw new \InvalidArgumentException("Last update runs can only be for one of: minor, major, global");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->activeUpdate = $update;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function with(string $key, $value): JsonSetting
|
||||||
|
{
|
||||||
|
$this->data[$this->activeUpdate][$key] = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function save(): array
|
||||||
|
{
|
||||||
|
$this->data[$this->activeUpdate]['ranAt'] = Carbon::now();
|
||||||
|
|
||||||
|
$this->settings->set(self::key(), json_encode($this->data));
|
||||||
|
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(): array
|
||||||
|
{
|
||||||
|
$lastUpdateRun = json_decode($this->settings->get(self::key()), true);
|
||||||
|
|
||||||
|
if ($this->activeUpdate) {
|
||||||
|
return $lastUpdateRun[$this->activeUpdate];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $lastUpdateRun;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function key(): string
|
||||||
|
{
|
||||||
|
return 'flarum-package-manager.last_update_run';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function default(): array
|
||||||
|
{
|
||||||
|
$defaultState = [
|
||||||
|
'ranAt' => null,
|
||||||
|
'status' => null,
|
||||||
|
'limitedPackages' => [],
|
||||||
|
'incompatibleExtensions' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
return [
|
||||||
|
FlarumUpdated::GLOBAL => $defaultState,
|
||||||
|
FlarumUpdated::MINOR => $defaultState,
|
||||||
|
FlarumUpdated::MAJOR => $defaultState,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
22
extensions/package-manager/src/UpdateExtensionValidator.php
Executable file
22
extensions/package-manager/src/UpdateExtensionValidator.php
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager;
|
||||||
|
|
||||||
|
use Flarum\Foundation\AbstractValidator;
|
||||||
|
|
||||||
|
class UpdateExtensionValidator extends AbstractValidator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected $rules = [
|
||||||
|
'extensionId' => 'required|string'
|
||||||
|
];
|
||||||
|
}
|
23
extensions/package-manager/src/WhyNotValidator.php
Normal file
23
extensions/package-manager/src/WhyNotValidator.php
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager;
|
||||||
|
|
||||||
|
use Flarum\Foundation\AbstractValidator;
|
||||||
|
|
||||||
|
class WhyNotValidator extends AbstractValidator
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
protected $rules = [
|
||||||
|
'package' => ['required', 'string', 'regex:'.RequirePackageValidator::PACKAGE_NAME_REGEX],
|
||||||
|
'version' => ['sometimes', 'string', 'regex:/(?:\*|[A-z0-9.-]+)/i']
|
||||||
|
];
|
||||||
|
}
|
0
extensions/package-manager/tests/fixtures/.gitkeep
vendored
Normal file
0
extensions/package-manager/tests/fixtures/.gitkeep
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration;
|
||||||
|
|
||||||
|
trait ChangeComposerConfig
|
||||||
|
{
|
||||||
|
protected function setComposerConfig(array $requirements): void
|
||||||
|
{
|
||||||
|
$composerSetup = new SetupComposer($requirements);
|
||||||
|
$composerSetup->run();
|
||||||
|
|
||||||
|
$this->composer('install');
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration;
|
||||||
|
|
||||||
|
use Flarum\Foundation\Paths;
|
||||||
|
|
||||||
|
trait DummyExtensions
|
||||||
|
{
|
||||||
|
protected function makeDummyExtensionCompatibleWith(string $name, string $coreVersions): void
|
||||||
|
{
|
||||||
|
$dirName = $this->tmpDir() . "/packages/" . str_replace('/', '-', $name);
|
||||||
|
|
||||||
|
if (! file_exists($dirName)) {
|
||||||
|
mkdir($dirName);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_put_contents($dirName."/composer.json", json_encode([
|
||||||
|
'name' => $name,
|
||||||
|
'version' => '1.0.0',
|
||||||
|
'require' => [
|
||||||
|
'flarum/core' => $coreVersions
|
||||||
|
],
|
||||||
|
], JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration;
|
||||||
|
|
||||||
|
use FilesystemIterator;
|
||||||
|
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||||
|
use RecursiveDirectoryIterator;
|
||||||
|
use RecursiveIteratorIterator;
|
||||||
|
|
||||||
|
trait RefreshComposerSetup
|
||||||
|
{
|
||||||
|
public function tearDown(): void
|
||||||
|
{
|
||||||
|
$composerSetup = new SetupComposer();
|
||||||
|
@unlink($this->tmpDir().'/composer.lock');
|
||||||
|
|
||||||
|
$this->deleteDummyExtensions();
|
||||||
|
|
||||||
|
$composerSetup->run();
|
||||||
|
|
||||||
|
$this->composer('install');
|
||||||
|
|
||||||
|
parent::tearDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteDummyExtensions(): void
|
||||||
|
{
|
||||||
|
$dir = $this->tmpDir().'/packages';
|
||||||
|
|
||||||
|
$it = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
|
||||||
|
$files = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
|
||||||
|
|
||||||
|
foreach($files as $file) {
|
||||||
|
if ($file->isDir()){
|
||||||
|
rmdir($file->getRealPath());
|
||||||
|
} else {
|
||||||
|
unlink($file->getRealPath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rmdir($dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function forgetComposerApp(): void
|
||||||
|
{
|
||||||
|
$this->app()->getContainer()->instance(ComposerAdapter::class, null);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,57 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration;
|
||||||
|
|
||||||
|
use Flarum\Testing\integration\UsesTmpDir;
|
||||||
|
|
||||||
|
class SetupComposer
|
||||||
|
{
|
||||||
|
use UsesTmpDir;
|
||||||
|
|
||||||
|
private $config = [
|
||||||
|
'require' => [
|
||||||
|
'flarum/core' => '1.0.0',
|
||||||
|
'flarum/tags' => '1.0.3',
|
||||||
|
'flarum/lang-english' => '*',
|
||||||
|
],
|
||||||
|
'config' => [
|
||||||
|
'preferred-install' => 'dist',
|
||||||
|
'sort-packages' => true,
|
||||||
|
],
|
||||||
|
'repositories' => [
|
||||||
|
[
|
||||||
|
'type' => 'path',
|
||||||
|
'url' => __DIR__.'/tmp/packages/*',
|
||||||
|
]
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(array $config = null)
|
||||||
|
{
|
||||||
|
$this->config = array_merge($this->config, $config ?? []);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
$composerJson = $this->tmpDir().'/composer.json';
|
||||||
|
$composerLock = $this->tmpDir().'/composer.lock';
|
||||||
|
$packages = $this->tmpDir().'/packages';
|
||||||
|
|
||||||
|
file_put_contents($composerJson, json_encode($this->config, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT));
|
||||||
|
|
||||||
|
if (! file_exists($packages)) {
|
||||||
|
mkdir($packages);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_exists($composerLock)) {
|
||||||
|
unlink($composerLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
extensions/package-manager/tests/integration/TestCase.php
Normal file
99
extensions/package-manager/tests/integration/TestCase.php
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration;
|
||||||
|
|
||||||
|
use Flarum\Foundation\Paths;
|
||||||
|
use Flarum\PackageManager\Composer\ComposerAdapter;
|
||||||
|
use Flarum\PackageManager\Composer\ComposerJson;
|
||||||
|
use Flarum\PackageManager\Extension\ExtensionUtils;
|
||||||
|
use Flarum\Testing\integration\RetrievesAuthorizedUsers;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Symfony\Component\Console\Input\StringInput;
|
||||||
|
|
||||||
|
class TestCase extends \Flarum\Testing\integration\TestCase
|
||||||
|
{
|
||||||
|
use RetrievesAuthorizedUsers;
|
||||||
|
|
||||||
|
protected function setUp(): void
|
||||||
|
{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->extension('flarum-package-manager', 'flarum-tags');
|
||||||
|
|
||||||
|
$tmp = realpath($this->tmpDir());
|
||||||
|
|
||||||
|
$this->app()->getContainer()->instance('flarum.paths', new Paths([
|
||||||
|
'base' => $tmp,
|
||||||
|
'public' => "$tmp/public",
|
||||||
|
'storage' => "$tmp/storage",
|
||||||
|
'vendor' => "$tmp/vendor",
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertExtension(string $id, bool $exists)
|
||||||
|
{
|
||||||
|
$installed = json_decode(file_get_contents($this->app()->getContainer()->make(Paths::class)->vendor.'/composer/installed.json'), true);
|
||||||
|
$installedExtensions = Arr::where($installed['packages'] ?? $installed, function (array $package) {
|
||||||
|
return $package['type'] === 'flarum-extension';
|
||||||
|
});
|
||||||
|
$installedExtensionIds = array_map(function (string $name) {
|
||||||
|
return ExtensionUtils::nameToId($name);
|
||||||
|
}, Arr::pluck($installedExtensions, 'name'));
|
||||||
|
|
||||||
|
if ($exists) {
|
||||||
|
$this->assertTrue(in_array($id, $installedExtensionIds), "Failed asserting that extension $id is installed");
|
||||||
|
} else {
|
||||||
|
$this->assertFalse(in_array($id, $installedExtensionIds), "Failed asserting that extension $id is not installed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertExtensionExists(string $id)
|
||||||
|
{
|
||||||
|
$this->assertExtension($id, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertExtensionNotExists(string $id)
|
||||||
|
{
|
||||||
|
$this->assertExtension($id, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function assertPackageVersion(string $packageName, string $version)
|
||||||
|
{
|
||||||
|
$composerJson = $this->app()->getContainer()->make(ComposerJson::class)->get();
|
||||||
|
|
||||||
|
$this->assertArrayHasKey($packageName, $composerJson['require'], "$packageName is not required.");
|
||||||
|
$this->assertEquals($version, $composerJson['require'][$packageName], "Expected $packageName to be $version, found {$composerJson['require'][$packageName]} instead.");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function requireExtension(string $package)
|
||||||
|
{
|
||||||
|
$this->composer("require $package");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function removeExtension(string $package)
|
||||||
|
{
|
||||||
|
$this->composer("remove $package");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function composer(string $command): void
|
||||||
|
{
|
||||||
|
/** @var ComposerAdapter $composer */
|
||||||
|
$composer = $this->app()->getContainer()->make(ComposerAdapter::class);
|
||||||
|
$composer->run(new StringInput($command));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function errorDetails(ResponseInterface $response): array
|
||||||
|
{
|
||||||
|
$json = json_decode((string) $response->getBody(), true);
|
||||||
|
|
||||||
|
return $json['errors'] ? ($json['errors'][0] ?? []) : [];
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration\api;
|
||||||
|
|
||||||
|
use Flarum\PackageManager\Tests\integration\ChangeComposerConfig;
|
||||||
|
use Flarum\PackageManager\Tests\integration\RefreshComposerSetup;
|
||||||
|
use Flarum\PackageManager\Tests\integration\TestCase;
|
||||||
|
use Illuminate\Support\Arr;
|
||||||
|
|
||||||
|
class CheckForUpdatesTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshComposerSetup, ChangeComposerConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function can_check_for_updates()
|
||||||
|
{
|
||||||
|
$this->setComposerConfig([
|
||||||
|
'require' => [
|
||||||
|
'flarum/core' => '^1.0.0',
|
||||||
|
'flarum/tags' => '1.0.0',
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/check-for-updates', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertEquals(['flarum/tags'], Arr::pluck(json_decode((string) $response->getBody(), true)['updates']['installed'], 'name'));
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration\api;
|
||||||
|
|
||||||
|
use Flarum\PackageManager\Tests\integration\RefreshComposerSetup;
|
||||||
|
use Flarum\PackageManager\Tests\integration\TestCase;
|
||||||
|
|
||||||
|
class GlobalUpdateTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshComposerSetup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function can_global_update()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/global-update', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,127 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration\api;
|
||||||
|
|
||||||
|
use Flarum\PackageManager\Tests\integration\ChangeComposerConfig;
|
||||||
|
use Flarum\PackageManager\Tests\integration\DummyExtensions;
|
||||||
|
use Flarum\PackageManager\Tests\integration\RefreshComposerSetup;
|
||||||
|
use Flarum\PackageManager\Tests\integration\TestCase;
|
||||||
|
|
||||||
|
class MajorUpdateTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshComposerSetup, ChangeComposerConfig, DummyExtensions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function cannot_update_when_no_update_check_ran()
|
||||||
|
{
|
||||||
|
$this->makeDummyExtensionCompatibleWith("flarum/dummy-incompatible-extension", ">=0.1.0-beta.15 <=0.1.0-beta.16");
|
||||||
|
$this->setComposerConfig([
|
||||||
|
'require' => [
|
||||||
|
'flarum/core' => '^0.1.0-beta.15',
|
||||||
|
'flarum/tags' => '^0.1.0-beta.15',
|
||||||
|
'flarum/dummy-incompatible-extension' => '^1.0.0'
|
||||||
|
],
|
||||||
|
'minimum-stability' => 'beta',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/major-update', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(409, $response->getStatusCode());
|
||||||
|
$this->assertEquals('no_new_major_version', $this->errorDetails($response)['code']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function can_update_when_major_update_available()
|
||||||
|
{
|
||||||
|
$this->makeDummyExtensionCompatibleWith("flarum/dummy-compatible-extension", "^0.1.0-beta.15 | ^1.0.0");
|
||||||
|
$this->setComposerConfig([
|
||||||
|
'require' => [
|
||||||
|
'flarum/core' => '^0.1.0-beta.15',
|
||||||
|
'flarum/tags' => '^0.1.0-beta.15',
|
||||||
|
'flarum/dummy-compatible-extension' => '^1.0.0'
|
||||||
|
],
|
||||||
|
'minimum-stability' => 'beta',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$lastUpdateCheck = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/check-for-updates', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->forgetComposerApp();
|
||||||
|
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/major-update', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$newMinorCoreVersion = array_filter(
|
||||||
|
json_decode((string) $lastUpdateCheck->getBody(), true)['updates']['installed'],
|
||||||
|
function ($package) {
|
||||||
|
return $package['name'] === 'flarum/core';
|
||||||
|
}
|
||||||
|
)[0]['latest-major'];
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertPackageVersion("flarum/core", str_replace('v', '^', $newMinorCoreVersion));
|
||||||
|
$this->assertPackageVersion("flarum/tags", "*");
|
||||||
|
$this->assertPackageVersion("flarum/dummy-compatible-extension", "*");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function cannot_update_with_incompatible_extensions()
|
||||||
|
{
|
||||||
|
$this->makeDummyExtensionCompatibleWith("flarum/dummy-incompatible-extension-a", ">=0.1.0-beta.16 <0.1.0-beta.17");
|
||||||
|
$this->makeDummyExtensionCompatibleWith("flarum/dummy-incompatible-extension-b", ">=0.1.0-beta.16 <=0.1.0-beta.17");
|
||||||
|
$this->makeDummyExtensionCompatibleWith("flarum/dummy-incompatible-extension-c", "0.1.0-beta.16");
|
||||||
|
$this->setComposerConfig([
|
||||||
|
'require' => [
|
||||||
|
'flarum/core' => '^0.1.0-beta.16',
|
||||||
|
'flarum/tags' => '^0.1.0-beta.16',
|
||||||
|
'flarum/dummy-incompatible-extension-a' => '^1.0.0',
|
||||||
|
'flarum/dummy-incompatible-extension-b' => '^1.0.0',
|
||||||
|
'flarum/dummy-incompatible-extension-c' => '^1.0.0',
|
||||||
|
],
|
||||||
|
'minimum-stability' => 'beta',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/check-for-updates', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/major-update', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(409, $response->getStatusCode());
|
||||||
|
$this->assertEquals('extensions_incompatible_with_new_major', $this->errorDetails($response)['guessed_cause']);
|
||||||
|
$this->assertEquals([
|
||||||
|
'flarum/dummy-incompatible-extension-a',
|
||||||
|
'flarum/dummy-incompatible-extension-b',
|
||||||
|
'flarum/dummy-incompatible-extension-c'
|
||||||
|
], $this->errorDetails($response)['incompatible_extensions']);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,95 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration\api;
|
||||||
|
|
||||||
|
use Flarum\PackageManager\Event\FlarumUpdated;
|
||||||
|
use Flarum\PackageManager\Settings\LastUpdateRun;
|
||||||
|
use Flarum\PackageManager\Tests\integration\ChangeComposerConfig;
|
||||||
|
use Flarum\PackageManager\Tests\integration\DummyExtensions;
|
||||||
|
use Flarum\PackageManager\Tests\integration\RefreshComposerSetup;
|
||||||
|
use Flarum\PackageManager\Tests\integration\TestCase;
|
||||||
|
|
||||||
|
class MinorUpdateTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshComposerSetup, ChangeComposerConfig, DummyExtensions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test--
|
||||||
|
*/
|
||||||
|
public function can_update_to_next_minor_version()
|
||||||
|
{
|
||||||
|
$this->makeDummyExtensionCompatibleWith("flarum/dummy-compatible-extension", "^1.0.0");
|
||||||
|
$this->setComposerConfig([
|
||||||
|
'require' => [
|
||||||
|
// The only reason we don't set this to `^1.0.0` and let it update to latest minor,
|
||||||
|
// is because migrations that run DDL queries might be introduced in future versions,
|
||||||
|
// therefore breaking the test transaction.
|
||||||
|
'flarum/core' => '>=1.0.0 <= 1.1.0',
|
||||||
|
// We leave tags fixed to a version,
|
||||||
|
// the update handler must be able to set it to `*`.
|
||||||
|
'flarum/tags' => '1.0.3',
|
||||||
|
'flarum/lang-english' => '*',
|
||||||
|
'flarum/dummy-compatible-extension' => '^1.0.0'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/minor-update', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertPackageVersion('flarum/tags', '*');
|
||||||
|
$this->assertPackageVersion('flarum/dummy-compatible-extension', '*');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function can_update_with_latest_ext_incompatible_with_latest_core()
|
||||||
|
{
|
||||||
|
$this->makeDummyExtensionCompatibleWith("flarum/dummy-extension", "1.0.0");
|
||||||
|
$this->setComposerConfig([
|
||||||
|
'require' => [
|
||||||
|
'flarum/core' => '>=1.0.0 <=1.1.0',
|
||||||
|
'flarum/tags' => '1.0.3',
|
||||||
|
'flarum/lang-english' => '*',
|
||||||
|
'flarum/dummy-extension' => '^1.0.0'
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/check-for-updates', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->forgetComposerApp();
|
||||||
|
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/minor-update', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
/** @var LastUpdateRun $lastUpdateRun */
|
||||||
|
$lastUpdateRun = $this->app()->getContainer()->make(LastUpdateRun::class);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertPackageVersion("flarum/tags", "*");
|
||||||
|
$this->assertPackageVersion("flarum/dummy-extension", "*");
|
||||||
|
$this->assertEquals([
|
||||||
|
'flarum/core',
|
||||||
|
'flarum/lang-english',
|
||||||
|
'flarum/tags'
|
||||||
|
], $lastUpdateRun->for(FlarumUpdated::MINOR)->get()['limitedPackages']);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration\api\extensions;
|
||||||
|
|
||||||
|
use Flarum\PackageManager\Tests\integration\RefreshComposerSetup;
|
||||||
|
use Flarum\PackageManager\Tests\integration\TestCase;
|
||||||
|
|
||||||
|
class RemoveExtensionTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshComposerSetup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function extension_installed_by_default()
|
||||||
|
{
|
||||||
|
$this->assertExtensionExists('flarum-tags');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function removing_an_extension_works()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('DELETE', '/api/package-manager/extensions/flarum-tags', [
|
||||||
|
'authenticatedAs' => 1
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertExtensionNotExists('flarum-tags');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function removing_a_non_existant_extension_fails()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('DELETE', '/api/package-manager/extensions/flarum-potato', [
|
||||||
|
'authenticatedAs' => 1
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(409, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,125 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration\api\extensions;
|
||||||
|
|
||||||
|
use Flarum\PackageManager\Tests\integration\RefreshComposerSetup;
|
||||||
|
use Flarum\PackageManager\Tests\integration\TestCase;
|
||||||
|
|
||||||
|
class RequireExtensionTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshComposerSetup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function extension_uninstalled_by_default()
|
||||||
|
{
|
||||||
|
$this->assertExtensionNotExists('v17development-blog');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function requiring_an_existing_extension_fails()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/extensions', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
'json' => [
|
||||||
|
'data' => [
|
||||||
|
'package' => 'flarum/tags'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(409, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function requiring_a_compatible_extension_works()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/extensions', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
'json' => [
|
||||||
|
'data' => [
|
||||||
|
'package' => 'v17development/flarum-blog'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertExtensionExists('v17development-blog');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function requiring_a_compatible_extension_with_specific_version_works()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/extensions', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
'json' => [
|
||||||
|
'data' => [
|
||||||
|
'package' => 'v17development/flarum-blog:0.4.0'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertExtensionExists('v17development-blog');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function requiring_an_uncompatible_extension_fails()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/extensions', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
'json' => [
|
||||||
|
'data' => [
|
||||||
|
'package' => 'flarum/auth-github'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(409, $response->getStatusCode());
|
||||||
|
$this->assertEquals('extension_incompatible_with_instance', $this->errorDetails($response)['guessed_cause']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function requiring_an_uncompatible_extension_with_specific_version_fails()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('POST', '/api/package-manager/extensions', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
'json' => [
|
||||||
|
'data' => [
|
||||||
|
'package' => 'flarum/auth-github:0.1.0-beta.9'
|
||||||
|
]
|
||||||
|
]
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(409, $response->getStatusCode());
|
||||||
|
$this->assertEquals('extension_incompatible_with_instance', $this->errorDetails($response)['guessed_cause']);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Flarum.
|
||||||
|
*
|
||||||
|
* For detailed copyright and license information, please view the
|
||||||
|
* LICENSE file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Flarum\PackageManager\Tests\integration\api\extensions;
|
||||||
|
|
||||||
|
use Flarum\PackageManager\Tests\integration\RefreshComposerSetup;
|
||||||
|
use Flarum\PackageManager\Tests\integration\TestCase;
|
||||||
|
|
||||||
|
class UpdateExtensionTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshComposerSetup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function extension_installed_by_default()
|
||||||
|
{
|
||||||
|
$this->assertExtensionExists('flarum-tags');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function updating_an_existing_extension_works()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('PATCH', '/api/package-manager/extensions/flarum-tags', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response->getStatusCode());
|
||||||
|
$this->assertExtensionExists('flarum-tags');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @test
|
||||||
|
*/
|
||||||
|
public function updating_a_non_existing_extension_fails()
|
||||||
|
{
|
||||||
|
$response = $this->send(
|
||||||
|
$this->request('PATCH', '/api/package-manager/extensions/flarum-potato', [
|
||||||
|
'authenticatedAs' => 1,
|
||||||
|
])
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->assertEquals(409, $response->getStatusCode());
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user