diff --git a/.gitignore b/.gitignore
index 53b6ba82..c0a17182 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/vendor
.phpunit.result.cache
+/storage/framework/
diff --git a/app/Actions/CompileAssets.php b/app/Actions/CompileAssets.php
new file mode 100644
index 00000000..c5c4d3aa
--- /dev/null
+++ b/app/Actions/CompileAssets.php
@@ -0,0 +1,42 @@
+shell = $shell;
+ $this->silentDevScript = $silentDevScript;
+ }
+
+ public function __invoke()
+ {
+ if (! config('lambo.store.mix')) {
+ return;
+ }
+
+ $this->silentDevScript->add();
+
+ $this->logStep('Compiling project assets');
+
+ $process = $this->shell->execInProject("npm run dev{$this->extraOptions()}");
+
+ $this->abortIf(! $process->isSuccessful(), 'Compilation of project assets did not complete successfully', $process);
+
+ $this->silentDevScript->remove();
+
+ $this->info('Project assets compiled successfully.');
+ }
+ public function extraOptions()
+ {
+ return config('lambo.store.with_output') ? '' : ' --silent';
+ }
+}
diff --git a/app/Actions/ConfigureFrontendFramework.php b/app/Actions/ConfigureFrontendFramework.php
new file mode 100644
index 00000000..12d252c9
--- /dev/null
+++ b/app/Actions/ConfigureFrontendFramework.php
@@ -0,0 +1,37 @@
+shell = $shell;
+ $this->laravelUi = $laravelUi;
+ }
+
+ public function __invoke()
+ {
+ if (! config('lambo.store.frontend')) {
+ return;
+ }
+
+ $this->laravelUi->install();
+
+ $this->logStep('Configuring frontend scaffolding');
+
+ $process = $this->shell->execInProject('php artisan ui ' . config('lambo.store.frontend'));
+
+ $this->abortIf(! $process->isSuccessful(), "Installation of UI scaffolding did not complete successfully.", $process);
+
+ $this->info('UI scaffolding has been set to ' . config('lambo.store.frontend'));
+ }
+}
diff --git a/app/Actions/CreateDatabase.php b/app/Actions/CreateDatabase.php
index 9b05e57b..20359c69 100644
--- a/app/Actions/CreateDatabase.php
+++ b/app/Actions/CreateDatabase.php
@@ -2,10 +2,54 @@
namespace App\Actions;
+use App\Shell\Shell;
+use Illuminate\Support\Str;
+use Symfony\Component\Process\ExecutableFinder;
+
class CreateDatabase
{
+ use LamboAction;
+
+ protected $finder;
+ protected $shell;
+
+ public function __construct(Shell $shell, ExecutableFinder $finder)
+ {
+ $this->finder = $finder;
+ $this->shell = $shell;
+ }
+
public function __invoke()
{
- // @todo
+ if (! config('lambo.store.create_database')) {
+ return;
+ }
+
+ if (! $this->mysqlExists()) {
+ return "MySQL does not seem to be installed. Skipping new database creation.";
+ }
+
+ $this->logStep('Creating database');
+
+ $process = $this->shell->execInProject($this->command());
+
+ $this->abortIf(! $process->isSuccessful(), "The new database was not created.", $process);
+
+ return 'Created a new database ' . config('lambo.store.database_name');
+ }
+
+ protected function mysqlExists()
+ {
+ return $this->finder->find('mysql') !== null;
+ }
+
+ protected function command()
+ {
+ return sprintf(
+ 'mysql --user=%s --password=%s -e "CREATE DATABASE IF NOT EXISTS %s";',
+ config('lambo.store.database_username'),
+ config('lambo.store.database_password'),
+ config('lambo.store.database_name')
+ );
}
}
diff --git a/app/Actions/CustomizeDotEnv.php b/app/Actions/CustomizeDotEnv.php
index 510ea587..1d8e889f 100644
--- a/app/Actions/CustomizeDotEnv.php
+++ b/app/Actions/CustomizeDotEnv.php
@@ -2,20 +2,25 @@
namespace App\Actions;
-use Facades\App\Utilities;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\File;
class CustomizeDotEnv
{
+ use LamboAction;
+
public function __invoke()
{
+ $this->logStep('Customizing .env and .env.example');
+
$filePath = config('lambo.store.project_path') . '/.env.example';
$output = $this->customize(File::get($filePath));
File::put($filePath, $output);
File::put(str_replace('.env.example', '.env', $filePath), $output);
+
+ $this->info('.env files configured.');
}
public function customize($contents)
@@ -41,17 +46,11 @@ public function value($key, $fallback)
$replacements = [
'APP_NAME' => config('lambo.store.project_name'),
'APP_URL' => config('lambo.store.project_url'),
- 'DB_DATABASE' => $this->databaseName(),
- 'DB_USERNAME' => 'root',
- 'DB_PASSWORD' => null,
+ 'DB_DATABASE' => config('lambo.store.database_name'),
+ 'DB_USERNAME' => config('lambo.store.database_username'),
+ 'DB_PASSWORD' => config('lambo.store.database_password'),
];
return Arr::get($replacements, $key, $fallback);
}
-
- public function databaseName()
- {
- // @todo allow for flag for custom database name.. TEST IT!
- return Utilities::prepNameForDatabase(config('lambo.store.project_name'));
- }
}
diff --git a/app/Actions/DisplayHelpScreen.php b/app/Actions/DisplayHelpScreen.php
index 588894d2..59545c2f 100644
--- a/app/Actions/DisplayHelpScreen.php
+++ b/app/Actions/DisplayHelpScreen.php
@@ -3,37 +3,35 @@
namespace App\Actions;
use App\Options;
-use Illuminate\Support\Arr;
class DisplayHelpScreen
{
+ use LamboAction;
+
protected $indent = 30;
protected $commands = [
'help-screen' => 'Display this screen',
- 'make-config' => 'Generate config file',
'edit-config' => 'Edit config file',
- 'make-after' => 'Generate "after" file',
'edit-after' => 'Edit "after" file',
];
public function __invoke()
{
- $console = app('console');
- $console->line("\nUsage:");
- $console->line(" lambo new myApplication [arguments]\n");
- $console->line("Commands (lambo COMMANDNAME):");
+ $this->line("\nUsage:");
+ $this->line(" lambo new myApplication [arguments]\n");
+ $this->line("Commands (lambo COMMANDNAME):");
foreach ($this->commands as $command => $description) {
$spaces = $this->makeSpaces(strlen($command));
- $console->line(" {$command}{$spaces}{$description}");
+ $this->line(" {$command}{$spaces}{$description}");
}
- $console->line("\nOptions (lambo new myApplication OPTIONS):");
+ $this->line("\nOptions (lambo new myApplication OPTIONS):");
foreach ((new Options)->all() as $option) {
- $console->line($this->createCliStringForOption($option));
+ $this->line($this->createCliStringForOption($option));
}
}
diff --git a/app/Actions/DisplayLamboWelcome.php b/app/Actions/DisplayLamboWelcome.php
index c74b820d..2f89800a 100644
--- a/app/Actions/DisplayLamboWelcome.php
+++ b/app/Actions/DisplayLamboWelcome.php
@@ -4,6 +4,8 @@
class DisplayLamboWelcome
{
+ use LamboAction;
+
protected $lamboLogo = "
__ __ :version:
/ / ____ _____ ___ / /_ ____
@@ -23,12 +25,12 @@ public function __invoke()
{
foreach (explode("\n", $this->lamboLogo) as $line) {
// Extra space on the end fixes an issue with console when it ends with backslash
- app('console')->info($line . " ");
+ $this->info($line . " ");
}
foreach (explode("\n", $this->welcomeText) as $line) {
// Extra space on the end fixes an issue with console when it ends with backslash
- app('console')->line($line . " ");
+ $this->line($line . " ");
}
}
}
diff --git a/app/Actions/EditAfter.php b/app/Actions/EditAfter.php
new file mode 100644
index 00000000..6c7ca9fb
--- /dev/null
+++ b/app/Actions/EditAfter.php
@@ -0,0 +1,16 @@
+createOrEditConfigFile("after", File::get(base_path('stubs/after.stub')));
+ }
+}
diff --git a/app/Actions/EditConfig.php b/app/Actions/EditConfig.php
new file mode 100644
index 00000000..25e2c0b5
--- /dev/null
+++ b/app/Actions/EditConfig.php
@@ -0,0 +1,16 @@
+createOrEditConfigFile("config", File::get(base_path('stubs/config.stub')));
+ }
+}
diff --git a/app/Actions/GenerateAppKey.php b/app/Actions/GenerateAppKey.php
index 5a2dc17e..ff157768 100644
--- a/app/Actions/GenerateAppKey.php
+++ b/app/Actions/GenerateAppKey.php
@@ -2,10 +2,12 @@
namespace App\Actions;
-use App\Shell;
+use App\Shell\Shell;
class GenerateAppKey
{
+ use LamboAction;
+
protected $shell;
public function __construct(Shell $shell)
@@ -15,6 +17,12 @@ public function __construct(Shell $shell)
public function __invoke()
{
- $this->shell->execInProject('php artisan key:generate');
+ $this->logStep('Running php artisan key:generate');
+
+ $process = $this->shell->execInProject('php artisan key:generate');
+
+ $this->abortIf(! $process->isSuccessful(), 'Failed to generate application key successfully', $process);
+
+ $this->info('Application key has been set.');
}
}
diff --git a/app/Actions/InitializeGitRepo.php b/app/Actions/InitializeGitRepo.php
index 2ebb7a5c..b9349b82 100644
--- a/app/Actions/InitializeGitRepo.php
+++ b/app/Actions/InitializeGitRepo.php
@@ -2,10 +2,12 @@
namespace App\Actions;
-use App\Shell;
+use App\Shell\Shell;
class InitializeGitRepo
{
+ use LamboAction;
+
protected $shell;
public function __construct(Shell $shell)
@@ -15,15 +17,18 @@ public function __construct(Shell $shell)
public function __invoke()
{
- $this->shell->execInProject('git init');
- $this->shell->execInProject('git add .');
- $this->shell->execInProject('git commit -m "' . $this->gitCommit() . '"');
+ $this->logStep('Initializing git repository');
- app('console')->info('Git repository initialized.');
+ $this->execAndCheck('git init');
+ $this->execAndCheck('git add .');
+ $this->execAndCheck('git commit -m "' . config('lambo.store.commit_message') . '"');
+ $this->info('New git repository initialized.');
}
- public function gitCommit()
+ public function execAndCheck($command)
{
- return app('console')->option('message') ?? 'Initial commit.';
+ $process = $this->shell->execInProject($command);
+
+ $this->abortIf(! $process->isSuccessful(), 'Initialization of git repository did not complete successfully.', $process);
}
}
diff --git a/app/Actions/InstallNpmDependencies.php b/app/Actions/InstallNpmDependencies.php
index 48b59013..b4560487 100644
--- a/app/Actions/InstallNpmDependencies.php
+++ b/app/Actions/InstallNpmDependencies.php
@@ -2,10 +2,12 @@
namespace App\Actions;
-use App\Shell;
+use App\Shell\Shell;
class InstallNpmDependencies
{
+ use LamboAction;
+
protected $shell;
public function __construct(Shell $shell)
@@ -15,8 +17,19 @@ public function __construct(Shell $shell)
public function __invoke()
{
- app('console')->info('Installing NPM dependencies.');
+ if (! config('lambo.store.node')) {
+ return;
+ }
+
+ $process = $this->shell->execInProject("npm install{$this->extraOptions()}");
+
+ $this->abortIf(! $process->isSuccessful(), 'Installation of npm dependencies did not complete successfully', $process);
- $this->shell->execInProject("npm install");
+ $this->info('Npm dependencies installed.');
+ }
+
+ public function extraOptions()
+ {
+ return config('lambo.store.with-output') ? '' : ' --silent';
}
}
diff --git a/app/Actions/LamboAction.php b/app/Actions/LamboAction.php
new file mode 100644
index 00000000..40e7c8a9
--- /dev/null
+++ b/app/Actions/LamboAction.php
@@ -0,0 +1,23 @@
+comment("\n{$step}...");
+ }
+
+ public function abortIf(bool $abort, string $message, $process)
+ {
+ if ($abort) {
+ throw new Exception("{$message}\n Failed to run: '{$process->getCommandLine()}'");
+ }
+ }
+}
diff --git a/app/Actions/LaravelUi.php b/app/Actions/LaravelUi.php
new file mode 100644
index 00000000..9b8e270b
--- /dev/null
+++ b/app/Actions/LaravelUi.php
@@ -0,0 +1,40 @@
+shell = $shell;
+ }
+
+ public function install()
+ {
+ if ($this->laravelUiInstalled()) {
+ return;
+ }
+
+ $this->logStep('To use Laravel frontend scaffolding the composer package laravel/ui is required. Installing now...');
+
+ $process = $this->shell->execInProject('composer require laravel/ui --quiet');
+
+ $this->abortIf(! $process->isSuccessful(), "Installation of laravel/ui did not complete successfully.", $process);
+
+ $this->info('laravel/ui installed.');
+ }
+
+ private function laravelUiInstalled(): bool
+ {
+ $composeConfig = json_decode(File::get(config('lambo.store.project_path') . '/composer.json'), true);
+ return Arr::has($composeConfig, 'require.laravel/ui');
+ }
+}
diff --git a/app/Actions/OpenInBrowser.php b/app/Actions/OpenInBrowser.php
index ff45a3ef..8a2cd017 100644
--- a/app/Actions/OpenInBrowser.php
+++ b/app/Actions/OpenInBrowser.php
@@ -2,10 +2,13 @@
namespace App\Actions;
-use App\Shell;
+use App\Environment;
+use App\Shell\Shell;
class OpenInBrowser
{
+ use LamboAction;
+
protected $shell;
public function __construct(Shell $shell)
@@ -15,24 +18,23 @@ public function __construct(Shell $shell)
public function __invoke()
{
- if ($this->isMac() && $this->browser()) {
+ $this->logStep('Opening in Browser');
+
+ if (Environment::isMac() && $this->browser()) {
$this->shell->execInProject(sprintf(
'open -a "%s" "%s"',
$this->browser(),
config('lambo.store.project_url')
));
- } else {
- $this->shell->execInProject("valet open");
+
+ return;
}
- }
- public function isMac()
- {
- return PHP_OS === 'Darwin';
+ $this->shell->execInProject("valet open");
}
public function browser()
{
- return app('console')->option('browser');
+ return config('lambo.store.browser');
}
}
diff --git a/app/Actions/OpenInEditor.php b/app/Actions/OpenInEditor.php
index 57afa105..ebd96b66 100644
--- a/app/Actions/OpenInEditor.php
+++ b/app/Actions/OpenInEditor.php
@@ -2,10 +2,12 @@
namespace App\Actions;
-use App\Shell;
+use App\Shell\Shell;
class OpenInEditor
{
+ use LamboAction;
+
protected $shell;
public function __construct(Shell $shell)
@@ -15,17 +17,18 @@ public function __construct(Shell $shell)
public function __invoke()
{
- app('console')->info('Opening your editor.');
-
- if ($this->editor()) {
- $this->shell->execInProject($this->editor() . " .");
+ if (! $this->editor()) {
+ return;
}
- // @todo: should we default to $EDITOR (environment var)
+ $this->logStep('Opening In Editor');
+
+ $this->shell->execInProject($this->editor() . " .");
+ $this->info('Opening your project in ' . $this->editor());
}
public function editor()
{
- return app('console')->option('editor');
+ return config('lambo.store.editor');
}
}
diff --git a/app/Actions/RunAfterScript.php b/app/Actions/RunAfterScript.php
new file mode 100644
index 00000000..930a824e
--- /dev/null
+++ b/app/Actions/RunAfterScript.php
@@ -0,0 +1,33 @@
+shell = $shell;
+ }
+
+ public function __invoke()
+ {
+ if (! $this->configFileExists('after')) {
+ return;
+ }
+
+ $this->logStep('Running after script');
+
+ $process = $this->shell->execInProject("sh " . $this->getConfigFilePath("after"));
+
+ $this->abortIf(! $process->isSuccessful(), 'After file did not complete successfully', $process);
+
+ $this->info('After script has completed.');
+ }
+}
diff --git a/app/Actions/RunLaravelInstaller.php b/app/Actions/RunLaravelInstaller.php
index 12bda995..f88c69ae 100644
--- a/app/Actions/RunLaravelInstaller.php
+++ b/app/Actions/RunLaravelInstaller.php
@@ -2,10 +2,12 @@
namespace App\Actions;
-use App\Shell;
+use App\Shell\Shell;
class RunLaravelInstaller
{
+ use LamboAction;
+
protected $shell;
public function __construct(Shell $shell)
@@ -15,19 +17,34 @@ public function __construct(Shell $shell)
public function __invoke()
{
- $projectName = config('lambo.store.project_name');
+ $this->logStep('Running the Laravel installer');
+
+ $process = $this->shell->execInRoot('laravel new ' . config('lambo.store.project_name') . $this->extraOptions());
- app('console')->info('Creating application using the Laravel installer.');
+ $this->abortIf(! $process->isSuccessful(), "The laravel installer did not complete successfully.", $process);
+
+ if ($process->isSuccessful()) {
+ $this->info($this->getFeedback());
+ return;
+ }
+ }
- $this->shell->execInRoot("laravel new {$projectName}");
+ public function extraOptions()
+ {
+ return sprintf('%s%s%s',
+ config('lambo.store.auth') ? ' --auth' : '',
+ config('lambo.store.dev') ? ' --dev' : '',
+ config('lambo.store.with_output') ? '' : ' --quiet'
+ );
+ }
- // @todo
- // if ($isDev) {
- // $this->console->info('Creating application from dev branch.');
- // $this->shell->inDirectory($directory, "laravel new {$projectName} --dev");
- // } else {
- // $this->console->info('Creating application from release branch.');
- // $this->shell->inDirectory($directory, "laravel new {$projectName}");
- // }
+ public function getFeedback(): string
+ {
+ return sprintf("A new application '%s'%s has been created from the %s branch.",
+ config('lambo.store.project_name'),
+ config('lambo.store.auth') ? ' with auth scaffolding' : '',
+ config('lambo.store.dev') ? 'develop' : 'release'
+ );
}
+
}
diff --git a/app/Actions/SetConfig.php b/app/Actions/SetConfig.php
new file mode 100644
index 00000000..879323d4
--- /dev/null
+++ b/app/Actions/SetConfig.php
@@ -0,0 +1,235 @@
+savedConfig = $this->loadSavedConfig();
+ }
+
+ public function __invoke()
+ {
+ $tld = $this->getTld();
+
+ config()->set('lambo.store', [
+ 'tld' => $tld,
+ 'project_name' => $this->argument('projectName'),
+ 'root_path' => $this->getBasePath(),
+ 'project_path' => $this->getBasePath() . '/' . $this->argument('projectName'),
+ 'project_url' => $this->getProtocol() . $this->argument('projectName') . '.' . $tld,
+ 'database_name' => $this->getDatabaseName(),
+ 'database_username' => $this->getOptionValue('dbuser', self::DB_USERNAME) ?? 'root',
+ 'database_password' => $this->getOptionValue('dbpassword', self::DB_PASSWORD) ?? '',
+ 'create_database' => $this->shouldCreateDatabase(),
+ 'commit_message' => $this->getOptionValue('message', self::MESSAGE) ?? 'Initial commit.',
+ 'valet_link' => $this->shouldLink(),
+ 'valet_secure' => $this->shouldSecure(),
+ 'quiet' => $this->getBooleanOptionValue('quiet', self::QUIET),
+ 'with_output' => $this->getBooleanOptionValue('with-output', self::WITH_OUTPUT),
+ 'editor' => $this->getOptionValue('editor', self::CODEEDITOR),
+ 'node' => $this->shouldInstallNpmDependencies(),
+ 'mix' => $this->shouldRunMix(),
+ 'dev' => $this->getBooleanOptionValue('dev', self::DEVELOP),
+ 'auth' => $this->shouldInstallAuthentication(),
+ 'browser' => $this->getOptionValue('browser', self::BROWSER),
+ 'frontend' => $this->getFrontendType(),
+ 'full' => $this->getBooleanOptionValue('full'),
+ ]);
+ }
+
+ public function loadSavedConfig()
+ {
+ (Dotenv::create($this->configDir(), 'config'))->safeLoad();
+
+ return collect($this->keys)->reject(function ($key) {
+ return ! Arr::has($_ENV, $key);
+ })->mapWithKeys(function($value){
+ return [$value => $_ENV[$value]];
+ })->toArray();
+ }
+
+ public function getTld()
+ {
+ $home = config('home_dir');
+
+ if (File::exists($home . '/.config/valet/config.json')) {
+ return json_decode(File::get($home . '/.config/valet/config.json'))->tld;
+ }
+
+ return json_decode(File::get($home . '/.valet/config.json'))->domain;
+ }
+
+ public function getOptionValue($optionCommandLineName, $optionConfigFileName = null)
+ {
+ if (is_null($optionConfigFileName)) {
+ $optionConfigFileName = $optionCommandLineName;
+ }
+
+ if ($this->option($optionCommandLineName)) {
+ return $this->option($optionCommandLineName);
+ }
+
+ if (Arr::has($this->savedConfig, $optionConfigFileName)) {
+ return Arr::get($this->savedConfig, $optionConfigFileName);
+ }
+ }
+
+ /*
+ * Cast "1", "true", "on" and "yes" to bool true. Everything else to bool false.
+ */
+ public function getBooleanOptionValue($optionCommandLineName, $optionConfigFileName = null)
+ {
+ return filter_var($this->getOptionValue($optionCommandLineName, $optionConfigFileName), FILTER_VALIDATE_BOOLEAN);
+ }
+
+ public function getFrontendType()
+ {
+ $frontEndType = $this->getOptionValue('frontend', self::FRONTEND);
+
+ if (empty($frontEndType) || is_null($frontEndType)) {
+ return false;
+ }
+
+ if (in_array($frontEndType, self::FRONTEND_FRAMEWORKS)) {
+ return $frontEndType;
+ }
+ $this->error("Oops. '{$frontEndType}' is not a valid option for -f, --frontend.\nValid options are: bootstrap, react or vue.");
+ app(DisplayHelpScreen::class)();
+ exit();
+ }
+
+ public function getBasePath()
+ {
+ if ($value = $this->getOptionValue('path', self::PROJECTPATH)) {
+ return str_replace('~', config('home_dir'), $value);
+ }
+
+ return getcwd();
+ }
+
+ public function getProtocol()
+ {
+ return $this->shouldSecure() ? 'https://' : 'http://';
+ }
+
+ public function getDatabaseName()
+ {
+ $configuredDatabaseName = $this->getOptionValue('dbname', self::DB_NAME)
+ ? $this->getOptionValue('dbname', self::DB_NAME)
+ : $this->argument('projectName');
+
+ if (! Str::contains($configuredDatabaseName, '-')) {
+ return $configuredDatabaseName;
+ }
+
+ $newDatabaseName = str_replace('-', '_', $configuredDatabaseName);
+ $this->warn("Your configured database name {$configuredDatabaseName} contains hyphens which can cause problems in some instances.");
+ $this->warn('The hyphens have been replaced with underscores to prevent problems.');
+ $this->warn("New database name: {$newDatabaseName}.");
+ return $newDatabaseName;
+ }
+
+ public function argument($key)
+ {
+ return app('console')->argument($key);
+ }
+
+ public function option($key)
+ {
+ return app('console')->option($key);
+ }
+
+ public function shouldRunMix(): bool
+ {
+ return $this->getBooleanOptionValue('full')
+ || $this->getBooleanOptionValue('mix', self::MIX);
+ }
+
+ public function shouldInstallNpmDependencies(): bool
+ {
+ return $this->shouldRunMix()
+ || $this->getBooleanOptionValue('node', self::NODE);
+ }
+
+ public function shouldCreateDatabase(): bool
+ {
+ return $this->getBooleanOptionValue('full')
+ || $this->getBooleanOptionValue('create-db', self::CREATE_DATABASE);
+ }
+
+ public function shouldLink(): bool
+ {
+ return $this->getBooleanOptionValue('full')
+ || $this->getBooleanOptionValue('link', self::LINK);
+ }
+
+ public function shouldSecure(): bool
+ {
+ return $this->getBooleanOptionValue('full')
+ || $this->getBooleanOptionValue('secure', self::SECURE);
+ }
+
+ public function shouldInstallAuthentication(): bool
+ {
+ return $this->getBooleanOptionValue('full')
+ || $this->getBooleanOptionValue('auth', self::AUTH);
+ }
+}
diff --git a/app/Actions/SilentDevScript.php b/app/Actions/SilentDevScript.php
new file mode 100644
index 00000000..f10efe1c
--- /dev/null
+++ b/app/Actions/SilentDevScript.php
@@ -0,0 +1,38 @@
+packageJsonPath = config('lambo.store.project_path') . '/package.json';
+ $this->backupPackageJsonPath = config('lambo.store.project_path') . '/package-original.json';
+ }
+
+ public function add()
+ {
+ File::copy($this->packageJsonPath, $this->backupPackageJsonPath);
+ File::replace($this->packageJsonPath, $this->getSilentPackageJson($this->packageJsonPath));
+ }
+
+ public function remove()
+ {
+ File::move($this->backupPackageJsonPath, $this->packageJsonPath);
+ }
+
+ private function getSilentPackageJson(string $originalPackageJson)
+ {
+ $packageJson = json_decode(File::get($originalPackageJson), true);
+ $silentDevelopmentCommand = str_replace('--progress', '--no-progress', Arr::get($packageJson, 'scripts.development'));
+ Arr::set($packageJson, 'scripts.development', $silentDevelopmentCommand);
+
+ return json_encode($packageJson, JSON_UNESCAPED_SLASHES);
+ }
+}
diff --git a/app/Actions/ValetLink.php b/app/Actions/ValetLink.php
new file mode 100644
index 00000000..caaac87b
--- /dev/null
+++ b/app/Actions/ValetLink.php
@@ -0,0 +1,32 @@
+shell = $shell;
+ }
+
+ public function __invoke()
+ {
+ if (! config('lambo.store.valet_link')) {
+ return;
+ }
+
+ $this->logStep('Running valet link');
+
+ $process = $this->shell->execInProject('valet link');
+
+ $this->abortIf(! $process->isSuccessful(), 'valet link did not complete successfully', $process);
+
+ $this->info('valet link successful');
+ }
+}
diff --git a/app/Actions/ValetSecure.php b/app/Actions/ValetSecure.php
index caa81e61..e4122eb0 100644
--- a/app/Actions/ValetSecure.php
+++ b/app/Actions/ValetSecure.php
@@ -2,10 +2,12 @@
namespace App\Actions;
-use App\Shell;
+use App\Shell\Shell;
class ValetSecure
{
+ use LamboAction;
+
protected $shell;
public function __construct(Shell $shell)
@@ -15,6 +17,16 @@ public function __construct(Shell $shell)
public function __invoke()
{
- $this->shell->execInProject("valet secure");
+ if (! config('lambo.store.valet_secure')) {
+ return;
+ }
+
+ $this->logStep('Running valet secure');
+
+ $process = $this->shell->execInProject("valet secure");
+
+ $this->abortIf(! $process->isSuccessful(), 'valet secure did not complete successfully', $process);
+
+ $this->info('valet secure successful');
}
}
diff --git a/app/Actions/VerifyDependencies.php b/app/Actions/VerifyDependencies.php
index 20858a3b..758041d1 100644
--- a/app/Actions/VerifyDependencies.php
+++ b/app/Actions/VerifyDependencies.php
@@ -7,25 +7,23 @@
class VerifyDependencies
{
- protected $finder;
+ use LamboAction;
- protected $dependencies = [
- 'laravel',
- 'git',
- 'valet',
- ];
+ protected $finder;
public function __construct(ExecutableFinder $finder)
{
$this->finder = $finder;
}
- public function __invoke()
+ public function __invoke(array $dependencies)
{
- foreach ($this->dependencies as $dependency) {
+ $this->logStep('Verifying dependencies');
+ foreach ($dependencies as $dependency) {
if ($this->finder->find($dependency) === null) {
throw new Exception($dependency . ' not installed');
}
}
+ $this->info('Dependencies: ' . implode(', ', $dependencies) . ' are available.');
}
}
diff --git a/app/Actions/VerifyPathAvailable.php b/app/Actions/VerifyPathAvailable.php
index a4da212b..09e2b0d6 100644
--- a/app/Actions/VerifyPathAvailable.php
+++ b/app/Actions/VerifyPathAvailable.php
@@ -4,28 +4,28 @@
use Exception;
use Illuminate\Filesystem\Filesystem;
+use Illuminate\Support\Facades\File;
class VerifyPathAvailable
{
- protected $filesystem;
-
- public function __construct(Filesystem $filesystem)
- {
- $this->filesystem = $filesystem;
- }
+ use LamboAction;
public function __invoke()
{
+ $this->logStep('Verifying path availability');
+
$rootPath = config('lambo.store.root_path');
- if (! $this->filesystem->isDirectory($rootPath)) {
+ if (! File::isDirectory($rootPath)) {
throw new Exception($rootPath . ' is not a directory.');
}
$projectPath = config('lambo.store.project_path');
- if ($this->filesystem->isDirectory($projectPath)) {
+ if (File::isDirectory($projectPath)) {
throw new Exception($projectPath . ' is already a directory.');
}
+
+ $this->info('Directory ' . $projectPath . ' is available.');
}
}
diff --git a/app/Commands/EditAfter.php b/app/Commands/EditAfter.php
index 871193df..2096ab5f 100644
--- a/app/Commands/EditAfter.php
+++ b/app/Commands/EditAfter.php
@@ -2,16 +2,21 @@
namespace App\Commands;
+use App\Actions\EditAfter as EditAfterAction;
use LaravelZero\Framework\Commands\Command;
class EditAfter extends Command
{
- protected $signature = 'edit-after';
+ protected $signature = 'edit-after {--editor= : Open the config file in the specified EDITOR or the system default if none is specified.}';
- protected $description = 'Edit After File';
+ protected $description = 'Edit Config File. A new config file is created if one does not already exist.';
public function handle()
{
- // @todo
+ app()->bind('console', function () {
+ return $this;
+ });
+
+ app(EditAfterAction::class)();
}
}
diff --git a/app/Commands/EditConfig.php b/app/Commands/EditConfig.php
index 56cf8667..f28705c3 100644
--- a/app/Commands/EditConfig.php
+++ b/app/Commands/EditConfig.php
@@ -2,16 +2,21 @@
namespace App\Commands;
+use App\Actions\EditConfig as EditConfigAction;
use LaravelZero\Framework\Commands\Command;
class EditConfig extends Command
{
- protected $signature = 'edit-config';
+ protected $signature = 'edit-config {--editor= : Open the config file in the specified EDITOR or the system default if none is specified.}';
- protected $description = 'Edit Config File';
+ protected $description = 'Edit Config File. A new config file is created if one does not already exist.';
public function handle()
{
- // @todo
+ app()->bind('console', function () {
+ return $this;
+ });
+
+ app(EditConfigAction::class)();
}
}
diff --git a/app/Commands/MakeAfter.php b/app/Commands/MakeAfter.php
deleted file mode 100644
index 7834d5e2..00000000
--- a/app/Commands/MakeAfter.php
+++ /dev/null
@@ -1,17 +0,0 @@
-setConfig();
+ app()->bind('console', function () {
+ return $this;
+ });
app(DisplayLamboWelcome::class)();
@@ -63,93 +69,43 @@ public function handle()
$this->alert('Creating a Laravel app ' . $this->argument('projectName'));
+ app(SetConfig::class)();
+
try {
- $this->logStep('Verifying Path Availability');
app(VerifyPathAvailable::class)();
- $this->logStep('Verifying Dependencies');
- app(VerifyDependencies::class)();
+ app(VerifyDependencies::class)(['laravel', 'git', 'valet']);
- $this->logStep('Running the Laravel Installer');
app(RunLaravelInstaller::class)();
- $this->logStep('Opening In Editor');
app(OpenInEditor::class)();
- $this->logStep('Customizing .env and .env.example');
app(CustomizeDotEnv::class)();
- $this->logStep('Creating database if selected...');
- app(CreateDatabase::class)();
+ $this->info(app(CreateDatabase::class)());
- $this->logStep('Running php artisan key:generate');
app(GenerateAppKey::class)();
- $this->logStep('Initializing Git Repo');
+ app(ConfigureFrontendFramework::class)();
+
app(InitializeGitRepo::class)();
- $this->logStep('Installing NPM dependencies');
app(InstallNpmDependencies::class)();
- $this->logStep('Running valet secure');
- app(ValetSecure::class)();
-
- $this->logStep('Opening in Browser');
- app(OpenInBrowser::class)();
- } catch (Exception $e) {
- $this->error("\nFAILURE RUNNING COMMAND:");
- $this->error($e->getMessage());
- }
- // @todo cd into it
- }
-
- public function setConfig()
- {
- app()->bind('console', function () {
- return $this;
- });
+ app(CompileAssets::class)();
- $tld = $this->getTld();
+ app(RunAfterScript::class)();
- config()->set('lambo.store', [
- 'tld' => $tld,
- 'project_name' => $this->argument('projectName'),
- 'root_path' => $this->getBasePath(),
- 'project_path' => $this->getBasePath() . '/' . $this->argument('projectName'),
- 'project_url' => $this->getProtocol() . $this->argument('projectName') . '.' . $tld,
- ]);
- }
+ app(ValetLink::class)();
- public function getTld()
- {
- $home = config('home_dir');
-
- if (File::exists($home . '/.config/valet/config.json')) {
- return json_decode(File::get($home . '/.config/valet/config.json'))->tld;
- }
-
- return json_decode(File::get($home . '/.valet/config.json'))->domain;
- }
-
- public function getBasePath()
- {
- if ($this->option('path')) {
- return str_replace('~', config('home_dir'), $this->option('path'));
- }
-
- return getcwd();
- }
+ app(ValetSecure::class)();
- public function getProtocol()
- {
- // @todo: If securing, change to https
- return 'http://';
- }
+ app(OpenInBrowser::class)();
- public function logStep($step)
- {
- if ($this->option('verbose')) {
- $this->comment("$step...\n");
+ $this->info("\nDone. Happy coding!");
+ } catch (Exception $e) {
+ $this->error("\nFAILURE: " . $e->getMessage());
}
+ // @todo cd into it
}
}
diff --git a/app/Environment.php b/app/Environment.php
new file mode 100644
index 00000000..8b60af97
--- /dev/null
+++ b/app/Environment.php
@@ -0,0 +1,11 @@
+ensureConfigFileExists($fileName, $fileTemplate);
+
+ $this->editConfigFile($this->getConfigFilePath($fileName));
+ }
+
+ protected function ensureConfigFileExists(string $fileName, string $fileTemplate)
+ {
+ $this->ensureConfigDirExists();
+
+ if (! $this->configFileExists($fileName)) {
+ app('console')->info("File: {$this->getConfigFilePath($fileName)} does not exist, creating it now.");
+ File::put($this->getConfigFilePath($fileName), $fileTemplate);
+ }
+ }
+
+ public function ensureConfigDirExists()
+ {
+ if (! File::exists($this->configDir())) {
+ app('console')->info("Config directory: {$this->configDir()} does not exist, creating it now.");
+ File::makeDirectory($this->configDir());
+ }
+ }
+
+ public function configFileExists($fileName)
+ {
+ return File::exists($this->configDir() . '/' . $fileName);
+ }
+
+ public function getConfigFilePath(string $fileName)
+ {
+ return $this->configDir() . "/" . $fileName;
+ }
+
+ protected function editConfigFile(string $filePath)
+ {
+ if (! Environment::isMac()) {
+ exec("xdg-open {$filePath}");
+ return;
+ }
+
+ if ($this->editor()) {
+ exec(sprintf('"%s" "%s"',
+ $this->editor(),
+ $filePath
+ ));
+ return;
+ }
+
+ exec("open {$filePath}");
+ }
+
+ public function editor()
+ {
+ return app('console')->option('editor');
+ }
+}
diff --git a/app/LogsToConsole.php b/app/LogsToConsole.php
new file mode 100644
index 00000000..10037d65
--- /dev/null
+++ b/app/LogsToConsole.php
@@ -0,0 +1,31 @@
+alert($message);
+ }
+
+ public function warn(string $message)
+ {
+ app('console')->warn($message);
+ }
+
+ public function error(string $message)
+ {
+ app('console')->error($message);
+ }
+
+ public function line(string $message)
+ {
+ app('console')->line($message);
+ }
+
+ public function info(string $message)
+ {
+ app('console')->info($message);
+ }
+}
diff --git a/app/Options.php b/app/Options.php
index 0074b62d..6e8ff59a 100644
--- a/app/Options.php
+++ b/app/Options.php
@@ -27,13 +27,19 @@ class Options
[
'short' => 'b',
'long' => 'browser',
- 'param_description' => '"browser path"',
+ 'param_description' => '"path"',
'cli_description' => "Open the site in the specified browser (macOS-only)",
],
[
- 'long' => 'create-db',
- 'param_description' => 'DBNAME', // Maybe?? @todo
- 'cli_description' => "Create a new MySQL database",
+ 'short' => 'f',
+ 'long' => 'frontend',
+ 'param_description' => '"FRONTEND"',
+ 'cli_description' => "Specify the FRONTEND framework to use. Must be one of bootstrap, react or vue",
+ ],
+ [
+ 'long' => 'dbname',
+ 'param_description' => 'DBNAME',
+ 'cli_description' => "Specify the database name",
],
[
'long' => 'dbuser',
@@ -42,23 +48,12 @@ class Options
],
[
'long' => 'dbpassword',
- 'param_description' => ' PASSWORD',
+ 'param_description' => 'PASSWORD',
'cli_description' => "Specify the database password",
],
[
- 'short' => 'l',
- 'long' => 'link',
- 'cli_description' => "Create a Valet link to the project directory",
- ],
- [
- 'short' => 's',
- 'long' => 'secure',
- 'cli_description' => "Generate and use an HTTPS cert with Valet",
- ],
- [
- 'short' => 'q',
- 'long' => 'quiet',
- 'cli_description' => "Use quiet mode to hide most messages",
+ 'long' => 'create-db',
+ 'cli_description' => "Create a new MySQL database",
],
[
'short' => 'd',
@@ -71,21 +66,37 @@ class Options
'cli_description' => "Scaffold the routes and views for basic Laravel auth",
],
[
- 'short' => 'n',
+ // 'short' => 'n',
'long' => 'node',
'cli_description' => "Run 'npm install' after creating the project",
],
[
- 'long' => 'vue',
- 'cli_description' => "Specify Vue as the frontend",
+ 'short' => 'x',
+ 'long' => 'mix',
+ 'cli_description' => "Run 'npm run dev' after creating the project",
+ ],
+ [
+ 'short' => 'l',
+ 'long' => 'link',
+ 'cli_description' => "Create a Valet link to the project directory",
+ ],
+ [
+ 'short' => 's',
+ 'long' => 'secure',
+ 'cli_description' => "Generate and use an HTTPS cert with Valet",
+ ],
+ [
+ 'long' => 'full',
+ 'cli_description' => "Shortcut of --create-db --link --secure --auth --node --mix",
],
[
- 'long' => 'react',
- 'cli_description' => "Specify React as the frontend",
+ 'long' => 'with-output',
+ 'cli_description' => "Show command line output from shell commands",
],
[
- 'long' => 'bootstrap',
- 'cli_description' => "Specify Bootstrap as the frontend",
+ 'short' => 'q',
+ 'long' => 'quiet',
+ 'cli_description' => "Use quiet mode to hide most messages from lambo",
],
];
diff --git a/app/Shell.php b/app/Shell.php
deleted file mode 100644
index 7a3ee0a3..00000000
--- a/app/Shell.php
+++ /dev/null
@@ -1,48 +0,0 @@
-rootPath = $config->get('lambo.store.root_path');
- $this->projectPath = $config->get('lambo.store.project_path');
- }
-
- public function execInRoot($command)
- {
- return $this->exec("cd {$this->rootPath} && $command");
- }
-
- public function execInProject($command)
- {
- return $this->exec("cd {$this->projectPath} && $command");
- }
-
- protected function exec($command)
- {
- $process = app()->make(Process::class, [
- 'command' => $command,
- ]);
-
- $process->setTimeout(null);
-
- // @todo resolve this
- $process->run(function ($type, $buffer) /*use ($showOutput)*/ {
- echo $buffer;
-
- // if (Process::ERR === $type) {
- // echo 'ERR > ' . $buffer;
- // } elseif ($showOutput) {
- // echo $buffer;
- // }
- });
- }
-}
diff --git a/app/Shell/ColorOutputFormatter.php b/app/Shell/ColorOutputFormatter.php
new file mode 100644
index 00000000..6554b96e
--- /dev/null
+++ b/app/Shell/ColorOutputFormatter.php
@@ -0,0 +1,21 @@
+ RUN > %s>";
+ }
+
+ public function getErrorMessageFormat(): string
+ {
+ return " ERR > %s";
+ }
+
+ public function getMessageFormat(): string
+ {
+ return " OUT > %s";
+ }
+}
diff --git a/app/Shell/ConsoleOutputFormatter.php b/app/Shell/ConsoleOutputFormatter.php
new file mode 100644
index 00000000..3e7abb11
--- /dev/null
+++ b/app/Shell/ConsoleOutputFormatter.php
@@ -0,0 +1,26 @@
+getStartMessageFormat(), $message);
+ }
+
+ public function progress(string $buffer, bool $error)
+ {
+ if ($error) {
+ return rtrim(sprintf($this->getErrorMessageFormat(), $buffer));
+ }
+
+ return rtrim(sprintf($this->getMessageFormat(), $buffer));
+ }
+
+ abstract function getStartMessageFormat(): string;
+
+ abstract function getErrorMessageFormat(): string;
+
+ abstract function getMessageFormat(): string;
+}
diff --git a/app/Shell/PlainOutputFormatter.php b/app/Shell/PlainOutputFormatter.php
new file mode 100644
index 00000000..8f32afaf
--- /dev/null
+++ b/app/Shell/PlainOutputFormatter.php
@@ -0,0 +1,21 @@
+rootPath = $config->get('lambo.store.root_path');
+ $this->projectPath = $config->get('lambo.store.project_path');
+ }
+
+ public function execInRoot($command)
+ {
+ return $this->exec("cd {$this->rootPath} && $command", $command);
+ }
+
+ public function execInProject($command)
+ {
+ return $this->exec("cd {$this->projectPath} && $command", $command);
+ }
+
+ public function getOutputFormatter()
+ {
+ return app('console')->option('no-ansi')
+ ? new PlainOutputFormatter
+ : new ColorOutputFormatter;
+ }
+
+ public function buildProcess($command): Process
+ {
+ $process = app()->make(Process::class, [
+ 'command' => $command,
+ ]);
+ $process->setTimeout(null);
+ return $process;
+ }
+
+ protected function exec($command, $description)
+ {
+ $showConsoleOutput = config('lambo.store.with_output');
+ $out = app(\Symfony\Component\Console\Output\ConsoleOutput::class);
+
+ $outputFormatter = $this->getOutputFormatter();
+ $out->writeln($outputFormatter->start($description));
+
+ $process = $this->buildProcess($command);
+ $process->run(function ($type, $buffer) use ($out, $outputFormatter, $showConsoleOutput) {
+ if (empty($buffer) || $buffer === PHP_EOL) {
+ return;
+ }
+
+ if (Process::ERR === $type || $showConsoleOutput) {
+ $out->writeln(
+ $outputFormatter->progress(
+ $buffer,
+ Process::ERR === $type
+ )
+ );
+ }
+ });
+
+ return $process;
+ }
+}
diff --git a/app/Utilities.php b/app/Utilities.php
deleted file mode 100644
index 1b5bf9fb..00000000
--- a/app/Utilities.php
+++ /dev/null
@@ -1,11 +0,0 @@
-make(Illuminate\Contracts\Console\Kernel::class);
+$app->bind('Symfony\Component\Console\Output\ConsoleOutput', function () {
+ return new Symfony\Component\Console\Output\ConsoleOutput;
+});
+
+
$status = $kernel->handle(
$input = new Symfony\Component\Console\Input\ArgvInput,
- new Symfony\Component\Console\Output\ConsoleOutput
+ $app->make('Symfony\Component\Console\Output\ConsoleOutput')
);
/*
diff --git a/readme.md b/readme.md
index da9ca2da..837df98d 100644
--- a/readme.md
+++ b/readme.md
@@ -90,7 +90,7 @@ There are also a few optional behaviors based on the parameters you pass (or def
lambo new superApplication --auth
```
-- `-n` or `--node` to run `yarn` if installed, otherwise runs `npm install` after creating the project
+- `--node` to run `yarn` if installed, otherwise runs `npm install` after creating the project
```bash
lambo new superApplication --node
@@ -147,12 +147,6 @@ There are also a few optional behaviors based on the parameters you pass (or def
### Commands
-- `make-config` creates a config file so you don't have to pass the parameters every time you use Lambo
-
- ```bash
- lambo make-config
- ```
-
- `edit-config` edits your config file
```bash
diff --git a/storage/framework/cache/facade-1a2f843361f87bd19bd57e0f1cb9c778ea2d3e08.php b/storage/framework/cache/facade-1a2f843361f87bd19bd57e0f1cb9c778ea2d3e08.php
deleted file mode 100644
index 4fa34496..00000000
--- a/storage/framework/cache/facade-1a2f843361f87bd19bd57e0f1cb9c778ea2d3e08.php
+++ /dev/null
@@ -1,21 +0,0 @@
-silentDevScript = $this->mock(SilentDevScript::class);
+ $this->shell = $this->mock(Shell::class);
+ }
+
+ /** @test */
+ function it_compiles_project_assets_and_hides_console_output()
+ {
+ Config::set('lambo.store.mix', true);
+
+ $this->silentDevScript->shouldReceive('add')
+ ->once()
+ ->globally()
+ ->ordered();
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('npm run dev --silent')
+ ->once()
+ ->andReturn(FakeProcess::success())
+ ->globally()
+ ->ordered();
+
+ $this->silentDevScript->shouldReceive('remove')
+ ->once()
+ ->globally()
+ ->ordered();
+
+ app(CompileAssets::class)();
+ }
+
+ /** @test */
+ function it_compiles_project_assets_and_shows_console_output()
+ {
+ Config::set('lambo.store.mix', true);
+ Config::set('lambo.store.with_output', true);
+
+ $this->silentDevScript->shouldReceive('add')
+ ->once()
+ ->globally()
+ ->ordered();
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('npm run dev')
+ ->once()
+ ->andReturn(FakeProcess::success())
+ ->globally()
+ ->ordered();
+
+ $this->silentDevScript->shouldReceive('remove')
+ ->once()
+ ->globally()
+ ->ordered();
+
+ app(CompileAssets::class)();
+ }
+
+ /** @test */
+ function it_skips_asset_compilation_if_it_is_not_requested()
+ {
+ $this->silentDevScript = $this->spy(SilentDevScript::class);
+ $this->shell = $this->spy(Shell::class);
+
+ Config::set('lambo.store.mix', false);
+
+ app(CompileAssets::class)();
+
+ $this->silentDevScript->shouldNotHaveReceived('add');
+ $this->shell->shouldNotHaveReceived('execInProject');
+ $this->silentDevScript->shouldNotHaveReceived('remove');
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_asset_compilation_fails()
+ {
+ Config::set('lambo.store.mix', true);
+
+ $this->silentDevScript->shouldReceive('add')
+ ->once()
+ ->globally()
+ ->ordered();
+
+ $command = 'npm run dev --silent';
+ $this->shell->shouldReceive('execInProject')
+ ->with($command)
+ ->once()
+ ->andReturn(FakeProcess::fail($command))
+ ->globally()
+ ->ordered();
+
+ $this->expectException(Exception::class);
+
+ app(CompileAssets::class)();
+ }
+}
diff --git a/tests/Feature/ConfigureFrontendFrameworkTest.php b/tests/Feature/ConfigureFrontendFrameworkTest.php
new file mode 100644
index 00000000..0919c53e
--- /dev/null
+++ b/tests/Feature/ConfigureFrontendFrameworkTest.php
@@ -0,0 +1,81 @@
+shell = $this->mock(Shell::class);
+ $this->laravelUi = $this->mock(LaravelUi::class);
+ }
+
+ /** @test */
+ function it_installs_the_specified_front_end_framework()
+ {
+ Config::set('lambo.store.frontend', 'foo-frontend');
+
+ $this->laravelUi->shouldReceive('install')
+ ->once()
+ ->globally()
+ ->ordered();
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('php artisan ui foo-frontend')
+ ->once()
+ ->andReturn(FakeProcess::success())
+ ->globally()
+ ->ordered();
+
+ app(ConfigureFrontendFramework::class)();
+ }
+
+ /** @test */
+ function it_does_not_install_a_frontend_framework_when_none_is_specified()
+ {
+ $shell = $this->spy(Shell::class);
+ $laravelUi = $this->spy(LaravelUi::class);
+
+ $this->assertEmpty(Config::get('lambo.store.frontend'));
+
+ app(ConfigureFrontendFramework::class);
+
+ $laravelUi->shouldNotHaveReceived('install');
+ $shell->shouldNotHaveReceived('execInProject');
+ }
+
+ /** @test */
+ function it_throws_and_exception_if_the_ui_framework_installation_fails()
+ {
+ Config::set('lambo.store.frontend', 'foo-frontend');
+
+ $this->laravelUi->shouldReceive('install')
+ ->once()
+ ->globally()
+ ->ordered();
+
+ $command = 'php artisan ui foo-frontend';
+ $this->shell->shouldReceive('execInProject')
+ ->with($command)
+ ->once()
+ ->andReturn(FakeProcess::fail($command))
+ ->globally()
+ ->ordered();
+
+ $this->expectException(Exception::class);
+
+ app(ConfigureFrontendFramework::class)();
+ }
+}
diff --git a/tests/Feature/CreateDatabaseTest.php b/tests/Feature/CreateDatabaseTest.php
new file mode 100644
index 00000000..fcac8a94
--- /dev/null
+++ b/tests/Feature/CreateDatabaseTest.php
@@ -0,0 +1,83 @@
+shell = $this->mock(Shell::class);
+ }
+
+ /** @test */
+ function it_creates_a_mysql_database()
+ {
+ Config::set('lambo.store.create_database', true);
+ Config::set('lambo.store.database_username', 'user');
+ Config::set('lambo.store.database_password', 'password');
+ Config::set('lambo.store.database_name', 'database_name');
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('mysql --user=user --password=password -e "CREATE DATABASE IF NOT EXISTS database_name";')
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ $this->assertStringContainsString(Config::get('lambo.store.database_name'), app(CreateDatabase::class)());
+ }
+
+ /** @test */
+ function it_checks_if_mysql_is_installed()
+ {
+ $executableFinder = $this->mock(ExecutableFinder::class);
+
+ Config::set('lambo.store.create_database', true);
+ Config::set('lambo.store.database_username', 'user');
+ Config::set('lambo.store.database_password', 'password');
+ Config::set('lambo.store.database_name', 'database_name');
+
+ $executableFinder->shouldReceive('find')
+ ->with('mysql')
+ ->once()
+ ->andReturn(null);
+
+ $this->assertEquals('MySQL does not seem to be installed. Skipping new database creation.', app(CreateDatabase::class)());
+ }
+
+ /**
+ * @todo do we need to test that database creation only happens when MySQL is the configured database?
+ * @test
+ */
+ function it_only_runs_when_mysql_is_the_configured_database()
+ {
+ $this->markTestSkipped('*** @TODO: Add Test: "it only runs when mysql is the configured database" ***');
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_database_creation_fails()
+ {
+ Config::set('lambo.store.create_database', true);
+ Config::set('lambo.store.database_username', 'user');
+ Config::set('lambo.store.database_password', 'password');
+ Config::set('lambo.store.database_name', 'database_name');
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('mysql --user=user --password=password -e "CREATE DATABASE IF NOT EXISTS database_name";')
+ ->once()
+ ->andReturn(FakeProcess::fail('mysql --user=user --password=password -e "CREATE DATABASE IF NOT EXISTS database_name";'));
+
+ $this->expectException(Exception::class);
+
+ app(CreateDatabase::class)();
+ }
+}
diff --git a/tests/Feature/CustomizeDotEnvTest.php b/tests/Feature/CustomizeDotEnvTest.php
index 06b33d90..7b869d86 100644
--- a/tests/Feature/CustomizeDotEnvTest.php
+++ b/tests/Feature/CustomizeDotEnvTest.php
@@ -3,13 +3,50 @@
namespace Tests\Feature;
use App\Actions\CustomizeDotEnv;
+use Illuminate\Support\Facades\Config;
+use Illuminate\Support\Facades\File;
use Tests\TestCase;
class CustomizeDotEnvTest extends TestCase
{
+ /** @test */
+ function it_saves_the_customized_dot_env_files()
+ {
+ app()->bind('console', function () {
+ return new class{
+ public function comment(){}
+ public function info(){}
+ };
+ });
+
+ Config::set('lambo.store.project_name', 'my-project');
+ Config::set('lambo.store.database_name', 'my_project');
+ Config::set('lambo.store.project_url', 'http://my-project.example.com');
+ Config::set('lambo.store.database_username', 'username');
+ Config::set('lambo.store.database_password', 'password');
+ Config::set('lambo.store.project_path', '/some/project/path');
+
+ $originalDotEnv = File::get(base_path('tests/Feature/Fixtures/.env.original'));
+ $customizedDotEnv = File::get(base_path('tests/Feature/Fixtures/.env.customized'));
+
+ File::shouldReceive('get')
+ ->once()->with('/some/project/path/.env.example')
+ ->andReturn($originalDotEnv);
+
+ File::shouldReceive('put')
+ ->with('/some/project/path/.env.example', $customizedDotEnv);
+
+ File::shouldReceive('put')
+ ->with('/some/project/path/.env', $customizedDotEnv);
+
+ (new CustomizeDotEnv)();
+ }
+
/** @test */
function it_replaces_static_strings()
{
+ config()->set('lambo.store.database_username', 'root');
+
$customizeDotEnv = new CustomizeDotEnv;
$contents = "DB_USERNAME=previous";
$contents = $customizeDotEnv->customize($contents);
@@ -19,6 +56,8 @@ function it_replaces_static_strings()
/** @test */
function un_targeted_lines_are_unchanged()
{
+ config()->set('lambo.store.database_username', 'root');
+
$customizeDotEnv = new CustomizeDotEnv;
$contents = "DB_USERNAME=previous\nDONT_TOUCH_ME=cant_touch_me";
$contents = $customizeDotEnv->customize($contents);
@@ -42,23 +81,4 @@ function line_breaks_remain()
$contents = $customizeDotEnv->customize($contents);
$this->assertEquals("A=B\n\nC=D", $contents);
}
-
- /** @test */
- function it_replaces_dashes_with_underscores_in_database_names()
- {
- config()->set('lambo.store.project_name', 'with-dashes');
-
- $customizeDotEnv = new CustomizeDotEnv;
- $contents = "DB_DATABASE=previous";
- $contents = $customizeDotEnv->customize($contents);
- $this->assertEquals("DB_DATABASE=with_dashes", $contents);
- }
-
- /** @test */
- function it_uses_passed_database_name_if_passed()
- {
- $this->markTestIncomplete('@todo');
-
-
- }
}
diff --git a/tests/Feature/Fakes/FakeProcess.php b/tests/Feature/Fakes/FakeProcess.php
new file mode 100644
index 00000000..79e7edf8
--- /dev/null
+++ b/tests/Feature/Fakes/FakeProcess.php
@@ -0,0 +1,35 @@
+isSuccessful = $isSuccessful;
+ $this->failedCommand = $failedCommand;
+ }
+
+ public function isSuccessful()
+ {
+ return $this->isSuccessful;
+ }
+
+ public function getCommandLine()
+ {
+ return $this->failedCommand;
+ }
+}
diff --git a/tests/Feature/Fixtures/.env.customized b/tests/Feature/Fixtures/.env.customized
new file mode 100644
index 00000000..fb499c1d
--- /dev/null
+++ b/tests/Feature/Fixtures/.env.customized
@@ -0,0 +1,6 @@
+APP_NAME=my-project
+APP_URL=http://my-project.example.com
+
+DB_DATABASE=my_project
+DB_USERNAME=username
+DB_PASSWORD=password
diff --git a/tests/Feature/Fixtures/.env.original b/tests/Feature/Fixtures/.env.original
new file mode 100644
index 00000000..b0a5f9a2
--- /dev/null
+++ b/tests/Feature/Fixtures/.env.original
@@ -0,0 +1,6 @@
+APP_NAME=Laravel
+APP_URL=http://my-project.example.com
+
+DB_DATABASE=laravel
+DB_USERNAME=root
+DB_PASSWORD=
diff --git a/tests/Feature/Fixtures/composer-with-laravel-ui.json b/tests/Feature/Fixtures/composer-with-laravel-ui.json
new file mode 100644
index 00000000..ad373d7e
--- /dev/null
+++ b/tests/Feature/Fixtures/composer-with-laravel-ui.json
@@ -0,0 +1,5 @@
+{
+ "require": {
+ "laravel/ui": "^1.0"
+ }
+}
diff --git a/tests/Feature/Fixtures/composer-without-laravel-ui.json b/tests/Feature/Fixtures/composer-without-laravel-ui.json
new file mode 100644
index 00000000..df8dd9cf
--- /dev/null
+++ b/tests/Feature/Fixtures/composer-without-laravel-ui.json
@@ -0,0 +1,4 @@
+{
+ "require": {
+ }
+}
diff --git a/tests/Feature/Fixtures/package-silent.json b/tests/Feature/Fixtures/package-silent.json
new file mode 100644
index 00000000..84cd72d8
--- /dev/null
+++ b/tests/Feature/Fixtures/package-silent.json
@@ -0,0 +1 @@
+{"scripts":{"development":"cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"}}
diff --git a/tests/Feature/Fixtures/package.json b/tests/Feature/Fixtures/package.json
new file mode 100644
index 00000000..be5a15de
--- /dev/null
+++ b/tests/Feature/Fixtures/package.json
@@ -0,0 +1,5 @@
+{
+ "scripts": {
+ "development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
+ }
+}
diff --git a/tests/Feature/GenerateAppKeyTest.php b/tests/Feature/GenerateAppKeyTest.php
new file mode 100644
index 00000000..96d489e2
--- /dev/null
+++ b/tests/Feature/GenerateAppKeyTest.php
@@ -0,0 +1,44 @@
+shell = $this->mock(Shell::class);
+ }
+
+ /** @test */
+ function it_generates_a_new_app_key()
+ {
+ $this->shell->shouldReceive('execInProject')
+ ->with('php artisan key:generate')
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ app(GenerateAppKey::class)();
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_new_app_key_generation_fails()
+ {
+ $this->shell->shouldReceive('execInProject')
+ ->with('php artisan key:generate')
+ ->once()
+ ->andReturn(FakeProcess::fail('php artisan key:generate'));
+
+ $this->expectException(Exception::class);
+
+ app(GenerateAppKey::class)();
+ }
+}
diff --git a/tests/Feature/InitializeGitRepoTest.php b/tests/Feature/InitializeGitRepoTest.php
new file mode 100644
index 00000000..edbc897a
--- /dev/null
+++ b/tests/Feature/InitializeGitRepoTest.php
@@ -0,0 +1,101 @@
+shell = $this->mock(Shell::class);
+ }
+
+ /** @test */
+ function it_initialises_the_projects_git_repository()
+ {
+ Config::set('lambo.store.commit_message', 'Initial commit');
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('git init')
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('git add .')
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('git commit -m "' . 'Initial commit' . '"')
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ app(InitializeGitRepo::class)();
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_git_init_fails()
+ {
+ $this->shell->shouldReceive('execInProject')
+ ->with('git init')
+ ->once()
+ ->andReturn(FakeProcess::fail('git init'));
+
+ $this->expectException(Exception::class);
+
+ app(InitializeGitRepo::class)();
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_git_add_fails()
+ {
+ $this->shell->shouldReceive('execInProject')
+ ->with('git init')
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('git add .')
+ ->once()
+ ->andReturn(FakeProcess::fail('git add .'));
+
+ $this->expectException(Exception::class);
+
+ app(InitializeGitRepo::class)();
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_git_commit_fails()
+ {
+ Config::set('lambo.store.commit_message', 'Initial commit');
+
+ $command = 'git init';
+ $this->shell->shouldReceive('execInProject')
+ ->with($command)
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('git add .')
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('git commit -m "Initial commit"')
+ ->once()
+ ->andReturn(FakeProcess::fail('git commit -m "Initial commit"'));
+
+ $this->expectException(Exception::class);
+
+ app(InitializeGitRepo::class)();
+ }
+}
diff --git a/tests/Feature/InstallNpmDependenciesTest.php b/tests/Feature/InstallNpmDependenciesTest.php
new file mode 100644
index 00000000..8f297480
--- /dev/null
+++ b/tests/Feature/InstallNpmDependenciesTest.php
@@ -0,0 +1,65 @@
+shell = $this->mock(Shell::class);
+ }
+
+ /** @test */
+ function it_installs_npm_dependencies()
+ {
+ Config::set('lambo.store.node', true);
+ Config::set('lambo.store.with-output', false);
+
+ $this->shell->shouldReceive('execInProject')
+ ->with("npm install --silent")
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ app(InstallNpmDependencies::class)();
+ }
+
+ /** @test */
+ function it_installs_npm_dependencies_and_shows_console_output()
+ {
+ Config::set('lambo.store.node', true);
+ Config::set('lambo.store.with-output', true);
+
+ $this->shell->shouldReceive('execInProject')
+ ->with("npm install")
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ app(InstallNpmDependencies::class)();
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_npm_install_fails()
+ {
+ Config::set('lambo.store.node', true);
+ Config::set('lambo.store.with-output', false);
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('npm install --silent')
+ ->once()
+ ->andReturn(FakeProcess::fail('npm install --silent'));
+
+ $this->expectException(Exception::class);
+
+ app(InstallNpmDependencies::class)();
+ }
+}
diff --git a/tests/Feature/LaravelUiTest.php b/tests/Feature/LaravelUiTest.php
new file mode 100644
index 00000000..230413d5
--- /dev/null
+++ b/tests/Feature/LaravelUiTest.php
@@ -0,0 +1,82 @@
+shell = $this->mock(Shell::class);
+ }
+
+ /** @test */
+ function it_installs_laravel_ui()
+ {
+ Config::set('lambo.store.project_path', '/some/project/path');
+
+ $composerJsonFixture = File::get(base_path('tests/Feature/Fixtures/composer-without-laravel-ui.json'));
+
+ File::shouldReceive('get')
+ ->with('/some/project/path/composer.json')
+ ->once()
+ ->andReturn($composerJsonFixture)
+ ->globally()
+ ->ordered();
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('composer require laravel/ui --quiet')
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ app(LaravelUi::class)->install();
+ }
+
+ /** @test */
+ function it_does_not_install_laravel_ui_if_it_is_already_present()
+ {
+ $shell = $this->spy(Shell::class);
+
+ Config::set('lambo.store.project_path', '/some/project/path');
+
+ $composerJsonFixture = File::get(base_path('tests/Feature/Fixtures/composer-with-laravel-ui.json'));
+
+ File::shouldReceive('get')
+ ->with('/some/project/path/composer.json')
+ ->once()
+ ->andReturn($composerJsonFixture)
+ ->globally()
+ ->ordered();
+
+ app(LaravelUi::class)->install();
+
+ $shell->shouldNotHaveReceived('execInProject');
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_laravel_ui_fails_to_install()
+ {
+ File::shouldReceive('get');
+
+ Config::set('lambo.store.project_path', '/some/project/path');
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('composer require laravel/ui --quiet')
+ ->once()
+ ->andReturn(FakeProcess::fail('composer require laravel/ui --quiet'));
+
+ $this->expectException(Exception::class);
+
+ app(LaravelUi::class)->install();
+ }
+}
diff --git a/tests/Feature/OpenInBrowserTest.php b/tests/Feature/OpenInBrowserTest.php
new file mode 100644
index 00000000..1bbce671
--- /dev/null
+++ b/tests/Feature/OpenInBrowserTest.php
@@ -0,0 +1,85 @@
+shell = $this->mock(Shell::class);
+ $this->environment = $this->mock('alias:App\Environment');
+ }
+
+ /** @test */
+ function it_opens_the_project_homepage_using_the_specified_browser_on_mac()
+ {
+ Config::set('lambo.store.browser', '/Applications/my/browser.app');
+ Config::set('lambo.store.project_url', 'http://my-project.test');
+
+ $this->environment->shouldReceive('isMac')
+ ->once()
+ ->andReturn(true);
+
+ $this->shell->shouldReceive('execInProject')
+ ->once()
+ ->with('open -a "/Applications/my/browser.app" "http://my-project.test"');
+
+ app(OpenInBrowser::class)();
+ }
+
+ /** @test */
+ function it_opens_the_project_homepage_using_valet_open_when_no_browser_is_specified_on_mac()
+ {
+ $this->assertEmpty(Config::get('lambo.store.browser'));
+
+ $this->environment->shouldReceive('isMac')
+ ->once()
+ ->andReturn(true);
+
+ $this->shell->shouldReceive('execInProject')
+ ->once()
+ ->with('valet open');
+
+ app(OpenInBrowser::class)();
+ }
+
+ /** @test */
+ function it_uses_valet_open_when_not_running_on_mac()
+ {
+ $this->environment->shouldReceive('isMac')
+ ->once()
+ ->andReturn(false);
+
+ $this->shell->shouldReceive('execInProject')
+ ->once()
+ ->with('valet open');
+
+ app(OpenInBrowser::class)();
+ }
+
+ /** @test */
+ function it_ignores_the_specified_browser_when_not_running_on_mac()
+ {
+ Config::set('lambo.store.browser', '/path/to/a/browser');
+ Config::set('lambo.store.project_url', 'http://my-project.test');
+
+ $this->environment->shouldReceive('isMac')
+ ->once()
+ ->andReturn(false);
+
+ $this->shell->shouldReceive('execInProject')
+ ->once()
+ ->with('valet open');
+
+ app(OpenInBrowser::class)();
+ }
+}
diff --git a/tests/Feature/OpenInEditorTest.php b/tests/Feature/OpenInEditorTest.php
new file mode 100644
index 00000000..b2ce01b2
--- /dev/null
+++ b/tests/Feature/OpenInEditorTest.php
@@ -0,0 +1,37 @@
+mock(Shell::class);
+
+ Config::set('lambo.store.editor', 'my-editor');
+
+ $shell->shouldReceive('execInProject')
+ ->with("my-editor .")
+ ->once();
+
+ app(OpenInEditor::class)();
+ }
+
+ /** @test */
+ function it_does_not_open_the_project_folder_if_an_editor_is_not_specified()
+ {
+ $shell = $this->spy(Shell::class);
+
+ $this->assertEmpty(Config::get('lambo.store.editor'));
+
+ app(OpenInEditor::class)();
+
+ $shell->shouldNotHaveReceived('execInProject');
+ }
+}
diff --git a/tests/Feature/RunAfterScriptTest.php b/tests/Feature/RunAfterScriptTest.php
new file mode 100644
index 00000000..543bae95
--- /dev/null
+++ b/tests/Feature/RunAfterScriptTest.php
@@ -0,0 +1,66 @@
+shell = $this->mock(Shell::class);
+ }
+
+ /** @test */
+ function it_runs_the_after_script_if_one_exists()
+ {
+ Config::set('home_dir', '/my/home/dir');
+
+ File::shouldReceive('exists')
+ ->with('/my/home/dir/.lambo/after')
+ ->andReturn(true)
+ ->globally()
+ ->ordered();
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('sh /my/home/dir/.lambo/after')
+ ->once()
+ ->andReturn(FakeProcess::success())
+ ->globally()
+ ->ordered();
+
+ app(RunAfterScript::class)();
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_the_after_script_fails()
+ {
+ Config::set('home_dir', '/my/home/dir');
+
+ File::shouldReceive('exists')
+ ->with('/my/home/dir/.lambo/after')
+ ->andReturn(true)
+ ->globally()
+ ->ordered();
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('sh /my/home/dir/.lambo/after')
+ ->once()
+ ->andReturn(FakeProcess::fail('sh /my/home/dir/.lambo/after'))
+ ->globally()
+ ->ordered();
+
+ $this->expectException(Exception::class);
+
+ app(RunAfterScript::class)();
+ }
+}
diff --git a/tests/Feature/RunLaravelInstallerTest.php b/tests/Feature/RunLaravelInstallerTest.php
new file mode 100644
index 00000000..e303e92f
--- /dev/null
+++ b/tests/Feature/RunLaravelInstallerTest.php
@@ -0,0 +1,110 @@
+shell = $this->mock(Shell::class);
+ }
+
+ /** @test */
+ function it_runs_the_laravel_installer()
+ {
+ collect([
+ [
+ 'command' => 'laravel new my-project --quiet',
+ 'lambo.store.auth' => false,
+ 'lambo.store.dev' => false,
+ 'lambo.store.with_output' => false,
+ ],
+ [
+ 'command' => 'laravel new my-project',
+ 'lambo.store.auth' => false,
+ 'lambo.store.dev' => false,
+ 'lambo.store.with_output' => true,
+ ],
+ [
+ 'command' => 'laravel new my-project --dev --quiet',
+ 'lambo.store.auth' => false,
+ 'lambo.store.dev' => true,
+ 'lambo.store.with_output' => false,
+ ],
+ [
+ 'command' => 'laravel new my-project --dev',
+ 'lambo.store.auth' => false,
+ 'lambo.store.dev' => true,
+ 'lambo.store.with_output' => true,
+ ],
+ [
+ 'command' => 'laravel new my-project --auth --quiet',
+ 'lambo.store.auth' => true,
+ 'lambo.store.dev' => false,
+ 'lambo.store.with_output' => false,
+ ],
+ [
+ 'command' => 'laravel new my-project --auth',
+ 'lambo.store.auth' => true,
+ 'lambo.store.dev' => false,
+ 'lambo.store.with_output' => true,
+ ],
+ [
+ 'command' => 'laravel new my-project --auth --dev --quiet',
+ 'lambo.store.auth' => true,
+ 'lambo.store.dev' => true,
+ 'lambo.store.with_output' => false,
+ ],
+
+ [
+ 'command' => 'laravel new my-project --auth --dev',
+ 'lambo.store.auth' => true,
+ 'lambo.store.dev' => true,
+ 'lambo.store.with_output' => true,
+ ],
+ ])->each(function ($options) {
+ Config::set('lambo.store.project_name', 'my-project');
+ Config::set('lambo.store.auth', $options['lambo.store.auth']);
+ Config::set('lambo.store.dev', $options['lambo.store.dev']);
+ Config::set('lambo.store.with_output', $options['lambo.store.with_output']);
+
+ $this->runLaravelInstaller($options['command']);
+ });
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_the_laravel_installer_fails()
+ {
+ Config::set('lambo.store.project_name', 'my-project');
+ Config::set('lambo.store.auth', false);
+ Config::set('lambo.store.dev', false);
+ Config::set('lambo.store.with_output', false);
+
+ $this->shell->shouldReceive('execInRoot')
+ ->andReturn(FakeProcess::fail('failed command'));
+
+ $this->expectException(Exception::class);
+
+ app(RunLaravelInstaller::class)();
+ }
+
+ function runLaravelInstaller(string $expectedCommand)
+ {
+ $this->shell->shouldReceive('execInRoot')
+ ->with($expectedCommand)
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ app(RunLaravelInstaller::class)();
+ }
+}
diff --git a/tests/Feature/SilentDevScriptTest.php b/tests/Feature/SilentDevScriptTest.php
new file mode 100644
index 00000000..5d6a4cef
--- /dev/null
+++ b/tests/Feature/SilentDevScriptTest.php
@@ -0,0 +1,53 @@
+with('/some/project/path/package.json', '/some/project/path/package-original.json')
+ ->once()
+ ->globally()
+ ->ordered();
+
+ File::shouldReceive('get')
+ ->with('/some/project/path/package.json')
+ ->once()
+ ->andReturn($packageJson)
+ ->globally()
+ ->ordered();
+
+ File::shouldReceive('replace')
+ ->with('/some/project/path/package.json', trim($silentPackageJson))
+ ->once()
+ ->globally()
+ ->ordered();
+
+ app(SilentDevScript::class)->add();
+ }
+
+ /** @test */
+ function it_replaces_the_silent_compilation_script_with_the_original()
+ {
+ Config::set('lambo.store.project_path', '/some/project/path');
+
+ File::shouldReceive('move')
+ ->with('/some/project/path/package-original.json', '/some/project/path/package.json')
+ ->once();
+
+ app(SilentDevScript::class)->remove();
+ }
+}
diff --git a/tests/Feature/ValetLinkTest.php b/tests/Feature/ValetLinkTest.php
new file mode 100644
index 00000000..d94971a6
--- /dev/null
+++ b/tests/Feature/ValetLinkTest.php
@@ -0,0 +1,50 @@
+shell = $this->mock(Shell::class);
+ }
+
+ /** @test */
+ function it_runs_valet_link()
+ {
+ Config::set('lambo.store.valet_link', true);
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('valet link')
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ app(ValetLink::class)();
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_the_after_script_fails()
+ {
+ Config::set('lambo.store.valet_link', true);
+
+ $command = 'valet link';
+ $this->shell->shouldReceive('execInProject')
+ ->with($command)
+ ->once()
+ ->andReturn(FakeProcess::fail($command));
+
+ $this->expectException(Exception::class);
+
+ app(ValetLink::class)();
+ }
+}
diff --git a/tests/Feature/ValetSecureTest.php b/tests/Feature/ValetSecureTest.php
new file mode 100644
index 00000000..3773f262
--- /dev/null
+++ b/tests/Feature/ValetSecureTest.php
@@ -0,0 +1,49 @@
+shell = $this->mock(Shell::class);
+ }
+
+ /** @test */
+ function it_runs_valet_link()
+ {
+ Config::set('lambo.store.valet_secure', true);
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('valet secure')
+ ->once()
+ ->andReturn(FakeProcess::success());
+
+ app(ValetSecure::class)();
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_the_after_script_fails()
+ {
+ Config::set('lambo.store.valet_secure', true);
+
+ $this->shell->shouldReceive('execInProject')
+ ->with('valet secure')
+ ->once()
+ ->andReturn(FakeProcess::fail('valet secure'));
+
+ $this->expectException(Exception::class);
+
+ app(ValetSecure::class)();
+ }
+}
diff --git a/tests/Feature/VerifyDependenciesTest.php b/tests/Feature/VerifyDependenciesTest.php
new file mode 100644
index 00000000..7bebee3b
--- /dev/null
+++ b/tests/Feature/VerifyDependenciesTest.php
@@ -0,0 +1,53 @@
+executableFinder = $this->mock(ExecutableFinder::class);
+ }
+
+ /** @test */
+ function it_checks_that_required_dependencies_are_available()
+ {
+ $this->executableFinder->shouldReceive('find')
+ ->with('dependencyA')
+ ->once()
+ ->andReturn('/path/to/dependencyA');
+
+ $this->executableFinder->shouldReceive('find')
+ ->with('dependencyB')
+ ->once()
+ ->andReturn('/path/to/dependencyB');
+
+ app(VerifyDependencies::class)(['dependencyA', 'dependencyB']);
+ }
+
+ /** @test */
+ function it_throws_and_exception_if_a_required_dependency_is_missing_missing()
+ {
+ $this->executableFinder->shouldReceive('find')
+ ->with('dependencyA')
+ ->once()
+ ->andReturn('/path/to/dependencyA');
+
+ $this->executableFinder->shouldReceive('find')
+ ->with('missingDependency')
+ ->once()
+ ->andReturn(null);
+
+ $this->expectException(Exception::class);
+
+ app(VerifyDependencies::class)(['dependencyA', 'missingDependency']);
+ }
+}
diff --git a/tests/Feature/VerifyPathAvailableTest.php b/tests/Feature/VerifyPathAvailableTest.php
new file mode 100644
index 00000000..310c98e7
--- /dev/null
+++ b/tests/Feature/VerifyPathAvailableTest.php
@@ -0,0 +1,71 @@
+with('/some/filesystem/path')
+ ->once()
+ ->andReturn(true);
+
+ File::shouldReceive('isDirectory')
+ ->with('/some/filesystem/path/my-project')
+ ->once()
+ ->andReturn(false);
+
+ app(VerifyPathAvailable::class)();
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_the_root_path_is_not_available()
+ {
+ Config::set('lambo.store.root_path', '/non/existent/filesystem/path');
+
+ File::shouldReceive('isDirectory')
+ ->with('/non/existent/filesystem/path')
+ ->once()
+ ->andReturn(false);
+
+ $this->expectException(Exception::class);
+
+ app(VerifyPathAvailable::class)();
+ }
+
+ /** @test */
+ function it_throws_an_exception_if_the_project_path_already_exists()
+ {
+ Config::set('lambo.store.root_path', '/some/filesystem/path');
+ Config::set('lambo.store.project_path', '/some/filesystem/path/existing-directory');
+
+ File::shouldReceive('isDirectory')
+ ->with('/some/filesystem/path')
+ ->once()
+ ->andReturn(true)
+ ->globally()
+ ->ordered();
+
+ File::shouldReceive('isDirectory')
+ ->with('/some/filesystem/path/existing-directory')
+ ->once()
+ ->andReturn(true)
+ ->globally()
+ ->ordered();;
+
+ $this->expectException(Exception::class);
+
+ app( VerifyPathAvailable::class)();
+ }
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index 50602b9b..71f2d05c 100755
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -7,4 +7,16 @@
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
+
+ function setUp(): void
+ {
+ parent::setUp(); // TODO: Change the autogenerated stub
+ app()->bind('console', function () {
+ return new class {
+ public function comment($message = '') {}
+ public function info() {}
+ public function warn($message = '') {}
+ };
+ });
+ }
}