mirror of
https://github.com/flarum/core.git
synced 2025-07-30 21:20:24 +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