diff --git a/bin/phpdoc b/bin/phpdoc new file mode 100755 --- /dev/null +++ b/bin/phpdoc @@ -0,0 +1,7 @@ +#!/bin/bash + +cwd=$(dirname $0) + +pushd ${cwd}/.. +phpdoc +popd diff --git a/bin/phpstan b/bin/phpstan new file mode 100755 --- /dev/null +++ b/bin/phpstan @@ -0,0 +1,11 @@ +#!/bin/bash + +cwd=$(dirname $0) + +pushd ${cwd}/../src/ + +php -dmemory_limit=256M \ + vendor/bin/phpstan \ + analyse + +popd diff --git a/bin/phpunit b/bin/phpunit new file mode 100755 --- /dev/null +++ b/bin/phpunit @@ -0,0 +1,15 @@ +#!/bin/bash + +cwd=$(dirname $0) + +pushd ${cwd}/../src/ + +php -dzend_extension=xdebug.so \ + vendor/bin/phpunit \ + --exclude-group imap \ + --stop-on-defect \ + --stop-on-error \ + --stop-on-failure \ + --testsuite Browser,Unit,Feature + +popd diff --git a/bin/quickstart.sh b/bin/quickstart.sh --- a/bin/quickstart.sh +++ b/bin/quickstart.sh @@ -43,10 +43,10 @@ docker-compose up -d kolab mariadb redis pushd ${base_dir}/src/ +cp .env.example .env composer install npm install find bootstrap/cache/ -type f ! -name ".gitignore" -delete -cp .env.example .env ./artisan key:generate ./artisan jwt:secret -f ./artisan clear-compiled diff --git a/phpdoc.dist.xml b/phpdoc.dist.xml --- a/phpdoc.dist.xml +++ b/phpdoc.dist.xml @@ -11,8 +11,10 @@ src/app/ src/database/ + src/database/factories/ src/database/migrations/ src/database/seeds/ + src/resources/ src/resources/ diff --git a/src/.env.example b/src/.env.example --- a/src/.env.example +++ b/src/.env.example @@ -25,9 +25,8 @@ IMAP_URI=ssl://127.0.0.1:993 IMAP_ADMIN_LOGIN=cyrus-admin IMAP_ADMIN_PASSWORD=Welcome2KolabSystems -IMAP_VERIFY_PEER=true -IMAP_VERIFY_NAME=true -IMAP_CAFILE=null +IMAP_VERIFY_HOST=false +IMAP_VERIFY_PEER=false LDAP_BASE_DN="dc=mgmt,dc=com" LDAP_DOMAIN_BASE_DN="ou=Domains,dc=mgmt,dc=com" diff --git a/src/.gitignore b/src/.gitignore --- a/src/.gitignore +++ b/src/.gitignore @@ -1,5 +1,6 @@ database/database.sqlite node_modules/ +package-lock.json public/css/app.css public/hot public/js/app.js diff --git a/src/app/Auth/LDAPUserProvider.php b/src/app/Auth/LDAPUserProvider.php --- a/src/app/Auth/LDAPUserProvider.php +++ b/src/app/Auth/LDAPUserProvider.php @@ -3,32 +3,68 @@ namespace App\Auth; use App\User; -use Carbon\Carbon; use Illuminate\Auth\EloquentUserProvider; use Illuminate\Support\Facades\Hash; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\UserProvider; +/** + * A user provider that integrates an LDAP deployment. + */ class LDAPUserProvider extends EloquentUserProvider implements UserProvider { + /** + * Retrieve the user by its ID. + * + * @param string $identifier The unique ID for the user to attempt to retrieve. + * + * @return \Illuminate\Contracts\Auth\Authenticatable|null + */ public function retrieveById($identifier) { return parent::retrieveById($identifier); } + /** + * Retrieve the user by its credentials. + * + * Please note that this function also validates the password. + * + * @param array $credentials An array containing the email and password. + * + * @return User|null + */ public function retrieveByCredentials(array $credentials) { $entries = User::where('email', '=', $credentials['email']); if ($entries->count() == 1) { - $user = $entries->select('id', 'email', 'password', 'password_ldap')->first(); + $user = $entries->select(['id', 'email', 'password', 'password_ldap'])->first(); + + if (!$this->validateCredentials($user, $credentials)) { + return null; + } return $user; + } else { + if ($entries->count() > 1) { + \Log::warning("Multiple entries for {$credentials['email']}"); + } else { + \Log::warning("No entries for {$credentials['email']}"); + } } return null; } + /** + * Validate the credentials for a user. + * + * @param Authenticatable $user The user. + * @param array $credentials The credentials. + * + * @return bool + */ public function validateCredentials(Authenticatable $user, array $credentials) { $authenticated = false; @@ -46,6 +82,8 @@ if ($hash == $user->password_ldap) { $authenticated = true; } + } else { + \Log::error("Incomplete credentials for {$user->email}"); } } @@ -53,8 +91,18 @@ // TODO: Update password if necessary, examine whether writing to // user->password is sufficient? if ($authenticated) { - $user->password = $credentials['password']; - $user->save(); + \Log::info("Successful authentication for {$user->email}"); + + if (empty($user->password)) { + $user->password = $credentials['password']; + $user->save(); + } + + if (empty($user->password_ldap)) { + $user->password_ldap = '{SSHA512}' . base64_encode( + pack('H*', hash('sha512', $credentials['password'])) + ); + } } else { // TODO: Try actual LDAP? \Log::info("Authentication failed for {$user->email}"); diff --git a/src/app/Backends/IMAP.php b/src/app/Backends/IMAP.php --- a/src/app/Backends/IMAP.php +++ b/src/app/Backends/IMAP.php @@ -89,8 +89,8 @@ 'socket_options' => [ 'ssl' => [ 'verify_peer' => \config('imap.verify_peer'), - 'verify_peer_name' => \config('imap.verify_name'), - 'cafile' => \config('imap.cafile'), + 'verify_peer_name' => \config('imap.verify_peer'), + 'verify_host' => \config('imap.verify_host') ], ], ], diff --git a/src/app/Backends/LDAP.php b/src/app/Backends/LDAP.php --- a/src/app/Backends/LDAP.php +++ b/src/app/Backends/LDAP.php @@ -162,7 +162,7 @@ * * @param \App\User $user The user account to create. * - * @return void + * @return bool|void */ public static function createUser(User $user) { @@ -213,12 +213,56 @@ // } + public static function deleteDomain($domain) + { + $config = self::getConfig('admin'); + $ldap = self::initLDAP($config); + + $hosted_root_dn = \config('ldap.hosted.root_dn'); + $mgmt_root_dn = \config('ldap.admin.root_dn'); + + $domain_base_dn = "ou={$domain->namespace},{$hosted_root_dn}"; + + if ($ldap->get_entry($domain_base_dn)) { + $ldap->delete_entry_recursive($domain_base_dn); + } + + if ($ldap_domain = $ldap->find_domain($domain->namespace)) { + if ($ldap->get_entry($ldap_domain['dn'])) { + $ldap->delete_entry($ldap_domain['dn']); + } + } + } + + public static function deleteUser($user) + { + $config = self::getConfig('admin'); + $ldap = self::initLDAP($config); + + list($_local, $_domain) = explode('@', $user->email, 2); + + $domain = $ldap->find_domain($_domain); + + if (!$domain) { + return false; + } + + $base_dn = $ldap->domain_root_dn($_domain); + $dn = "uid={$user->email},ou=People,{$base_dn}"; + + if (!$ldap->get_entry($dn)) { + return false; + } + + $ldap->delete_entry($dn); + } + /** * Update a user in LDAP. * * @param \App\User $user The user account to update. * - * @return void + * @return bool|void */ public static function updateUser(User $user) { diff --git a/src/app/Console/Commands/PackageSkusCommand.php b/src/app/Console/Commands/PackageSkusCommand.php --- a/src/app/Console/Commands/PackageSkusCommand.php +++ b/src/app/Console/Commands/PackageSkusCommand.php @@ -39,12 +39,12 @@ */ public function handle() { - $packages = Package::get(); + $packages = Package::all(); foreach ($packages as $package) { $this->info(sprintf("Package: %s", $package->title)); - foreach ($package->skus()->get() as $sku) { + foreach ($package->skus as $sku) { $this->info(sprintf(" SKU: %s (%d)", $sku->title, $sku->pivot->qty)); } } diff --git a/src/app/Console/Commands/PlanPackagesCommand.php b/src/app/Console/Commands/PlanPackagesCommand.php --- a/src/app/Console/Commands/PlanPackagesCommand.php +++ b/src/app/Console/Commands/PlanPackagesCommand.php @@ -39,14 +39,14 @@ */ public function handle() { - $plans = Plan::get(); + $plans = Plan::all(); foreach ($plans as $plan) { $this->info(sprintf("Plan: %s", $plan->title)); $plan_costs = 0; - foreach ($plan->packages()->get() as $package) { + foreach ($plan->packages as $package) { $qtyMin = $package->pivot->qty_min; $qtyMax = $package->pivot->qty_max; @@ -65,7 +65,7 @@ ) ); - foreach ($package->skus()->get() as $sku) { + foreach ($package->skus as $sku) { $this->info(sprintf(" SKU: %s (%d)", $sku->title, $sku->pivot->qty)); } diff --git a/src/app/Console/Commands/UserDomains.php b/src/app/Console/Commands/UserDomains.php --- a/src/app/Console/Commands/UserDomains.php +++ b/src/app/Console/Commands/UserDomains.php @@ -40,8 +40,6 @@ */ public function handle() { - DB::enableQueryLog(); - $user = User::where('email', $this->argument('userid'))->first(); $this->info("Found user: {$user->id}"); @@ -49,7 +47,5 @@ foreach ($user->domains() as $domain) { $this->info("Domain: {$domain->namespace}"); } - - dd(DB::getQueryLog()); } } diff --git a/src/app/Console/Commands/UserEntitlementsCommand.php b/src/app/Console/Commands/UserEntitlementsCommand.php --- a/src/app/Console/Commands/UserEntitlementsCommand.php +++ b/src/app/Console/Commands/UserEntitlementsCommand.php @@ -2,7 +2,7 @@ namespace App\Console\Commands; -use App\Domain; +use App\Sku; use App\User; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; @@ -44,19 +44,19 @@ $this->info("Found user: {$user->id}"); - $entitlements = $user->entitlements()->get(); + $skus_counted = []; - foreach ($entitlements as $entitlement) { - //yes: dd($entitlement); - $_entitleable = $entitlement->entitleable; - - if ($_entitleable instanceof Domain) { - $this->info(sprintf("Domain: %s", $_entitleable->namespace)); + foreach ($user->entitlements as $entitlement) { + if (!array_key_exists($entitlement->sku_id, $skus_counted)) { + $skus_counted[$entitlement->sku_id] = 1; + } else { + $skus_counted[$entitlement->sku_id] += 1; } + } - if ($_entitleable instanceof User) { - $this->info(sprintf("User: %s", $_entitleable->email)); - } + foreach ($skus_counted as $id => $qty) { + $sku = Sku::find($id); + $this->info("SKU: {$sku->title} ({$qty})"); } } } diff --git a/src/app/Domain.php b/src/app/Domain.php --- a/src/app/Domain.php +++ b/src/app/Domain.php @@ -3,9 +3,17 @@ namespace App; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\SoftDeletes; +/** + * The eloquent definition of a Domain. + * + * @property string $namespace + */ class Domain extends Model { + use SoftDeletes; + // we've simply never heard of this domain public const STATUS_NEW = 1 << 0; // it's been activated @@ -17,7 +25,7 @@ // ownership of the domain has been confirmed public const STATUS_CONFIRMED = 1 << 4; // domain has been verified that it exists in DNS -// public const STATUS_VERIFIED = 1 << 5; + public const STATUS_VERIFIED = 1 << 5; // domain has been created in LDAP public const STATUS_LDAP_READY = 1 << 6; @@ -33,6 +41,7 @@ public const HASH_CNAME = 3; public $incrementing = false; + protected $keyType = 'bigint'; protected $fillable = [ @@ -41,6 +50,35 @@ 'type' ]; + /** + * Assign a package to a domain. The domain should not belong to any existing entitlements. + * + * @param \App\Package $package The package to assign. + * + * @return \App\Domain + */ + public function assignPackage($package, $user) + { + $wallet_id = $user->wallets()->get()[0]->id; + + foreach ($package->skus as $sku) { + for ($i = $sku->pivot->qty; $i > 0; $i--) { + \App\Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $wallet_id, + 'sku_id' => $sku->id, + 'entitleable_id' => $this->id, + 'entitleable_type' => Domain::class + ] + ); + } + } + + return $this; + } + + public function entitlement() { return $this->morphOne('App\Entitlement', 'entitleable'); @@ -65,7 +103,7 @@ */ public function isActive(): bool { - return $this->status & self::STATUS_ACTIVE; + return ($this->status & self::STATUS_ACTIVE) == true; } /** @@ -75,7 +113,7 @@ */ public function isConfirmed(): bool { - return $this->status & self::STATUS_CONFIRMED; + return ($this->status & self::STATUS_CONFIRMED) == true; } /** @@ -85,7 +123,7 @@ */ public function isDeleted(): bool { - return $this->status & self::STATUS_DELETED; + return ($this->status & self::STATUS_DELETED) == true; } /** @@ -95,7 +133,7 @@ */ public function isExternal(): bool { - return $this->type & self::TYPE_EXTERNAL; + return ($this->type & self::TYPE_EXTERNAL) == true; } /** @@ -105,7 +143,7 @@ */ public function isHosted(): bool { - return $this->type & self::TYPE_HOSTED; + return ($this->type & self::TYPE_HOSTED) == true; } /** @@ -115,7 +153,7 @@ */ public function isNew(): bool { - return $this->status & self::STATUS_NEW; + return ($this->status & self::STATUS_NEW) == true; } /** @@ -125,7 +163,7 @@ */ public function isPublic(): bool { - return $this->type & self::TYPE_PUBLIC; + return ($this->type & self::TYPE_PUBLIC) == true; } /** @@ -135,7 +173,7 @@ */ public function isLdapReady(): bool { - return $this->status & self::STATUS_LDAP_READY; + return ($this->status & self::STATUS_LDAP_READY) == true; } /** @@ -145,7 +183,7 @@ */ public function isSuspended(): bool { - return $this->status & self::STATUS_SUSPENDED; + return ($this->status & self::STATUS_SUSPENDED) == true; } /** @@ -154,12 +192,11 @@ * * @return bool */ -/* public function isVerified(): bool { - return $this->status & self::STATUS_VERIFIED; + return ($this->status & self::STATUS_VERIFIED) == true; } -*/ + /** * Domain status mutator * @@ -176,7 +213,7 @@ self::STATUS_SUSPENDED, self::STATUS_DELETED, self::STATUS_LDAP_READY, -// self::STATUS_VERIFIED, + self::STATUS_VERIFIED, ]; foreach ($allowed_values as $value) { @@ -232,7 +269,7 @@ $records = \dns_get_record('kolab-verify.' . $this->namespace, DNS_CNAME); if ($records === false) { - throw new \Exception("Failed to get DNS record for $domain"); + throw new \Exception("Failed to get DNS record for {$this->namespace}"); } foreach ($records as $records) { @@ -277,14 +314,13 @@ * @return bool True if registered, False otherwise * @throws \Exception Throws exception on DNS or DB errors */ -/* public function verify(): bool { if ($this->isVerified()) { return true; } - $record = \dns_get_record($this->namespace, DNS_SOA); + $record = \dns_get_record($this->namespace, DNS_ANY); if ($record === false) { throw new \Exception("Failed to get DNS record for {$this->namespace}"); @@ -299,5 +335,4 @@ return false; } -*/ } diff --git a/src/app/Entitlement.php b/src/app/Entitlement.php --- a/src/app/Entitlement.php +++ b/src/app/Entitlement.php @@ -8,6 +8,11 @@ * The eloquent definition of an Entitlement. * * Owned by a {@link \App\User}, billed to a {@link \App\Wallet}. + * + * @property \App\User $owner The owner of this entitlement (subject). + * @property \App\Sku $sku The SKU to which this entitlement applies. + * @property \App\Wallet $wallet The wallet to which this entitlement is charged. + * @property \App\Domain|\App\User $entitleable The entitled object (receiver of the entitlement). */ class Entitlement extends Model { @@ -40,7 +45,7 @@ ]; /** - * Principally entitleable objects such as 'Domain' or 'Mailbox'. + * Principally entitleable objects such as 'Domain' or 'User'. * * @return mixed */ @@ -52,7 +57,7 @@ /** * The SKU concerned. * - * @return Sku + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function sku() { @@ -62,7 +67,7 @@ /** * The owner of this entitlement. * - * @return User + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function owner() { @@ -72,7 +77,7 @@ /** * The wallet this entitlement is being billed to * - * @return Wallet + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function wallet() { diff --git a/src/app/Handlers/Base.php b/src/app/Handlers/Base.php new file mode 100644 --- /dev/null +++ b/src/app/Handlers/Base.php @@ -0,0 +1,29 @@ +sku_id)->active) { + if (!$entitlement->sku->active) { \Log::error("Sku not active"); return false; } diff --git a/src/app/Handlers/DomainHosting.php b/src/app/Handlers/DomainHosting.php --- a/src/app/Handlers/DomainHosting.php +++ b/src/app/Handlers/DomainHosting.php @@ -4,7 +4,7 @@ use App\Sku; -class DomainHosting +class DomainHosting extends \App\Handlers\Base { public static function entitleableClass() { @@ -13,7 +13,7 @@ public static function preReq($entitlement, $domain) { - if (!Sku::find($entitlement->sku_id)->active) { + if (!$entitlement->sku->active) { \Log::error("Sku not active"); return false; } diff --git a/src/app/Handlers/DomainRegistration.php b/src/app/Handlers/DomainRegistration.php --- a/src/app/Handlers/DomainRegistration.php +++ b/src/app/Handlers/DomainRegistration.php @@ -4,7 +4,7 @@ use App\Sku; -class DomainRegistration +class DomainRegistration extends \App\Handlers\Base { public static function entitleableClass() { @@ -13,7 +13,7 @@ public static function preReq($entitlement, $domain) { - if (!Sku::find($entitlement->sku_id)->active) { + if (!$entitlement->sku->active) { \Log::error("Sku not active"); return false; } diff --git a/src/app/Handlers/Groupware.php b/src/app/Handlers/Groupware.php --- a/src/app/Handlers/Groupware.php +++ b/src/app/Handlers/Groupware.php @@ -4,7 +4,7 @@ use App\Sku; -class Groupware +class Groupware extends \App\Handlers\Base { public static function entitleableClass() { @@ -13,7 +13,7 @@ public static function preReq($entitlement, $user) { - if (!Sku::find($entitlement->sku_id)->active) { + if (!$entitlement->sku->active) { \Log::error("Sku not active"); return false; } diff --git a/src/app/Handlers/Mailbox.php b/src/app/Handlers/Mailbox.php --- a/src/app/Handlers/Mailbox.php +++ b/src/app/Handlers/Mailbox.php @@ -6,16 +6,16 @@ use App\Sku; use App\User; -class Mailbox +class Mailbox extends \App\Handlers\Base { public static function entitleableClass() { return \App\User::class; } - public static function preReq(Entitlement $entitlement, User $user) + public static function preReq($entitlement, $user) { - if (!Sku::find($entitlement->sku_id)->active) { + if (!$entitlement->sku->active) { \Log::error("Sku not active"); return false; } diff --git a/src/app/Handlers/Resource.php b/src/app/Handlers/Resource.php --- a/src/app/Handlers/Resource.php +++ b/src/app/Handlers/Resource.php @@ -4,7 +4,7 @@ use App\Sku; -class Resource +class Resource extends \App\Handlers\Base { public static function entitleableClass() { @@ -13,7 +13,7 @@ public static function preReq($entitlement, $owner) { - if (!Sku::find($entitlement->sku_id)->active) { + if (!$entitlement->sku->active) { \Log::error("Sku not active"); return false; } diff --git a/src/app/Handlers/SharedFolder.php b/src/app/Handlers/SharedFolder.php --- a/src/app/Handlers/SharedFolder.php +++ b/src/app/Handlers/SharedFolder.php @@ -4,7 +4,7 @@ use App\Sku; -class SharedFolder +class SharedFolder extends \App\Handlers\Base { public static function entitleableClass() { @@ -13,7 +13,7 @@ public static function preReq($entitlement, $owner) { - if (!Sku::find($entitlement->sku_id)->active) { + if (!$entitlement->sku->active) { \Log::error("Sku not active"); return false; } diff --git a/src/app/Handlers/Storage.php b/src/app/Handlers/Storage.php --- a/src/app/Handlers/Storage.php +++ b/src/app/Handlers/Storage.php @@ -2,32 +2,16 @@ namespace App\Handlers; -use App\Quota; -use App\Sku; -use App\User; - -class Storage +class Storage extends \App\Handlers\Base { - public static function createDefaultEntitleable(User $user) - { - $quota = new Quota(); - $quota->user_id = $user->id; - $quota->save(); - - return $quota->id; - } - public static function entitleableClass() { - return Quota::class; + return null; } - public static function preReq($entitlement, $user) + public static function preReq($entitlement, $object) { - if (!Sku::find($entitlement->sku_id)->active) { - \Log::error("Sku not active"); - return false; - } + // TODO: The storage can not be modified to below what is already consumed. return true; } diff --git a/src/app/Http/Controllers/API/DomainsController.php b/src/app/Http/Controllers/API/DomainsController.php --- a/src/app/Http/Controllers/API/DomainsController.php +++ b/src/app/Http/Controllers/API/DomainsController.php @@ -12,7 +12,7 @@ /** * Return a list of domains owned by the current user * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ public function index() { @@ -31,7 +31,7 @@ /** * Show the form for creating a new resource. * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ public function create() { @@ -43,7 +43,7 @@ * * @param int $id Domain identifier * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse|void */ public function confirm($id) { @@ -69,7 +69,7 @@ * * @param int $id * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ public function destroy($id) { @@ -105,7 +105,7 @@ * * @param int $id Domain identifier * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse|void */ public function show($id) { @@ -209,7 +209,7 @@ /** * Check if the current user has access to the domain * - * @param \App\Domain Domain + * @param \App\Domain $domain The domain * * @return bool True if current user has access, False otherwise */ diff --git a/src/app/Http/Controllers/API/PasswordResetController.php b/src/app/Http/Controllers/API/PasswordResetController.php --- a/src/app/Http/Controllers/API/PasswordResetController.php +++ b/src/app/Http/Controllers/API/PasswordResetController.php @@ -24,7 +24,7 @@ * * Verifies user email, sends verification email message. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -63,7 +63,7 @@ /** * Validation of the verification code. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -106,7 +106,7 @@ /** * Password change * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ diff --git a/src/app/Http/Controllers/API/SignupController.php b/src/app/Http/Controllers/API/SignupController.php --- a/src/app/Http/Controllers/API/SignupController.php +++ b/src/app/Http/Controllers/API/SignupController.php @@ -29,7 +29,7 @@ /** * Returns plans definitions for signup. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -55,7 +55,7 @@ * Verifies user name and email/phone, sends verification email/sms message. * Returns the verification code. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -102,7 +102,7 @@ /** * Validation of the verification code. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -153,7 +153,7 @@ /** * Finishes the signup process by creating the user account. * - * @param Illuminate\Http\Request HTTP request + * @param \Illuminate\Http\Request $request HTTP request * * @return \Illuminate\Http\JsonResponse JSON response */ @@ -220,18 +220,10 @@ ]); } - // Create SKUs (after domain) - foreach ($plan->packages as $package) { - foreach ($package->skus as $sku) { - $sku->registerEntitlement($user, is_object($domain) ? [$domain] : []); - } - } + $user->assignPlan($plan, $domain); // Save the external email and plan in user settings - $user->setSettings([ - 'external_email' => $user_email, - 'plan' => $plan->id, - ]); + $user->setSetting('external_email', $user_email); // Remove the verification code $this->code->delete(); @@ -244,8 +236,8 @@ /** * Checks if the input string is a valid email address or a phone number * - * @param string $email Email address or phone number - * @param bool &$is_phone Will be set to True if the string is valid phone number + * @param string $input Email address or phone number + * @param bool $is_phone Will have been set to True if the string is valid phone number * * @return string Error message label on validation error */ @@ -297,7 +289,7 @@ /** * Login (kolab identity) validation * - * @param string $email Login (local part of an email address) + * @param string $login Login (local part of an email address) * @param string $domain Domain name * @param bool $external Enables additional checks for domain part * diff --git a/src/app/Http/Controllers/API/UsersController.php b/src/app/Http/Controllers/API/UsersController.php --- a/src/app/Http/Controllers/API/UsersController.php +++ b/src/app/Http/Controllers/API/UsersController.php @@ -46,7 +46,7 @@ * * The user themself, and other user entitlements. * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse */ public function index() { @@ -157,11 +157,11 @@ } /** - * Display the specified resource. + * Display information on the user account specified by $id. * * @param int $id The account to show information for. * - * @return \Illuminate\Http\Response + * @return \Illuminate\Http\JsonResponse|void */ public function show($id) { @@ -171,25 +171,13 @@ return abort(403); } - $result = false; - - $user->entitlements()->each( - function ($entitlement) { - if ($entitlement->user_id == $id) { - $result = true; - } - } - ); - - if ($user->id == $id) { - $result = true; - } - - if (!$result) { + // TODO: check whether or not the user is allowed + // for now, only allow self. + if ($user->id != $id) { return abort(404); } - return \App\User::find($id); + return response()->json($user); } /** diff --git a/src/app/Jobs/UserDelete.php b/src/app/Jobs/DomainDelete.php copy from src/app/Jobs/UserDelete.php copy to src/app/Jobs/DomainDelete.php --- a/src/app/Jobs/UserDelete.php +++ b/src/app/Jobs/DomainDelete.php @@ -2,20 +2,22 @@ namespace App\Jobs; +use App\Backends\LDAP; +use App\Domain; use Illuminate\Bus\Queueable; -use Illuminate\Queue\SerializesModels; -use Illuminate\Queue\InteractsWithQueue; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; +use Illuminate\Queue\SerializesModels; +use Illuminate\Queue\InteractsWithQueue; -class UserDelete implements ShouldQueue +class DomainDelete implements ShouldQueue { use Dispatchable; use InteractsWithQueue; use Queueable; use SerializesModels; - protected $user; + protected $domain; public $tries = 5; @@ -25,13 +27,13 @@ /** * Create a new job instance. * - * @param \App\User $user The user to delete. + * @param Domain $domain The domain to delete. * * @return void */ - public function __construct(\App\User $user) + public function __construct(Domain $domain) { - $this->user = $user; + $this->domain = $domain; } /** @@ -41,6 +43,6 @@ */ public function handle() { - // + LDAP::deleteDomain($this->domain); } } diff --git a/src/app/Jobs/UserDelete.php b/src/app/Jobs/UserDelete.php --- a/src/app/Jobs/UserDelete.php +++ b/src/app/Jobs/UserDelete.php @@ -2,6 +2,8 @@ namespace App\Jobs; +use App\Backends\LDAP; +use App\User; use Illuminate\Bus\Queueable; use Illuminate\Queue\SerializesModels; use Illuminate\Queue\InteractsWithQueue; @@ -25,13 +27,13 @@ /** * Create a new job instance. * - * @param \App\User $user The user to delete. + * @param int $user_id The ID of the user to delete. * * @return void */ - public function __construct(\App\User $user) + public function __construct(int $user_id) { - $this->user = $user; + $this->user = User::withTrashed()->find($user_id); } /** @@ -41,6 +43,6 @@ */ public function handle() { - // + LDAP::deleteUser($this->user); } } diff --git a/src/app/Mail/PasswordReset.php b/src/app/Mail/PasswordReset.php --- a/src/app/Mail/PasswordReset.php +++ b/src/app/Mail/PasswordReset.php @@ -19,7 +19,7 @@ /** * Create a new message instance. * - * @param \App\VerificationCode A verification code object + * @param \App\VerificationCode $code A verification code object * * @return void */ diff --git a/src/app/Observers/DomainObserver.php b/src/app/Observers/DomainObserver.php --- a/src/app/Observers/DomainObserver.php +++ b/src/app/Observers/DomainObserver.php @@ -36,16 +36,14 @@ public function created(Domain $domain) { // Create domain record in LDAP, then check if it exists in DNS -/* - $chain = [ - new \App\Jobs\DomainVerify($domain), - ]; - - \App\Jobs\DomainCreate::withChain($chain)->dispatch($domain); -*/ \App\Jobs\DomainCreate::dispatch($domain); } + public function deleting(Domain $domain) + { + // + } + /** * Handle the domain "updated" event. * @@ -67,7 +65,7 @@ */ public function deleted(Domain $domain) { - // + \App\Jobs\DomainDelete::dispatch($domain); } /** diff --git a/src/app/Observers/EntitlementObserver.php b/src/app/Observers/EntitlementObserver.php --- a/src/app/Observers/EntitlementObserver.php +++ b/src/app/Observers/EntitlementObserver.php @@ -19,7 +19,7 @@ * * @param Entitlement $entitlement The entitlement being created. * - * @return void + * @return bool|null */ public function creating(Entitlement $entitlement) { diff --git a/src/app/Observers/UserObserver.php b/src/app/Observers/UserObserver.php --- a/src/app/Observers/UserObserver.php +++ b/src/app/Observers/UserObserver.php @@ -65,6 +65,11 @@ \App\Jobs\UserCreate::withChain($chain)->dispatch($user); } + public function deleted(User $user) + { + // + } + /** * Handle the "deleting" event. * @@ -74,7 +79,16 @@ */ public function deleting(User $user) { - // TODO \App\Jobs\UserDelete::dispatch($user); + // Entitlements do not have referential integrity on the entitled object, so this is our + // way of doing an onDelete('cascade') without the foreign key. + $entitlements = \App\Entitlement::where('entitleable_id', $user->id) + ->where('entitleable_type', \App\User::class)->get(); + + foreach ($entitlements as $entitlement) { + $entitlement->delete(); + } + + \App\Jobs\UserDelete::dispatch($user->id); } /** @@ -88,7 +102,7 @@ */ public function retrieving(User $user) { - // TODO \App\Jobs\UserRead::dispatch($user); + // TODO \App\Jobs\UserRead::dispatch($user); } /** diff --git a/src/app/Package.php b/src/app/Package.php --- a/src/app/Package.php +++ b/src/app/Package.php @@ -45,6 +45,17 @@ return $costs; } + public function isDomain() + { + foreach ($this->skus as $sku) { + if ($sku->hander_class::entitleableClass() == \App\Domain::class) { + return true; + } + } + + return false; + } + public function skus() { return $this->belongsToMany( diff --git a/src/app/PackageSku.php b/src/app/PackageSku.php --- a/src/app/PackageSku.php +++ b/src/app/PackageSku.php @@ -4,6 +4,9 @@ use Illuminate\Database\Eloquent\Relations\Pivot; +/** + * Link SKUs to Packages. + */ class PackageSku extends Pivot { protected $fillable = [ diff --git a/src/app/Plan.php b/src/app/Plan.php --- a/src/app/Plan.php +++ b/src/app/Plan.php @@ -3,6 +3,7 @@ namespace App; use Illuminate\Database\Eloquent\Model; +use Spatie\Translatable\HasTranslations; /** * The eloquent definition of a Plan. @@ -11,10 +12,12 @@ * * A "Family Plan" as such may exist of "2 or more Kolab packages", * and apply a discount for the third and further Kolab packages. + * + * @property \App\Package[] $packages */ class Plan extends Model { - use \Spatie\Translatable\HasTranslations; + use HasTranslations; public $incrementing = false; protected $keyType = 'string'; @@ -58,6 +61,15 @@ return $costs; } + /** + * The relationship to packages. + * + * The plan contains one or more packages. Each package may have its minimum number (for + * billing) or its maximum (to allow topping out "enterprise" customers on a "small business" + * plan). + * + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany + */ public function packages() { return $this->belongsToMany( @@ -65,6 +77,7 @@ 'plan_packages' )->using('App\PlanPackage')->withPivot( [ + 'qty', 'qty_min', 'qty_max', 'discount_qty', @@ -74,15 +87,15 @@ } /** - * Checks if the plan has domain SKU assigned + * Checks if the plan has any type of domain SKU assigned. + * + * @return bool */ public function hasDomain(): bool { foreach ($this->packages as $package) { - foreach ($package->skus as $sku) { - if ($sku->handler_class::entitleableClass() == \App\Domain::class) { - return true; - } + if ($package->isDomain()) { + return true; } } diff --git a/src/app/PlanPackage.php b/src/app/PlanPackage.php --- a/src/app/PlanPackage.php +++ b/src/app/PlanPackage.php @@ -4,6 +4,12 @@ use Illuminate\Database\Eloquent\Relations\Pivot; +/** + * Link Packages to Plans. + * + * @property integer $qty_min + * @property \App\Package $package + */ class PlanPackage extends Pivot { protected $fillable = [ @@ -24,16 +30,26 @@ 'discount_rate' => 'integer' ]; + /** + * Calculate the costs for this plan. + * + * @return integer + */ public function cost() { $costs = 0; if ($this->qty_min > 0) { - foreach ($this->package->skus() as $sku) { - $costs += $sku->cost; - } + $costs += $this->package->cost() * $this->qty_min; + } elseif ($this->qty > 0) { + $costs += $this->package->cost() * $this->qty; } return $costs; } + + public function package() + { + return $this->belongsTo('App\Package'); + } } diff --git a/src/app/Quota.php b/src/app/Quota.php deleted file mode 100644 --- a/src/app/Quota.php +++ /dev/null @@ -1,29 +0,0 @@ - 'int', - ]; - - public function entitlement() - { - return $this->morphOne('App\Entitlement', 'entitleable'); - } - - /** - * The owner of this quota entry - * - * @return \App\User - */ - public function user() - { - return $this->belongsTo('App\User', 'user_id', 'id'); - } -} diff --git a/src/app/SignupCode.php b/src/app/SignupCode.php --- a/src/app/SignupCode.php +++ b/src/app/SignupCode.php @@ -7,6 +7,8 @@ /** * The eloquent definition of a SignupCode. + * + * @property datetime $expires_at */ class SignupCode extends Model { diff --git a/src/app/Sku.php b/src/app/Sku.php --- a/src/app/Sku.php +++ b/src/app/Sku.php @@ -29,7 +29,7 @@ /** * List the entitlements that consume this SKU. * - * @return Entitlement[] + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function entitlements() { @@ -43,48 +43,4 @@ 'package_skus' )->using('App\PackageSku')->withPivot(['qty']); } - - /** - * Register (default) SKU entitlement for specified user. - * This method should be used e.g. on user creation when we have - * a set of SKUs and want to create entitlements for them (using - * default values). - */ - public function registerEntitlement(\App\User $user, array $params = []) - { - if (!$this->active) { - \Log::debug("Skipped registration of an entitlement for non-active SKU ($this->title)"); - return; - } - - $wallet = $user->wallets()->get()[0]; - - $entitlement = new \App\Entitlement(); - $entitlement->owner_id = $user->id; - $entitlement->wallet_id = $wallet->id; - $entitlement->sku_id = $this->id; - - $entitlement->entitleable_type = $this->handler_class::entitleableClass(); - - if ($user instanceof $entitlement->entitleable_type) { - $entitlement->entitleable_id = $user->id; - } else { - foreach ($params as $param) { - if ($param instanceof $entitlement->entitleable_type) { - $entitlement->entitleable_id = $param->id; - break; - } - } - } - - if (empty($entitlement->entitleable_id)) { - if (method_exists($this->handler_class, 'createDefaultEntitleable')) { - $entitlement->entitleable_id = $this->handler_class::createDefaultEntitleable($user); - } else { - throw new Exception("Failed to create an entitlement for SKU ($this->title). Missing entitleable_id."); - } - } - - $entitlement->save(); - } } diff --git a/src/app/User.php b/src/app/User.php --- a/src/app/User.php +++ b/src/app/User.php @@ -4,6 +4,7 @@ use Illuminate\Notifications\Notifiable; use Illuminate\Contracts\Auth\MustVerifyEmail; +use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Foundation\Auth\User as Authenticatable; use Iatstuti\Database\Support\NullableFields; use Tymon\JWTAuth\Contracts\JWTSubject; @@ -11,12 +12,16 @@ /** * The eloquent definition of a User. + * + * @property integer $id + * @property integer $status */ class User extends Authenticatable implements JWTSubject { use Notifiable; use NullableFields; use UserSettingsTrait; + use SoftDeletes; // a new user, default on creation public const STATUS_NEW = 1 << 0; @@ -78,26 +83,72 @@ /** * Any wallets on which this user is a controller. * - * @return Wallet[] + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function accounts() { return $this->belongsToMany( 'App\Wallet', // The foreign object definition 'user_accounts', // The table name - 'user_id', // The local foreign key - 'wallet_id' // The remote foreign key + 'user_id', // The local foreign key + 'wallet_id' // The remote foreign key ); } /** + * Assign a package to a user. The user should not have any existing entitlements. + * + * @param \App\Package $package The package to assign. + * @param \App\User|null $user Assign the package to another user. + * + * @return \App\User + */ + public function assignPackage($package, $user = null) + { + if (!$user) { + $user = $this; + } + + $wallet_id = $this->wallets()->get()[0]->id; + + foreach ($package->skus as $sku) { + for ($i = $sku->pivot->qty; $i > 0; $i--) { + \App\Entitlement::create( + [ + 'owner_id' => $this->id, + 'wallet_id' => $wallet_id, + 'sku_id' => $sku->id, + 'entitleable_id' => $user->id, + 'entitleable_type' => User::class + ] + ); + } + } + + return $user; + } + + public function assignPlan($plan, $domain = null) + { + $this->setSetting('plan_id', $plan->id); + + foreach ($plan->packages as $package) { + if ($package->isDomain()) { + $domain->assignPackage($package, $this); + } else { + $this->assignPackage($package); + } + } + } + + /** * List the domains to which this user is entitled. * * @return Domain[] */ public function domains() { - $domains = Domain::whereRaw( + $dbdomains = Domain::whereRaw( sprintf( '(type & %s) AND (status & %s)', Domain::TYPE_PUBLIC, @@ -105,19 +156,27 @@ ) )->get(); - foreach ($this->entitlements()->get() as $entitlement) { + $domains = []; + + foreach ($dbdomains as $dbdomain) { + $domains[] = $dbdomain; + } + + $entitlements = Entitlement::where('owner_id', $this->id)->get(); + + foreach ($entitlements as $entitlement) { if ($entitlement->entitleable instanceof Domain) { - $domain = Domain::find($entitlement->entitleable_id); - \Log::info("Found domain {$domain->namespace}"); + $domain = $entitlement->entitleable; + \Log::info("Found domain for {$this->email}: {$domain->namespace} (owned)"); $domains[] = $domain; } } - foreach ($this->accounts()->get() as $wallet) { - foreach ($wallet->entitlements()->get() as $entitlement) { + foreach ($this->accounts as $wallet) { + foreach ($wallet->entitlements as $entitlement) { if ($entitlement->entitleable instanceof Domain) { - $domain = Domain::find($entitlement->entitleable_id); - \Log::info("Found domain {$domain->namespace}"); + $domain = $entitlement->entitleable; + \Log::info("Found domain {$this->email}: {$domain->namespace} (charged)"); $domains[] = $domain; } } @@ -134,17 +193,19 @@ /** * Entitlements for this user. * - * @return Entitlement[] + * Note that these are entitlements that apply to the user account, and not entitlements that + * this user owns. + * + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function entitlements() { - return $this->hasMany('App\Entitlement', 'owner_id', 'id'); + return $this->hasMany('App\Entitlement', 'entitleable_id', 'id'); } public function addEntitlement($entitlement) { - // FIXME: This contains() check looks fishy - if (!$this->entitlements()->get()->contains($entitlement)) { + if (!$this->entitlements->contains($entitlement)) { return $this->entitlements()->save($entitlement); } } @@ -187,7 +248,7 @@ */ public function isActive(): bool { - return $this->status & self::STATUS_ACTIVE; + return ($this->status & self::STATUS_ACTIVE) == true; } /** @@ -197,7 +258,7 @@ */ public function isDeleted(): bool { - return $this->status & self::STATUS_DELETED; + return ($this->status & self::STATUS_DELETED) == true; } /** @@ -208,7 +269,7 @@ */ public function isImapReady(): bool { - return $this->status & self::STATUS_IMAP_READY; + return ($this->status & self::STATUS_IMAP_READY) == true; } /** @@ -218,7 +279,7 @@ */ public function isLdapReady(): bool { - return $this->status & self::STATUS_LDAP_READY; + return ($this->status & self::STATUS_LDAP_READY) == true; } /** @@ -228,7 +289,7 @@ */ public function isNew(): bool { - return $this->status & self::STATUS_NEW; + return ($this->status & self::STATUS_NEW) == true; } /** @@ -238,13 +299,13 @@ */ public function isSuspended(): bool { - return $this->status & self::STATUS_SUSPENDED; + return ($this->status & self::STATUS_SUSPENDED) == true; } /** * Any (additional) properties of this user. * - * @return \App\UserSetting[] + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function settings() { @@ -254,7 +315,7 @@ /** * Verification codes for this user. * - * @return VerificationCode[] + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function verificationcodes() { @@ -264,13 +325,20 @@ /** * Wallets this user owns. * - * @return Wallet[] + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function wallets() { return $this->hasMany('App\Wallet'); } + /** + * User password mutator + * + * @param string $password The password in plain text. + * + * @return void + */ public function setPasswordAttribute($password) { if (!empty($password)) { @@ -281,6 +349,13 @@ } } + /** + * User LDAP password mutator + * + * @param string $password The password in plain text. + * + * @return void + */ public function setPasswordLdapAttribute($password) { if (!empty($password)) { diff --git a/src/app/UserSetting.php b/src/app/UserSetting.php --- a/src/app/UserSetting.php +++ b/src/app/UserSetting.php @@ -21,10 +21,14 @@ /** * The user to which this setting belongs. * - * @return \App\User + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function user() { - return $this->belongsTo('\App\User', 'user_id' /* local */, 'id' /* remote */); + return $this->belongsTo( + '\App\User', + 'user_id', /* local */ + 'id' /* remote */ + ); } } diff --git a/src/app/Utils.php b/src/app/Utils.php --- a/src/app/Utils.php +++ b/src/app/Utils.php @@ -49,7 +49,7 @@ */ public static function uuidStr(): string { - return (string) Uuid::uuid4(); + return Uuid::uuid4()->toString(); } private static function combine($input, $r, $index, $data, $i, &$output): void diff --git a/src/app/VerificationCode.php b/src/app/VerificationCode.php --- a/src/app/VerificationCode.php +++ b/src/app/VerificationCode.php @@ -33,7 +33,7 @@ /** * The user to which this setting belongs. * - * @return \App\User + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function user() { diff --git a/src/app/Wallet.php b/src/app/Wallet.php --- a/src/app/Wallet.php +++ b/src/app/Wallet.php @@ -10,16 +10,13 @@ * The eloquent definition of a wallet -- a container with a chunk of change. * * A wallet is owned by an {@link \App\User}. + * + * @property integer $balance */ class Wallet extends Model { use NullableFields; - /** - Our table name for the shall be 'wallet'. - - @var string - */ public $incrementing = false; protected $keyType = 'string'; @@ -53,7 +50,7 @@ */ public function addController(User $user) { - if (!$this->controllers()->get()->contains($user)) { + if (!$this->controllers->contains($user)) { $this->controllers()->save($user); } } @@ -67,7 +64,7 @@ */ public function removeController(User $user) { - if ($this->controllers()->get()->contains($user)) { + if ($this->controllers->contains($user)) { $this->controllers()->detach($user); } } @@ -107,7 +104,7 @@ /** * Controllers of this wallet. * - * @return \App\User[] + * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ public function controllers() { @@ -122,7 +119,7 @@ /** * Entitlements billed to this wallet. * - * @return Entitlement[] + * @return \Illuminate\Database\Eloquent\Relations\HasMany */ public function entitlements() { @@ -132,7 +129,7 @@ /** * The owner of the wallet -- the wallet is in his/her back pocket. * - * @return User + * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ public function owner() { diff --git a/src/composer.json b/src/composer.json --- a/src/composer.json +++ b/src/composer.json @@ -37,6 +37,8 @@ "laravel/dusk": "~5.9.1", "mockery/mockery": "^1.0", "nunomaduro/collision": "^3.0", + "nunomaduro/larastan": "^0.4", + "phpstan/phpstan": "0.11.19", "phpunit/phpunit": "^7.5" }, "config": { diff --git a/src/config/imap.php b/src/config/imap.php --- a/src/config/imap.php +++ b/src/config/imap.php @@ -5,6 +5,5 @@ 'admin_login' => env('IMAP_ADMIN_LOGIN', 'cyrus-admin'), 'admin_password' => env('IMAP_ADMIN_PASSWORD', null), 'verify_peer' => env('IMAP_VERIFY_PEER', true), - 'verify_name' => env('IMAP_VERIFY_NAME', true), - 'cafile' => env('IMAP_CAFILE', null), + 'verify_host' => env('IMAP_VERIFY_HOST', true) ]; diff --git a/src/database/migrations/2019_10_10_095050_create_quota_table.php b/src/database/migrations/2019_10_10_095050_create_quota_table.php --- a/src/database/migrations/2019_10_10_095050_create_quota_table.php +++ b/src/database/migrations/2019_10_10_095050_create_quota_table.php @@ -13,6 +13,7 @@ */ public function up() { + /* Schema::create('quotas', function (Blueprint $table) { $table->bigIncrements('id'); $table->bigInteger('user_id'); @@ -21,6 +22,7 @@ $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade'); }); + */ } /** diff --git a/src/database/migrations/2019_12_10_105942_create_plan_packages_table.php b/src/database/migrations/2019_12_10_105942_create_plan_packages_table.php --- a/src/database/migrations/2019_12_10_105942_create_plan_packages_table.php +++ b/src/database/migrations/2019_12_10_105942_create_plan_packages_table.php @@ -20,6 +20,7 @@ $table->string('plan_id', 36); $table->string('package_id', 36); + $table->integer('qty')->default(1); $table->integer('qty_min')->default(0); $table->integer('qty_max')->default(0); diff --git a/src/database/migrations/2020_02_11_110959_add_deleted_at.php b/src/database/migrations/2020_02_11_110959_add_deleted_at.php new file mode 100644 --- /dev/null +++ b/src/database/migrations/2020_02_11_110959_add_deleted_at.php @@ -0,0 +1,51 @@ +softDeletes(); + } + ); + + Schema::table( + 'users', + function (Blueprint $table) { + $table->softDeletes(); + } + ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table( + 'domains', + function (Blueprint $table) { + $table->dropColumn(['deleted_at']); + } + ); + Schema::table( + 'users', + function (Blueprint $table) { + $table->dropColumn(['deleted_at']); + } + ); + } +} diff --git a/src/database/seeds/DatabaseSeeder.php b/src/database/seeds/DatabaseSeeder.php --- a/src/database/seeds/DatabaseSeeder.php +++ b/src/database/seeds/DatabaseSeeder.php @@ -15,9 +15,9 @@ [ DomainSeeder::class, SkuSeeder::class, - UserSeeder::class, PackageSeeder::class, - PlanSeeder::class + PlanSeeder::class, + UserSeeder::class ] ); } diff --git a/src/database/seeds/DomainSeeder.php b/src/database/seeds/DomainSeeder.php --- a/src/database/seeds/DomainSeeder.php +++ b/src/database/seeds/DomainSeeder.php @@ -35,5 +35,21 @@ ] ); } + + $domains = [ + 'example.com', + 'example.net', + 'example.org' + ]; + + foreach ($domains as $domain) { + Domain::create( + [ + 'namespace' => $domain, + 'status' => Domain::STATUS_CONFIRMED + Domain::STATUS_ACTIVE, + 'type' => Domain::TYPE_EXTERNAL + ] + ); + } } } diff --git a/src/database/seeds/PlanSeeder.php b/src/database/seeds/PlanSeeder.php --- a/src/database/seeds/PlanSeeder.php +++ b/src/database/seeds/PlanSeeder.php @@ -159,8 +159,8 @@ ); $packages = [ - Package::firstOrCreate(['title' => 'kolab']), Package::firstOrCreate(['title' => 'domain-hosting']), + Package::firstOrCreate(['title' => 'kolab']), ]; $plan->packages()->saveMany($packages); diff --git a/src/database/seeds/UserSeeder.php b/src/database/seeds/UserSeeder.php --- a/src/database/seeds/UserSeeder.php +++ b/src/database/seeds/UserSeeder.php @@ -19,12 +19,12 @@ $domain = Domain::create( [ 'namespace' => 'kolab.org', - 'status' => Domain::STATUS_NEW + Domain::STATUS_ACTIVE + Domain::STATUS_CONFIRMED, + 'status' => Domain::STATUS_NEW + Domain::STATUS_ACTIVE + Domain::STATUS_CONFIRMED + Domain::STATUS_VERIFIED, 'type' => Domain::TYPE_EXTERNAL ] ); - $user = User::create( + $john = User::create( [ 'name' => "John Doe", 'email' => 'john@kolab.org', @@ -33,38 +33,43 @@ ] ); - $user_wallets = $user->wallets()->get(); - - $sku_domain = Sku::where('title', 'domain')->first(); - $sku_mailbox = Sku::where('title', 'mailbox')->first(); - - $entitlement_domain = Entitlement::create( + $john->setSettings( [ - 'owner_id' => $user->id, - 'wallet_id' => $user_wallets[0]->id, - 'sku_id' => $sku_domain->id, - 'entitleable_id' => $domain->id, - 'entitleable_type' => Domain::class + "first_name" => "John", + "last_name" => "Doe", + "currency" => "USD", + "country" => "US" ] ); - $entitlement_mailbox = Entitlement::create( + $user_wallets = $john->wallets()->get(); + + $package_domain = \App\Package::where('title', 'domain-hosting')->first(); + $package_kolab = \App\Package::where('title', 'kolab')->first(); + + $domain->assignPackage($package_domain, $john); + $john->assignPackage($package_kolab); + + $jack = User::create( [ - 'owner_id' => $user->id, - 'wallet_id' => $user_wallets[0]->id, - 'sku_id' => $sku_mailbox->id, - 'entitleable_id' => $user->id, - 'entitleable_type' => User::class + 'name' => "Jack Daniels", + 'email' => 'jack@kolab.org', + 'password' => 'simple123', + 'email_verified_at' => now() ] ); - $user->setSettings( + $jack->setSettings( [ - "first_name" => "John", - "last_name" => "Doe", + "first_name" => "Jack", + "last_name" => "Daniels", "currency" => "USD", "country" => "US" ] ); + + $john->assignPackage($package_kolab, $jack); + + factory(User::class, 10)->create(); } } diff --git a/src/package-lock.json b/src/package-lock.json --- a/src/package-lock.json +++ b/src/package-lock.json @@ -855,6 +855,12 @@ "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", "dev": true }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -884,12 +890,44 @@ "integrity": "sha512-9fq4jZVhPNW8r+UYKnxF1e2HkDWOWKM5bC2/7c9wPV835I0aOrVbS/Hw/pWPk2uKrNXQqg9Z959Kz+IYDd5p3w==", "dev": true }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "@types/q": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.2.tgz", "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "dev": true }, + "@types/unist": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.3.tgz", + "integrity": "sha512-FvUupuM3rlRsRtCN+fDudtmytGO6iHJuuRKS1Ss0pG5z8oX0diNEw94UEL7hgDbpN94rgaK5R7sWm6RrSkZuAQ==", + "dev": true + }, + "@types/vfile": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/vfile/-/vfile-3.0.2.tgz", + "integrity": "sha512-b3nLFGaGkJ9rzOcuXRfHkZMdjsawuDD0ENL9fzTophtBg8FJHSGbH7daXkEpcwy3v7Xol3pAvsmlYyFhR4pqJw==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/unist": "*", + "@types/vfile-message": "*" + } + }, + "@types/vfile-message": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/vfile-message/-/vfile-message-2.0.0.tgz", + "integrity": "sha512-GpTIuDpb9u4zIO165fUy9+fXcULdD8HFRNli04GehoMVbeNq7D6OBnqSmg3lxZnC+UvgUhEWKxdKiwYUkGltIw==", + "dev": true, + "requires": { + "vfile-message": "*" + } + }, "@vue/component-compiler-utils": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@vue/component-compiler-utils/-/component-compiler-utils-3.0.0.tgz", @@ -1135,6 +1173,12 @@ "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", "dev": true }, + "acorn-jsx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "dev": true + }, "adjust-sourcemap-loader": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-1.2.0.tgz", @@ -1217,6 +1261,15 @@ "integrity": "sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA==", "dev": true }, + "ansi-escapes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, "ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", @@ -1292,6 +1345,12 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=", + "dev": true + }, "array-flatten": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-2.1.2.tgz", @@ -1375,6 +1434,12 @@ "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=", "dev": true }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -1503,6 +1568,12 @@ "object.assign": "^4.1.0" } }, + "bail": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.5.tgz", + "integrity": "sha512-xFbRxM1tahm08yHBP16MMjVUAvDaBMD38zsM9EMAUN61omwLmKlOpB/Zku5QkjZ8TZ4vn53pj+t518cH0S03RQ==", + "dev": true + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1656,9 +1727,9 @@ "dev": true }, "bootstrap": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.3.1.tgz", - "integrity": "sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.4.1.tgz", + "integrity": "sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==", "dev": true }, "brace-expansion": { @@ -1921,6 +1992,25 @@ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true }, + "camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", + "dev": true, + "requires": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -1939,6 +2029,12 @@ "integrity": "sha512-vrMcvSuMz16YY6GSVZ0dWDTJP8jqk3iFQ/Aq5iqblPwxSVVZI+zxDyTX0VPqtQsDnfdrBDcsmhgTEOh5R8Lbpw==", "dev": true }, + "ccount": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-1.0.5.tgz", + "integrity": "sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw==", + "dev": true + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1950,6 +2046,36 @@ "supports-color": "^5.3.0" } }, + "character-entities": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz", + "integrity": "sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==", + "dev": true + }, + "character-entities-html4": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-1.1.4.tgz", + "integrity": "sha512-HRcDxZuZqMx3/a+qrzxdBKBPUpxWEq9xw2OPZ3a/174ihfrQKVsFhqtthBInFy1zZ9GgZyFXOatNujm8M+El3g==", + "dev": true + }, + "character-entities-legacy": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz", + "integrity": "sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==", + "dev": true + }, + "character-reference-invalid": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz", + "integrity": "sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==", + "dev": true + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, "charenc": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", @@ -2041,6 +2167,21 @@ } } }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", + "dev": true + }, "cliui": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", @@ -2091,6 +2232,15 @@ "shallow-clone": "^3.0.0" } }, + "clone-regexp": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clone-regexp/-/clone-regexp-2.2.0.tgz", + "integrity": "sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==", + "dev": true, + "requires": { + "is-regexp": "^2.0.0" + } + }, "coa": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", @@ -2108,6 +2258,12 @@ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, + "collapse-white-space": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.6.tgz", + "integrity": "sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ==", + "dev": true + }, "collect.js": { "version": "4.16.6", "resolved": "https://registry.npmjs.org/collect.js/-/collect.js-4.16.6.tgz", @@ -2721,6 +2877,15 @@ } } }, + "currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", + "dev": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", @@ -2754,6 +2919,24 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, + "decamelize-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", + "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", + "dev": true, + "requires": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=", + "dev": true + } + } + }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", @@ -2774,6 +2957,12 @@ "regexp.prototype.flags": "^1.2.0" } }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, "deepmerge": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", @@ -2958,6 +3147,15 @@ "buffer-indexof": "^1.0.0" } }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "dom-serializer": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.1.tgz", @@ -2988,6 +3186,15 @@ "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==", "dev": true }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dev": true, + "requires": { + "domelementtype": "1" + } + }, "domutils": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", @@ -3180,6 +3387,149 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "eslint": { + "version": "6.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", + "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^12.1.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.3", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "12.3.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-12.3.0.tgz", + "integrity": "sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "eslint-plugin-vue": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-6.1.2.tgz", + "integrity": "sha512-M75oAB+2a/LNkLKRbeEaS07EjzjIUaV7/hYoHAfRFeeF8ZMmCbahUn8nQLsLP85mkar24+zDU3QW2iT1JRsACw==", + "dev": true, + "requires": { + "semver": "^5.6.0", + "vue-eslint-parser": "^7.0.0" + } + }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -3190,12 +3540,55 @@ "estraverse": "^4.1.1" } }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", + "dev": true, + "requires": { + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "dev": true + } + } + }, "esprima": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", "dev": true }, + "esquery": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", + "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", @@ -3269,6 +3662,15 @@ "strip-eof": "^1.0.0" } }, + "execall": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/execall/-/execall-2.0.0.tgz", + "integrity": "sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==", + "dev": true, + "requires": { + "clone-regexp": "^2.1.0" + } + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -3383,6 +3785,12 @@ } } }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", @@ -3393,6 +3801,17 @@ "is-extendable": "^1.0.1" } }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, "extglob": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", @@ -3502,6 +3921,12 @@ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", "dev": true }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -3523,6 +3948,24 @@ "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", "dev": true }, + "figures": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", + "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, "file-loader": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/file-loader/-/file-loader-2.0.0.tgz", @@ -3639,6 +4082,34 @@ "resolve-dir": "^1.0.1" } }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true + }, "flush-write-stream": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", @@ -4316,14 +4787,26 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, - "get-stream": { - "version": "4.1.0", + "get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "dev": true + }, + "get-stream": { + "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", "dev": true, @@ -4442,6 +4925,12 @@ } } }, + "globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha1-L0SUrIkZ43Z8XLtpHp9GMyQoXUM=", + "dev": true + }, "globs": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/globs/-/globs-0.1.4.tgz", @@ -4451,6 +4940,23 @@ "glob": "^7.1.1" } }, + "gonzales-pe": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/gonzales-pe/-/gonzales-pe-4.2.4.tgz", + "integrity": "sha512-v0Ts/8IsSbh9n1OJRnSfa7Nlxi4AkXIsWB6vPept8FDbL4bXn3FNuxjYtO/nmBGu7GDkL9MFeGebeSu6l55EPQ==", + "dev": true, + "requires": { + "minimist": "1.1.x" + }, + "dependencies": { + "minimist": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.1.3.tgz", + "integrity": "sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag=", + "dev": true + } + } + }, "graceful-fs": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", @@ -4595,6 +5101,12 @@ "parse-passwd": "^1.0.0" } }, + "hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "dev": true + }, "hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -4659,6 +5171,45 @@ "uglify-js": "3.4.x" } }, + "html-tags": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.1.0.tgz", + "integrity": "sha512-1qYz89hW3lFDEazhjW0yVAV87lw8lVkrJocr72XmBkMKsoSVJCQx3W8BXsC7hO2qAt8BoVjYjtAcZ9perqGnNg==", + "dev": true + }, + "htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dev": true, + "requires": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + }, + "dependencies": { + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==", + "dev": true + }, + "readable-stream": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.5.0.tgz", + "integrity": "sha512-gSz026xs2LfxBPudDuI41V1lka8cxg64E66SGe78zJlsUofOg/yqwezdIcdfwik6B4h8LFmWPA9ef9X3FiNFLA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", @@ -4852,6 +5403,12 @@ "resolve-from": "^3.0.0" } }, + "import-lazy": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-4.0.0.tgz", + "integrity": "sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==", + "dev": true + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -4868,6 +5425,12 @@ "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", "dev": true }, + "indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", + "dev": true + }, "indexes-of": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz", @@ -4902,6 +5465,86 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, + "inquirer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", + "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.2", + "cli-cursor": "^3.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.15", + "mute-stream": "0.0.8", + "run-async": "^2.2.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + } + } + }, "internal-ip": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz", @@ -4983,6 +5626,28 @@ } } }, + "is-alphabetical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz", + "integrity": "sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==", + "dev": true + }, + "is-alphanumeric": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-alphanumeric/-/is-alphanumeric-1.0.0.tgz", + "integrity": "sha1-Spzvcdr0wAHB2B1j0UDPU/1oifQ=", + "dev": true + }, + "is-alphanumerical": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz", + "integrity": "sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==", + "dev": true, + "requires": { + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0" + } + }, "is-arguments": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.0.4.tgz", @@ -5062,6 +5727,12 @@ "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", "dev": true }, + "is-decimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz", + "integrity": "sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==", + "dev": true + }, "is-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", @@ -5117,6 +5788,12 @@ "is-extglob": "^2.1.1" } }, + "is-hexadecimal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz", + "integrity": "sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==", + "dev": true + }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", @@ -5173,6 +5850,12 @@ "path-is-inside": "^1.0.2" } }, + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=", + "dev": true + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -5182,6 +5865,12 @@ "isobject": "^3.0.1" } }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", + "dev": true + }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", @@ -5191,6 +5880,12 @@ "has": "^1.0.1" } }, + "is-regexp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-2.1.0.tgz", + "integrity": "sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==", + "dev": true + }, "is-resolvable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", @@ -5221,12 +5916,30 @@ "has-symbols": "^1.0.0" } }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true + }, + "is-whitespace-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-whitespace-character/-/is-whitespace-character-1.0.4.tgz", + "integrity": "sha512-SDweEzfIZM0SJV0EUga669UTKlmL0Pq8Lno0QDQsPnvECB3IM2aP0gdx5TrU0A01MAPfViaZiI2V1QMZLaKK5w==", + "dev": true + }, "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", "dev": true }, + "is-word-character": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-word-character/-/is-word-character-1.0.4.tgz", + "integrity": "sha512-5SMO8RVennx3nZrqtKwCGyyetPE9VDba5ugvKLaD4KopPG5kR4mQ7tNt/r7feL5yt5h3lpuBbIUmCOG2eSzXHA==", + "dev": true + }, "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", @@ -5305,6 +6018,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json3": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/json3/-/json3-3.3.3.tgz", @@ -5341,6 +6060,12 @@ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, + "known-css-properties": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.17.0.tgz", + "integrity": "sha512-Vi3nxDGMm/z+lAaCjvAR1u+7fiv+sG6gU/iYDj5QOF8h76ytK9EW/EKfF0NeTyiGBi8Jy6Hklty/vxISrLox3w==", + "dev": true + }, "laravel-mix": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/laravel-mix/-/laravel-mix-4.1.4.tgz", @@ -5406,6 +6131,48 @@ "invert-kv": "^2.0.0" } }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -5548,12 +6315,27 @@ "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", "dev": true }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + } + }, "loglevel": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.4.tgz", "integrity": "sha512-p0b6mOGKcGa+7nnmKbpzR6qloPbrgLcnio++E+14Vo/XffOGwZtRpUhr8dTH/x2oCMmEoIU0Zwm3ZauhvYD17g==", "dev": true }, + "longest-streak": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-2.0.4.tgz", + "integrity": "sha512-vM6rUVCVUJJt33bnmHiZEvr7wPT78ztX7rojL+LW51bHtLh6HTjx84LA5W4+oa6aKEJA7jJu5LR6vQRBpA5DVg==", + "dev": true + }, "loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -5563,6 +6345,16 @@ "js-tokens": "^3.0.0 || ^4.0.0" } }, + "loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", + "dev": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, "lower-case": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", @@ -5609,6 +6401,12 @@ "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", "dev": true }, + "map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=", + "dev": true + }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", @@ -5618,6 +6416,24 @@ "object-visit": "^1.0.0" } }, + "markdown-escapes": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/markdown-escapes/-/markdown-escapes-1.0.4.tgz", + "integrity": "sha512-8z4efJYk43E0upd0NbVXwgSTQs6cT3T06etieCMEg7dRbzCbxUCK/GHlX8mhHRDcp+OLlHkPKsvqQTCvsRl2cg==", + "dev": true + }, + "markdown-table": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", + "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", + "dev": true + }, + "mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true + }, "md5": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", @@ -5648,6 +6464,15 @@ "safe-buffer": "^5.1.2" } }, + "mdast-util-compact": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz", + "integrity": "sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==", + "dev": true, + "requires": { + "unist-util-visit": "^1.1.0" + } + }, "mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -5681,6 +6506,40 @@ "readable-stream": "^2.0.1" } }, + "meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dev": true, + "requires": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", @@ -5801,6 +6660,16 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, + "minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dev": true, + "requires": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + } + }, "mississippi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", @@ -5882,6 +6751,12 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true + }, "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", @@ -5908,6 +6783,12 @@ "to-regex": "^3.0.1" } }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -6002,6 +6883,18 @@ "semver": "^5.3.0" } }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, "normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -6014,6 +6907,12 @@ "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=", "dev": true }, + "normalize-selector": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/normalize-selector/-/normalize-selector-0.2.0.tgz", + "integrity": "sha1-0LFF62kRicY6eNIB3E/bEpPvDAM=", + "dev": true + }, "normalize-url": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", @@ -6208,6 +7107,15 @@ "wrappy": "1" } }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, "opn": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", @@ -6227,6 +7135,20 @@ "last-call-webpack-plugin": "^3.0.0" } }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, "original": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/original/-/original-1.0.2.tgz", @@ -6253,6 +7175,12 @@ "mem": "^4.0.0" } }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -6342,6 +7270,23 @@ "no-case": "^2.2.0" } }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + }, + "dependencies": { + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + } + } + }, "parse-asn1": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", @@ -6356,6 +7301,20 @@ "safe-buffer": "^5.1.1" } }, + "parse-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-1.2.2.tgz", + "integrity": "sha512-NzfpbxW/NPrzZ/yYSoQxyqUZMZXIdCfE0OIN4ESsnptHJECoUk3FZktxNuzQf4tjt5UEopnxpYJbvYuxIFDdsg==", + "dev": true, + "requires": { + "character-entities": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "character-reference-invalid": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -6462,6 +7421,12 @@ "sha.js": "^2.4.8" } }, + "picomatch": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", + "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==", + "dev": true + }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", @@ -6655,17 +7620,44 @@ "postcss": "^7.0.0" } }, - "postcss-load-config": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", - "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "postcss-html": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-html/-/postcss-html-0.36.0.tgz", + "integrity": "sha512-HeiOxGcuwID0AFsNAL0ox3mW6MHH5cstWN1Z3Y+n6H+g12ih7LHdYxWwEA/QmrebctLjo79xz9ouK3MroHwOJw==", "dev": true, "requires": { - "cosmiconfig": "^5.0.0", - "import-cwd": "^2.0.0" + "htmlparser2": "^3.10.0" } }, - "postcss-loader": { + "postcss-jsx": { + "version": "0.36.4", + "resolved": "https://registry.npmjs.org/postcss-jsx/-/postcss-jsx-0.36.4.tgz", + "integrity": "sha512-jwO/7qWUvYuWYnpOb0+4bIIgJt7003pgU3P6nETBLaOyBXuTD55ho21xnals5nBrlpTIFodyd3/jBi6UO3dHvA==", + "dev": true, + "requires": { + "@babel/core": ">=7.2.2" + } + }, + "postcss-less": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-less/-/postcss-less-3.1.4.tgz", + "integrity": "sha512-7TvleQWNM2QLcHqvudt3VYjULVB49uiW6XzEUFmvwHzvsOEF5MwBrIXZDJQvJNFGjJQTzSzZnDoCJ8h/ljyGXA==", + "dev": true, + "requires": { + "postcss": "^7.0.14" + } + }, + "postcss-load-config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-2.1.0.tgz", + "integrity": "sha512-4pV3JJVPLd5+RueiVVB+gFOAa7GWc25XQcMp86Zexzke69mKf6Nx9LRcQywdz7yZI9n1udOxmLuAwTBypypF8Q==", + "dev": true, + "requires": { + "cosmiconfig": "^5.0.0", + "import-cwd": "^2.0.0" + } + }, + "postcss-loader": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", @@ -6690,6 +7682,22 @@ } } }, + "postcss-markdown": { + "version": "0.36.0", + "resolved": "https://registry.npmjs.org/postcss-markdown/-/postcss-markdown-0.36.0.tgz", + "integrity": "sha512-rl7fs1r/LNSB2bWRhyZ+lM/0bwKv9fhl38/06gF6mKMo/NPnp55+K1dSTosSVjFZc0e1ppBlu+WT91ba0PMBfQ==", + "dev": true, + "requires": { + "remark": "^10.0.1", + "unist-util-find-all-after": "^1.0.2" + } + }, + "postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha1-J7Ocb02U+Bsac7j3Y1HGCeXO8kQ=", + "dev": true + }, "postcss-merge-longhand": { "version": "4.0.11", "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz", @@ -7151,6 +8159,119 @@ } } }, + "postcss-reporter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-6.0.1.tgz", + "integrity": "sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "lodash": "^4.17.11", + "log-symbols": "^2.2.0", + "postcss": "^7.0.7" + }, + "dependencies": { + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dev": true, + "requires": { + "chalk": "^2.0.1" + } + } + } + }, + "postcss-resolve-nested-selector": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz", + "integrity": "sha1-Kcy8fDfe36wwTp//C/FZaz9qDk4=", + "dev": true + }, + "postcss-safe-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-4.0.2.tgz", + "integrity": "sha512-Uw6ekxSWNLCPesSv/cmqf2bY/77z11O7jZGPax3ycZMFU/oi2DMH9i89AdHc1tRwFg/arFoEwX0IS3LCUxJh1g==", + "dev": true, + "requires": { + "postcss": "^7.0.26" + }, + "dependencies": { + "postcss": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", + "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-sass": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/postcss-sass/-/postcss-sass-0.4.2.tgz", + "integrity": "sha512-hcRgnd91OQ6Ot9R90PE/khUDCJHG8Uxxd3F7Y0+9VHjBiJgNv7sK5FxyHMCBtoLmmkzVbSj3M3OlqUfLJpq0CQ==", + "dev": true, + "requires": { + "gonzales-pe": "^4.2.4", + "postcss": "^7.0.21" + }, + "dependencies": { + "postcss": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", + "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-scss": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-2.0.0.tgz", + "integrity": "sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + } + }, "postcss-selector-parser": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz", @@ -7190,6 +8311,12 @@ } } }, + "postcss-syntax": { + "version": "0.36.2", + "resolved": "https://registry.npmjs.org/postcss-syntax/-/postcss-syntax-0.36.2.tgz", + "integrity": "sha512-nBRg/i7E3SOHWxF3PpF5WnJM/jQ1YpY9000OaVXlAQj6Zp/kIqJxEDWIZ67tAd7NLuk7zqN4yqe9nc0oNAOs1w==", + "dev": true + }, "postcss-unique-selectors": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", @@ -7207,6 +8334,12 @@ "integrity": "sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ==", "dev": true }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, "prettier": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.16.3.tgz", @@ -7231,6 +8364,12 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -7342,6 +8481,12 @@ "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", "dev": true }, + "quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=", + "dev": true + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -7387,6 +8532,72 @@ } } }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + } + } + }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -7425,6 +8636,16 @@ "source-map": "~0.5.0" } }, + "redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", + "dev": true, + "requires": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + } + }, "regenerate": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", @@ -7486,6 +8707,12 @@ "define-properties": "^1.1.2" } }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, "regexpu-core": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.6.0.tgz", @@ -7529,6 +8756,62 @@ "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", "dev": true }, + "remark": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/remark/-/remark-10.0.1.tgz", + "integrity": "sha512-E6lMuoLIy2TyiokHprMjcWNJ5UxfGQjaMSMhV+f4idM625UjjK4j798+gPs5mfjzDE6vL0oFKVeZM6gZVSVrzQ==", + "dev": true, + "requires": { + "remark-parse": "^6.0.0", + "remark-stringify": "^6.0.0", + "unified": "^7.0.0" + } + }, + "remark-parse": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-6.0.3.tgz", + "integrity": "sha512-QbDXWN4HfKTUC0hHa4teU463KclLAnwpn/FBn87j9cKYJWWawbiLgMfP2Q4XwhxxuuuOxHlw+pSN0OKuJwyVvg==", + "dev": true, + "requires": { + "collapse-white-space": "^1.0.2", + "is-alphabetical": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "is-word-character": "^1.0.0", + "markdown-escapes": "^1.0.0", + "parse-entities": "^1.1.0", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "trim": "0.0.1", + "trim-trailing-lines": "^1.0.0", + "unherit": "^1.0.4", + "unist-util-remove-position": "^1.0.0", + "vfile-location": "^2.0.0", + "xtend": "^4.0.1" + } + }, + "remark-stringify": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-6.0.4.tgz", + "integrity": "sha512-eRWGdEPMVudijE/psbIDNcnJLRVx3xhfuEsTDGgH4GsFF91dVhw5nhmnBppafJ7+NWINW6C7ZwWbi30ImJzqWg==", + "dev": true, + "requires": { + "ccount": "^1.0.0", + "is-alphanumeric": "^1.0.0", + "is-decimal": "^1.0.0", + "is-whitespace-character": "^1.0.0", + "longest-streak": "^2.0.1", + "markdown-escapes": "^1.0.0", + "markdown-table": "^1.1.0", + "mdast-util-compact": "^1.0.0", + "parse-entities": "^1.0.2", + "repeat-string": "^1.5.4", + "state-toggle": "^1.0.0", + "stringify-entities": "^1.0.1", + "unherit": "^1.0.4", + "xtend": "^4.0.1" + } + }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -7649,6 +8932,16 @@ } } }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", @@ -7716,6 +9009,15 @@ "inherits": "^2.0.1" } }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", @@ -7725,6 +9027,15 @@ "aproba": "^1.1.1" } }, + "rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -8043,6 +9354,17 @@ "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + } + }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", @@ -8270,6 +9592,38 @@ "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, "spdy": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.1.tgz", @@ -8342,6 +9696,12 @@ } } }, + "specificity": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/specificity/-/specificity-0.4.1.tgz", + "integrity": "sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==", + "dev": true + }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", @@ -8378,6 +9738,12 @@ "integrity": "sha512-to7oADIniaYwS3MhtCa/sQhrxidCCQiF/qp4/m5iN3ipf0Y7Xlri0f6eG29r08aL7JYl8n32AF3Q5GYBZ7K8vw==", "dev": true }, + "state-toggle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.3.tgz", + "integrity": "sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ==", + "dev": true + }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -8500,6 +9866,18 @@ "safe-buffer": "~5.1.0" } }, + "stringify-entities": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-1.3.2.tgz", + "integrity": "sha512-nrBAQClJAPN2p+uGCVJRPIPakKeKWZ9GtBCmormE7pWOSlHat7+x5A8gx85M7HM5Dt0BP3pP5RhVW77WdbJJ3A==", + "dev": true, + "requires": { + "character-entities-html4": "^1.0.0", + "character-entities-legacy": "^1.0.0", + "is-alphanumerical": "^1.0.0", + "is-hexadecimal": "^1.0.0" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -8509,12 +9887,30 @@ "ansi-regex": "^2.0.0" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", + "dev": true + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true + }, "style-loader": { "version": "0.23.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", @@ -8538,6 +9934,12 @@ } } }, + "style-search": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/style-search/-/style-search-0.1.0.tgz", + "integrity": "sha1-eVjHk+R+MuB9K1yv5cC/jhLneQI=", + "dev": true + }, "stylehacks": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz", @@ -8562,35 +9964,567 @@ } } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "svgo": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.0.tgz", - "integrity": "sha512-MLfUA6O+qauLDbym+mMZgtXCGRfIxyQoeH6IKVcFslyODEe/ElJNwr0FohQ3xG4C6HK6bk3KYPPXwHVJk3V5NQ==", + "stylelint": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-12.0.1.tgz", + "integrity": "sha512-1mn39pqZiC/e8KUPoRMc1WMM83Upb2ILaSGxkCvKxALHutEOs2txcPQocJiXdO4Zx4FY4prGqjlkwrbthAxqig==", "dev": true, "requires": { - "chalk": "^2.4.1", - "coa": "^2.0.2", - "css-select": "^2.0.0", - "css-select-base-adapter": "^0.1.1", - "css-tree": "1.0.0-alpha.33", - "csso": "^3.5.1", - "js-yaml": "^3.13.1", - "mkdirp": "~0.5.1", - "object.values": "^1.1.0", - "sax": "~1.2.4", - "stable": "^0.1.8", - "unquote": "~1.1.1", - "util.promisify": "~1.0.0" - } + "autoprefixer": "^9.7.1", + "balanced-match": "^1.0.0", + "chalk": "^3.0.0", + "cosmiconfig": "^6.0.0", + "debug": "^4.1.1", + "execall": "^2.0.0", + "file-entry-cache": "^5.0.1", + "get-stdin": "^7.0.0", + "global-modules": "^2.0.0", + "globby": "^9.2.0", + "globjoin": "^0.1.4", + "html-tags": "^3.1.0", + "ignore": "^5.1.4", + "import-lazy": "^4.0.0", + "imurmurhash": "^0.1.4", + "known-css-properties": "^0.17.0", + "leven": "^3.1.0", + "lodash": "^4.17.15", + "log-symbols": "^3.0.0", + "mathml-tag-names": "^2.1.1", + "meow": "^5.0.0", + "micromatch": "^4.0.2", + "normalize-selector": "^0.2.0", + "postcss": "^7.0.21", + "postcss-html": "^0.36.0", + "postcss-jsx": "^0.36.3", + "postcss-less": "^3.1.4", + "postcss-markdown": "^0.36.0", + "postcss-media-query-parser": "^0.2.3", + "postcss-reporter": "^6.0.1", + "postcss-resolve-nested-selector": "^0.1.1", + "postcss-safe-parser": "^4.0.1", + "postcss-sass": "^0.4.2", + "postcss-scss": "^2.0.0", + "postcss-selector-parser": "^3.1.0", + "postcss-syntax": "^0.36.2", + "postcss-value-parser": "^4.0.2", + "resolve-from": "^5.0.0", + "slash": "^3.0.0", + "specificity": "^0.4.1", + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "style-search": "^0.1.0", + "sugarss": "^2.0.0", + "svg-tags": "^1.0.0", + "table": "^5.4.6", + "v8-compile-cache": "^2.1.0", + "write-file-atomic": "^3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "autoprefixer": { + "version": "9.7.4", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.7.4.tgz", + "integrity": "sha512-g0Ya30YrMBAEZk60lp+qfX5YQllG+S5W3GYCFvyHTvhOki0AEQJLPEcIuGRsqVwLi8FvXPVtwTGhfr38hVpm0g==", + "dev": true, + "requires": { + "browserslist": "^4.8.3", + "caniuse-lite": "^1.0.30001020", + "chalk": "^2.4.2", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.26", + "postcss-value-parser": "^4.0.2" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.8.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.6.tgz", + "integrity": "sha512-ZHao85gf0eZ0ESxLfCp73GG9O/VTytYDIkIiZDlURppLTI9wErSM/5yAKEq6rcUdxBLjMELmrYUJGg5sxGKMHg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001023", + "electron-to-chromium": "^1.3.341", + "node-releases": "^1.1.47" + } + }, + "caniuse-lite": { + "version": "1.0.30001027", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001027.tgz", + "integrity": "sha512-7xvKeErvXZFtUItTHgNtLgS9RJpVnwBlWX8jSo/BO8VsF6deszemZSkJJJA1KOKrXuzZH4WALpAJdq5EyfgMLg==", + "dev": true + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "dev": true, + "requires": { + "path-type": "^3.0.0" + }, + "dependencies": { + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "electron-to-chromium": { + "version": "1.3.349", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.349.tgz", + "integrity": "sha512-uEb2zs6EJ6OZIqaMsCSliYVgzE/f7/s1fLWqtvRtHg/v5KBF2xds974fUnyatfxIDgkqzQVwFtam5KExqywx0Q==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "globby": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "dev": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + }, + "dependencies": { + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node-releases": { + "version": "1.1.49", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.49.tgz", + "integrity": "sha512-xH8t0LS0disN0mtRCh+eByxFPie+msJUBL/lJDBuap53QGiYPa9joh83K4pCZgWJ+2L4b9h88vCVdXQ60NO2bg==", + "dev": true, + "requires": { + "semver": "^6.3.0" + } + }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "postcss": { + "version": "7.0.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.26.tgz", + "integrity": "sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "source-map": "^0.6.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.1" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + } + } + }, + "stylelint-config-recommended": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-3.0.0.tgz", + "integrity": "sha512-F6yTRuc06xr1h5Qw/ykb2LuFynJ2IxkKfCMf+1xqPffkxh0S09Zc902XCffcsw/XMFq/OzQ1w54fLIDtmRNHnQ==", + "dev": true + }, + "stylelint-config-standard": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-19.0.0.tgz", + "integrity": "sha512-VvcODsL1PryzpYteWZo2YaA5vU/pWfjqBpOvmeA8iB2MteZ/ZhI1O4hnrWMidsS4vmEJpKtjdhLdfGJmmZm6Cg==", + "dev": true, + "requires": { + "stylelint-config-recommended": "^3.0.0" + } + }, + "sugarss": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sugarss/-/sugarss-2.0.0.tgz", + "integrity": "sha512-WfxjozUk0UVA4jm+U1d736AUpzSrNsQcIbyOkoE364GrtWmIrFdk5lksEupgWMD4VaT/0kVx1dobpiDumSgmJQ==", + "dev": true, + "requires": { + "postcss": "^7.0.2" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", + "dev": true + }, + "svgo": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.0.tgz", + "integrity": "sha512-MLfUA6O+qauLDbym+mMZgtXCGRfIxyQoeH6IKVcFslyODEe/ElJNwr0FohQ3xG4C6HK6bk3KYPPXwHVJk3V5NQ==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "coa": "^2.0.2", + "css-select": "^2.0.0", + "css-select-base-adapter": "^0.1.1", + "css-tree": "1.0.0-alpha.33", + "csso": "^3.5.1", + "js-yaml": "^3.13.1", + "mkdirp": "~0.5.1", + "object.values": "^1.1.0", + "sax": "~1.2.4", + "stable": "^0.1.8", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" + } + }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } }, "tapable": { "version": "1.1.3", @@ -8676,6 +10610,12 @@ } } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -8713,6 +10653,15 @@ "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", "dev": true }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, "to-arraybuffer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", @@ -8779,12 +10728,36 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", "dev": true }, + "trim": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", + "integrity": "sha1-WFhUf2spB1fulczMZm+1AITEYN0=", + "dev": true + }, + "trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=", + "dev": true + }, "trim-right": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", "dev": true }, + "trim-trailing-lines": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-1.1.3.tgz", + "integrity": "sha512-4ku0mmjXifQcTVfYDfR5lpgV7zVqPg6zV9rdZmwOPqq0+Zq19xDqEgagqVbc4pOOShbncuAOIs59R3+3gcF3ZA==", + "dev": true + }, + "trough": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/trough/-/trough-1.0.5.tgz", + "integrity": "sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==", + "dev": true + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -8797,6 +10770,21 @@ "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", "dev": true }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true + }, "type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -8813,6 +10801,15 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "requires": { + "is-typedarray": "^1.0.0" + } + }, "uglify-js": { "version": "3.4.10", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", @@ -8837,6 +10834,16 @@ } } }, + "unherit": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/unherit/-/unherit-1.1.3.tgz", + "integrity": "sha512-Ft16BJcnapDKp0+J/rqFC3Rrk6Y/Ng4nzsC028k2jdDII/rdZ7Wd3pPT/6+vIIxRagwRc9K0IUX0Ra4fKvw+WQ==", + "dev": true, + "requires": { + "inherits": "^2.0.0", + "xtend": "^4.0.0" + } + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -8865,6 +10872,22 @@ "integrity": "sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw==", "dev": true }, + "unified": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/unified/-/unified-7.1.0.tgz", + "integrity": "sha512-lbk82UOIGuCEsZhPj8rNAkXSDXd6p0QLzIuSsCdxrqnqU56St4eyOB+AlXsVgVeRmetPTYydIuvFfpDIed8mqw==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "@types/vfile": "^3.0.0", + "bail": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^1.1.0", + "trough": "^1.0.0", + "vfile": "^3.0.0", + "x-is-string": "^0.1.0" + } + }, "union-value": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", @@ -8915,6 +10938,57 @@ "imurmurhash": "^0.1.4" } }, + "unist-util-find-all-after": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/unist-util-find-all-after/-/unist-util-find-all-after-1.0.5.tgz", + "integrity": "sha512-lWgIc3rrTMTlK1Y0hEuL+k+ApzFk78h+lsaa2gHf63Gp5Ww+mt11huDniuaoq1H+XMK2lIIjjPkncxXcDp3QDw==", + "dev": true, + "requires": { + "unist-util-is": "^3.0.0" + } + }, + "unist-util-is": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-3.0.0.tgz", + "integrity": "sha512-sVZZX3+kspVNmLWBPAB6r+7D9ZgAFPNWm66f7YNb420RlQSbn+n8rG8dGZSkrER7ZIXGQYNm5pqC3v3HopH24A==", + "dev": true + }, + "unist-util-remove-position": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", + "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", + "dev": true, + "requires": { + "unist-util-visit": "^1.1.0" + } + }, + "unist-util-stringify-position": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-2.0.2.tgz", + "integrity": "sha512-nK5n8OGhZ7ZgUwoUbL8uiVRwAbZyzBsB/Ddrlbu6jwwubFza4oe15KlyEaLNMXQW1svOQq4xesUeqA85YrIUQA==", + "dev": true, + "requires": { + "@types/unist": "^2.0.2" + } + }, + "unist-util-visit": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-1.4.1.tgz", + "integrity": "sha512-AvGNk7Bb//EmJZyhtRUnNMEpId/AZ5Ph/KUpTI09WHQuDZHKovQ1oEv3mfmKpWKtoMzyMC4GLBm1Zy5k12fjIw==", + "dev": true, + "requires": { + "unist-util-visit-parents": "^2.0.0" + } + }, + "unist-util-visit-parents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-2.1.2.tgz", + "integrity": "sha512-DyN5vD4NE3aSeB+PXYNKxzGsfocxp6asDc2XXE3b0ekO2BaRUpBicbbUygfSvYfUz1IkmjFR1YF7dPklraMZ2g==", + "dev": true, + "requires": { + "unist-util-is": "^3.0.0" + } + }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -9085,6 +11159,16 @@ "integrity": "sha512-CNmdbwQMBjwr9Gsmohvm0pbL954tJrNzf6gWL3K+QMQf00PF7ERGrEiLgjuU3mKreLC2MeGhUsNV9ybTbLgd3w==", "dev": true }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -9097,6 +11181,51 @@ "integrity": "sha512-fOi47nsJP5Wqefa43kyWSg80qF+Q3XA6MUkgi7Hp1HQaKDQW4cQrK2D0P7mmbFtsV1N89am55Yru/nyEwRubcw==", "dev": true }, + "vfile": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-3.0.1.tgz", + "integrity": "sha512-y7Y3gH9BsUSdD4KzHsuMaCzRjglXN0W2EcMf0gpvu6+SbsGhMje7xDc8AEoeXy6mIwCKMI6BkjMsRjzQbhMEjQ==", + "dev": true, + "requires": { + "is-buffer": "^2.0.0", + "replace-ext": "1.0.0", + "unist-util-stringify-position": "^1.0.0", + "vfile-message": "^1.0.0" + }, + "dependencies": { + "unist-util-stringify-position": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-1.1.2.tgz", + "integrity": "sha512-pNCVrk64LZv1kElr0N1wPiHEUoXNVFERp+mlTg/s9R5Lwg87f9bM/3sQB99w+N9D/qnM9ar3+AKDBwo/gm/iQQ==", + "dev": true + }, + "vfile-message": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-1.1.1.tgz", + "integrity": "sha512-1WmsopSGhWt5laNir+633LszXvZ+Z/lxveBf6yhGsqnQIhlhzooZae7zV6YVM1Sdkw68dtAW3ow0pOdPANugvA==", + "dev": true, + "requires": { + "unist-util-stringify-position": "^1.1.1" + } + } + } + }, + "vfile-location": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", + "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", + "dev": true + }, + "vfile-message": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-2.0.2.tgz", + "integrity": "sha512-gNV2Y2fDvDOOqq8bEe7cF3DXU6QgV4uA9zMR2P8tix11l1r7zju3zry3wZ8sx+BEfuO6WQ7z2QzfWTvqHQiwsA==", + "dev": true, + "requires": { + "@types/unist": "^2.0.0", + "unist-util-stringify-position": "^2.0.0" + } + }, "vm-browserify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", @@ -9109,6 +11238,47 @@ "integrity": "sha512-ImThpeNU9HbdZL3utgMCq0oiMzAkt1mcgy3/E6zWC/G6AaQoeuFdsl9nDhTDU3X1R6FK7nsIUuRACVcjI+A2GQ==", "dev": true }, + "vue-eslint-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.0.0.tgz", + "integrity": "sha512-yR0dLxsTT7JfD2YQo9BhnQ6bUTLsZouuzt9SKRP7XNaZJV459gvlsJo4vT2nhZ/2dH9j3c53bIx9dnqU2prM9g==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-scope": "^5.0.0", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "lodash": "^4.17.15" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, "vue-hot-reload-api": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.4.tgz", @@ -9496,6 +11666,12 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -9550,6 +11726,27 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "write-file-atomic": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.1.tgz", + "integrity": "sha512-JPStrIyyVJ6oCSz/691fAjFtefZ6q+fP6tm+OS4Qw6o+TGQxNp1ziY2PgS+X/m0V8OWhZiO/m4xSj+Pr4RrZvw==", + "dev": true, + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, "ws": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", @@ -9559,6 +11756,12 @@ "async-limiter": "~1.0.0" } }, + "x-is-string": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/x-is-string/-/x-is-string-0.1.0.tgz", + "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", + "dev": true + }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", @@ -9577,6 +11780,26 @@ "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true }, + "yaml": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.7.2.tgz", + "integrity": "sha512-qXROVp90sb83XtAoqE8bP9RwAkTTZbugRUTm5YeFCBfNRPEp2YzTeqWiz7m5OORHzEvrA/qcGS8hp/E+MMROYw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.6.3" + }, + "dependencies": { + "@babel/runtime": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", + "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + } + } + }, "yargs": { "version": "12.0.5", "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", diff --git a/src/phpdoc b/src/phpdoc new file mode 120000 --- /dev/null +++ b/src/phpdoc @@ -0,0 +1 @@ +../bin/phpdoc \ No newline at end of file diff --git a/src/phpstan b/src/phpstan new file mode 120000 --- /dev/null +++ b/src/phpstan @@ -0,0 +1 @@ +../bin/phpstan \ No newline at end of file diff --git a/src/phpstan.neon b/src/phpstan.neon new file mode 100644 --- /dev/null +++ b/src/phpstan.neon @@ -0,0 +1,6 @@ +includes: + - ./vendor/nunomaduro/larastan/extension.neon +parameters: + level: 3 + paths: + - app/ diff --git a/src/phpunit b/src/phpunit new file mode 120000 --- /dev/null +++ b/src/phpunit @@ -0,0 +1 @@ +../bin/phpunit \ No newline at end of file diff --git a/src/phpunit.xml b/src/phpunit.xml --- a/src/phpunit.xml +++ b/src/phpunit.xml @@ -9,6 +9,10 @@ processIsolation="false" stopOnFailure="false"> + + tests/Browser + + tests/Unit @@ -23,6 +27,9 @@ ./app + + + diff --git a/src/tests/Browser/PasswordResetTest.php b/src/tests/Browser/PasswordResetTest.php --- a/src/tests/Browser/PasswordResetTest.php +++ b/src/tests/Browser/PasswordResetTest.php @@ -22,8 +22,7 @@ { parent::setUp(); - $user = User::firstOrCreate(['email' => 'passwordresettestdusk@' . \config('app.domain')]); - $user->setSetting('external_email', 'external@domain.tld'); + $this->deleteTestUser('passwordresettestdusk@' . \config('app.domain')); } /** @@ -33,7 +32,9 @@ */ public function tearDown(): void { - User::where('email', 'passwordresettestdusk@' . \config('app.domain'))->delete(); + $this->deleteTestUser('passwordresettestdusk@' . \config('app.domain')); + + parent::tearDown(); } /** @@ -61,6 +62,9 @@ */ public function testPasswordResetStep1() { + $user = $this->getTestUser('passwordresettestdusk@' . \config('app.domain')); + $user->setSetting('external_email', 'external@domain.tld'); + $this->browse(function (Browser $browser) { $browser->visit(new PasswordReset()); diff --git a/src/tests/Browser/SignupTest.php b/src/tests/Browser/SignupTest.php --- a/src/tests/Browser/SignupTest.php +++ b/src/tests/Browser/SignupTest.php @@ -22,10 +22,18 @@ { parent::setUp(); - Domain::where('namespace', 'user-domain-signup.com')->delete(); - User::where('email', 'signuptestdusk@' . \config('app.domain')) - ->orWhere('email', 'admin@user-domain-signup.com') - ->delete(); + $this->deleteTestUser('signuptestdusk@' . \config('app.domain')); + $this->deleteTestUser('admin@user-domain-signup.com'); + $this->deleteTestDomain('user-domain-signup.com'); + } + + public function tearDown(): void + { + $this->deleteTestUser('signuptestdusk@' . \config('app.domain')); + $this->deleteTestUser('admin@user-domain-signup.com'); + $this->deleteTestDomain('user-domain-signup.com'); + + parent::tearDown(); } /** diff --git a/src/tests/DuskTestCase.php b/src/tests/DuskTestCase.php --- a/src/tests/DuskTestCase.php +++ b/src/tests/DuskTestCase.php @@ -2,15 +2,69 @@ namespace Tests; -use Laravel\Dusk\TestCase as BaseTestCase; +use App\Domain; +use App\User; use Facebook\WebDriver\Chrome\ChromeOptions; use Facebook\WebDriver\Remote\RemoteWebDriver; use Facebook\WebDriver\Remote\DesiredCapabilities; +use Illuminate\Support\Facades\Queue; +use Laravel\Dusk\TestCase as BaseTestCase; abstract class DuskTestCase extends BaseTestCase { use CreatesApplication; + protected function deleteTestDomain($name) + { + Queue::fake(); + $domain = Domain::withTrashed()->where('namespace', $name)->first(); + if (!$domain) { + return; + } + + $job = new \App\Jobs\DomainDelete($domain); + $job->handle(); + + $domain->forceDelete(); + } + + protected function deleteTestUser($email) + { + Queue::fake(); + $user = User::withTrashed()->where('email', $email)->first(); + + if (!$user) { + return; + } + + $job = new \App\Jobs\UserDelete($user->id); + $job->handle(); + + $user->forceDelete(); + } + + /** + * Get Domain object by namespace, create it if needed. + * Skip LDAP jobs. + */ + protected function getTestDomain($name, $attrib = []) + { + // Disable jobs (i.e. skip LDAP oprations) + Queue::fake(); + return Domain::firstOrCreate(['namespace' => $name], $attrib); + } + + /** + * Get User object by email, create it if needed. + * Skip LDAP jobs. + */ + protected function getTestUser($email, $attrib = []) + { + // Disable jobs (i.e. skip LDAP oprations) + Queue::fake(); + return User::firstOrCreate(['email' => $email], $attrib); + } + /** * Prepare for Dusk test execution. * diff --git a/src/tests/Feature/Controller/DomainsTest.php b/src/tests/Feature/Controller/DomainsTest.php --- a/src/tests/Feature/Controller/DomainsTest.php +++ b/src/tests/Feature/Controller/DomainsTest.php @@ -20,8 +20,16 @@ { parent::setUp(); - User::where('email', 'test1@domainscontroller.com')->delete(); - Domain::where('namespace', 'domainscontroller.com')->delete(); + $this->deleteTestUser('test1@domainscontroller.com'); + $this->deleteTestDomain('domainscontroller.com'); + } + + public function tearDown(): void + { + $this->deleteTestUser('test1@domainscontroller.com'); + $this->deleteTestDomain('domainscontroller.com'); + + parent::tearDown(); } /** diff --git a/src/tests/Feature/Controller/PasswordResetTest.php b/src/tests/Feature/Controller/PasswordResetTest.php --- a/src/tests/Feature/Controller/PasswordResetTest.php +++ b/src/tests/Feature/Controller/PasswordResetTest.php @@ -16,7 +16,7 @@ { parent::setUp(); - $user = $this->getTestUser('passwordresettest@' . \config('app.domain')); + $this->deleteTestUser('passwordresettest@' . \config('app.domain')); } /** @@ -24,8 +24,9 @@ */ public function tearDown(): void { - User::where('email', 'passwordresettest@' . \config('app.domain')) - ->delete(); + $this->deleteTestUser('passwordresettest@' . \config('app.domain')); + + parent::tearDown(); } /** @@ -101,7 +102,7 @@ Queue::assertNothingPushed(); // Add required external email address to user settings - $user = User::where('email', 'passwordresettest@' . \config('app.domain'))->first(); + $user = $this->getTestUser('passwordresettest@' . \config('app.domain')); $user->setSetting('external_email', 'ext@email.com'); $data = [ @@ -150,7 +151,7 @@ $this->assertArrayHasKey('short_code', $json['errors']); // Add verification code and required external email address to user settings - $user = User::where('email', 'passwordresettest@' . \config('app.domain'))->first(); + $user = $this->getTestUser('passwordresettest@' . \config('app.domain')); $code = new VerificationCode(['mode' => 'password-reset']); $user->verificationcodes()->save($code); @@ -192,7 +193,7 @@ public function testPasswordResetVerifyValidInput() { // Add verification code and required external email address to user settings - $user = User::where('email', 'passwordresettest@' . \config('app.domain'))->first(); + $user = $this->getTestUser('passwordresettest@' . \config('app.domain')); $code = new VerificationCode(['mode' => 'password-reset']); $user->verificationcodes()->save($code); @@ -228,7 +229,7 @@ $this->assertCount(1, $json['errors']); $this->assertArrayHasKey('password', $json['errors']); - $user = User::where('email', 'passwordresettest@' . \config('app.domain'))->first(); + $user = $this->getTestUser('passwordresettest@' . \config('app.domain')); $code = new VerificationCode(['mode' => 'password-reset']); $user->verificationcodes()->save($code); @@ -285,7 +286,7 @@ */ public function testPasswordResetValidInput() { - $user = User::where('email', 'passwordresettest@' . \config('app.domain'))->first(); + $user = $this->getTestUser('passwordresettest@' . \config('app.domain')); $code = new VerificationCode(['mode' => 'password-reset']); $user->verificationcodes()->save($code); diff --git a/src/tests/Feature/Controller/SignupTest.php b/src/tests/Feature/Controller/SignupTest.php --- a/src/tests/Feature/Controller/SignupTest.php +++ b/src/tests/Feature/Controller/SignupTest.php @@ -11,7 +11,7 @@ class SignupTest extends TestCase { - private static $domain; + private $domain; /** * {@inheritDoc} @@ -22,8 +22,14 @@ // TODO: Some tests depend on existence of individual and group plans, // we should probably create plans here to not depend on that - $domain = self::getPublicDomain(); - $user = $this->getTestUser("SignupControllerTest1@$domain"); + $this->domain = $this->getPublicDomain(); + + $this->deleteTestUser("SignupControllerTest1@$this->domain"); + $this->deleteTestUser("signuplogin@$this->domain"); + $this->deleteTestUser("admin@external.com"); + + $this->deleteTestDomain('external.com'); + $this->deleteTestDomain('signup-domain.com'); } /** @@ -31,39 +37,37 @@ */ public function tearDown(): void { - $domain = self::getPublicDomain(); + $this->deleteTestUser("SignupControllerTest1@$this->domain"); + $this->deleteTestUser("signuplogin@$this->domain"); + $this->deleteTestUser("admin@external.com"); - User::where('email', "signuplogin@$domain") - ->orWhere('email', "SignupControllerTest1@$domain") - ->orWhere('email', 'admin@external.com') - ->delete(); + $this->deleteTestDomain('external.com'); + $this->deleteTestDomain('signup-domain.com'); - Domain::where('namespace', 'signup-domain.com') - ->orWhere('namespace', 'external.com') - ->delete(); + parent::tearDown(); } /** * Return a public domain for signup tests */ - public function getPublicDomain(): string + private function getPublicDomain(): string { - if (!self::$domain) { + if (!$this->domain) { $this->refreshApplication(); $public_domains = Domain::getPublicDomains(); - self::$domain = reset($public_domains); + $this->domain = reset($public_domains); - if (empty(self::$domain)) { - self::$domain = 'signup-domain.com'; + if (empty($this->domain)) { + $this->domain = 'signup-domain.com'; Domain::create([ - 'namespace' => self::$domain, + 'namespace' => $this->domain, 'status' => Domain::STATUS_ACTIVE, 'type' => Domain::TYPE_PUBLIC, ]); } } - return self::$domain; + return $this->domain; } /** @@ -418,7 +422,7 @@ }); // Check if the code has been removed - $this->assertNull(SignupCode::where($result['code'])->first()); + $this->assertNull(SignupCode::where('code', $result['code'])->first()); // Check if the user has been created $user = User::where('email', $identity)->first(); @@ -451,7 +455,7 @@ 'plan' => 'group', ]; - $response = $this->post('/api/auth/signup/init', $data); + $response = $this->withoutMiddleware()->post('/api/auth/signup/init', $data); $json = $response->json(); $response->assertStatus(200); @@ -603,15 +607,17 @@ ['administrator', $domain, false, ['login' => 'validation.loginexists']], ['sales', $domain, false, ['login' => 'validation.loginexists']], ['root', $domain, false, ['login' => 'validation.loginexists']], + // existing user - ['SignupControllerTest1', $domain, false, ['login' => 'validation.loginexists']], + ['jack', 'kolab.org', true, ['domain' => 'validation.domainexists']], // Domain account ['admin', 'kolabsys.com', true, null], ['testnonsystemdomain', 'invalid', true, ['domain' => 'validation.domaininvalid']], ['testnonsystemdomain', '.com', true, ['domain' => 'validation.domaininvalid']], + // existing user - ['SignupControllerTest1', $domain, true, ['domain' => 'validation.domainexists']], + ['john', 'kolab.org', true, ['domain' => 'validation.domainexists']], ]; } @@ -630,6 +636,6 @@ $result = $method->invoke(new SignupController(), $login, $domain, $external); - $this->assertSame($expected_result, $result); + $this->assertSame($expected_result, $result, var_export(func_get_args(), true)); } } diff --git a/src/tests/Feature/Controller/UsersTest.php b/src/tests/Feature/Controller/UsersTest.php --- a/src/tests/Feature/Controller/UsersTest.php +++ b/src/tests/Feature/Controller/UsersTest.php @@ -17,8 +17,18 @@ { parent::setUp(); - User::where('email', 'UsersControllerTest1@userscontroller.com')->delete(); - Domain::where('namespace', 'userscontroller.com')->delete(); + $this->deleteTestUser('UsersControllerTest1@userscontroller.com'); + $this->deleteTestUser('UserEntitlement2A@UserEntitlement.com'); + $this->deleteTestDomain('userscontroller.com'); + } + + public function tearDown(): void + { + $this->deleteTestUser('UsersControllerTest1@userscontroller.com'); + $this->deleteTestUser('UserEntitlement2A@UserEntitlement.com'); + $this->deleteTestDomain('userscontroller.com'); + + parent::tearDown(); } /** diff --git a/src/tests/Feature/DomainOwnerTest.php b/src/tests/Feature/DomainOwnerTest.php new file mode 100644 --- /dev/null +++ b/src/tests/Feature/DomainOwnerTest.php @@ -0,0 +1,46 @@ +deleteTestUser('jane@kolab.org'); + } + + public function tearDown(): void + { + $this->deleteTestUser('jane@kolab.org'); + + parent::tearDown(); + } + + public function testJohnCreateJane(): void + { + $john = User::where('email', 'john@kolab.org')->first(); + + $jane = User::create( + [ + 'name' => 'Jane Doe', + 'email' => 'jane@kolab.org', + 'password' => 'simple123', + 'email_verified_at' => now() + ] + ); + + $package = Package::where('title', 'kolab')->first(); + + $john->assignPackage($package, $jane); + + // assert jane has a mailbox entitlement + $this->assertTrue($jane->entitlements->count() == 4); + } +} diff --git a/src/tests/Feature/DomainTest.php b/src/tests/Feature/DomainTest.php --- a/src/tests/Feature/DomainTest.php +++ b/src/tests/Feature/DomainTest.php @@ -13,21 +13,32 @@ class DomainTest extends TestCase { + private $domains = [ + 'public-active.com', + 'gmail.com', + 'ci-success-cname.kolab.org', + 'ci-success-txt.kolab.org', + 'ci-failure-cname.kolab.org', + 'ci-failure-txt.kolab.org', + 'ci-failure-none.kolab.org', + ]; + public function setUp(): void { parent::setUp(); - $domains = [ - 'public-active.com', - 'gmail.com', - 'ci-success-cname.kolab.org', - 'ci-success-txt.kolab.org', - 'ci-failure-cname.kolab.org', - 'ci-failure-txt.kolab.org', - 'ci-failure-none.kolab.org', - ]; - - Domain::whereIn('namespace', $domains)->delete(); + foreach ($this->domains as $domain) { + $this->deleteTestDomain($domain); + } + } + + public function tearDown(): void + { + foreach ($this->domains as $domain) { + $this->deleteTestDomain($domain); + } + + parent::tearDown(); } /** @@ -46,31 +57,19 @@ ]); Queue::assertPushed(\App\Jobs\DomainCreate::class, 1); - Queue::assertPushed(\App\Jobs\DomainCreate::class, function ($job) use ($domain) { - $job_domain = TestCase::getObjectProperty($job, 'domain'); - - return $job_domain->id === $domain->id - && $job_domain->namespace === $domain->namespace; - }); -/* - Queue::assertPushedWithChain(\App\Jobs\DomainCreate::class, [ - \App\Jobs\DomainVerify::class, - ]); -*/ -/* - FIXME: Looks like we can't really do detailed assertions on chained jobs - Another thing to consider is if we maybe should run these jobs - independently (not chained) and make sure there's no race-condition - in status update - - Queue::assertPushed(\App\Jobs\DomainVerify::class, 1); - Queue::assertPushed(\App\Jobs\DomainVerify::class, function ($job) use ($domain) { - $job_domain = TestCase::getObjectProperty($job, 'domain'); - - return $job_domain->id === $domain->id - && $job_domain->namespace === $domain->namespace; - }); -*/ + + Queue::assertPushed( + \App\Jobs\DomainCreate::class, + function ($job) use ($domain) { + $job_domain = TestCase::getObjectProperty($job, 'domain'); + + return $job_domain->id === $domain->id && + $job_domain->namespace === $domain->namespace; + } + ); + + $job = new \App\Jobs\DomainCreate($domain); + $job->handle(); } /** @@ -142,17 +141,17 @@ $domain = $this->getTestDomain('ci-failure-none.kolab.org', $domain_props); $this->assertTrue($domain->confirm() === false); - $this->assertTrue(!$domain->isConfirmed()); + $this->assertFalse($domain->isConfirmed()); $domain = $this->getTestDomain('ci-failure-txt.kolab.org', $domain_props); $this->assertTrue($domain->confirm() === false); - $this->assertTrue(!$domain->isConfirmed()); + $this->assertFalse($domain->isConfirmed()); $domain = $this->getTestDomain('ci-failure-cname.kolab.org', $domain_props); $this->assertTrue($domain->confirm() === false); - $this->assertTrue(!$domain->isConfirmed()); + $this->assertFalse($domain->isConfirmed()); $domain = $this->getTestDomain('ci-success-txt.kolab.org', $domain_props); diff --git a/src/tests/Feature/EntitlementTest.php b/src/tests/Feature/EntitlementTest.php --- a/src/tests/Feature/EntitlementTest.php +++ b/src/tests/Feature/EntitlementTest.php @@ -15,9 +15,16 @@ { parent::setUp(); - User::where('email', 'entitlement-test@kolabnow.com') - ->orWhere('email', 'entitled-user@custom-domain.com') - ->delete(); + $this->deleteTestUser('entitlement-test@kolabnow.com'); + $this->deleteTestUser('entitled-user@custom-domain.com'); + } + + public function tearDown(): void + { + $this->deleteTestUser('entitlement-test@kolabnow.com'); + $this->deleteTestUser('entitled-user@custom-domain.com'); + + parent::tearDown(); } /** @@ -37,16 +44,14 @@ ] ); - $this->assertTrue($owner->id != $user->id); - - $wallets = $owner->wallets()->get(); + $wallet = $owner->wallets()->first(); $entitlement_own_mailbox = new Entitlement( [ 'owner_id' => $owner->id, 'entitleable_id' => $owner->id, 'entitleable_type' => User::class, - 'wallet_id' => $wallets[0]->id, + 'wallet_id' => $wallet->id, 'sku_id' => $sku_mailbox->id, 'description' => "Owner Mailbox Entitlement Test" ] @@ -57,7 +62,7 @@ 'owner_id' => $owner->id, 'entitleable_id' => $domain->id, 'entitleable_type' => Domain::class, - 'wallet_id' => $wallets[0]->id, + 'wallet_id' => $wallet->id, 'sku_id' => $sku_domain->id, 'description' => "User Domain Entitlement Test" ] @@ -68,7 +73,7 @@ 'owner_id' => $owner->id, 'entitleable_id' => $user->id, 'entitleable_type' => User::class, - 'wallet_id' => $wallets[0]->id, + 'wallet_id' => $wallet->id, 'sku_id' => $sku_mailbox->id, 'description' => "User Mailbox Entitlement Test" ] @@ -81,9 +86,43 @@ $this->assertTrue($owner->entitlements()->count() == 3); $this->assertTrue($sku_domain->entitlements()->where('owner_id', $owner->id)->count() == 1); $this->assertTrue($sku_mailbox->entitlements()->where('owner_id', $owner->id)->count() == 2); - $this->assertTrue($wallets[0]->entitlements()->count() == 3); - $this->assertTrue($wallets[0]->fresh()->balance < 0.00); + $this->assertTrue($wallet->entitlements()->count() == 3); + $this->assertTrue($wallet->fresh()->balance < 0.00); + } + + public function testAddExistingEntitlement(): void + { + $this->markTestIncomplete(); + } + + public function testEntitlementFunctions(): void + { + $user = $this->getTestUser('entitlement-test@kolabnow.com'); + + $package = \App\Package::where('title', 'kolab')->first(); + + $user->assignPackage($package); + + $wallet = $user->wallets()->first(); + $this->assertNotNull($wallet); + + $sku = \App\Sku::where('title', 'mailbox')->first(); + $this->assertNotNull($sku); + + $entitlement = Entitlement::where('owner_id', $user->id)->where('sku_id', $sku->id)->first(); + $this->assertNotNull($entitlement); + + $e_sku = $entitlement->sku; + $this->assertSame($sku->id, $e_sku->id); + + $e_wallet = $entitlement->wallet; + $this->assertSame($wallet->id, $e_wallet->id); + + $e_owner = $entitlement->owner; + $this->assertSame($user->id, $e_owner->id); - // TODO: Test case of adding entitlement that already exists + $e_entitleable = $entitlement->entitleable; + $this->assertSame($user->id, $e_entitleable->id); + $this->assertTrue($e_entitleable instanceof \App\User); } } diff --git a/src/tests/Feature/Jobs/DomainCreateTest.php b/src/tests/Feature/Jobs/DomainCreateTest.php --- a/src/tests/Feature/Jobs/DomainCreateTest.php +++ b/src/tests/Feature/Jobs/DomainCreateTest.php @@ -5,6 +5,7 @@ use App\Jobs\DomainCreate; use App\Domain; use Illuminate\Support\Facades\Mail; +use Illuminate\Support\Facades\Queue; use Tests\TestCase; class DomainCreateTest extends TestCase @@ -16,7 +17,14 @@ { parent::setUp(); - Domain::where('namespace', 'domain-create-test.com')->delete(); + $this->deleteTestDomain('domain-create-test.com'); + } + + public function tearDown(): void + { + $this->deleteTestDomain('domain-create-test.com'); + + parent::tearDown(); } /** @@ -24,6 +32,8 @@ */ public function testHandle(): void { + Queue::fake(); + $domain = $this->getTestDomain( 'domain-create-test.com', [ @@ -34,12 +44,6 @@ $this->assertFalse($domain->isLdapReady()); - $mock = \Mockery::mock('alias:App\Backends\LDAP'); - $mock->shouldReceive('createDomain') - ->once() - ->with($domain) - ->andReturn(null); - $job = new DomainCreate($domain); $job->handle(); diff --git a/src/tests/Feature/Jobs/DomainVerifyTest.php b/src/tests/Feature/Jobs/DomainVerifyTest.php --- a/src/tests/Feature/Jobs/DomainVerifyTest.php +++ b/src/tests/Feature/Jobs/DomainVerifyTest.php @@ -16,9 +16,16 @@ { parent::setUp(); - Domain::where('namespace', 'gmail.com') - ->orWhere('namespace', 'some-non-existing-domain.fff') - ->delete(); + $this->deleteTestDomain('gmail.com'); + $this->deleteTestDomain('some-non-existing-domain.fff'); + } + + public function tearDown(): void + { + $this->deleteTestDomain('gmail.com'); + $this->deleteTestDomain('some-non-existing-domain.fff'); + + parent::tearDown(); } /** diff --git a/src/tests/Feature/Jobs/PasswordResetEmailTest.php b/src/tests/Feature/Jobs/PasswordResetEmailTest.php --- a/src/tests/Feature/Jobs/PasswordResetEmailTest.php +++ b/src/tests/Feature/Jobs/PasswordResetEmailTest.php @@ -22,13 +22,7 @@ { parent::setUp(); - $this->code = new VerificationCode([ - 'mode' => 'password-reset', - ]); - - $user = $this->getTestUser('PasswordReset@UserAccount.com'); - $user->verificationcodes()->save($this->code); - $user->setSettings(['external_email' => 'etx@email.com']); + $this->deleteTestUser('PasswordReset@UserAccount.com'); } /** @@ -38,7 +32,9 @@ */ public function tearDown(): void { - $this->code->user->delete(); + $this->deleteTestUser('PasswordReset@UserAccount.com'); + + parent::tearDown(); } /** @@ -48,20 +44,28 @@ */ public function testPasswordResetEmailHandle() { + $code = new VerificationCode([ + 'mode' => 'password-reset', + ]); + + $user = $this->getTestUser('PasswordReset@UserAccount.com'); + $user->verificationcodes()->save($code); + $user->setSettings(['external_email' => 'etx@email.com']); + Mail::fake(); // Assert that no jobs were pushed... Mail::assertNothingSent(); - $job = new PasswordResetEmail($this->code); + $job = new PasswordResetEmail($code); $job->handle(); // Assert the email sending job was pushed once Mail::assertSent(PasswordReset::class, 1); // Assert the mail was sent to the code's email - Mail::assertSent(PasswordReset::class, function ($mail) { - return $mail->hasTo($this->code->user->getSetting('external_email')); + Mail::assertSent(PasswordReset::class, function ($mail) use ($code) { + return $mail->hasTo($code->user->getSetting('external_email')); }); } } diff --git a/src/tests/Feature/Jobs/UserCreateTest.php b/src/tests/Feature/Jobs/UserCreateTest.php --- a/src/tests/Feature/Jobs/UserCreateTest.php +++ b/src/tests/Feature/Jobs/UserCreateTest.php @@ -16,7 +16,14 @@ { parent::setUp(); - User::where('email', 'new-job-user@' . \config('app.domain'))->delete(); + $this->deleteTestUser('new-job-user@' . \config('app.domain')); + } + + public function tearDown(): void + { + $this->deleteTestUser('new-job-user@' . \config('app.domain')); + + parent::tearDown(); } /** @@ -28,11 +35,13 @@ $this->assertFalse($user->isLdapReady()); + /* $mock = \Mockery::mock('alias:App\Backends\LDAP'); $mock->shouldReceive('createUser') ->once() ->with($user) ->andReturn(null); + */ $job = new UserCreate($user); $job->handle(); diff --git a/src/tests/Feature/Jobs/UserVerifyTest.php b/src/tests/Feature/Jobs/UserVerifyTest.php --- a/src/tests/Feature/Jobs/UserVerifyTest.php +++ b/src/tests/Feature/Jobs/UserVerifyTest.php @@ -16,7 +16,14 @@ { parent::setUp(); - User::where('email', 'new-job-user@' . \config('app.domain'))->delete(); + $this->deleteTestUser('new-job-user@' . \config('app.domain')); + } + + public function tearDown(): void + { + $this->deleteTestUser('new-job-user@' . \config('app.domain')); + + parent::tearDown(); } /** @@ -28,24 +35,9 @@ $this->assertFalse($user->isImapReady()); - $mock = \Mockery::mock('alias:App\Backends\IMAP'); - $mock->shouldReceive('verifyAccount') - ->once() - ->with($user->email) - ->andReturn(false); - $job = new UserVerify($user); $job->handle(); $this->assertTrue($user->fresh()->isImapReady() === false); - - $mock->shouldReceive('verifyAccount') - ->once() - ->with($user->email) - ->andReturn(true); - - $job->handle(); - - $this->assertTrue($user->fresh()->isImapReady()); } } diff --git a/src/tests/Feature/PlanTest.php b/src/tests/Feature/PlanTest.php --- a/src/tests/Feature/PlanTest.php +++ b/src/tests/Feature/PlanTest.php @@ -25,6 +25,8 @@ public function tearDown(): void { Plan::where('title', 'test-plan')->delete(); + + parent::tearDown(); } /** @@ -78,4 +80,30 @@ $this->assertTrue($plan->hasDomain() === true); } + + /** + * Test for a plan's cost. + */ + public function testCost(): void + { + $plan = Plan::where('title', 'individual')->first(); + + $package_costs = 0; + + foreach ($plan->packages as $package) { + $package_costs += $package->cost(); + } + + $this->assertTrue( + $package_costs == 999, + "The total costs of all packages for this plan is not 9.99" + ); + + $this->assertTrue( + $plan->cost() == 999, + "The total costs for this plan is not 9.99" + ); + + $this->assertTrue($plan->cost() == $package_costs); + } } diff --git a/src/tests/Feature/SignupCodeTest.php b/src/tests/Feature/SignupCodeTest.php --- a/src/tests/Feature/SignupCodeTest.php +++ b/src/tests/Feature/SignupCodeTest.php @@ -29,10 +29,21 @@ $this->assertFalse($code->isExpired()); $this->assertTrue(strlen($code->code) === SignupCode::CODE_LENGTH); - $this->assertTrue(strlen($code->short_code) === env('VERIFICATION_CODE_LENGTH', SignupCode::SHORTCODE_LENGTH)); + + $this->assertTrue( + strlen($code->short_code) === env( + 'VERIFICATION_CODE_LENGTH', + SignupCode::SHORTCODE_LENGTH + ) + ); + $this->assertSame($data['data'], $code->data); $this->assertInstanceOf(\DateTime::class, $code->expires_at); - $this->assertSame(env('SIGNUP_CODE_EXPIRY', SignupCode::CODE_EXP_HOURS), $code->expires_at->diff($now)->h + 1); + + $this->assertSame( + env('SIGNUP_CODE_EXPIRY', SignupCode::CODE_EXP_HOURS), + $code->expires_at->diff($now)->h + 1 + ); $inst = SignupCode::find($code->code); diff --git a/src/tests/Feature/SkuTest.php b/src/tests/Feature/SkuTest.php --- a/src/tests/Feature/SkuTest.php +++ b/src/tests/Feature/SkuTest.php @@ -5,7 +5,6 @@ use App\Domain; use App\Entitlement; use App\Handlers; -use App\Quota; use App\Sku; use App\User; use Tests\TestCase; @@ -17,8 +16,16 @@ { parent::setUp(); - User::where('email', 'sku-test-user@custom-domain.com')->delete(); - Domain::where('namespace', 'custom-domain.com')->delete(); + $this->deleteTestUser('sku-test-user@custom-domain.com'); + $this->deleteTestDomain('custom-domain.com'); + } + + public function tearDown(): void + { + $this->deleteTestUser('sku-test-user@custom-domain.com'); + $this->deleteTestDomain('custom-domain.com'); + + parent::tearDown(); } /** @@ -35,13 +42,24 @@ ] ); + \Log::debug(var_export($domain->toArray(), true)); + $user = $this->getTestUser('sku-test-user@custom-domain.com'); $wallet = $user->wallets()->first(); // \App\Handlers\Mailbox SKU - // Note, we're testing mailbox SKU before domain SKU as it may potentially fail in that order + // Note, we're testing mailbox SKU before domain SKU as it may potentially fail in that + // order $sku = Sku::where('title', 'mailbox')->first(); - $sku->registerEntitlement($user); + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $wallet->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $user->id, + 'entitleable_type' => User::class + ] + ); $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); $wallet->refresh(); @@ -50,7 +68,10 @@ $balance = -$sku->cost; $this->assertCount(1, $entitlements); $this->assertEquals($user->id, $entitlements[0]->entitleable_id); - $this->assertSame(Handlers\Mailbox::entitleableClass(), $entitlements[0]->entitleable_type); + $this->assertSame( + Handlers\Mailbox::entitleableClass(), + $entitlements[0]->entitleable_type + ); } else { $this->assertCount(0, $entitlements); } @@ -59,16 +80,40 @@ // \App\Handlers\Domain SKU $sku = Sku::where('title', 'domain')->first(); - $sku->registerEntitlement($user, [$domain]); + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $wallet->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $domain->id, + 'entitleable_type' => Domain::class + ] + ); + + $entitlements = $sku->entitlements->where('owner_id', $user->id); + + foreach ($entitlements as $entitlement) { + \Log::debug(var_export($entitlement->toArray(), true)); + } - $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); $wallet->refresh(); if ($sku->active) { $balance -= $sku->cost; $this->assertCount(1, $entitlements); - $this->assertEquals($domain->id, $entitlements[0]->entitleable_id); - $this->assertSame(Handlers\Domain::entitleableClass(), $entitlements[0]->entitleable_type); + + $_domain = Domain::find($entitlements->first()->entitleable_id); + + $this->assertEquals( + $domain->id, + $entitlements->first()->entitleable_id, + var_export($_domain->toArray(), true) + ); + + $this->assertSame( + Handlers\Domain::entitleableClass(), + $entitlements->first()->entitleable_type + ); } else { $this->assertCount(0, $entitlements); } @@ -77,16 +122,27 @@ // \App\Handlers\DomainRegistration SKU $sku = Sku::where('title', 'domain-registration')->first(); - $sku->registerEntitlement($user, [$domain]); + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $user->wallets()->get()[0]->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $domain->id, + 'entitleable_type' => Domain::class + ] + ); - $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); + $entitlements = $sku->entitlements->where('owner_id', $user->id); $wallet->refresh(); if ($sku->active) { $balance -= $sku->cost; $this->assertCount(1, $entitlements); - $this->assertEquals($domain->id, $entitlements[0]->entitleable_id); - $this->assertSame(Handlers\DomainRegistration::entitleableClass(), $entitlements[0]->entitleable_type); + $this->assertEquals($domain->id, $entitlements->first()->entitleable_id); + $this->assertSame( + Handlers\DomainRegistration::entitleableClass(), + $entitlements->first()->entitleable_type + ); } else { $this->assertCount(0, $entitlements); } @@ -95,16 +151,27 @@ // \App\Handlers\DomainHosting SKU $sku = Sku::where('title', 'domain-hosting')->first(); - $sku->registerEntitlement($user, [$domain]); + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $wallet->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $domain->id, + 'entitleable_type' => Domain::class + ] + ); - $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); + $entitlements = $sku->entitlements->where('owner_id', $user->id); $wallet->refresh(); if ($sku->active) { $balance -= $sku->cost; $this->assertCount(1, $entitlements); - $this->assertEquals($domain->id, $entitlements[0]->entitleable_id); - $this->assertSame(Handlers\DomainHosting::entitleableClass(), $entitlements[0]->entitleable_type); + $this->assertEquals($domain->id, $entitlements->first()->entitleable_id); + $this->assertSame( + Handlers\DomainHosting::entitleableClass(), + $entitlements->first()->entitleable_type + ); } else { $this->assertCount(0, $entitlements); } @@ -113,16 +180,27 @@ // \App\Handlers\Groupware SKU $sku = Sku::where('title', 'groupware')->first(); - $sku->registerEntitlement($user, [$domain]); + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $user->wallets()->get()[0]->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $user->id, + 'entitleable_type' => User::class + ] + ); - $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); + $entitlements = $sku->entitlements->where('owner_id', $user->id); $wallet->refresh(); if ($sku->active) { $balance -= $sku->cost; $this->assertCount(1, $entitlements); - $this->assertEquals($user->id, $entitlements[0]->entitleable_id); - $this->assertSame(Handlers\Mailbox::entitleableClass(), $entitlements[0]->entitleable_type); + $this->assertEquals($user->id, $entitlements->first()->entitleable_id); + $this->assertSame( + Handlers\Mailbox::entitleableClass(), + $entitlements->first()->entitleable_type + ); } else { $this->assertCount(0, $entitlements); } @@ -131,26 +209,35 @@ // \App\Handlers\Storage SKU $sku = Sku::where('title', 'storage')->first(); - $sku->registerEntitlement($user, [$domain]); + Entitlement::create( + [ + 'owner_id' => $user->id, + 'wallet_id' => $wallet->id, + 'sku_id' => $sku->id, + 'entitleable_id' => $user->id, + 'entitleable_type' => User::class + ] + ); - $entitlements = $sku->entitlements()->where('owner_id', $user->id)->get(); + $entitlements = $sku->entitlements->where('owner_id', $user->id); $wallet->refresh(); if ($sku->active) { $balance -= $sku->cost; - // For Storage entitlement we expect additional Quota record - $quota = Quota::where('user_id', $user->id)->first(); - $this->assertTrue(!empty($quota)); - // TODO: This should be a constant and/or config option, and probably - // quota should not be in bytes - $this->assertSame(2147483648, $quota->value); $this->assertCount(1, $entitlements); - $this->assertEquals($quota->id, $entitlements[0]->entitleable_id); - $this->assertSame(Handlers\Storage::entitleableClass(), $entitlements[0]->entitleable_type); } else { $this->assertCount(0, $entitlements); } $this->assertEquals($balance, $wallet->balance); } + + public function testSkuPackages(): void + { + $sku = Sku::where('title', 'mailbox')->first(); + + $packages = $sku->packages; + + $this->assertCount(2, $packages); + } } diff --git a/src/tests/Feature/UserTest.php b/src/tests/Feature/UserTest.php --- a/src/tests/Feature/UserTest.php +++ b/src/tests/Feature/UserTest.php @@ -12,7 +12,20 @@ { parent::setUp(); - User::where('email', 'user-create-test@' . \config('app.domain'))->delete(); + $this->deleteTestUser('user-create-test@' . \config('app.domain')); + $this->deleteTestUser('UserAccountA@UserAccount.com'); + $this->deleteTestUser('UserAccountB@UserAccount.com'); + $this->deleteTestUser('userdeletejob@kolabnow.com'); + } + + public function tearDown(): void + { + $this->deleteTestUser('user-create-test@' . \config('app.domain')); + $this->deleteTestUser('UserAccountA@UserAccount.com'); + $this->deleteTestUser('UserAccountB@UserAccount.com'); + $this->deleteTestUser('userdeletejob@kolabnow.com'); + + parent::tearDown(); } /** @@ -87,6 +100,47 @@ $this->assertContains('kolab.org', $domains); } + public function testUserQuota(): void + { + $user = $this->getTestUser('john@kolab.org'); + $storage_sku = \App\Sku::where('title', 'storage')->first(); + + $count = 0; + + foreach ($user->entitlements()->get() as $entitlement) { + if ($entitlement->sku_id == $storage_sku->id) { + $count += 1; + } + } + + $this->assertTrue($count == 2); + } + + /** + * Test user deletion + */ + public function testUserDelete(): void + { + $user = $this->getTestUser('userdeletejob@kolabnow.com'); + + $package = \App\Package::where('title', 'kolab')->first(); + + $user->assignPackage($package); + + $id = $user->id; + + $user->delete(); + + $job = new \App\Jobs\UserDelete($id); + $job->handle(); + + $user->forceDelete(); + + $entitlements = \App\Entitlement::where('owner_id', 'id')->get(); + + $this->assertCount(0, $entitlements); + } + /** * Tests for User::findByEmail() */ diff --git a/src/tests/Feature/VerificationCodeTest.php b/src/tests/Feature/VerificationCodeTest.php --- a/src/tests/Feature/VerificationCodeTest.php +++ b/src/tests/Feature/VerificationCodeTest.php @@ -10,6 +10,20 @@ class VerificationCodeTest extends TestCase { + public function setUp(): void + { + parent::setUp(); + + $this->deleteTestUser('UserAccountA@UserAccount.com'); + } + + public function tearDown(): void + { + $this->deleteTestUser('UserAccountA@UserAccount.com'); + + parent::tearDown(); + } + /** * Test VerificationCode creation */ diff --git a/src/tests/Feature/WalletTest.php b/src/tests/Feature/WalletTest.php --- a/src/tests/Feature/WalletTest.php +++ b/src/tests/Feature/WalletTest.php @@ -27,15 +27,17 @@ parent::setUp(); foreach ($this->users as $user) { - User::where('email', $user)->delete(); + $this->deleteTestUser($user); } } public function tearDown(): void { foreach ($this->users as $user) { - User::where('email', $user)->delete(); + $this->deleteTestUser($user); } + + parent::tearDown(); } /** @@ -138,7 +140,10 @@ } ); - $this->assertTrue($userB->accounts()->count() == 1); + $this->assertTrue( + $userB->accounts()->count() == 1, + "number of accounts (1 expected): {$userB->accounts()->count()}" + ); $aWallet = $userA->wallets()->get(); $bAccount = $userB->accounts()->get(); diff --git a/src/tests/TestCase.php b/src/tests/TestCase.php --- a/src/tests/TestCase.php +++ b/src/tests/TestCase.php @@ -11,6 +11,35 @@ { use CreatesApplication; + protected function deleteTestDomain($name) + { + Queue::fake(); + $domain = Domain::withTrashed()->where('namespace', $name)->first(); + if (!$domain) { + return; + } + + $job = new \App\Jobs\DomainDelete($domain); + $job->handle(); + + $domain->forceDelete(); + } + + protected function deleteTestUser($email) + { + Queue::fake(); + $user = User::withTrashed()->where('email', $email)->first(); + + if (!$user) { + return; + } + + $job = new \App\Jobs\UserDelete($user->id); + $job->handle(); + + $user->forceDelete(); + } + /** * Get Domain object by namespace, create it if needed. * Skip LDAP jobs.