Merge pull request #1286 from coollabsio/next

v4.0.0-beta.65
This commit is contained in:
Andras Bacsai 2023-10-05 11:00:56 +02:00 committed by GitHub
commit adac728a60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 201 additions and 59 deletions

View File

@ -8,6 +8,7 @@ use App\Models\StandalonePostgresql;
use Illuminate\Console\Command;
use function Laravel\Prompts\confirm;
use function Laravel\Prompts\multiselect;
use function Laravel\Prompts\select;
class ResourcesDelete extends Command
@ -50,16 +51,18 @@ class ResourcesDelete extends Command
$this->error('There are no applications to delete.');
return;
}
$application = select(
$applicationsToDelete = multiselect(
'What application do you want to delete?',
$applications->pluck('name')->toArray(),
);
$application = $applications->where('name', $application)->first();
$confirmed = confirm("Are you sure you want to delete {$application->name}?");
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$application->delete();
foreach ($applicationsToDelete as $application) {
$toDelete = $applications->where('name', $application)->first();
$toDelete->delete();
}
}
private function deleteDatabase()
{
@ -68,16 +71,19 @@ class ResourcesDelete extends Command
$this->error('There are no databases to delete.');
return;
}
$database = select(
$databasesToDelete = multiselect(
'What database do you want to delete?',
$databases->pluck('name')->toArray(),
);
$database = $databases->where('name', $database)->first();
$confirmed = confirm("Are you sure you want to delete {$database->name}?");
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$database->delete();
foreach ($databasesToDelete as $database) {
$toDelete = $databases->where('name', $database)->first();
$toDelete->delete();
}
}
private function deleteService()
{
@ -86,15 +92,17 @@ class ResourcesDelete extends Command
$this->error('There are no services to delete.');
return;
}
$service = select(
$servicesToDelete = multiselect(
'What service do you want to delete?',
$services->pluck('name')->toArray(),
);
$service = $services->where('name', $service)->first();
$confirmed = confirm("Are you sure you want to delete {$service->name}?");
$confirmed = confirm("Are you sure you want to delete all selected resources?");
if (!$confirmed) {
return;
}
$service->delete();
foreach ($servicesToDelete as $service) {
$toDelete = $services->where('name', $service)->first();
$toDelete->delete();
}
}
}

View File

@ -76,7 +76,6 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
Team::find(currentTeam()->id)->update([
'show_boarding' => false
]);
ray(currentTeam());
refreshSession();
return redirect()->route('dashboard');
}

View File

@ -0,0 +1,28 @@
<?php
namespace App\Http\Livewire\Dev;
use Livewire\Component;
class Compose extends Component
{
public string $compose = '';
public string $base64 = '';
public $services;
public function mount() {
$this->services = getServiceTemplates();
}
public function setService(string $selected) {
$this->base64 = data_get($this->services, $selected . '.compose');
if ($this->base64) {
$this->compose = base64_decode($this->base64);
}
}
public function updatedCompose($value) {
$this->base64 = base64_encode($value);
}
public function render()
{
return view('livewire.dev.compose');
}
}

View File

@ -22,7 +22,7 @@ class Project extends BaseModel
'project_id' => $project->id,
]);
});
static::deleted(function ($project) {
static::deleting(function ($project) {
$project->environments()->delete();
$project->settings()->delete();
});

View File

@ -16,7 +16,7 @@ class Service extends BaseModel
protected static function booted()
{
static::deleted(function ($service) {
static::deleting(function ($service) {
$storagesToDelete = collect([]);
foreach ($service->applications()->get() as $application) {
instant_remote_process(["docker rm -f {$application->name}-{$service->uuid}"], $service->server, false);
@ -266,7 +266,7 @@ class Service extends BaseModel
// Collect/create/update volumes
if ($serviceVolumes->count() > 0) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes, $isNew) {
$serviceVolumes = $serviceVolumes->map(function ($volume) use ($savedService, $topLevelVolumes) {
$type = null;
$source = null;
$target = null;
@ -384,9 +384,20 @@ class Service extends BaseModel
$value = Str::of($variable);
}
if ($key->startsWith('SERVICE_FQDN')) {
if (is_null(data_get($savedService, 'fqdn'))) {
$fqdn = generateFqdn($this->server, $containerName);
if (substr_count($key->value(), '_') === 2) {
if ($isNew) {
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
$fqdn = generateFqdn($this->server, "{$name->value()}-{$this->uuid}");
if (substr_count($key->value(), '_') === 3) {
// SERVICE_FQDN_UMAMI_1000
$port = $key->afterLast('_');
} else {
// SERVICE_FQDN_UMAMI
$port = null;
}
if ($port) {
$fqdn = "$fqdn:$port";
}
if (substr_count($key->value(), '_') >= 2) {
if (is_null($value)) {
$value = Str::of('/');
}
@ -403,11 +414,22 @@ class Service extends BaseModel
}
$fqdn = "$fqdn$path";
}
if (!$isDatabase) {
if ($savedService->fqdn) {
$fqdn = $savedService->fqdn . ',' . $fqdn;
} else {
$fqdn = $fqdn;
}
$savedService->fqdn = $fqdn;
$savedService->save();
}
}
// data_forget($service, "environment.$variableName");
// $yaml = data_forget($yaml, "services.$serviceName.environment.$variableName");
// if (count(data_get($yaml, 'services.' . $serviceName . '.environment')) === 0) {
// $yaml = data_forget($yaml, "services.$serviceName.environment");
// }
continue;
}
if ($value?->startsWith('$')) {
@ -422,10 +444,17 @@ class Service extends BaseModel
$forService = $value->afterLast('_');
$generatedValue = null;
if ($command->value() === 'FQDN' || $command->value() === 'URL') {
$fqdn = generateFqdn($this->server, $containerName);
if (Str::lower($forService) === $serviceName) {
$fqdn = generateFqdn($this->server, $containerName);
} else {
$fqdn = generateFqdn($this->server, Str::lower($forService) . '-' . $this->uuid);
}
if ($foundEnv) {
$fqdn = data_get($foundEnv, 'value');
} else {
if ($command->value() === 'URL') {
$fqdn = Str::of($fqdn)->after('://')->value();
}
EnvironmentVariable::create([
'key' => $key,
'value' => $fqdn,
@ -434,10 +463,11 @@ class Service extends BaseModel
'is_preview' => false,
]);
}
if (!$isDatabase) {
$savedService->fqdn = $fqdn;
$savedService->save();
if ($command->value() === 'FQDN') {
$savedService->fqdn = $fqdn;
$savedService->save();
}
}
} else {
switch ($command) {
@ -513,19 +543,19 @@ class Service extends BaseModel
$serviceLabels = $serviceLabels->merge(fqdnLabelsForTraefik($fqdns, $containerName, true));
}
}
data_set($service, 'labels', $serviceLabels->toArray());
data_forget($service, 'is_database');
data_set($service, 'restart', RESTART_MODE);
data_set($service, 'container_name', $containerName);
data_forget($service, 'volumes.*.content');
data_forget($service, 'volumes.*.isDirectory');
// Remove unnecessary variables from service.environment
$withoutServiceEnvs = collect([]);
collect(data_get($service, 'environment'))->each(function ($value, $key) use ($withoutServiceEnvs) {
if (!Str::of($key)->startsWith('$SERVICE_')) {
$withoutServiceEnvs->put($key, $value);
if (!Str::of($key)->startsWith('$SERVICE_') && !Str::of($value)->startsWith('SERVICE_')) {
$k = Str::of($value)->before("=");
$v = Str::of($value)->after("=");
$withoutServiceEnvs->put($k->value(), $v->value());
}
});
data_set($service, 'environment', $withoutServiceEnvs->toArray());

View File

@ -28,7 +28,7 @@ class StandalonePostgresql extends BaseModel
'is_readonly' => true
]);
});
static::deleted(function ($database) {
static::deleting(function ($database) {
// Stop Container
instant_remote_process(
["docker rm -f {$database->uuid}"],

View File

@ -157,11 +157,13 @@ function fqdnLabelsForTraefik(Collection $domains, $container_name, $is_force_ht
$path = $url->getPath();
$schema = $url->getScheme();
$port = $url->getPort();
$slug = Str::slug($host . $path);
$http_label = "{$container_name}-{$slug}-http";
$https_label = "{$container_name}-{$slug}-https";
$http_label = "{$container_name}-http";
$https_label = "{$container_name}-https";
if ($port) {
$http_label = "{$http_label}-{$port}";
$https_label = "{$https_label}-{$port}";
}
if ($schema === 'https') {
// Set labels for https
$labels->push("traefik.http.routers.{$https_label}.rule=Host(`{$host}`) && PathPrefix(`{$path}`)");

View File

@ -137,13 +137,18 @@ function updateCompose($resource)
// Update FQDN
$variableName = "SERVICE_FQDN_" . Str::of($resource->name)->upper();
ray($variableName);
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
if ($generatedEnv) {
$generatedEnv->value = $resource->fqdn;
$generatedEnv->save();
}
$variableName = "SERVICE_URL_" . Str::of($resource->name)->upper();
$generatedEnv = EnvironmentVariable::where('service_id', $resource->service_id)->where('key', $variableName)->first();
if ($generatedEnv) {
$url = Str::of($resource->fqdn)->after('://');
$generatedEnv->value = $url;
$generatedEnv->save();
}
$dockerComposeRaw = Yaml::dump($dockerCompose, 10, 2);
$resource->service->docker_compose_raw = $dockerComposeRaw;

View File

@ -14,6 +14,7 @@ use Illuminate\Mail\Message;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Route;
@ -425,6 +426,16 @@ function getServiceTemplates()
if (isDev()) {
$services = File::get(base_path('templates/service-templates.json'));
$services = collect(json_decode($services))->sortKeys();
$deprecated = File::get(base_path('templates/deprecated.json'));
$deprecated = collect(json_decode($deprecated))->sortKeys();
$services = $services->merge($deprecated);
$version = config('version');
$services = $services->map(function ($service) use ($version) {
if (version_compare($version, data_get($service,'minVersion', '0.0.0'), '<')) {
$service->disabled = true;
}
return $service;
});
} else {
$services = Http::get(config('constants.services.official'));
if ($services->failed()) {

View File

@ -7,7 +7,7 @@ return [
// The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.64',
'release' => '4.0.0-beta.65',
// When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'),

View File

@ -1,3 +1,3 @@
<?php
return '4.0.0-beta.64';
return '4.0.0-beta.65';

View File

@ -34,14 +34,14 @@ services:
POSTGRES_DB: "${DB_DATABASE:-coolify}"
POSTGRES_HOST_AUTH_METHOD: "trust"
volumes:
- /data/coolify/_volumes/database/:/var/lib/postgresql/data
- coolify-pg-data-dev:/var/lib/postgresql/data
redis:
ports:
- "${FORWARD_REDIS_PORT:-6379}:6379"
env_file:
- .env
volumes:
- /data/coolify/_volumes/redis/:/data
- coolify-redis-data-dev:/data
vite:
image: node:19
working_dir: /var/www/html
@ -56,7 +56,7 @@ services:
volumes:
- /:/host
- /var/run/docker.sock:/var/run/docker.sock
- /data/coolify/:/data/coolify
- coolify-data-dev:/data/coolify
mailpit:
image: "axllent/mailpit:latest"
container_name: coolify-mail
@ -76,6 +76,12 @@ services:
MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY:-minioadmin}"
MINIO_SECRET_KEY: "${MINIO_SECRET_KEY:-minioadmin}"
volumes:
- /data/coolify/_volumes/minio/:/data
- coolify-minio-data-dev:/data
networks:
- coolify
volumes:
coolify-data-dev:
coolify-pg-data-dev:
coolify-redis-data-dev:
coolify-minio-data-dev:

View File

@ -11,7 +11,8 @@ services:
- database__connection__password=$SERVICE_PASSWORD_MYSQL
- database__connection__database=${MYSQL_DATABASE-ghost}
depends_on:
- mysql
mysql:
condition: service_healthy
mysql:
image: mysql:8.0
volumes:
@ -20,4 +21,9 @@ services:
- MYSQL_USER=${SERVICE_USER_MYSQL}
- MYSQL_PASSWORD=${SERVICE_PASSWORD_MYSQL}
- MYSQL_DATABASE=${MYSQL_DATABASE}
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQL_ROOT}
- MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_MYSQLROOT}
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 10

View File

@ -0,0 +1,16 @@
services:
minio:
image: quay.io/minio/minio:RELEASE.2023-09-30T07-02-29Z
command: server /data --console-address ":9001"
environment:
SERVICE_FQDN_MINIO_9000:
SERVICE_FQDN_CONSOLE_9001:
MINIO_ROOT_USER: $SERVICE_USER_MINIO
MINIO_ROOT_PASSWORD: $SERVICE_PASSWORD_MINIO
volumes:
- minio-data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 5s
timeout: 20s
retries: 10

View File

@ -55,6 +55,9 @@ a {
.box {
@apply flex items-center p-2 transition-colors cursor-pointer min-h-16 bg-coolgray-200 hover:bg-coollabs-100 hover:text-white hover:no-underline min-w-[24rem];
}
.box-without-bg {
@apply flex items-center p-2 transition-colors min-h-16 hover:text-white hover:no-underline min-w-[24rem];
}
.lds-heart {
animation: lds-heart 1.2s infinite cubic-bezier(0.215, 0.61, 0.355, 1);

View File

@ -1,2 +1,2 @@
<a {{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-20 hover:opacity-100 hover:text-white z-50']) }}
<a {{ $attributes->merge(['class' => 'text-xs cursor-pointer opacity-60 hover:opacity-100 hover:text-white z-50']) }}
href="https://github.com/coollabsio/coolify/releases/tag/v{{ config('version') }}">v{{ config('version') }}</a>

View File

@ -0,0 +1,13 @@
<div class="pb-10" x-data>
<h1>Compose</h1>
<div>All kinds of compose files.</div>
<h3 class="pt-4">Services</h3>
@foreach ($services as $serviceName => $value)
<x-forms.button wire:click="setService('{{ $serviceName }}')">{{ Str::headline($serviceName) }}</x-forms.button>
@endforeach
<h3 class="pt-4">Base64 En/Decode</h3>
<x-forms.button x-on:click="copyToClipboard('{{ $base64 }}')">Copy Base64 Compose</x-forms.button>
<div class="pt-4">
<x-forms.textarea realtimeValidation rows="40" id="compose"></x-forms.textarea>
</div>
</div>

View File

@ -95,19 +95,31 @@
<span class="loading loading-xs loading-spinner"></span>
@else
@foreach ($services as $serviceName => $service)
<button class="text-left box group"
wire:loading.attr="disabled" wire:click="setType('one-click-service-{{ $serviceName }}')">
<div class="flex flex-col mx-6">
<div class="font-bold text-white group-hover:text-white">
{{ Str::headline($serviceName) }}
</div>
@if (data_get($service, 'slogan'))
<div class="text-xs">
{{ data_get($service, 'slogan') }}
@if (data_get($service, 'disabled'))
<button class="text-left bg-black cursor-not-allowed bg-coolgray-200/20 box-without-bg"
disabled>
<div class="flex flex-col mx-6">
<div class="font-bold text-coolgray-500">
{{ Str::headline($serviceName) }}
</div>
@endif
</div>
</button>
You need to upgrade to {{ data_get($service, 'minVersion') }} to use this service.
</div>
</button>
@else
<button class="text-left box group" wire:loading.attr="disabled"
wire:click="setType('one-click-service-{{ $serviceName }}')">
<div class="flex flex-col mx-6">
<div class="font-bold text-white group-hover:text-white">
{{ Str::headline($serviceName) }}
</div>
@if (data_get($service, 'slogan'))
<div class="text-xs">
{{ data_get($service, 'slogan') }}
</div>
@endif
</div>
</button>
@endif
@endforeach
@endif
</div>

View File

@ -9,6 +9,7 @@ use App\Http\Controllers\ServerController;
use App\Http\Livewire\Boarding\Index as BoardingIndex;
use App\Http\Livewire\Project\Service\Index as ServiceIndex;
use App\Http\Livewire\Project\Service\Show as ServiceShow;
use App\Http\Livewire\Dev\Compose as Compose;
use App\Http\Livewire\Dashboard;
use App\Http\Livewire\Project\Shared\Logs;
use App\Http\Livewire\Server\All;
@ -29,6 +30,9 @@ use Laravel\Fortify\Contracts\FailedPasswordResetLinkRequestResponse;
use Laravel\Fortify\Contracts\SuccessfulPasswordResetLinkRequestResponse;
use Laravel\Fortify\Fortify;
if (isDev()) {
Route::get('/dev/compose', Compose::class)->name('dev.compose');
}
Route::post('/forgot-password', function (Request $request) {
if (is_transactional_emails_active()) {
$arrayOfRequest = $request->only(Fortify::email());
@ -94,7 +98,6 @@ Route::middleware(['auth'])->group(function () {
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}', ServiceIndex::class)->name('project.service');
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}', ServiceShow::class)->name('project.service.show');
Route::get('/project/{project_uuid}/{environment_name}/service/{service_uuid}/{service_name}/logs', Logs::class)->name('project.service.logs');
});
Route::middleware(['auth'])->group(function () {

View File

@ -21,8 +21,8 @@ function help {
}
function setup:dev {
docker exec coolify bash -c "composer install"
docker exec coolify bash -c "php artisan key:generate"
docker exec coolify bash -c "composer install"
docker exec coolify bash -c "php artisan migrate:fresh --seed"
}
function sync:v3 {

View File

@ -3,5 +3,5 @@
"documentation": "https://plausible.io/docs",
"slogan": "A lighweight and open-source website analytics tool.",
"compose": "dmVyc2lvbjogJzMuMycKc2VydmljZXM6CiAgcGxhdXNpYmxlX2RiOgogICAgaW1hZ2U6ICdwb3N0Z3JlczoxNC1hbHBpbmUnCiAgICByZXN0YXJ0OiBhbHdheXMKICAgIHZvbHVtZXM6CiAgICAgIC0gJy9ldGMvZGF0YS9wbGF1c2libGUvZGItZGF0YTovdmFyL2xpYi9wb3N0Z3Jlc3FsL2RhdGEnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBQT1NUR1JFU19QQVNTV09SRD0kUE9TVEdSRVNfUEFTU1dPUkQKICBwbGF1c2libGVfZXZlbnRzX2RiOgogICAgaW1hZ2U6ICdjbGlja2hvdXNlL2NsaWNraG91c2Utc2VydmVyOjIzLjMuNy41LWFscGluZScKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgdm9sdW1lczoKICAgICAgLSAnL2V0Yy9kYXRhL3BsYXVzaWJsZS9ldmVudC1kYXRhOi92YXIvbGliL2NsaWNraG91c2UnCiAgICAgIC0gdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogL2V0Yy9kYXRhL3BsYXVzaWJsZS9jbGlja2hvdXNlL2NsaWNraG91c2UtY29uZmlnLnhtbAogICAgICAgIHRhcmdldDogL2V0Yy9jbGlja2hvdXNlLXNlcnZlci9jb25maWcuZC9sb2dnaW5nLnhtbAogICAgICAgIHJlYWRfb25seTogdHJ1ZQogICAgICAgIGNvbnRlbnQ6ID4tCiAgICAgICAgICA8Y2xpY2tob3VzZT48cHJvZmlsZXM+PGRlZmF1bHQ+PGxvZ19xdWVyaWVzPjA8L2xvZ19xdWVyaWVzPjxsb2dfcXVlcnlfdGhyZWFkcz4wPC9sb2dfcXVlcnlfdGhyZWFkcz48L2RlZmF1bHQ+PC9wcm9maWxlcz48L2NsaWNraG91c2U+CiAgICAgIC0gdHlwZTogYmluZAogICAgICAgIHNvdXJjZTogL2V0Yy9kYXRhL3BsYXVzaWJsZS9jbGlja2hvdXNlL2NsaWNraG91c2UtdXNlci1jb25maWcueG1sCiAgICAgICAgdGFyZ2V0OiAvZXRjL2NsaWNraG91c2Utc2VydmVyL3VzZXJzLmQvbG9nZ2luZy54bWwKICAgICAgICByZWFkX29ubHk6IHRydWUKICAgICAgICBjb250ZW50OiA+LQogICAgICAgICAgPGNsaWNraG91c2U+PGxvZ2dlcj48bGV2ZWw+d2FybmluZzwvbGV2ZWw+PGNvbnNvbGU+dHJ1ZTwvY29uc29sZT48L2xvZ2dlcj48cXVlcnlfdGhyZWFkX2xvZwogICAgICAgICAgcmVtb3ZlPSJyZW1vdmUiLz48cXVlcnlfbG9nIHJlbW92ZT0icmVtb3ZlIi8+PHRleHRfbG9nCiAgICAgICAgICByZW1vdmU9InJlbW92ZSIvPjx0cmFjZV9sb2cgcmVtb3ZlPSJyZW1vdmUiLz48bWV0cmljX2xvZwogICAgICAgICAgcmVtb3ZlPSJyZW1vdmUiLz48YXN5bmNocm9ub3VzX21ldHJpY19sb2cKICAgICAgICAgIHJlbW92ZT0icmVtb3ZlIi8+PHNlc3Npb25fbG9nIHJlbW92ZT0icmVtb3ZlIi8+PHBhcnRfbG9nCiAgICAgICAgICByZW1vdmU9InJlbW92ZSIvPjwvY2xpY2tob3VzZT4KICAgIHVsaW1pdHM6CiAgICAgICAgbm9maWxlOgogICAgICAgICAgc29mdDogMjYyMTQ0CiAgICAgICAgICBoYXJkOiAyNjIxNDQKICBwbGF1c2libGU6CiAgICBpbWFnZTogJ3BsYXVzaWJsZS9hbmFseXRpY3M6djIuMCcKICAgIHJlc3RhcnQ6IGFsd2F5cwogICAgY29tbWFuZDogJ3NoIC1jICJzbGVlcCAxMCAmJiAvZW50cnlwb2ludC5zaCBkYiBjcmVhdGVkYiAmJiAvZW50cnlwb2ludC5zaCBkYiBtaWdyYXRlICYmIC9lbnRyeXBvaW50LnNoIHJ1biInCiAgICBkZXBlbmRzX29uOgogICAgICAtIHBsYXVzaWJsZV9kYgogICAgICAtIHBsYXVzaWJsZV9ldmVudHNfZGIKICAgIHBvcnRzOgogICAgICAtICc4MDAwOjgwMDAnCiAgICBlbnZpcm9ubWVudDoKICAgICAgLSBTRUNSRVRfS0VZX0JBU0U9JFNFQ1JFVF9LRVlfQkFTRQogICAgICAtIERBVEFCQVNFX1VSTD0kREFUQUJBU0VfVVJMCiAgICAgIC0gJ0NMSUNLSE9VU0VfREFUQUJBU0VfVVJMPWh0dHA6Ly9wbGF1c2libGVfZXZlbnRzX2RiOjgxMjMvcGxhdXNpYmxlX2V2ZW50c19kYicKICAgICAgLSBNQUlMRVJfQURBUFRFUj0kTUFJTEVSX0FEQVBURVIKICAgICAgLSBTRU5ER1JJRF9BUElfS0VZPSRTRU5ER1JJRF9BUElfS0VZCiAgICAgIC0gR09PR0xFX0NMSUVOVF9JRD0kR09PR0xFX0NMSUVOVF9JRAogICAgICAtIEdPT0dMRV9DTElFTlRfU0VDUkVUPSRHT09HTEVfQ0xJRU5UX1NFQ1JFVAogICAgICAtIERJU0FCTEVfUkVHSVNUUkFUSU9OPSRESVNBQkxFX1JFR0lTVFJBVElPTgogICAgICAtIEJBU0VfVVJMPSRCQVNFX1VSTAogICAgICAtIExPR19GQUlMRURfTE9HSU5fQVRURU1QVFM9JExPR19GQUlMRURfTE9HSU5fQVRURU1QVFMK"
},
}
}

View File

@ -4,7 +4,7 @@
"version": "3.12.36"
},
"v4": {
"version": "4.0.0-beta.64"
"version": "4.0.0-beta.65"
}
}
}