├── public
└── mix-manifest.json
├── resources
├── sass
│ ├── app.scss
│ └── base.scss
├── js
│ ├── app.js
│ ├── routes.js
│ ├── screens
│ │ ├── repositories
│ │ │ └── index.vue
│ │ ├── requirements
│ │ │ └── index.vue
│ │ ├── jobs
│ │ │ ├── item.vue
│ │ │ └── index.vue
│ │ ├── dashboard
│ │ │ └── index.vue
│ │ └── laravel
│ │ │ └── index.vue
│ ├── components
│ │ ├── Job
│ │ │ ├── StatusBadge.vue
│ │ │ └── OptionsMenu.vue
│ │ ├── Requirement
│ │ │ ├── Requirement.vue
│ │ │ ├── InstallButton.vue
│ │ │ ├── UninstallButton.vue
│ │ │ ├── Suggestions.vue
│ │ │ └── InstallForm.vue
│ │ ├── Repository
│ │ │ └── Repository.vue
│ │ ├── PaketCard.vue
│ │ └── TopMenu.vue
│ ├── globals.js
│ └── store.js
└── views
│ └── app.blade.php
├── tailwind.js
├── contracts
├── Exceptions
│ └── PaketThrowable.php
├── Process
│ └── Entities
│ │ └── Process.php
├── Job
│ ├── Entities
│ │ └── Job.php
│ ├── Repositories
│ │ └── Job.php
│ └── Exceptions
│ │ └── JobFailed.php
└── Requirement
│ └── Entities
│ └── Requirement.php
├── src
├── Http
│ ├── Controllers
│ │ ├── AppAction.php
│ │ ├── Api
│ │ │ ├── Jobs
│ │ │ │ ├── CollectAction.php
│ │ │ │ ├── GetAction.php
│ │ │ │ ├── DeleteAction.php
│ │ │ │ ├── PostRequest.php
│ │ │ │ ├── GetResponse.php
│ │ │ │ ├── DeleteResponse.php
│ │ │ │ ├── PostResponse.php
│ │ │ │ ├── CollectResponse.php
│ │ │ │ └── PostAction.php
│ │ │ ├── Repositories
│ │ │ │ ├── CollectResponse.php
│ │ │ │ └── CollectAction.php
│ │ │ └── Requirements
│ │ │ │ ├── CollectResponse.php
│ │ │ │ └── CollectAction.php
│ │ └── AppResponse.php
│ └── Middlewares
│ │ └── Authorize.php
├── Job
│ ├── Listeners
│ │ └── JobRunner.php
│ ├── Events
│ │ ├── JobHasBeenCreated.php
│ │ └── JobHasBeenTerminated.php
│ ├── QueueJobs
│ │ └── RunJob.php
│ ├── Entities
│ │ └── Job.php
│ └── Repositories
│ │ └── JobFileRepository.php
├── Process
│ └── Entities
│ │ └── Process.php
├── Requirement
│ └── Entities
│ │ └── Requirement.php
├── Console
│ └── Commands
│ │ └── Setup.php
├── PaketServiceProvider.php
└── Support
│ └── Composer.php
├── LICENSE
├── routes
└── web.php
├── webpack.mix.js
├── package.json
├── config
└── paket.php
├── composer.json
├── CHANGELOG.md
└── README.md
/public/mix-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "/app.js": "/app.js?id=5240d5562cd611adb118",
3 | "/app.css": "/app.css?id=eba6b5b861d7be75b2c7"
4 | }
5 |
--------------------------------------------------------------------------------
/resources/sass/app.scss:
--------------------------------------------------------------------------------
1 | @import '~tailwindcss/base';
2 |
3 | @import '~tailwindcss/components';
4 |
5 | @import '~tailwindcss/utilities';
6 |
7 | @import '~@ibm/plex/scss/ibm-plex';
8 |
9 | @import 'base';
--------------------------------------------------------------------------------
/tailwind.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | theme: {
3 | extend: {}
4 | },
5 | variants: {},
6 | plugins: [],
7 | purge: [
8 | './resources/js/**/*.vue',
9 | './resources/js/**/**/*.vue',
10 | ],
11 | };
12 |
--------------------------------------------------------------------------------
/contracts/Exceptions/PaketThrowable.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Contracts\Paket\Exceptions;
15 |
16 | interface PaketThrowable
17 | {
18 | }
19 |
--------------------------------------------------------------------------------
/contracts/Process/Entities/Process.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Contracts\Paket\Process\Entities;
15 |
16 | interface Process
17 | {
18 | public static function fromArray(array $process): self;
19 |
20 | public function toArray(): array;
21 | }
22 |
--------------------------------------------------------------------------------
/src/Http/Controllers/AppAction.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers;
15 |
16 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
17 |
18 | final class AppAction
19 | {
20 | public function __invoke(): ResponsableContract
21 | {
22 | return new AppResponse();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/resources/sass/base.scss:
--------------------------------------------------------------------------------
1 | body,
2 | .font-mono {
3 | font-family: 'IBM Plex Mono', monospace;
4 | }
5 |
6 | @-webkit-keyframes spin {
7 | 0% {
8 | -webkit-transform: rotate(0deg);
9 | transform: rotate(0deg)
10 | }
11 | to {
12 | -webkit-transform: rotate(1turn);
13 | transform: rotate(1turn)
14 | }
15 | }
16 |
17 | @keyframes spin {
18 | 0% {
19 | -webkit-transform: rotate(0deg);
20 | transform: rotate(0deg)
21 | }
22 | to {
23 | -webkit-transform: rotate(1turn);
24 | transform: rotate(1turn)
25 | }
26 | }
27 |
28 | .spin {
29 | -webkit-animation: spin 2s linear infinite;
30 | animation: spin 2s linear infinite;
31 | }
32 |
--------------------------------------------------------------------------------
/src/Job/Listeners/JobRunner.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Job\Listeners;
15 |
16 | use Cog\Laravel\Paket\Job\Events\JobHasBeenCreated;
17 | use Cog\Laravel\Paket\Job\QueueJobs\RunJob;
18 | use Illuminate\Contracts\Queue\ShouldQueue;
19 |
20 | final class JobRunner implements ShouldQueue
21 | {
22 | public function handle(JobHasBeenCreated $event): void
23 | {
24 | RunJob::dispatch($event->getJob());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/Job/Events/JobHasBeenCreated.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Job\Events;
15 |
16 | use Cog\Contracts\Paket\Job\Entities\Job as JobContract;
17 |
18 | final class JobHasBeenCreated
19 | {
20 | private $job;
21 |
22 | public function __construct(JobContract $job)
23 | {
24 | $this->job = $job;
25 | }
26 |
27 | public function getJob(): JobContract
28 | {
29 | return $this->job;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Job/Events/JobHasBeenTerminated.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Job\Events;
15 |
16 | use Cog\Contracts\Paket\Job\Entities\Job as JobContract;
17 |
18 | final class JobHasBeenTerminated
19 | {
20 | private $job;
21 |
22 | public function __construct(JobContract $job)
23 | {
24 | $this->job = $job;
25 | }
26 |
27 | public function getJob(): JobContract
28 | {
29 | return $this->job;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/contracts/Job/Entities/Job.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Contracts\Paket\Job\Entities;
15 |
16 | use Cog\Contracts\Paket\Requirement\Entities\Requirement;
17 |
18 | interface Job
19 | {
20 | public static function fromArray(array $job): self;
21 |
22 | public function toArray(): array;
23 |
24 | public function getType(): string;
25 |
26 | public function getId(): string;
27 |
28 | public function getRequirement(): Requirement;
29 | }
30 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Jobs/CollectAction.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Jobs;
15 |
16 | use Cog\Contracts\Paket\Job\Repositories\Job as JobRepositoryContract;
17 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
18 |
19 | final class CollectAction
20 | {
21 | public function __invoke(JobRepositoryContract $jobs): ResponsableContract
22 | {
23 | return new CollectResponse($jobs->all());
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Jobs/GetAction.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Jobs;
15 |
16 | use Cog\Contracts\Paket\Job\Repositories\Job as JobRepositoryContract;
17 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
18 |
19 | final class GetAction
20 | {
21 | public function __invoke(string $id, JobRepositoryContract $jobs): ResponsableContract
22 | {
23 | return new GetResponse($jobs->getById($id));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/contracts/Requirement/Entities/Requirement.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Contracts\Paket\Requirement\Entities;
15 |
16 | interface Requirement
17 | {
18 | public static function fromArray(array $requirement): self;
19 |
20 | public function toArray(): array;
21 |
22 | public function getName(): string;
23 |
24 | public function getVersion(): ?string;
25 |
26 | public function isDevelopment(): bool;
27 |
28 | public function isNotDevelopment(): bool;
29 | }
30 |
--------------------------------------------------------------------------------
/contracts/Job/Repositories/Job.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Contracts\Paket\Job\Repositories;
15 |
16 | use Cog\Contracts\Paket\Job\Entities\Job as JobEntity;
17 |
18 | interface Job
19 | {
20 | public function all(): iterable;
21 |
22 | public function getById(string $id): JobEntity;
23 |
24 | public function store(JobEntity $job): void;
25 |
26 | public function changeJobStatus(JobEntity $job, string $statusName): void;
27 |
28 | public function changeJobExitCode(JobEntity $job, int $exitCode): void;
29 | }
30 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Jobs/DeleteAction.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Jobs;
15 |
16 | use Cog\Contracts\Paket\Job\Repositories\Job as JobRepositoryContract;
17 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
18 |
19 | final class DeleteAction
20 | {
21 | public function __invoke(string $id, JobRepositoryContract $jobs): ResponsableContract
22 | {
23 | $job = $jobs->getById($id);
24 | $jobs->deleteById($id);
25 |
26 | return new DeleteResponse($job);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Http/Middlewares/Authorize.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Middlewares;
15 |
16 | use Closure;
17 | use Illuminate\Http\Request;
18 | use Symfony\Component\HttpFoundation\Response;
19 |
20 | final class Authorize
21 | {
22 | /**
23 | * Handle the incoming request.
24 | *
25 | * @param \Illuminate\Http\Request $request
26 | * @param \Closure $next
27 | * @return \Symfony\Component\HttpFoundation\Response
28 | */
29 | public function handle(Request $request, Closure $next): Response
30 | {
31 | return app()->environment('local') ? $next($request) : abort(403);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/resources/js/app.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue';
2 | import VueRouter from 'vue-router';
3 |
4 | import globals from './globals';
5 | import routes from './routes';
6 | import store from './store';
7 |
8 | Vue.use(VueRouter);
9 |
10 | const router = new VueRouter({
11 | routes: routes,
12 | mode: 'history',
13 | base: '/' + window.Paket.baseUri,
14 | });
15 |
16 | Vue.component('top-menu', require('./components/TopMenu.vue').default);
17 |
18 | Vue.mixin(globals);
19 |
20 | new Vue({
21 | el: '#paket',
22 |
23 | router,
24 |
25 | store,
26 |
27 | created() {
28 | this.fetchData();
29 | },
30 |
31 | methods: {
32 | async fetchData() {
33 | // Need it to run jobs state checker after page reload
34 | await this.$store.dispatch('autoRefreshJobs');
35 | await this.$store.dispatch('collectRequirements');
36 | await this.$store.dispatch('collectRepositories');
37 | },
38 | },
39 | });
40 |
--------------------------------------------------------------------------------
/resources/views/app.blade.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Paket | {{ config('app.name', 'Laravel') }}
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Jobs/PostRequest.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Jobs;
15 |
16 | use Illuminate\Foundation\Http\FormRequest;
17 |
18 | final class PostRequest extends FormRequest
19 | {
20 | public function rules(): array
21 | {
22 | return [
23 | 'requirement.name' => [
24 | 'required',
25 | 'string',
26 | ],
27 | 'requirement.version' => [
28 | 'nullable',
29 | 'string',
30 | ],
31 | 'requirement.isDevelopment' => [
32 | 'nullable',
33 | 'boolean',
34 | ],
35 | ];
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/resources/js/routes.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | path: '/',
4 | redirect: '/dashboard',
5 | },
6 |
7 | {
8 | path: '/dashboard',
9 | name: 'dashboard',
10 | component: require('./screens/dashboard/index').default,
11 | },
12 |
13 | {
14 | path: '/laravel',
15 | name: 'laravel',
16 | component: require('./screens/laravel/index').default,
17 | },
18 |
19 | {
20 | path: '/requirements',
21 | name: 'requirements',
22 | component: require('./screens/requirements/index').default,
23 | },
24 |
25 | {
26 | path: '/repositories',
27 | name: 'repositories',
28 | component: require('./screens/repositories/index').default,
29 | },
30 |
31 | {
32 | path: '/jobs',
33 | name: 'jobs',
34 | component: require('./screens/jobs/index').default,
35 | },
36 |
37 | {
38 | path: '/jobs/:id',
39 | name: 'job',
40 | component: require('./screens/jobs/item').default,
41 | },
42 | ];
43 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020, Anton Komarev
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/src/Process/Entities/Process.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Process\Entities;
15 |
16 | use Cog\Contracts\Paket\Process\Entities\Process as ProcessContract;
17 |
18 | final class Process implements ProcessContract
19 | {
20 | private $output;
21 |
22 | private $exitCode;
23 |
24 | public function __construct(?string $output = null, ?int $exitCode = null)
25 | {
26 | $this->output = $output;
27 | $this->exitCode = $exitCode;
28 | }
29 |
30 | public static function fromArray(array $process): ProcessContract
31 | {
32 | return new self(
33 | $process['output'] ?? null,
34 | $process['exitCode'] ?? null
35 | );
36 | }
37 |
38 | public function toArray(): array
39 | {
40 | return [
41 | 'output' => $this->output,
42 | 'exitCode' => $this->exitCode,
43 | ];
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Repositories/CollectResponse.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Repositories;
15 |
16 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
17 | use Illuminate\Http\JsonResponse;
18 |
19 | final class CollectResponse implements ResponsableContract
20 | {
21 | private $repositories;
22 |
23 | public function __construct(iterable $repositories)
24 | {
25 | $this->repositories = $repositories;
26 | }
27 |
28 | /**
29 | * Create an HTTP response that represents the object.
30 | *
31 | * @param \Illuminate\Http\Request $request
32 | * @return \Symfony\Component\HttpFoundation\Response
33 | */
34 | public function toResponse($request)
35 | {
36 | return $this->toJson();
37 | }
38 |
39 | private function toJson(): JsonResponse
40 | {
41 | return response()->json($this->repositories);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Requirements/CollectResponse.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Requirements;
15 |
16 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
17 | use Illuminate\Http\JsonResponse;
18 |
19 | final class CollectResponse implements ResponsableContract
20 | {
21 | private $requirements;
22 |
23 | public function __construct(iterable $requirements)
24 | {
25 | $this->requirements = $requirements;
26 | }
27 |
28 | /**
29 | * Create an HTTP response that represents the object.
30 | *
31 | * @param \Illuminate\Http\Request $request
32 | * @return \Symfony\Component\HttpFoundation\Response
33 | */
34 | public function toResponse($request)
35 | {
36 | return $this->toJson();
37 | }
38 |
39 | private function toJson(): JsonResponse
40 | {
41 | return response()->json($this->requirements);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Jobs/GetResponse.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Jobs;
15 |
16 | use Cog\Contracts\Paket\Job\Entities\Job as JobContract;
17 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
18 | use Illuminate\Http\JsonResponse;
19 |
20 | final class GetResponse implements ResponsableContract
21 | {
22 | private $job;
23 |
24 | public function __construct(JobContract $job)
25 | {
26 | $this->job = $job;
27 | }
28 |
29 | /**
30 | * Create an HTTP response that represents the object.
31 | *
32 | * @param \Illuminate\Http\Request $request
33 | * @return \Symfony\Component\HttpFoundation\Response
34 | */
35 | public function toResponse($request)
36 | {
37 | return $this->toJson();
38 | }
39 |
40 | private function toJson(): JsonResponse
41 | {
42 | return response()->json($this->job->toArray());
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Jobs/DeleteResponse.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Jobs;
15 |
16 | use Cog\Contracts\Paket\Job\Entities\Job as JobContract;
17 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
18 | use Illuminate\Http\JsonResponse;
19 |
20 | final class DeleteResponse implements ResponsableContract
21 | {
22 | private $job;
23 |
24 | public function __construct(JobContract $job)
25 | {
26 | $this->job = $job;
27 | }
28 |
29 | /**
30 | * Create an HTTP response that represents the object.
31 | *
32 | * @param \Illuminate\Http\Request $request
33 | * @return \Symfony\Component\HttpFoundation\Response
34 | */
35 | public function toResponse($request)
36 | {
37 | return $this->toJson();
38 | }
39 |
40 | private function toJson(): JsonResponse
41 | {
42 | return response()->json($this->job->toArray());
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Jobs/PostResponse.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Jobs;
15 |
16 | use Cog\Contracts\Paket\Job\Entities\Job as JobContract;
17 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
18 | use Illuminate\Http\JsonResponse;
19 |
20 | final class PostResponse implements ResponsableContract
21 | {
22 | private $job;
23 |
24 | public function __construct(JobContract $job)
25 | {
26 | $this->job = $job;
27 | }
28 |
29 | /**
30 | * Create an HTTP response that represents the object.
31 | *
32 | * @param \Illuminate\Http\Request $request
33 | * @return \Symfony\Component\HttpFoundation\Response
34 | */
35 | public function toResponse($request)
36 | {
37 | return $this->toJson();
38 | }
39 |
40 | private function toJson(): JsonResponse
41 | {
42 | return response()->json($this->job->toArray(), 201);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/routes/web.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | use Illuminate\Support\Facades\Route;
15 |
16 | Route::prefix('api')->namespace('Api')->name('paket.api.')->group(function () {
17 | Route::get('repositories')
18 | ->uses('Repositories\CollectAction')
19 | ->name('repositories.collect');
20 |
21 | Route::get('requirements')
22 | ->uses('Requirements\CollectAction')
23 | ->name('requirements.collect');
24 |
25 | Route::get('jobs')
26 | ->uses('Jobs\CollectAction')
27 | ->name('jobs.collect');
28 |
29 | Route::post('jobs')
30 | ->uses('Jobs\PostAction')
31 | ->name('jobs.post');
32 |
33 | Route::get('jobs/{job}')
34 | ->uses('Jobs\GetAction')
35 | ->name('jobs.get');
36 |
37 | Route::delete('jobs/{job}')
38 | ->uses('Jobs\DeleteAction')
39 | ->name('jobs.delete');
40 | });
41 |
42 | Route::get('{view?}', 'AppAction')
43 | ->where('view', '(.*)')
44 | ->name('paket');
45 |
--------------------------------------------------------------------------------
/resources/js/screens/repositories/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Repositories
4 |
5 |
6 |
Repositories list is empty
7 |
You can't install new packages without repositories!
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
39 |
--------------------------------------------------------------------------------
/src/Http/Controllers/AppResponse.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers;
15 |
16 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
17 | use Illuminate\Http\Response as IlluminateResponse;
18 | use Illuminate\Support\Facades\Config;
19 |
20 | final class AppResponse implements ResponsableContract
21 | {
22 | /**
23 | * Create an HTTP response that represents the object.
24 | *
25 | * @param \Illuminate\Http\Request $request
26 | * @return \Symfony\Component\HttpFoundation\Response
27 | */
28 | public function toResponse($request)
29 | {
30 | return $this->toHtml();
31 | }
32 |
33 | private function toHtml(): IlluminateResponse
34 | {
35 | return response()->view('paket::app', [
36 | 'cssFile' => 'app.css',
37 | 'paketScriptVariables' => [
38 | 'baseUri' => ltrim(Config::get('paket.base_uri', 'paket'), '/'),
39 | ],
40 | ]);
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Jobs/CollectResponse.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Jobs;
15 |
16 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
17 | use Illuminate\Http\JsonResponse;
18 |
19 | final class CollectResponse implements ResponsableContract
20 | {
21 | private $jobs;
22 |
23 | public function __construct(iterable $jobs)
24 | {
25 | $this->jobs = $jobs;
26 | }
27 |
28 | /**
29 | * Create an HTTP response that represents the object.
30 | *
31 | * @param \Illuminate\Http\Request $request
32 | * @return \Symfony\Component\HttpFoundation\Response
33 | */
34 | public function toResponse($request)
35 | {
36 | return $this->toJson();
37 | }
38 |
39 | private function toJson(): JsonResponse
40 | {
41 | $jobs = [];
42 | foreach ($this->jobs as $job) {
43 | $jobs[] = $job->toArray();
44 | }
45 |
46 | return response()->json($jobs);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/resources/js/components/Job/StatusBadge.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/webpack.mix.js:
--------------------------------------------------------------------------------
1 | const mix = require('laravel-mix');
2 | const tailwindcss = require('tailwindcss');
3 | const webpack = require('webpack');
4 |
5 | /*
6 | |--------------------------------------------------------------------------
7 | | Mix Asset Management
8 | |--------------------------------------------------------------------------
9 | |
10 | | Mix provides a clean, fluent API for defining some Webpack build steps
11 | | for your Laravel application. By default, we are compiling the Sass
12 | | file for the application as well as bundling up all the JS files.
13 | |
14 | */
15 |
16 | mix.options({
17 | uglify: {
18 | uglifyOptions: {
19 | compress: {
20 | drop_console: true,
21 | },
22 | },
23 | },
24 | processCssUrls: false,
25 | postCss: [tailwindcss('./tailwind.js')],
26 | })
27 | .setPublicPath('public')
28 | .js('resources/js/app.js', 'public')
29 | .sass('resources/sass/app.scss', 'public')
30 | .version()
31 | .copy('public', '../sandbox-laravel-paket/public/vendor/paket')
32 | .webpackConfig({
33 | resolve: {
34 | symlinks: false,
35 | alias: {
36 | '@': path.resolve(__dirname, 'resources/js/'),
37 | },
38 | },
39 | plugins: [new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)],
40 | });
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "private": true,
3 | "scripts": {
4 | "dev": "npm run development",
5 | "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
6 | "watch": "npm run development -- --watch",
7 | "watch-poll": "npm run watch -- --watch-poll",
8 | "hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
9 | "prod": "npm run production",
10 | "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
11 | },
12 | "devDependencies": {
13 | "@ibm/plex": "^2.0.0",
14 | "ansi-to-html": "^0.6.10",
15 | "axios": "^0.21",
16 | "cross-env": "^5.1",
17 | "laravel-mix": "^4.0.7",
18 | "moment": "^2.29.4",
19 | "moment-timezone": "^0.5.35",
20 | "resolve-url-loader": "^2.3.1",
21 | "sass": "^1.15.2",
22 | "sass-loader": "^7.1.0",
23 | "sweetalert2": "^8.13.0",
24 | "tailwindcss": "^1.1.2",
25 | "vue": "^2.5.7",
26 | "vue-router": "^3.0.1",
27 | "vue-template-compiler": "^2.5.21",
28 | "vuex": "^3.1.1"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/contracts/Job/Exceptions/JobFailed.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Contracts\Paket\Job\Exceptions;
15 |
16 | use Cog\Contracts\Paket\Exceptions\PaketThrowable;
17 | use Cog\Contracts\Paket\Job\Entities\Job as JobContract;
18 | use RuntimeException;
19 | use Throwable;
20 |
21 | final class JobFailed extends RuntimeException implements
22 | PaketThrowable
23 | {
24 | private $job;
25 |
26 | private $exitCode = 0;
27 |
28 | public function __construct(
29 | JobContract $job,
30 | int $exitCode,
31 | string $message = '',
32 | int $code = 0,
33 | Throwable $previous = null
34 | ) {
35 | parent::__construct($message, $code, $previous);
36 |
37 | $this->job = $job;
38 | $this->exitCode = $exitCode;
39 | }
40 |
41 | public static function withExitCode(JobContract $job, int $code): self
42 | {
43 | return new static($job, $code, sprintf('Job `%s` failed. Exit code: %d', $job->getId(), $code));
44 | }
45 |
46 | public function getJob(): JobContract
47 | {
48 | return $this->job;
49 | }
50 |
51 | public function getExitCode(): int
52 | {
53 | return $this->exitCode;
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Repositories/CollectAction.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Repositories;
15 |
16 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
17 | use MCStreetguy\ComposerParser\Factory as ComposerParser;
18 |
19 | final class CollectAction
20 | {
21 | public function __invoke(): ResponsableContract
22 | {
23 | $repositories = $this->getRepositories();
24 |
25 | return new CollectResponse($repositories);
26 | }
27 |
28 | private function getRepositories(): array
29 | {
30 | $jsonFile = ComposerParser::parseComposerJson(base_path('composer.json'));
31 |
32 | $repositories = $jsonFile->getRepositories();
33 | $output = [];
34 |
35 | foreach ($repositories as $key => $repository) {
36 | $package = $repository->getPackage();
37 |
38 | $output[$key] = [
39 | 'type' => $repository->getType(),
40 | 'url' => $repository->getUrl(),
41 | 'options' => $repository->getOptions(),
42 | 'package' => is_null($package) ? null : $package->toArray(),
43 | ];
44 | }
45 |
46 | return $output;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/config/paket.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | use Cog\Laravel\Paket\Http\Middlewares\Authorize;
15 |
16 | return [
17 |
18 | /*
19 | |--------------------------------------------------------------------------
20 | | Paket Base URI
21 | |--------------------------------------------------------------------------
22 | |
23 | | This is the URI path where Paket will be accessible from. Feel free
24 | | to change this path to anything you like.
25 | |
26 | */
27 |
28 | 'base_uri' => env('PAKET_BASE_URI', 'paket'),
29 |
30 | /*
31 | |--------------------------------------------------------------------------
32 | | Composer PATH
33 | |--------------------------------------------------------------------------
34 | |
35 | | Composer executable file location.
36 | |
37 | */
38 |
39 | 'composer_path' => env('PAKET_COMPOSER_PATH', '/usr/bin/composer'),
40 |
41 | /*
42 | |--------------------------------------------------------------------------
43 | | Paket Route Middlewares
44 | |--------------------------------------------------------------------------
45 | |
46 | | These middlewares will be assigned to every Paket route, giving you
47 | | the chance to add your own middleware to this list or change any of
48 | | the existing middlewares. Or, you can simply stick with this list.
49 | |
50 | */
51 |
52 | 'middlewares' => [
53 | 'web',
54 | Authorize::class,
55 | ],
56 |
57 | ];
58 |
--------------------------------------------------------------------------------
/resources/js/globals.js:
--------------------------------------------------------------------------------
1 | import Swal from 'sweetalert2';
2 |
3 | export default {
4 | computed: {
5 | Paket() {
6 | return window.Paket;
7 | },
8 | },
9 |
10 | methods: {
11 | alertSuccess: async (data) => {
12 | await Swal.fire({
13 | type: 'success',
14 | ...data,
15 | });
16 | },
17 |
18 | alertInfo: async (data) => {
19 | await Swal.fire({
20 | type: 'info',
21 | ...data,
22 | });
23 | },
24 |
25 | alertWarning: async (data) => {
26 | await Swal.fire({
27 | type: 'warning',
28 | ...data,
29 | });
30 | },
31 |
32 | alertError: async (exception) => {
33 | let errorData = {};
34 |
35 | if (exception.response.status === 422) {
36 | let error;
37 |
38 | for (let errorKey in exception.response.data.errors) {
39 | if (exception.response.data.errors.hasOwnProperty(errorKey)) {
40 | error = exception.response.data.errors[errorKey][0];
41 | break;
42 | }
43 | }
44 |
45 | errorData = {
46 | type: 'error',
47 | title: 'Validation Error!',
48 | text: error,
49 | };
50 | } else {
51 | errorData = {
52 | type: 'error',
53 | title: 'Error!',
54 | text: exception.message,
55 | };
56 | }
57 |
58 | await Swal.fire(errorData);
59 | },
60 | },
61 | }
62 |
--------------------------------------------------------------------------------
/src/Requirement/Entities/Requirement.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Requirement\Entities;
15 |
16 | use Cog\Contracts\Paket\Requirement\Entities\Requirement as RequirementContract;
17 |
18 | final class Requirement implements RequirementContract
19 | {
20 | private $name;
21 |
22 | private $version;
23 |
24 | private $isDevelopment;
25 |
26 | public function __construct(string $name, ?string $version = null, bool $isDevelopment = false)
27 | {
28 | $this->name = $name;
29 | $this->version = $version;
30 | $this->isDevelopment = $isDevelopment;
31 | }
32 |
33 | public function __toString(): string
34 | {
35 | if (is_null($this->version)) {
36 | return $this->name;
37 | }
38 |
39 | return sprintf('%s:%s', $this->name, $this->version);
40 | }
41 |
42 | public static function fromArray(array $requirement): RequirementContract
43 | {
44 | return new self(
45 | $requirement['name'],
46 | $requirement['version'] ?? null,
47 | $requirement['isDevelopment'] ?? false
48 | );
49 | }
50 |
51 | public function toArray(): array
52 | {
53 | return [
54 | 'name' => $this->name,
55 | 'version' => $this->version,
56 | 'isDevelopment' => $this->isDevelopment,
57 | ];
58 | }
59 |
60 | public function getName(): string
61 | {
62 | return $this->name;
63 | }
64 |
65 | public function getVersion(): ?string
66 | {
67 | return $this->version;
68 | }
69 |
70 | public function isDevelopment(): bool
71 | {
72 | return $this->isDevelopment;
73 | }
74 |
75 | public function isNotDevelopment(): bool
76 | {
77 | return !$this->isDevelopment();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/resources/js/components/Requirement/Requirement.vue:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
66 |
--------------------------------------------------------------------------------
/resources/js/components/Requirement/InstallButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
61 |
--------------------------------------------------------------------------------
/resources/js/components/Requirement/UninstallButton.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
61 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "cybercog/laravel-paket",
3 | "description": "Composer personal web interface. Manage Laravel dependencies without switching to command line!",
4 | "type": "library",
5 | "license": "MIT",
6 | "keywords": [
7 | "cybercog",
8 | "cog",
9 | "laravel",
10 | "composer",
11 | "packagist",
12 | "package",
13 | "manager",
14 | "management",
15 | "repository",
16 | "packet",
17 | "paket",
18 | "dependency",
19 | "artifact",
20 | "library",
21 | "development",
22 | "gui"
23 | ],
24 | "authors": [
25 | {
26 | "name": "Anton Komarev",
27 | "email": "anton@komarev.com",
28 | "homepage": "https://komarev.com",
29 | "role": "Developer"
30 | }
31 | ],
32 | "homepage": "https://komarev.com/sources/laravel-paket",
33 | "support": {
34 | "email": "open@cybercog.su",
35 | "issues": "https://github.com/cybercog/laravel-paket/issues",
36 | "wiki": "https://github.com/cybercog/laravel-paket/wiki",
37 | "source": "https://github.com/cybercog/laravel-paket",
38 | "docs": "https://laravel-paket.readme.io"
39 | },
40 | "require": {
41 | "php": "^7.1.3 | ^8.0",
42 | "ext-json": "*",
43 | "illuminate/support": "^5.6|^6.0|^7.0|^8.0",
44 | "mcstreetguy/composer-parser": "^1.1"
45 | },
46 | "require-dev": {
47 | "mockery/mockery": "^1.0",
48 | "orchestra/testbench": "^3.6|^4.0|^5.0|^6.0",
49 | "phpunit/phpunit": "^7.5|^8.0|^9.0"
50 | },
51 | "autoload": {
52 | "psr-4": {
53 | "Cog\\Contracts\\Paket\\": "contracts/",
54 | "Cog\\Laravel\\Paket\\": "src/"
55 | }
56 | },
57 | "autoload-dev": {
58 | "psr-4": {
59 | "Cog\\Tests\\Laravel\\Paket\\": "tests/"
60 | }
61 | },
62 | "scripts": {
63 | "test": "vendor/bin/phpunit"
64 | },
65 | "config": {
66 | "sort-packages": true
67 | },
68 | "extra": {
69 | "laravel": {
70 | "providers": [
71 | "Cog\\Laravel\\Paket\\PaketServiceProvider"
72 | ]
73 | }
74 | },
75 | "minimum-stability": "dev",
76 | "prefer-stable" : true
77 | }
78 |
--------------------------------------------------------------------------------
/src/Job/QueueJobs/RunJob.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Job\QueueJobs;
15 |
16 | use Cog\Contracts\Paket\Job\Entities\Job as JobContract;
17 | use Cog\Contracts\Paket\Job\Exceptions\JobFailed;
18 | use Cog\Contracts\Paket\Job\Repositories\Job as JobRepositoryContract;
19 | use Cog\Laravel\Paket\Job\Events\JobHasBeenTerminated;
20 | use Cog\Laravel\Paket\Support\Composer;
21 | use Illuminate\Bus\Queueable;
22 | use Illuminate\Contracts\Queue\ShouldQueue;
23 | use Illuminate\Foundation\Bus\Dispatchable;
24 | use Illuminate\Queue\InteractsWithQueue;
25 | use RuntimeException;
26 |
27 | final class RunJob implements ShouldQueue
28 | {
29 | use Dispatchable;
30 | use InteractsWithQueue;
31 | use Queueable;
32 |
33 | private $paketJob;
34 |
35 | public function __construct(JobContract $paketJob)
36 | {
37 | $this->paketJob = $paketJob;
38 | }
39 |
40 | public function handle(JobRepositoryContract $jobs, Composer $composer): void
41 | {
42 | $jobs->changeJobStatus($this->paketJob, 'Running');
43 |
44 | try {
45 | switch ($this->paketJob->getType()) {
46 | case 'RequirementInstall':
47 | $composer->install($this->paketJob->getRequirement(), $this->paketJob);
48 | break;
49 | case 'RequirementUninstall':
50 | $composer->uninstall($this->paketJob->getRequirement(), $this->paketJob);
51 | break;
52 | default:
53 | // TODO: Throw custom exception
54 | throw new RuntimeException('Unknown type of job');
55 | }
56 |
57 | $jobs->changeJobStatus($this->paketJob, 'Success');
58 | $jobs->changeJobExitCode($this->paketJob, 0);
59 | } catch (JobFailed $exception) {
60 | $jobs->changeJobStatus($this->paketJob, 'Failed');
61 | $jobs->changeJobExitCode($this->paketJob, $exception->getExitCode());
62 | }
63 |
64 | event(new JobHasBeenTerminated($this->paketJob));
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/Job/Entities/Job.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Job\Entities;
15 |
16 | use Cog\Contracts\Paket\Job\Entities\Job as JobContract;
17 | use Cog\Contracts\Paket\Process\Entities\Process as ProcessContract;
18 | use Cog\Contracts\Paket\Requirement\Entities\Requirement as RequirementContract;
19 | use Cog\Laravel\Paket\Process\Entities\Process;
20 | use Cog\Laravel\Paket\Requirement\Entities\Requirement;
21 | use DateTimeInterface;
22 | use Illuminate\Support\Carbon;
23 |
24 | final class Job implements JobContract
25 | {
26 | private $type;
27 |
28 | private $id;
29 |
30 | private $status;
31 |
32 | private $process;
33 |
34 | private $requirement;
35 |
36 | private $createdAt;
37 |
38 | public function __construct(
39 | string $type,
40 | string $id,
41 | string $status,
42 | DateTimeInterface $createdAt,
43 | ProcessContract $process,
44 | ?RequirementContract $requirement = null
45 | ) {
46 | $this->type = $type;
47 | $this->id = $id;
48 | $this->status = $status;
49 | $this->createdAt = $createdAt;
50 | $this->process = $process;
51 | $this->requirement = $requirement;
52 | }
53 |
54 | public static function fromArray(array $job): JobContract
55 | {
56 | return new self(
57 | $job['type'],
58 | $job['id'],
59 | $job['status'],
60 | Carbon::createFromFormat(DATE_RFC3339_EXTENDED, $job['createdAt']),
61 | Process::fromArray($job['process']),
62 | Requirement::fromArray($job['requirement'])
63 | );
64 | }
65 |
66 | public function toArray(): array
67 | {
68 | return [
69 | 'type' => $this->getType(),
70 | 'id' => $this->getId(),
71 | 'status' => $this->status,
72 | 'requirement' => $this->requirement->toArray(),
73 | 'process' => $this->process->toArray(),
74 | 'createdAt' => $this->createdAt->format(DATE_RFC3339_EXTENDED),
75 | ];
76 | }
77 |
78 | public function getType(): string
79 | {
80 | return $this->type;
81 | }
82 |
83 | public function getId(): string
84 | {
85 | return $this->id;
86 | }
87 |
88 | public function getRequirement(): RequirementContract
89 | {
90 | return $this->requirement;
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/resources/js/components/Job/OptionsMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/resources/js/components/Repository/Repository.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
13 | options.{{ optionName }}.{{ optionKey }}: {{ optionValue }}
14 |
15 |
16 | options.{{ optionName }}: {{ option }}
17 |
18 |
19 |
20 |
21 |
22 | package.dist.{{ distKey }}: {{ distValue }}
23 |
24 |
25 | package.source.{{ sourceKey }}: {{ sourceValue }}
26 |
27 |
28 |
29 |
30 |
31 |
79 |
--------------------------------------------------------------------------------
/src/Console/Commands/Setup.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Console\Commands;
15 |
16 | use Illuminate\Console\Command;
17 | use Illuminate\Support\Facades\File;
18 | use Symfony\Component\Console\Input\InputOption;
19 |
20 | final class Setup extends Command
21 | {
22 | protected $name = 'paket:setup';
23 |
24 | protected $description = 'Set up all of the Paket resources';
25 |
26 | public function handle(): int
27 | {
28 | $this->warn('Paket scaffolding set up starting');
29 | $this->publishAssets();
30 | $this->createPaketStorage(storage_path('paket'));
31 | $this->createPaketJobsStorage(storage_path('paket/jobs'));
32 | $this->info('Paket scaffolding set up completed');
33 |
34 | return 0;
35 | }
36 |
37 | protected function getOptions(): array
38 | {
39 | return [
40 | ['force', null, InputOption::VALUE_NONE, 'Overwrite any existing files'],
41 | ];
42 | }
43 |
44 | private function publishAssets(): void
45 | {
46 | if ($this->option('force')) {
47 | $this->warn('Force publishing Paket assets');
48 | $this->call('vendor:publish', [
49 | '--tag' => 'paket-assets',
50 | '--force' => $this->option('force'),
51 | ]);
52 | } else {
53 | $this->warn('Publishing Paket assets');
54 | $this->call('vendor:publish', [
55 | '--tag' => 'paket-assets',
56 | ]);
57 | }
58 | $this->info('Published Paket assets');
59 | }
60 |
61 | private function createPaketStorage(string $path): void
62 | {
63 | $this->createDirectory($path);
64 | $this->createGitIgnore($path, "*\n!jobs/\n!.gitignore\n");
65 | }
66 |
67 | private function createPaketJobsStorage(string $path): void
68 | {
69 | $this->createDirectory($path);
70 | $this->createGitIgnore($path, "*\n!.gitignore\n");
71 | }
72 |
73 | private function createDirectory(string $path): void
74 | {
75 | if (file_exists($path)) {
76 | return;
77 | }
78 |
79 | $this->warn("Creating `{$path}` directory");
80 | mkdir($path);
81 | $this->info("Created `{$path}` directory");
82 | }
83 |
84 | private function createGitIgnore(string $path, string $content): void
85 | {
86 | $filepath = sprintf('%s/.gitignore', $path);
87 | if (file_exists($filepath)) {
88 | return;
89 | }
90 |
91 | $this->warn("Creating `{$filepath}` file");
92 | File::put($filepath, $content);
93 | $this->info("Created `{$filepath}` file");
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Requirements/CollectAction.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Requirements;
15 |
16 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
17 | use MCStreetguy\ComposerParser\Factory as ComposerParser;
18 |
19 | final class CollectAction
20 | {
21 | public function __invoke(): ResponsableContract
22 | {
23 | $requirements = $this->getInstalledRequirements();
24 |
25 | return new CollectResponse($requirements);
26 | }
27 |
28 | private function getInstalledRequirements(): array
29 | {
30 | $lockFile = ComposerParser::parseLockfile(base_path('composer.lock'));
31 | $jsonFile = ComposerParser::parseComposerJson(base_path('composer.json'));
32 |
33 | $packages = $lockFile->getPackages()->getData();
34 | $devPackages = $lockFile->getPackagesDev()->getData();
35 | $platform = $lockFile->getPlatform()->getData();
36 | $devPlatform = $lockFile->getPlatformDev()->getData();
37 |
38 | foreach ($packages as &$package) {
39 | $package['isDevelopment'] = false;
40 | }
41 | foreach ($devPackages as &$package) {
42 | $package['isDevelopment'] = true;
43 | }
44 |
45 | $requires = $jsonFile->getRequire()->getData();
46 | $devRequires = $jsonFile->getRequireDev()->getData();
47 |
48 | $roots = [
49 | 'essential' => [],
50 | 'dev' => [],
51 | ];
52 | foreach ($platform as $name => $version) {
53 | $roots['essential'][] = [
54 | 'name' => $name,
55 | 'version' => $version,
56 | ];
57 | }
58 | foreach ($devPlatform as $name => $version) {
59 | $roots['dev'][] = [
60 | 'name' => $name,
61 | 'version' => $version,
62 | ];
63 | }
64 | foreach ($packages as $key => $package) {
65 | if (isset($requires[$package['name']]) || isset($devRequires[$package['name']])) {
66 | $roots['essential'][] = $package;
67 | unset($packages[$key]);
68 | }
69 | }
70 | foreach ($devPackages as $key => $package) {
71 | if (isset($requires[$package['name']]) || isset($devRequires[$package['name']])) {
72 | $roots['dev'][] = $package;
73 | unset($devPackages[$key]);
74 | }
75 | }
76 | $dependencies = [
77 | 'essential' => [],
78 | 'dev' => [],
79 | ];
80 | foreach ($packages as $key => $package) {
81 | $dependencies['essential'][] = $package;
82 | }
83 | foreach ($devPackages as $key => $package) {
84 | $dependencies['dev'][] = $package;
85 | }
86 |
87 | return [
88 | 'roots' => $roots,
89 | 'dependencies' => $dependencies,
90 | ];
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | All notable changes to `cybercog/laravel-paket` will be documented in this file.
4 |
5 | ## [Unreleased]
6 |
7 | ## [1.8.0]
8 |
9 | ### Added
10 |
11 | - ([#75]) Add configurable Composer path
12 |
13 | ## [1.7.0]
14 |
15 | ### Added
16 |
17 | - ([#71]) Add Laravel 8 support
18 |
19 | ## [1.6.0]
20 |
21 | ### Added
22 |
23 | - ([#68]) Add Laravel Sanctum to Laravel Ecosystem tab
24 | - ([#68]) Add PurgeCSS
25 |
26 | ## [1.5.2]
27 |
28 | ### Added
29 |
30 | - ([#64]) Add Laravel 7 support
31 |
32 | ## [1.5.1]
33 |
34 | ### Fixed
35 |
36 | - ([#58]) Fix fallback Base URI value
37 | - ([#61]) Fixed [GHSA-h9rv-jmmf-4pgx](https://github.com/advisories/GHSA-h9rv-jmmf-4pgx)
38 |
39 | ## [1.5.0]
40 |
41 | ### Added
42 |
43 | - ([#48]) Added repositories tab
44 | - ([#51]) Added refetch data button in top menu
45 | - ([#52]) Added polling to get realtime job output
46 | - ([#53]) Added requirement suggestions from Packagist.org
47 | - ([#55]) Trim first slash in .env PAKET_BASE_URI
48 |
49 | ## [1.4.0]
50 |
51 | ### Added
52 |
53 | - ([#39]) Added jobs deletion
54 | - ([#47]) Added job status badge in top menu
55 | - ([#42]) Lock installer when Job in progress
56 | - ([#47]) Unlock installer when Job in progress
57 |
58 | ## [1.3.0]
59 |
60 | ### Added
61 |
62 | - ([#35]) Tab with Laravel Packages
63 |
64 | ## [1.2.0]
65 |
66 | ### Changed
67 |
68 | - ([#33]) UI rebuilt with Tailwind CSS
69 |
70 | ## [1.1.0] - 2019-09-04
71 |
72 | ### Added
73 |
74 | - ([#32]) Laravel v6 support
75 |
76 | ### Changed
77 |
78 | - ([#27]) `Http\Controllers` namespace structure became more flat
79 |
80 | ### Fixed
81 |
82 | - ([#28]) Terminal jobs new line breaks
83 |
84 | ### Security
85 |
86 | - Upgraded `lodash`
87 |
88 | ## 1.0.0 - 2019-06-30
89 |
90 | - Initial release
91 |
92 | [Unreleased]: https://github.com/cybercog/laravel-paket/compare/1.8.0...master
93 | [1.8.0]: https://github.com/cybercog/laravel-paket/compare/1.7.0...1.8.0
94 | [1.7.0]: https://github.com/cybercog/laravel-paket/compare/1.6.0...1.7.0
95 | [1.6.0]: https://github.com/cybercog/laravel-paket/compare/1.5.2...1.6.0
96 | [1.5.2]: https://github.com/cybercog/laravel-paket/compare/1.5.1...1.5.2
97 | [1.5.1]: https://github.com/cybercog/laravel-paket/compare/1.5.0...1.5.1
98 | [1.5.0]: https://github.com/cybercog/laravel-paket/compare/1.4.0...1.5.0
99 | [1.4.0]: https://github.com/cybercog/laravel-paket/compare/1.3.0...1.4.0
100 | [1.3.0]: https://github.com/cybercog/laravel-paket/compare/1.2.0...1.3.0
101 | [1.2.0]: https://github.com/cybercog/laravel-paket/compare/1.1.0...1.2.0
102 | [1.1.0]: https://github.com/cybercog/laravel-paket/compare/1.0.0...1.1.0
103 |
104 | [#75]: https://github.com/cybercog/laravel-paket/pull/75
105 | [#71]: https://github.com/cybercog/laravel-paket/pull/71
106 | [#68]: https://github.com/cybercog/laravel-paket/pull/68
107 | [#64]: https://github.com/cybercog/laravel-paket/pull/64
108 | [#61]: https://github.com/cybercog/laravel-paket/pull/61
109 | [#58]: https://github.com/cybercog/laravel-paket/pull/58
110 | [#55]: https://github.com/cybercog/laravel-paket/pull/55
111 | [#53]: https://github.com/cybercog/laravel-paket/pull/53
112 | [#52]: https://github.com/cybercog/laravel-paket/pull/52
113 | [#51]: https://github.com/cybercog/laravel-paket/pull/51
114 | [#48]: https://github.com/cybercog/laravel-paket/pull/48
115 | [#47]: https://github.com/cybercog/laravel-paket/pull/47
116 | [#42]: https://github.com/cybercog/laravel-paket/pull/42
117 | [#39]: https://github.com/cybercog/laravel-paket/pull/39
118 | [#35]: https://github.com/cybercog/laravel-paket/pull/35
119 | [#33]: https://github.com/cybercog/laravel-paket/pull/33
120 | [#32]: https://github.com/cybercog/laravel-paket/pull/32
121 | [#28]: https://github.com/cybercog/laravel-paket/pull/28
122 | [#27]: https://github.com/cybercog/laravel-paket/pull/27
123 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Laravel Paket
2 |
3 | 
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ## Introduction
14 |
15 | Laravel Paket satisfies application requirements. Manage Laravel dependencies without switching to command line!
16 |
17 | Graphical User Interface made with [Tailwind CSS].
18 |
19 | ## Official Documentation
20 |
21 | Documentation can be found in [Laravel Paket Guide].
22 |
23 | ## Installation
24 |
25 | Pull in the package through Composer.
26 |
27 | ```shell script
28 | $ composer require cybercog/laravel-paket --dev
29 | ```
30 |
31 | Run Artisan `paket:setup` command to publish Paket assets to `public/vendor/paket` directory & create `storage/paket` directory for terminal job logs.
32 |
33 | ```shell script
34 | $ php artisan paket:setup
35 | ```
36 |
37 | ## Upgrading
38 |
39 | When upgrading Paket, you should re-publish assets to `public/vendor/paket` directory with force setup command.
40 |
41 | ```shell script
42 | $ php artisan paket:setup --force
43 | ```
44 |
45 | ## Quick Start
46 |
47 | Run local development server.
48 |
49 | ```shell script
50 | $ php artisan serve
51 | ```
52 |
53 | Go to URL [http://localhost:8000/paket](http://localhost:8000/paket) in your browser.
54 |
55 | You will only be able to access this dashboard when `APP_ENV=local` is set in `.env` file.
56 |
57 | ### Dashboard
58 |
59 | 
60 |
61 | ### Laravel Ecosystem
62 |
63 | 
64 |
65 | ### Composer Requirements
66 |
67 | 
68 |
69 | ### Terminal Jobs
70 |
71 | 
72 |
73 | ### Terminal Job Details
74 |
75 | 
76 |
77 | ## License
78 |
79 | - `Laravel Paket` package is open-sourced software licensed under the [MIT License](LICENSE) by [Anton Komarev].
80 | - `Laravel Paket` logo by [Caneco].
81 |
82 | ## About CyberCog
83 |
84 | [CyberCog] is a Social Unity of enthusiasts. Research best solutions in product & software development is our passion.
85 |
86 | - [Follow us on Twitter]
87 |
88 |
89 |
90 | [Anton Komarev]: https://komarev.com
91 | [Caneco]: http://twitter.com/caneco
92 | [CyberCog]: https://cybercog.su
93 | [Follow us on Twitter]: https://twitter.com/cybercog
94 | [Laravel Paket Guide]: https://laravel-paket.readme.io/docs
95 | [Tailwind CSS]: https://github.com/tailwindcss/tailwindcss
96 |
--------------------------------------------------------------------------------
/resources/js/screens/requirements/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Requirements
4 |
5 |
6 |
7 |
Roots
8 |
9 |
10 |
11 |
Essential
12 |
13 |
17 |
20 |
21 |
22 |
23 |
Development
24 |
25 |
29 |
32 |
33 |
34 |
35 |
36 |
Dependencies
37 |
38 |
39 |
40 |
Essential
41 |
42 |
46 |
49 |
50 |
51 |
52 |
Development
53 |
54 |
58 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
96 |
--------------------------------------------------------------------------------
/resources/js/screens/jobs/item.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Jobs
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
119 |
--------------------------------------------------------------------------------
/src/PaketServiceProvider.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket;
15 |
16 | use Cog\Contracts\Paket\Job\Repositories\Job as JobRepositoryContract;
17 | use Cog\Laravel\Paket\Console\Commands\Setup;
18 | use Cog\Laravel\Paket\Job\Events\JobHasBeenCreated;
19 | use Cog\Laravel\Paket\Job\Listeners\JobRunner;
20 | use Cog\Laravel\Paket\Job\Repositories\JobFileRepository;
21 | use Cog\Laravel\Paket\Support\Composer;
22 | use Illuminate\Filesystem\Filesystem;
23 | use Illuminate\Support\Facades\Config;
24 | use Illuminate\Support\Facades\Event;
25 | use Illuminate\Support\Facades\Route;
26 | use Illuminate\Support\ServiceProvider;
27 |
28 | final class PaketServiceProvider extends ServiceProvider
29 | {
30 | public function register(): void
31 | {
32 | $this->configure();
33 | $this->registerConsoleCommands();
34 | }
35 |
36 | public function boot(): void
37 | {
38 | $this->registerMiddlewareGroups();
39 | $this->registerPublishes();
40 | $this->registerResources();
41 | $this->registerRoutes();
42 | $this->registerBindings();
43 | $this->registerListeners();
44 | }
45 |
46 | private function getRouteConfiguration(): array
47 | {
48 | return [
49 | 'namespace' => 'Cog\Laravel\Paket\Http\Controllers',
50 | 'prefix' => ltrim(Config::get('paket.base_uri', 'paket'), '/'),
51 | 'middleware' => 'paket',
52 | ];
53 | }
54 |
55 | /**
56 | * Merge Paket configuration with the application configuration.
57 | *
58 | * @return void
59 | */
60 | private function configure(): void
61 | {
62 | if (!$this->app->configurationIsCached()) {
63 | $this->mergeConfigFrom(__DIR__ . '/../config/paket.php', 'paket');
64 | }
65 | }
66 |
67 | private function registerMiddlewareGroups(): void
68 | {
69 | Route::middlewareGroup('paket', Config::get('paket.middlewares', []));
70 | }
71 |
72 | private function registerResources(): void
73 | {
74 | $this->loadViewsFrom(__DIR__ . '/../resources/views', 'paket');
75 | }
76 |
77 | private function registerPublishes(): void
78 | {
79 | if ($this->app->runningInConsole()) {
80 | $this->publishes([
81 | __DIR__ . '/../public' => public_path('vendor/paket'),
82 | ], 'paket-assets');
83 |
84 | $this->publishes([
85 | __DIR__ . '/../config/paket.php' => config_path('paket.php'),
86 | ], 'paket-config');
87 | }
88 | }
89 |
90 | private function registerRoutes(): void
91 | {
92 | Route::group($this->getRouteConfiguration(), function () {
93 | $this->loadRoutesFrom(__DIR__ . '/../routes/web.php');
94 | });
95 | }
96 |
97 | private function registerConsoleCommands(): void
98 | {
99 | $this->commands([
100 | Setup::class,
101 | ]);
102 | }
103 |
104 | private function registerBindings(): void
105 | {
106 | $this->app->singleton(Composer::class, function () {
107 | return new Composer(
108 | $this->app->make(Filesystem::class),
109 | base_path(),
110 | storage_path('paket/jobs'),
111 | Config::get('paket.composer_path', '/usr/bin/composer')
112 | );
113 | });
114 |
115 | $this->app->singleton(JobRepositoryContract::class, function () {
116 | return new JobFileRepository(
117 | $this->app->make(Filesystem::class),
118 | storage_path('paket')
119 | );
120 | });
121 | }
122 |
123 | private function registerListeners(): void
124 | {
125 | Event::listen(JobHasBeenCreated::class, JobRunner::class);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/resources/js/screens/jobs/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Jobs
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
119 |
--------------------------------------------------------------------------------
/resources/js/components/PaketCard.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
![]()
9 |
13 |
14 |
29 |
30 |
31 |
32 |
132 |
--------------------------------------------------------------------------------
/resources/js/components/TopMenu.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
44 |
45 |
46 |
47 |
90 |
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/Jobs/PostAction.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Http\Controllers\Api\Jobs;
15 |
16 | use Cog\Contracts\Paket\Job\Repositories\Job as JobRepositoryContract;
17 | use Cog\Contracts\Paket\Requirement\Entities\Requirement as RequirementContract;
18 | use Cog\Laravel\Paket\Job\Entities\Job;
19 | use Cog\Laravel\Paket\Job\Events\JobHasBeenCreated;
20 | use Cog\Laravel\Paket\Process\Entities\Process;
21 | use Cog\Laravel\Paket\Requirement\Entities\Requirement;
22 | use Illuminate\Contracts\Support\Responsable as ResponsableContract;
23 | use Illuminate\Support\Arr;
24 | use Illuminate\Support\Carbon;
25 | use Illuminate\Validation\ValidationException;
26 | use MCStreetguy\ComposerParser\Factory as ComposerParser;
27 | use Ramsey\Uuid\Uuid;
28 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
29 |
30 | final class PostAction
31 | {
32 | private $jobs;
33 |
34 | public function __construct(JobRepositoryContract $jobs)
35 | {
36 | $this->jobs = $jobs;
37 | }
38 |
39 | public function __invoke(PostRequest $request): ResponsableContract
40 | {
41 | $type = $request->input('type');
42 | $requirement = Requirement::fromArray($request->input('requirement'));
43 |
44 | switch ($type) {
45 | case 'RequirementInstall':
46 | if ($this->isRequirementInstalled($requirement)) {
47 | throw ValidationException::withMessages([
48 | 'name' => [
49 | "Package `{$requirement}` already installed",
50 | ],
51 | ]);
52 | }
53 | break;
54 | case 'RequirementUninstall':
55 | $requirement = $this->getInstalledRequirement($requirement);
56 | break;
57 | }
58 |
59 | $job = new Job(
60 | $type,
61 | strval(Uuid::uuid4()),
62 | 'Pending',
63 | Carbon::now(),
64 | new Process(),
65 | $requirement
66 | );
67 |
68 | $this->jobs->store($job);
69 |
70 | event(new JobHasBeenCreated($job));
71 |
72 | return new PostResponse($job);
73 | }
74 |
75 | private function getInstalledRequirements(): array
76 | {
77 | $lockFile = ComposerParser::parseLockfile(base_path('composer.lock'));
78 |
79 | $packages = $lockFile->getPackages()->getData();
80 | $devPackages = $lockFile->getPackagesDev()->getData();
81 | $platform = $lockFile->getPlatform()->getData();
82 | $devPlatform = $lockFile->getPlatformDev()->getData();
83 | foreach ($packages as &$package) {
84 | $package['isDevelopment'] = false;
85 | }
86 | foreach ($devPackages as &$package) {
87 | $package['isDevelopment'] = true;
88 | }
89 | $platforms = [];
90 | foreach ($platform as $name => $version) {
91 | $platforms[] = [
92 | 'name' => $name,
93 | 'version' => $version,
94 | 'isDevelopment' => false,
95 | ];
96 | }
97 | foreach ($devPlatform as $name => $version) {
98 | $platforms[] = [
99 | 'name' => $name,
100 | 'version' => $version,
101 | 'isDevelopment' => true,
102 | ];
103 | }
104 |
105 | return array_merge($packages, $devPackages, $platforms);
106 | }
107 |
108 | private function getInstalledRequirement(RequirementContract $requirement): RequirementContract
109 | {
110 | $installedRequirements = $this->getInstalledRequirements();
111 |
112 | $installedRequirement = Arr::first($installedRequirements, function (array $value) use ($requirement) {
113 | return $value['name'] === $requirement->getName();
114 | });
115 |
116 | if (is_null($installedRequirement)) {
117 | throw new NotFoundHttpException();
118 | }
119 |
120 | $requirement = Requirement::fromArray($installedRequirement);
121 |
122 | return $requirement;
123 | }
124 |
125 | private function isRequirementInstalled(RequirementContract $requirement): bool
126 | {
127 | $installedRequirements = $this->getInstalledRequirements();
128 |
129 | $installedRequirement = Arr::first($installedRequirements, function (array $value) use ($requirement) {
130 | return $value['name'] === $requirement->getName();
131 | });
132 |
133 | return !is_null($installedRequirement)
134 | && $installedRequirement['version'] === $requirement->getVersion()
135 | && $installedRequirement['isDevelopment'] === $requirement->isDevelopment();
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/resources/js/components/Requirement/Suggestions.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 | /
12 |
13 |
14 |
15 |
16 |
17 | -
18 |
22 |
23 |
28 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
144 |
--------------------------------------------------------------------------------
/src/Job/Repositories/JobFileRepository.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Job\Repositories;
15 |
16 | use Cog\Contracts\Paket\Job\Entities\Job as JobContract;
17 | use Cog\Contracts\Paket\Job\Repositories\Job as JobRepositoryContract;
18 | use Cog\Laravel\Paket\Job\Entities\Job;
19 | use Illuminate\Contracts\Filesystem\FileNotFoundException;
20 | use Illuminate\Filesystem\Filesystem;
21 | use RuntimeException;
22 | use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
23 |
24 | final class JobFileRepository implements JobRepositoryContract
25 | {
26 | /**
27 | * The filesystem instance.
28 | *
29 | * @var \Illuminate\Filesystem\Filesystem
30 | */
31 | private $files;
32 |
33 | /**
34 | * The path to store jobs data.
35 | *
36 | * @var string
37 | */
38 | private $storagePath;
39 |
40 | /**
41 | * Create a new job file repository instance.
42 | *
43 | * @param \Illuminate\Filesystem\Filesystem $files
44 | * @param string $storagePath
45 | */
46 | public function __construct(Filesystem $files, string $storagePath)
47 | {
48 | $this->files = $files;
49 | $this->storagePath = $storagePath;
50 | }
51 |
52 | public function all(): iterable
53 | {
54 | $index = $this->getIndex();
55 |
56 | $jobs = [];
57 | foreach ($index as $job) {
58 | $jobs[] = Job::fromArray($job);
59 | }
60 |
61 | return $jobs;
62 | }
63 |
64 | public function getById(string $id): JobContract
65 | {
66 | $index = $this->getIndex();
67 |
68 | foreach ($index as $job) {
69 | if ($job['id'] === $id) {
70 | $job['process']['output'] = $this->getJobProcessOutput($job['id']);
71 |
72 | return Job::fromArray($job);
73 | }
74 | }
75 |
76 | throw new NotFoundHttpException("Job with id `{$id}` not found.");
77 | }
78 |
79 | // TODO: [v2.0] Add to contract
80 | public function deleteById(string $id): void
81 | {
82 | $index = $this->getIndex();
83 |
84 | foreach ($index as $key => $job) {
85 | if ($job['id'] === $id) {
86 | $jobKey = $key;
87 | break;
88 | }
89 | }
90 |
91 | if (!isset($jobKey)) {
92 | throw new NotFoundHttpException("Job with id `{$id}` not found.");
93 | }
94 |
95 | unset($index[$jobKey]);
96 | $index = array_values($index);
97 |
98 | $this->deleteJobProcessOutput($id);
99 |
100 | $this->putIndex($index);
101 | }
102 |
103 | public function store(JobContract $job): void
104 | {
105 | $index = $this->getIndex();
106 |
107 | $index[] = $job->toArray();
108 |
109 | $this->putIndex($index);
110 | }
111 |
112 | public function changeJobStatus(JobContract $job, string $statusName): void
113 | {
114 | $names = [
115 | 'Pending',
116 | 'Running',
117 | 'Success',
118 | 'Failed',
119 | ];
120 |
121 | if (!in_array($statusName, $names)) {
122 | // TODO: Throw custom exception
123 | throw new RuntimeException('Unknown job status');
124 | }
125 |
126 | $index = $this->getIndex();
127 |
128 | foreach ($index as $key => $record) {
129 | if ($record['id'] === $job->getId()) {
130 | $index[$key]['status'] = $statusName;
131 | break;
132 | }
133 | }
134 |
135 | $this->putIndex($index);
136 | }
137 |
138 | public function changeJobExitCode(JobContract $job, int $exitCode): void
139 | {
140 | $index = $this->getIndex();
141 |
142 | foreach ($index as $key => $record) {
143 | if ($record['id'] === $job->getId()) {
144 | $index[$key]['process']['exitCode'] = $exitCode;
145 | break;
146 | }
147 | }
148 |
149 | $this->putIndex($index);
150 | }
151 |
152 | private function getIndex(): array
153 | {
154 | try {
155 | return json_decode($this->files->get($this->getIndexFilePath(), $isLocked = true), true);
156 | } catch (FileNotFoundException $exception) {
157 | return [];
158 | }
159 | }
160 |
161 | private function putIndex(iterable $index): void
162 | {
163 | $this->files->put($this->getIndexFilePath(), json_encode($index));
164 | }
165 |
166 | private function getJobProcessOutput(string $jobId): string
167 | {
168 | $path = sprintf('%s/jobs/%s.log', $this->storagePath, $jobId);
169 |
170 | try {
171 | return $this->files->get($path);
172 | } catch (FileNotFoundException $exception) {
173 | return '';
174 | }
175 | }
176 |
177 | private function deleteJobProcessOutput(string $jobId): void
178 | {
179 | $path = sprintf('%s/jobs/%s.log', $this->storagePath, $jobId);
180 | $this->files->delete($path);
181 | }
182 |
183 | private function getIndexFilePath(): string
184 | {
185 | return $this->storagePath . '/jobs.json';
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/resources/js/screens/dashboard/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Environment
4 |
5 |
6 |
7 |
8 |
9 |
10 |

15 | Paket
16 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |

33 | Laravel
34 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
Requirements
45 |
46 |
47 |
48 |
49 |
50 |
51 | Essential
52 |
53 |
54 | {{ getRequirementsCount('roots', 'essential') }} roots
55 |
56 |
57 | {{ getRequirementsCount('dependencies', 'essential') }} dependencies
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | Development
67 |
68 |
69 |
70 | {{ getRequirementsCount('roots', 'dev') }} roots
71 |
72 |
73 | {{ getRequirementsCount('dependencies', 'dev') }} dependencies
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
147 |
--------------------------------------------------------------------------------
/resources/js/store.js:
--------------------------------------------------------------------------------
1 | import Axios from 'axios';
2 | import Vue from 'vue';
3 | import Vuex from 'vuex';
4 |
5 | Vue.use(Vuex);
6 |
7 | const state = {
8 | isAutoRefreshingJobs: false,
9 | isInstallerLocked: false,
10 | installerCurrentJob: null,
11 | requirements: [],
12 | requirementSuggestions: [],
13 | repositories: [],
14 | jobs: [],
15 | protectedRequirements: [
16 | 'php',
17 | 'cybercog/laravel-paket',
18 | 'laravel/framework',
19 | ],
20 | };
21 |
22 | const mutations = {
23 | lockInstaller(state, job) {
24 | state.isInstallerLocked = true;
25 | state.installerCurrentJob = job;
26 | },
27 |
28 | unlockInstaller(state) {
29 | state.isInstallerLocked = false;
30 | state.installerCurrentJob = null;
31 | },
32 | };
33 |
34 | const actions = {
35 | async collectRequirements() {
36 | const response = await Axios.get(this.getters.getUrl('/api/requirements'));
37 |
38 | this.state.requirements = response.data;
39 | },
40 |
41 | async postJobs(context, payload) {
42 | // Need this pre-lock to work with `sync` queue
43 | context.commit('lockInstaller', payload);
44 |
45 | try {
46 | const response = await Axios.post(this.getters.getUrl('/api/jobs'), payload);
47 | context.commit('lockInstaller', response.data);
48 | } catch (exception) {
49 | context.commit('unlockInstaller');
50 | }
51 |
52 | this.dispatch('collectRequirements');
53 | this.dispatch('collectJobs');
54 | },
55 |
56 | async deleteJobs(context, payload) {
57 | await Axios.delete(this.getters.getUrl(`/api/jobs/${payload.id}`));
58 | },
59 |
60 | async collectRepositories() {
61 | const url = this.getters.getUrl('/api/repositories');
62 |
63 | try {
64 | const response = await Axios.get(url);
65 |
66 | if (response.status === 200) {
67 | this.state.repositories = response.data;
68 | }
69 | } catch (exception) {
70 | console.warn(`Cannot fetch ${url}`);
71 | }
72 | },
73 |
74 | async collectJobs() {
75 | const url = this.getters.getUrl('/api/jobs');
76 |
77 | try {
78 | const response = await Axios.get(url);
79 |
80 | if (response.status === 200) {
81 | this.state.jobs = response.data.reverse();
82 | }
83 | } catch (exception) {
84 | console.warn(`Cannot fetch ${url}`);
85 | }
86 | },
87 |
88 | async autoRefreshJobs(context) {
89 | if (context.isAutoRefreshingJobs) {
90 | return;
91 | }
92 |
93 | context.isAutoRefreshingJobs = true;
94 |
95 | setTimeout(async () => {
96 | await context.dispatch('collectJobs');
97 |
98 | if (context.getters.getActiveJobs().length > 0) {
99 | await context.dispatch('autoRefreshJobs');
100 | } else {
101 | await context.dispatch('collectRequirements');
102 | context.commit('unlockInstaller');
103 | context.isAutoRefreshingJobs = false;
104 | }
105 | }, 1000);
106 | },
107 |
108 | async collectRequirementSuggestions(context, payload) {
109 | const response = await Axios.get(`https://packagist.org/search.json?q=${payload.query}`);
110 |
111 | context.state.requirementSuggestions = response.data.results;
112 | },
113 |
114 | clearRequirementSuggestions(context) {
115 | context.state.requirementSuggestions = [];
116 | },
117 | };
118 |
119 | const getters = {
120 | isRequirementInstalled: (state) => (name) => {
121 | return state.requirements['roots']['essential'].filter(requirement => requirement.name === name).length > 0
122 | || state.requirements['roots']['dev'].filter(requirement => requirement.name === name).length > 0
123 | || state.requirements['dependencies']['essential'].filter(requirement => requirement.name === name).length > 0
124 | || state.requirements['dependencies']['dev'].filter(requirement => requirement.name === name).length > 0;
125 | },
126 |
127 | isNotProtectedRequirement: (state) => (name) => {
128 | return state.protectedRequirements.indexOf(name) === -1;
129 | },
130 |
131 | getUrl: () => (uri) => {
132 | return window.location.origin + '/' + window.Paket.baseUri + uri;
133 | },
134 |
135 | getRepositories: (state, getters) => () => {
136 | return state.repositories;
137 | },
138 |
139 | getJobs: (state, getters) => () => {
140 | return state.jobs;
141 | },
142 |
143 | getJob: (state, getters) => async (jobId) => {
144 | const url = getters.getUrl(`/api/jobs/${jobId}`);
145 |
146 | try {
147 | return await Axios.get(url);
148 | } catch (exception) {
149 | console.warn(`Cannot fetch ${url}`);
150 | }
151 | },
152 |
153 | getRequirementJobs: (state, getters) => (requirement) => {
154 | return getters
155 | .getJobs()
156 | .filter(job => requirement && job.requirement && job.requirement.name === requirement.name);
157 | },
158 |
159 | getRequirementActiveJobs: (state, getters) => (requirement) => {
160 | return getters
161 | .getRequirementJobs(requirement)
162 | .filter(job => job.status === 'Pending' || job.status === 'Running');
163 | },
164 |
165 | getActiveJobs: (state, getters) => (requirement) => {
166 | return getters
167 | .getJobs()
168 | .filter(job => job.status === 'Pending' || job.status === 'Running');
169 | },
170 |
171 | isProcessingRequirement: (state, getters) => (requirement) => {
172 | if (state.installerCurrentJob === null) {
173 | return false;
174 | }
175 |
176 | return state.installerCurrentJob.requirement.name === requirement.name;
177 | },
178 | };
179 |
180 | export default new Vuex.Store({
181 | state,
182 | getters,
183 | actions,
184 | mutations,
185 | });
186 |
--------------------------------------------------------------------------------
/resources/js/screens/laravel/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Laravel Ecosystem
4 |
5 |
14 |
15 |
16 |
17 |
155 |
--------------------------------------------------------------------------------
/src/Support/Composer.php:
--------------------------------------------------------------------------------
1 |
7 | *
8 | * For the full copyright and license information, please view the LICENSE
9 | * file that was distributed with this source code.
10 | */
11 |
12 | declare(strict_types=1);
13 |
14 | namespace Cog\Laravel\Paket\Support;
15 |
16 | use Cog\Contracts\Paket\Job\Entities\Job as JobContract;
17 | use Cog\Contracts\Paket\Job\Exceptions\JobFailed;
18 | use Cog\Contracts\Paket\Requirement\Entities\Requirement as RequirementContract;
19 | use Illuminate\Filesystem\Filesystem;
20 | use Illuminate\Support\ProcessUtils;
21 | use Symfony\Component\Process\PhpExecutableFinder;
22 | use Symfony\Component\Process\Process;
23 |
24 | final class Composer
25 | {
26 | /**
27 | * The filesystem instance.
28 | *
29 | * @var \Illuminate\Filesystem\Filesystem
30 | */
31 | private $files;
32 |
33 | /**
34 | * The working path to execute Composer from.
35 | *
36 | * @var string
37 | */
38 | private $workingPath;
39 |
40 | /**
41 | * The logging path to store Composer logs.
42 | *
43 | * @var string
44 | */
45 | private $loggingPath;
46 |
47 | /**
48 | * The Composer executable file path.
49 | *
50 | * @var string
51 | */
52 | private $composerPath;
53 |
54 | /**
55 | * Create a new Composer manager instance.
56 | *
57 | * @param \Illuminate\Filesystem\Filesystem $files
58 | * @param string $workingPath
59 | * @param string $loggingPath
60 | * @param string $composerPath
61 | */
62 | public function __construct(Filesystem $files, string $workingPath, string $loggingPath, string $composerPath)
63 | {
64 | $this->files = $files;
65 | $this->workingPath = $workingPath;
66 | $this->loggingPath = $loggingPath;
67 | $this->composerPath = $composerPath;
68 | }
69 |
70 | /**
71 | * Regenerate the Composer autoloader files.
72 | *
73 | * @param string[] $extra
74 | * @return void
75 | */
76 | public function dumpAutoload(array $extra = []): void
77 | {
78 | $command = array_merge($this->getComposerExecutable(), ['dump-autoload'], $extra);
79 |
80 | $this->getProcess($command)->run();
81 | }
82 |
83 | /**
84 | * Regenerate the optimized Composer autoloader files.
85 | *
86 | * @return void
87 | */
88 | public function dumpOptimized(): void
89 | {
90 | $this->dumpAutoload(['--optimize']);
91 | }
92 |
93 | /**
94 | * Install Composer requirement.
95 | *
96 | * @param \Cog\Contracts\Paket\Requirement\Entities\Requirement $requirement
97 | * @param \Cog\Contracts\Paket\Job\Entities\Job $job
98 | * @return void
99 | *
100 | * @throws \Cog\Contracts\Paket\Job\Exceptions\JobFailed
101 | */
102 | public function install(RequirementContract $requirement, JobContract $job): void
103 | {
104 | $flags = [
105 | '--no-interaction',
106 | ];
107 |
108 | if ($requirement->isDevelopment()) {
109 | $flags[] = '--dev';
110 | }
111 |
112 | $command = sprintf(
113 | '%s require %s %s',
114 | $this->composerPath,
115 | $requirement,
116 | implode(' ', $flags)
117 | );
118 |
119 | $this->executeCommand($job, $command);
120 | }
121 |
122 | /**
123 | * Uninstall Composer requirement.
124 | *
125 | * @param \Cog\Contracts\Paket\Requirement\Entities\Requirement $requirement
126 | * @param \Cog\Contracts\Paket\Job\Entities\Job $job
127 | * @return void
128 | *
129 | * @throws \Cog\Contracts\Paket\Job\Exceptions\JobFailed
130 | */
131 | public function uninstall(RequirementContract $requirement, JobContract $job): void
132 | {
133 | $flags = [
134 | '--no-interaction',
135 | ];
136 |
137 | if ($requirement->isDevelopment()) {
138 | $flags[] = '--dev';
139 | }
140 |
141 | $command = sprintf(
142 | '%s remove %s %s',
143 | $this->composerPath,
144 | $requirement->getName(),
145 | implode(' ', $flags)
146 | );
147 |
148 | $this->executeCommand($job, $command);
149 | }
150 |
151 | /**
152 | * Builds full command, executes it and logs process output.
153 | *
154 | * @param \Cog\Contracts\Paket\Job\Entities\Job $job
155 | * @param string $command
156 | *
157 | * @throws \Cog\Contracts\Paket\Job\Exceptions\JobFailed
158 | */
159 | private function executeCommand(JobContract $job, string $command): void
160 | {
161 | $jobLogFile = sprintf('%s/%s.log', $this->loggingPath, $job->getId());
162 | $this->writeLineToLogFile($jobLogFile, "$ {$command}\n");
163 |
164 | $commands = [
165 | sprintf('export COMPOSER_HOME=%s', '~/.composer'),
166 | sprintf('cd %s', $this->workingPath),
167 | $command,
168 | ];
169 |
170 | $fullCommand = implode(' && ', $commands);
171 | $isPtyMode = true;
172 | $timeout = 5 * 60;
173 |
174 | $process = Process::fromShellCommandline($fullCommand);
175 | $process->setPty($isPtyMode);
176 | $process->setTimeout($timeout);
177 | $process->start();
178 |
179 | foreach ($process as $type => $data) {
180 | $this->writeLineToLogFile($jobLogFile, trim($data));
181 | }
182 |
183 | $this->writeLineToLogFile($jobLogFile, "\n\nDone. Job exited with {$process->getExitCode()}.");
184 |
185 | if ($process->getExitCode() !== 0) {
186 | throw JobFailed::withExitCode($job, $process->getExitCode());
187 | }
188 | }
189 |
190 | private function getComposerExecutable(): array
191 | {
192 | if ($this->files->exists($this->workingPath . '/composer.phar')) {
193 | return [$this->getPhpBinary(), 'composer.phar'];
194 | }
195 |
196 | return ['composer'];
197 | }
198 |
199 | private function getPhpBinary(): string
200 | {
201 | return ProcessUtils::escapeArgument((new PhpExecutableFinder)->find(false));
202 | }
203 |
204 | private function getProcess(array $command): Process
205 | {
206 | return (new Process($command, $this->workingPath))->setTimeout(null);
207 | }
208 |
209 | private function writeLineToLogFile(string $jobLogFile, string $line): void
210 | {
211 | $this->files->append($jobLogFile, $line . "\n");
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/resources/js/components/Requirement/InstallForm.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
19 |
20 |
26 |
27 |
28 |
29 |
213 |
--------------------------------------------------------------------------------